覆盖内存映射的可执行文件时会发生什么?

11 operating-system execution

在对我的一个问题发表评论后,我很想知道当一个人覆盖可执行文件时会发生什么.我需要检查一下我对此事的理解.

说我有/usr/bin/myprog.我运行它,因此操作系统加载/usr/bin/myprog,可能通过http://en.wikipedia.org/wiki/Memory-mapped_file#Common_uses.

无论出于什么原因,这个过程仍然留在内存中,我决定实际上已经修复了一个错误并且我会覆盖/usr/bin/myprog.

所以,据我所知:

  • 如果myprog已经加载了一个实例并且我替换了已经加载的文件myprog,那么该实例myprog是未修改的.
  • 如果我运行它的新实例myprog将使用新代码.

我对么?

但是,根据有关内存映射文件的文章,这种技术允许开发人员将文件的某些部分视为物理内存.

所以我看到了我如何理解事物的矛盾.如果真正只按需加载页面,那么假设myprog不是100%分页,这篇维基百科文章暗示将从磁盘上的文件加载新页面,该文件自加载原始图像后已更改.

但是,我很确定我的两个编译图像不会相同,并且每个文件的相关地址偏移量不相同.因此,假设发生这种情况,指令指针将会非常丢失......我非常确定操作系统不会将两个不同图像的一部分加载到内存中作为同一进程的一部分.

那么内存映射/请求分页的组合如何为程序的执行工作呢?覆盖该文件会在每个可执行文件的页面上触发页面错误,以确保它被加载到当前正在运行的进程中吗?

我做了一个快速的实验:

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv)
{
    printf("Program resident...");
    while(1)
    {
        printf("??? Just notifying you I'm still here...\n");
        usleep(1000000);
    }

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

当然,我可以a)在运行时更换这个可执行文件,b)它的输出没有改变.

那么发生了什么?我特别感谢任何建议,我可以做些什么来看看会发生什么(Linux或Windows).

谢谢大家.

编辑:我所指的问题引发了这个问题:没有重启的升级 - 在实践中会发生什么样的问题?

另外,我知道这与编程无关,而是更新可执行文件的结果.然而,我仍然感兴趣,我想不出一个更好的地方来问它.

Ted*_*ton 10

  1. 发生的事情首先取决于你是否rm /usr/bin/myprog然后创建一个新的,或者你open()write()现有的/usr/bin/myprog.

  2. 如果你rm是旧/usr/bin/myprog文件,然后创建一个具有相同名称的新文件,那么内核/文件系统驱动程序会为新版本提供一个新的inode,旧的inode会在/proc文件系统中保持不变,直到打开它的进程关闭它.您的现有进程/usr/bin/myprog拥有自己的文件私有版本,未经修改,直到它close()是文件描述符.

  3. 所有操作系统(Windows,Linux,可能是OS X)都使用需求页面内存映射(mmap()对于posix,我不记得Windows的等价物 - VirtualAlloc()?)进程加载.这样,可执行文件中任何未触及的部分都不会被加载到内存中.

  4. 如果这是一个传统的mmap()'d文件,并且两个进程都打开/映射它,并且它们都没有MAP_PRIVATE在调用中指定(即copy-on-write)mmap(),那么这两个进程实际上将查看相同的物理内存页面并且,如果他们两个都打电话mmap()PROT_READ| PROT_WRITE,他们会看到彼此的修改.

  5. 如果这是一个传统的mmap()'d文件,并且进程1打开/映射它,然后进程2开始通过write()调用(而不是我的mmaping)来摆弄硬盘驱动器上的文件,则进程1确实看到了这些更改.我猜内核注意到文件正在被修改并重新加载受影响的页面.

  6. 我不确切知道mmap()可执行映像是否有任何特殊行为?如果我破解了指向我的某个函数的指针并修改了代码,它会将页面标记为脏吗?脏页面会被写回去/usr/bin/myprog吗?当我尝试这个时,它会出现段错误,所以我想当_TEXT页面被映射时MAP_SHARED,它们也可能不会被PROT_WRITE写入,因此在写入时会出现段错误.当然,_DATA部分也会被加载到内存中,并且需要修改它们,但是那些可以被标记MAP_PRIVATE(写时复制) - 因此它们可能不会保持与/usr/bin/myprog文件的连接.

  7. 第6点涉及可执行文件直接修改自身.第5点涉及修改级别的任意mmap()d文件write().当我尝试mmap()在另一个进程中修改可执行文件(这是'd)时write(),我得不到与第5点相同的结果.我可以通过裸write()调用对可执行文件进行各种可怕的更改,但没有任何反应.然后,当我退出进程并尝试再次运行它时,它崩溃了(当然 - 在我对可执行文件做的所有事情之后).这让我很困惑.我无法置换参数以mmap()使其以这种方式运行 - 不是写入时复制而是不受映射文件更改的影响.

  8. 好吧,我回到圣经(史蒂文斯)和大的问题是MAP_PRIVATEVS MAP_SHARED.MAP_PRIVATE是写时复制而MAP_SHARED不是.MAP_PRIVATE只要对其进行修改,就会复制映射的页面.未定义对原始文件的修改是否会传播到映射的MAP_PRIVATE页面,但是OS X则不会.MAP_SHARED维护与原始文件的连接,允许对文件的更新传播到内存页面,反之亦然.如果映射了内存块MAP_PRIVATE,则不会对其进行任何修改.MAP_SHAREDOTOH允许通过写入映射页面来修改文件.

  9. 图像加载器将可执行文件映射为MAP_PRIVATE.这解释了第6点中的行为 - 攻击指向函数代码的指针,然后修改它,即使您有权这样做,也不会将数据写回磁盘.从理论上讲,应该可以/usr/bin/myprog在OS映像加载器之后立即更改可执行文件mmap(),但每当我查看非常大的可执行文件时vmmap,它们的TEXT部分似乎总是完全驻留.我不知道这是否是因为OS X的图像加载器触及所有页面以确保它们被复制,或OS X的页面管理器是否只是非常积极地使页面驻留(它是),但我没有能够在OS X中创建一个可执行文件,其TEXT部分一旦main()启动就没有完全驻留.

  10. OS X图像加载器非常积极地加载映射页面.我注意到,在mmap()输入文件时,它必须非常大,然后OS X决定将其中的任何一个非驻留.一个1GB的文件被完全加载,但只有大约1.7GB的3GB文件被占用.这是在具有8GB物理RAM并运行64位OS X内核的计算机上.


Mar*_*rkR 9

在Linux下,如果在运行时替换可执行文件,结果将无法预测,并且可能会崩溃.已修改的页面(例如"bss"初始化数据)不会受到影响,但未修改的页面(例如大多数代码)将会受到影响.

我的猜测是,在您的情况下,字符串位于修改(复制)页面的一部分,因此不受影响.

但是,只有在您实际覆盖同一文件时才会发生这一切.

大多数情况下,当您替换可执行文件时,您将使用不同的文件替换目录条目.这通常通过在现有文件上重命名临时文件(在同一目录中)来完成.这就是(例如)包管理器所做的事情.

在replacement-directory-entry的情况下,先前的可执行文件继续作为完全独立(仍在执行)的文件存在,并且先前的可执行文件可以放弃并重新加载其页面而没有问题 - 它仍然可以看到旧文件.

我不知道链接器对其输出做了什么.但/ usr/bin/install会创建一个新文件.我希望这种行为是非常慎重的.