如何实际共享Windows DLL?

mig*_*uel 5 c++ dll winapi memory-management shared-memory

通过检查我的Windows机器中的几个DLL(例如KERNEL32.DLL),我注意到它们的所有部分,甚至是只读数据部分都没有设置IMAGE_SCN_MEM_SHARED标志.

DLL是从.dll文件映射的,所以只有当你读取文件的页面时才会将它复制到物理内存中,但是,如果同一个页面让进程A和进程B都访问了kernel32.dll,那么页面将会在物理内存中存在两次.我要求最后一句话的真实性.

如果.text或.rodata段共享它们将被仅复制到物理内存中,即使启用了ASLR,因为ASLR的作用是在模块首次加载时随机化模块的基础(应用相应的重定位)但是加载此模块的下一个进程直到系统重新启动将使模块处于相同的地址,因此.text和.rodata可以以相同的方式共享.

这些都是我做的假设,请指正.

谢谢!

Mat*_*son 2

操作系统肯定能够将多个虚拟地址映射到同一物理内存页面,只要页面内容不(需要)改变[针对不同进程以不同的方式]。但是,如果代码使用绝对地址(DLL 内部或外部),例如 vtable/函数指针、指向全局数据(常量或非常量)的指针或使用绝对地址的简单函数调用,则该地址必须是修改以匹配操作系统给该内存部分的实际地址。这就是所谓的“搬迁”。

因此,至少在理论上,即使地址空间随机化,您也可以共享相同的 DLL,它只需要编译器和/或程序员做更多的工作。特别是,它要求没有重定位(在大块代码中)。如果代码具有根据代码地址重定位的绝对地址,则每个 DLL 需要有一份副本。

我实际上不知道操作系统如何处理这个问题。一个简单的解决方案显然是每个 DLL 仅随机化地址一次(直到卸载该特定 DLL),无论有多少应用程序使用相同的 DLL。它仍然使外人很难知道 DLL 加载到哪个地址,因为每次第一次加载时它都会加载到不同的地址(更重要的是,它不会是所有机器的静态值)使用相同版本的操作系统,如果没有此功能,就会出现这种情况)。然而,它确实意味着可以通过从具有已知内容的堆栈中复制内容来“检查”长时间运行的进程。Web 服务器、数据库服务器和系统服务通常是长时间运行的进程,因此只有当系统“关闭”(或至少重新启动长时间运行的进程)时才会有不同的地址。

第二个稍微棘手的版本是检查特定页面(通常是 4KB 内存区域)是否有重定位,并共享所有没有重定位的页面。重定位页面的每个基地址需要有一份副本。通常在 DLL 的一个块中包含“对外部资源的所有引用”(“thunk 部分”),因此无论代码的基地址是什么,DLL 的典型大部分都不会,这意味着绝对是一个可行的解决方案。

如果这些方案在操作系统中都“不起作用”,那么您必须多次加载相同的 DLL。无论如何,从操作系统的角度来看,这显然是有效的,与 ASLR 之前一样,如果两个 DLL 尝试加载到同一地址(例如由不同供应商生产的 DLL,碰巧为代码选择了相同的基地址,或者经典且常见的“我从未给出基地址,因此它使用默认地址”) - 操作系统将通过更改加载的基地址来解决此类冲突第一的。

至于 的含义IMAGE_SCN_MEM_SHARED,我以为开发人员会要求这样做,其中 DLL 中的页面共享是自动完成的。换句话说,IMAGE_SCN_MEM_SHARED将由特定 DLL 或 EXE 的开发人员设置,以表示内容与相同内容的其他用户共享,而不是“如果可以在内容用户不注意的情况下完成,则操作系统可以共享它” “(共享代码当然就是这种情况,并且(可写)数据通常不在 DLL 之间共享。只读数据,只要它没有重定位,当然可以隐式共享 [该内容的用户不能告诉它是否被共享]。