通过检查 Windows 计算机中的几个 DLL(例如 KERNEL32.DLL),我注意到它们的任何部分(甚至只读数据部分)都没有设置 IMAGE_SCN_MEM_SHARED 标志。
DLL 是从 .dll 文件映射的,因此只有当您读取文件的一页时,它才会被复制到物理内存,但如果进程 A 和进程 B 都访问 kernel32.dll 的同一页,则该页将被复制到物理内存中。在物理内存中存在两次。我想知道最后这句话的真实性。
如果共享 .text 或 .rodata 段,即使启用了 ASLR,它们也只会被复制到物理内存一次,因为 ASLR 的作用是在首次加载模块时随机化模块的基址(应用了相应的重定位),但加载此模块的下一个进程直到系统重新启动都会在同一地址获取该模块,因此 .text 和 .rodata 可以以相同的方式共享。
这些都是我的猜测,请指正。
Thanks!
操作系统肯定能够将多个虚拟地址映射到同一物理内存页面,只要页面内容不(需要)改变[针对不同进程以不同的方式]。但是,如果代码使用绝对地址(DLL 内部或外部),例如 vtable/函数指针、指向全局数据(常量或非常量)的指针或使用绝对地址的简单函数调用,则该地址必须是修改以匹配操作系统给该内存部分的实际地址。这就是所谓的“搬迁”。
因此,至少在理论上,即使地址空间随机化,您也可以共享相同的 DLL,它只需要编译器和/或程序员做更多的工作。特别是,它要求没有重定位(在大块代码中)。如果代码具有根据代码地址重定位的绝对地址,则每个 DLL 需要有一份副本。
我其实不know操作系统如何处理这个问题。一个简单的解决方案显然是每个 DLL 仅随机化一次地址(直到卸载该特定 DLL),无论有多少应用程序使用相同的 DLL。它仍然使外人很难知道 DLL 加载到哪个地址,因为每次第一次加载时它都会加载到不同的地址(更重要的是,它不会是所有机器的静态值)使用相同版本的操作系统,如果没有此功能,就会出现这种情况)。然而,它确实意味着可以通过从具有已知内容的堆栈中复制内容来“检查”长时间运行的进程。 Web 服务器、数据库服务器和系统服务通常是长时间运行的进程,因此只有当系统“关闭”(或至少重新启动长时间运行的进程)时才会有不同的地址。
第二个稍微棘手的版本是检查特定页面(通常是 4KB 内存区域)是否有重定位,并共享所有没有重定位的页面。重定位页面的每个基地址需要有一份副本。通常在 DLL 的一个块中包含“对外部资源的所有引用”(“thunk 部分”),因此无论代码的基地址是什么,DLL 的典型大部分都不会,这意味着绝对是一个可行的解决方案。
如果这些方案在操作系统中都“不起作用”,那么您必须多次加载相同的 DLL。无论如何,从操作系统的角度来看,这显然是有效的,与 ASLR 之前一样,如果两个 DLL 尝试加载到同一地址(例如由不同供应商生产的 DLL,碰巧为代码选择了相同的基地址,或者经典且常见的“我从未给出基地址,因此它使用默认地址”) - 操作系统将通过更改加载的基地址来解决此类冲突第一的。
至于意义IMAGE_SCN_MEM_SHARED
,我本以为开发人员会要求这样做,其中 DLL 中的页面共享是自动完成的。换句话说IMAGE_SCN_MEM_SHARED
由特定 DLL 或 EXE 的开发人员设置以表示内容should与相同内容的其他用户共享,而不是“如果可以在内容用户不注意的情况下完成,操作系统就可以共享它”(共享代码当然就是这种情况,并且(可写)数据通常不共享DLL之间。只读数据,只要它没有重定位,当然可以隐式共享[该内容的用户无法判断它是否被共享]。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)