Windows 是否将 DLL 映射到不同进程中的相同虚拟地址?

Does Windows map DLLs to the same virtual address in different processes?

假设两个进程正在使用 Kernel32.dll,Windows 是否将两个进程中的 DLL 映射到相同的虚拟地址 space?如果不是,分页机制如何最终使用相同的物理地址,实际上为两个进程加载了 DLL?我尝试在 windows 内部手册中查找此信息,但没有找到任何内容

TL;DR: 不,它可能在另一个进程的其他地方加载。

Ntdll 和 Kernel32 比较特殊,总是加载到同一个地址,所以最好专注于其他东西,例如 Shell32。

dll 具有所谓的首选基地址,它存储在 PE header (ImageBase) 中。加载程序将首先尝试在此地址加载 dll。如果该地址范围是空闲的,那么加载将成功,不需要额外的工作。

如果地址不可用,则加载程序必须将其加载到其他地方。在不同的地址加载通常需要重定位信息,如果在链接过程中将其删除 (/FIXED),则加载将失败!如果在其他地方有 space 加载 dll,加载程序将使用重定位信息用新的基地址修补 dll 中的给定位置。由于 dll 加载为 copy-on-write,与在首选地址加载相比,这将导致额外的内存使用,因为每个需要补丁的内存页面现在都是进程中的私有副本。这意味着您的问题的答案是否定的,如果该进程已经加载了其他内容,则 dll 可能不会加载到不同进程中的同一地址。

到目前为止我只谈了loader。加载程序在 Ntdll 中作为普通用户模式代码实现,不涉及映射到内存的文件的实际工作方式。内存映射文件(在 NT 内部称为 Sections)是操作系统内核和 CPU 硬件之间的 co-operation。这本身就是一个完整的主题,但重要的是要知道物理内存和 page/swap 文件机制与用户模式进程访问其虚拟内存页面的方式完全脱节。内核可以将物理内存页面映射到进程虚拟内存中的零个、一个或多个位置,并且 CPU 将在进程访问虚拟页面时自动转换。

最后一点,ASLR 确实使事情复杂化了一点,但“偏移量”仅在重新启动时发生变化,在当前实施中不应对这个特定问题产生影响。理论上 Windows 将来可以改变这一点,并始终在不同进程中的不同地址加载东西,但由于 copy-on-write 缺点,这不太可能发生。