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

问题描述 投票:0回答:4

我很好奇Loader如何将DLL映射到进程地址空间。装载机是如何发挥这种魔力的?

c++ c dll loader
4个回答
9
投票

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

  1. 使用 DLL 搜索语义(系统和补丁级别特定)定位每个 DLL,众所周知的 DLL 可以免受此影响
  2. 将文件映射到内存 (MMF),其中页面是写时复制 (CoW)
  3. 遍历导入目录,每次导入都从点 1 开始(递归)。
  4. 解决重定位,大多数时候只有非常有限数量的实体,因为代码本身是位置无关代码(PIC)
  5. (IIRC) 将 EAT 从 RVA(相对虚拟地址)修补为 VA(当前进程内存空间内的虚拟地址)
  6. 修补 IAT(导入地址表)以引用导入及其在进程内存空间内的实际地址
  7. 对于 DLL 调用
    DLLMain()
    对于 EXE 创建一个线程,其起始地址位于 PE 文件的入口点(这也过于简单化,因为实际的起始地址位于 Win32 进程的 kernel32.dll 内部)

现在,当您编译代码时,它取决于链接器如何引用外部函数。一些链接器创建存根,以便 - 理论上 - 尝试检查函数地址是否为 NULL 总是会说它不是 NULL。这是一个怪癖,您必须注意您的链接器是否以及何时受到影响。其他人直接引用 IAT 条目,在这种情况下,未引用的函数(认为延迟加载的 DLL)地址可以为 NULL,然后 SEH 处理程序将调用延迟加载帮助程序并(尝试)解析函数地址,然后再在指出它失败了。

上述过程涉及很多繁文缛节,是我过于简单化了。

您想知道的要点是,到进程的映射是作为 MMF 发生的,尽管您可以人为地模仿堆空间的行为。然而,如果您还记得 CoW 的要点,那就是 DLL 思想的关键。实际上,DLL(大部分)页面的“相同”副本将在加载特定 DLL 的进程之间共享。不共享的页面是我们写入的页面,例如在解决重定位和类似问题时。在这种情况下,每个进程都有一个(现已修改的)原始页面的副本。 还有关于 DLL 上的 EXE 加壳程序的警告。它们恰恰击败了我描述的这种 CoW 机制,因为它们在加载 DLL 的进程的堆上为 DLL 的解压内容分配空间。因此,虽然实际的文件内容仍然映射为 MMF 并共享,但解压的内容对于加载 DLL 的每个进程占用相同的内存量,而不是共享该内存。


8
投票

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

3
投票
链接器和加载器


2
投票
运行时动态链接

文档页面。它没有详细说明如何将 DLL 映射到地址空间;我想你不需要知道这一点。

© www.soinside.com 2019 - 2024. All rights reserved.