在运行时重新编译可执行文件是否安全?

如果我在运行时重新编译可执行文件,会发生什么情况? 当操作系统开始运行时,是否将所有可执行文件的内容读入到内存中,这样就不会读取新的可执行文件? 还是会阅读新的可执行文件的部分,认为它没有改变,导致可能未定义的行为?

如果我有一个正在运行的脚本,它会在循环中反复调用可执行文件,并在脚本运行时重新编译可执行文件。 是否保证循环的未来迭代将调用新的可执行文件,并且只有在进行切换时正在进行的调用的结果可能会被破坏?

我的操作系统是Linux,但我也很好奇Windows上发生了什么。

由于这是一个传统的编译器,它写出一个可执行文件,让我们在Linux中进行跟踪。

首先要知道的是,Linux文件名不会直接引用文件,而是直接引用到与文件名无关的目录条目。 一个文件实际上并不需要有一个文件名,但是如果没有,就很难引用它。

如果进程正在使用文件,并且替换或删除文件,则进程将通过其目录条目继续使用该文件。 任何使用这个文件的新进程,或者查看它,都会得到新的版本(如果你替换了它)或者找不到它(如果你删除了它)。 一旦所有进程都通过旧文件,它将被从文件系统中删除。

因此,如果您重新编译并创建一个同名的新可执行文件,则不会影响正在运行的进程。 它将继续使用旧的可执行文件。 任何尝试打开该文件的新进程都将得到新的进程。 如果你有system("foo"); 在一个循环中,每次执行它都会看到文件名foo的含义。

Windows以不同方式处理文件 一般来说,如果有一个使用文件的进程,文件被锁定,不能被删除或替换。

这取决于。

如果操作系统将整个可执行文件读入内存,并且不会引用磁盘映像,那么可以在“使用”时重新编译它。

实际上这并不总是发生。 如果操作系统在可执行文件上保持打开文件句柄(如Windows),这将防止文件被删除和/或覆盖。

使用Linux / Unix可以覆盖“正在使用”的文件。 详见David Thornley的回答。

在Windows中,您不能删除锁定的文件,但大多数人不知道的是,您可以移动或重命名正在运行的exe文件。

所以你可以

  • 将旧的exe移动到同一个驱动器上的临时目录
  • 安排它在下次重新启动时删除:MoveFileEx(name,NULL,MOVEFILE_DELAY_UNTIL_REBOOT);
  • 移动一个新的exe文件。

旧程序将继续运行,但新进程将使用新文件。

在Linux下,根据需要将可执行文件分页到内存中。 磁盘上的可执行文件成为应用程序的后备存储。 这意味着您不能修改磁盘上的可执行文件,否则会影响正在运行的应用程序。 如果您尝试open(2)正在使用的可执行文件进行写入,则会出现ETXTBSY (文本文件繁忙)错误(请检查手册页是否open(2) )。

正如许多人所说的,你可以从文件系统中删除文件( unlink(2) ),内核将维护一个引用,而不是从磁盘上删除它,直到没有更多的引用(当进程退出时,它会释放其对该文件的引用)。 这意味着您可以通过先删除正在使用的可执行文件,然后创建一个与旧文件同名的新文件,从而有效地“覆盖”正在使用的可执行文件。

所以,这涉及编译器在“覆盖”现有文件时如何创建可执行文件。 如果它只是打开文件进行写入和截断( O_WRONLY|O_CREAT|O_TRUNC ),它将失败,并发生ETXTBSY错误。 如果它首先删除现有的输出文件并创建一个新的,它将无误地工作。

我会想象它不会让你替换文件,因为Windows在使用时将其锁定。

这取决于。 从我所经历的情况来看,在Linux上,如果你删除它,它仍然可以运行一个程序(它不是太大)。 但我不认为这是定义的行为。

就循环而言,根据你如何调用可执行文件,当执行一个只写了一半的程序时,你的脚本可能会崩溃。

在Windows中,如果可执行文件仍在运行,那么文件将被锁定。 如果exe文件没有真正运行,新的运行应该选择新的文件,这取决于你的脚本是如何编码的。

我不知道Linux。

可执行文件可能在启动时被完全加载到内存中,但是如果它足够大,并且运行时间足够长,操作系统可能会决定将其中一些未使用的部分换掉。

由于操作系统假定程序的文件仍然存在,所以没有必要将这些内存块写入交换文件。 所以他们只是无效和重用。 如果程序再次需要这些页面,则操作系统从可执行文件中加载它们。

在Windows中,这实际上是自动发生的,因为加载的模块是内存映射文件。 这也意味着文件在执行过程中被锁定,您将无法轻易覆盖文件。

不知道有关Linux,但是IIRC以相同的方式进行交换。