为什么可以重命名一个正在运行的exe文件

我们都知道,当一个exe文件正在运行的时候,是不能被修改,也不能被删除的。然而,似乎有悖常理的是,这个文件却是可以被重命名的。这背后的原理是什么呢?

在运行一个exe文件之前,首先要打开它。打开文件的基础API是CreateFile函数,在它的参数中,与文件共享方式相关的参数是dwShareMode,它可以指定打开的文件是否允许被其它访问者读(FILE_SHARE_READ)、写(FILE_SHARE_WRITE)或删除(FILE_SHARE_DELETE)。于是自然有这样的猜想:系统在打开exe文件的时候使用了某种共享方式的组合,以致于出现这种行为。

可是,当使用不同组合的共享方式来调用CreateFile,尝试还原出这种行为时,却是怎么也行不通。API的文档明确指出,在指定了FILE_SHARE_DELETE的时候既可以删除文件,也可以重命名文件,但就是没有“不能删除,只能重命名”这种选项。

为了弄清楚这个问题,我们可以使用ProcessMonitor来观察系统在运行exe之前是如何打开文件的。首先写一个简单的程序,在程序中调用CreateProcess函数来启动另外一个进程,然后在ProcessMonitor中查看这个程序运行时产生的文件操作记录。这里额外写一个程序来启动进程的目的是为了减少干扰信息——直接在资源管理器中运行exe会产生很多文件操作记录,无法确定哪些才是真正运行所需的。

ProcessMonitor显示的结果如下,这是在ConsoleApplication.exe进程里启动了Application.exe进程的记录。

打开第一条“CreateFile”的记录,可以看到exe文件是以FILE_SHARE_READFILE_SHARE_DELETE方式打开的:

显然这并不能作出合理的解释,因为这种组合的共享方式已经尝试过是没有效果的,况且ProcessMonitor里的最后一条操作记录是“CloseFile”,文件已经被关闭了,打开文件的影响已经是不存在的。再看一下文件操作记录,在“CreateFile”之后有一次“CreateFileMapping”操作,也许这就是关键点。

让我们再稍微深入一下程序的启动流程。在打开了exe文件之后,系统需要读取文件的内容来运行,这是通过内存映射文件来实现的。系统在打开后的exe文件上创建了一个内存映射文件,把它映射到了进程的虚拟地址空间中,然后直接访问虚拟地址来读取文件的内容。对虚拟地址的访问触发了内存管理器的缺页机制,它发现内存页面中没有exe文件的内容,便从硬盘上对应的位置读取,加载到内存中。

由于内存映射文件的存在,exe文件的内容必须完整保留在硬盘上,以便系统在程序运行过程中随时读取,所以exe文件是不能删除的。而当程序运行起来之后,exe的文件名已经没有用处了,并且文件名不属于文件内容,它只是文件夹目录项中的一个属性,修改文件名实际上修改的是目录项,而不是文件内容,因此修改exe的文件名并不会影响程序运行。

我们可以写一个程序来验证上述论述:先打开一个文件,然后在这个文件上创建一个内存映射文件,再把先前打开的文件句柄关闭,保持程序不退出。此时这个文件的行为就跟正在运行的exe文件一样了,可以重命名但是不能删除。