加载程序如何将DLL映射到进程地址空间

时间:2008-12-03 10:30:40

标签: c++ c dll loader

我很想知道Loader如何将DLL映射到处理地址空间。装载机如何做到这一点。例子非常受欢迎。

提前致谢。

4 个答案:

答案 0 :(得分:9)

好的,我在这里假设Windows方面。加载PE文件时会发生的情况是加载程序(包含在NTDLL中)将执行以下操作:

  1. 使用DLL搜索语义(系统和补丁级别特定)找到每个DLL,众所周知的DLL可以免除此
  2. 将文件映射到内存(MMF),其中页面是写时复制(CoW)
  3. 遍历导入目录,并在第1点遍历(递归)每次导入。
  4. 解析重定位,大部分时间只是非常有限的实体,因为代码本身是与位置无关的代码(PIC)
  5. (IIRC)将EAT从RVA(相对虚拟地址)修补为VA(当前进程内存空间内的虚拟地址)
  6. 修补IAT(导入地址表)以使用进程内存空间中的实际地址引用导入
  7. 对于EXE的DLL调用DLLMain()创建一个线程,其起始地址位于PE文件的入口点(这也是过于简化的,因为实际的起始地址位于kernel32.dll for Win32进程内)
  8. 现在编译代码时,它依赖于链接器如何引用外部函数。一些链接器创建存根,以便 - 理论上 - 尝试检查函数地址与NULL将始终说它不是NULL。如果链接器受到影响,您必须注意这一点。其他人直接引用IAT条目,在这种情况下,未引用的函数(认为延迟加载的DLL)地址可以为NULL,然后SEH处理程序将调用延迟加载帮助程序并(尝试)解析函数地址,然后再继续执行指出它失败了。

    上述过程中涉及大量繁文缛节,我过于简单了。

    您想知道的要点是,流程映射是以MMF 形式发生的,尽管您可以人为地模仿堆空间的行为。但是,如果你还记得关于CoW的观点,那就是DLL概念的关键。实际上,DLL的(大部分)页面的相同副本将在加载特定DLL的进程之间共享。未共享的页面是我们写入的页面,例如在解析重定位和类似事物时。在这种情况下,每个进程都有一个 - 现在已修改 - 原始页面的副本。

    关于DLL的EXE打包程序的警告。它们完全打败了我所描述的这种CoW机制,因为它们在DLL加载到的进程堆上为DLL的解包内容分配空间。因此,虽然实际的文件内容仍然映射为MMF并且共享,但是解压缩的内容对于加载DLL而不是共享DLL的每个进程占用相同的内存量。

答案 1 :(得分:7)

您在寻找什么级别的细节?在基本层面上,所有动态链接器的工作方式几乎相同:

  1. 动态库被编译为可重定位代码(例如,使用相对跳转而不是绝对跳转)。
  2. 链接器在应用程序的内存映射中找到适当大小的空白空间,并将DLL的代码和任何静态数据读入该空间。
  3. 动态库包含每个导出函数开头的偏移表,并且在加载时对客户端程序中DLL函数的调用使用新的目标地址进行修补,具体取决于库的加载位置。 / LI>
  4. 大多数动态链接器系统都有一些系统,用于为特定库设置首选基址。如果在其首选地址加载库,则可以跳过步骤2和3中的重定位。

答案 2 :(得分:3)

如果您真的感兴趣,请阅读本书Linkers and Loaders

答案 3 :(得分:2)

假设这是在Windows中(DLL提示),您可能想要阅读Microsoft的Run-Time Dynamic Linking文档页面。它没有详细说明如何将 DLL映射到地址空间;我想你不应该知道这一点。