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

C++ 加载程序如何将DLL映射到进程地址空间,c++,c,dll,loader,C++,C,Dll,Loader,我很想知道加载程序是如何将DLL映射到进程地址空间的。加载器是如何产生这种魔力的。我们高度赞赏这个榜样 提前感谢。假设这是在Windows中(DLL提示),您可能需要阅读Microsoft的文档页。它没有详细说明DLL是如何映射到地址空间的;我想你不需要知道这一点。你想要什么样的细节?在基本层面上,所有动态链接器的工作方式基本相同: 动态库被编译成可重定位代码(例如,使用相对跳转而不是绝对跳转) 链接器在应用程序的内存映射中找到适当大小的空白空间,并将DLL代码和任何静态数据读入该空间 动态库包

我很想知道加载程序是如何将DLL映射到进程地址空间的。加载器是如何产生这种魔力的。我们高度赞赏这个榜样


提前感谢。

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

你想要什么样的细节?在基本层面上,所有动态链接器的工作方式基本相同:

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

  • 如果你真的感兴趣的话,你应该读这本书。

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

  • 使用DLL搜索语义(特定于系统和修补程序级别)查找每个DLL,众所周知的DLL不受此限制
  • 将文件映射到内存(MMF)中,在内存中页是写时复制(CoW)
  • 遍历导入目录,并在点1处(递归地)为每个导入开始
  • 解析重定位,因为代码本身是位置独立代码(PIC),所以在大多数情况下,重定位的实体数量非常有限
  • (IIRC)将EAT从RVA(相对虚拟地址)修补到VA(当前进程内存空间内的虚拟地址)
  • 对IAT(导入地址表)进行修补,以引用进程内存空间内的导入及其实际地址
  • 对于EXE的DLL调用
    DLLMain()
    ,创建一个开始地址位于PE文件入口点的线程(这也过于简单,因为实际的开始地址位于Win32进程的kernel32.DLL内)
  • 现在,编译代码时,它取决于链接器如何引用外部函数。一些链接器创建存根,因此理论上,试图检查函数地址是否为NULL时,总是会说它不是NULL。这是一个怪癖,你必须知道,如果和当你的链接器受到影响。其他人直接引用IAT条目,在这种情况下,未引用的函数(认为延迟加载的DLL)地址可以为空,SEH处理程序将调用延迟加载帮助程序并(尝试)解析函数地址,然后在失败点恢复执行

    在上面的过程中有很多繁文缛节,我把它们简单化了

    您想知道的要点是,到进程的映射是以MMF的形式进行的,尽管您可以使用堆空间人工模拟该行为。然而,如果你还记得CoW的观点,那就是DLL思想的关键所在。实际上,加载特定DLL的进程之间将共享DLL页面的相同副本(大部分)。未共享的页面是我们写入的页面,例如在解决重新定位和类似问题时。在这种情况下,每个进程都有一个-现在已修改-的原始页面副本


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

    @Mark Bessey。在机器级别:)。加载器是如何产生这种魔力的。我喜欢您的描述,如果您能给出解码m/c代码或asm代码的链接/示例来解释bit,我将不胜感激。请慢慢来,不要着急。:)关于DLL的CoW特性的精彩解释。