windows DLL 实际上是如何共享的?

How are windows DLL actually shared?

通过检查我 windows 机器中的几个 DLL(例如 KERNEL32.DLL),我注意到它们的 none 部分,甚至连只读数据部分都没有IMAGE_SCN_MEM_SHARED 标志集。

DLL 是从 .dll 文件映射的,因此只有当您读取文件的一页时,它才会被复制到物理内存,但是,如果 kernel32.dll 的同一页被两个进程 A 访问和进程 B 那么该页面将在物理内存中存在两次。 我想问问最后这句话的真实性。

如果共享的 .text 或 .rodata 段,它们只会被复制到物理内存一次,即使启用了 ASLR,因为 ASLR 所做的是在模块首次加载时随机化模块的基址(相应已应用重定位),但在系统重启之前加载此模块的下一个进程将在同一地址获取模块,因此 .text 和 .rodata 可以以相同的方式共享。

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

谢谢!

OS肯定可以将多个虚拟地址映射到同一个物理内存页,只要页面内容不(需要)改变[不同进程不同方式]。但是,如果代码使用绝对地址(在 DLL 内部或外部),例如 vtable/function 指针、指向全局数据(常量或非常量)的指针或使用绝对地址的简单函数调用,则地址必须修改以匹配 OS 给那部分内存的实际地址。这叫做"relocation".

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

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

第二个稍微棘手的版本是检查特定页面(通常为 4KB 内存区域)是否有重定位,并共享所有没有重定位的页面。重定位的页面需要每个基地址有一个副本。在 DLL 的一个块中通常有 "all references to external resources"(一个 "thunk section"),因此无论代码的基地址是什么,DLL 的典型大部分都不会,这意味着肯定是可行的解决方案。

如果OS中的这些方案"work"都不是,那么你必须多次加载同一个DLL。无论如何,从 OS 的角度来看,这显然是有效的,因为在 ASLR 之前,如果两个 DLL 试图加载到同一地址(例如生成的 DLL),则需要移动同一 DLL 的基地址不同的供应商,恰好为代码选择相同的基地址,或者经典和常见的 "I never gave a base address, so it uses the default address") - OS 将通过更改第一个加载的基地址来解决此类冲突。

至于IMAGE_SCN_MEM_SHARED的意思,我还以为开发者会这样要求呢,DLL中的页面共享是自动完成的。换句话说,IMAGE_SCN_MEM_SHARED 将由特定 DLL 或 EXE 的开发人员设置,以表示内容 应该 与相同内容的其他用户共享,而不是 "the OS can share it if it can be done without the user of the content noticing"(这当然是共享代码的情况,并且(可写)数据通常不在 DLL 之间共享。只读数据,只要它没有重定位,当然可以隐式共享 [该内容的用户可以不知道是否共享。