DLL 函数调用的间接跳转
Posted
技术标签:
【中文标题】DLL 函数调用的间接跳转【英文标题】:Indirect jumps for DLL function calls 【发布时间】:2014-05-05 22:37:30 【问题描述】:对 DLL 函数的调用的地址修复是一个多阶段过程:链接器将调用指令定向到间接跳转指令,并将间接跳转指令定向到 .rdata 部分中的导入表中的内存字,Windows 程序在其中loader 将在运行时加载 DLL 时放置函数的地址。
间接跳转指令必须由链接器生成,因为编译器不知道函数会在 DLL 中。通过为每个函数只生成一个间接跳转指令,可以最小化程序文件的大小,无论从多少个地方调用它。
鉴于此,显而易见的方法是在所有目标文件中的所有编译器生成代码之后,在文本部分的末尾收集所有间接跳转指令,而这似乎确实是当我使用 Microsoft 链接器 /nodefaultlib 开关尝试了一个简单的测试用例(它生成了一个足够小的可执行文件,我可以理解完整的反汇编)。
当我以正常方式将一个小程序与 C 标准库链接时,生成的可执行文件足够大,我无法遵循所有的反汇编,但据我所见,间接跳转指令似乎分散在整个代码中,一次可能三个小组。
我失踪有什么原因吗?
【问题讨论】:
【参考方案1】:间接跳转指令必须由链接器生成,因为 编译器不知道该函数会在 DLL 中。
实际上,情况并非总是如此。如果你用__declspec(dllimport)
标记函数,编译器确实知道它将是一个 DLL 导入,在这种情况下它可以生成一个间接调用:
; HMODULE = LoadLibrary("mylib");
push offset $SG66630
call [__imp__LoadLibraryA@4]
(__imp__LoadLibraryA@4
是指向 IAT 中导入的指针)
如果不使用dllimport
,那么编译器会生成一个相对函数调用:
push offset $SG66630
call _LoadLibraryA@4
在这种情况下,链接器必须生成一个跳转存根:
LoadLibraryA proc near
jmp [__imp__LoadLibraryA@4]
LoadLibraryA endp
事实上,它确实将这些跳转存根组合在一起(尽管可能通过编译单元和/或导入的 DLL,这里不是 100% 确定)。
注意:过去,链接器没有显式生成跳转存根,而是从导入库中获取它们。它们包含完整的目标文件,包括存根和生成 PE 导入目录所需的结构。请参阅这篇文章了解它是如何工作的:https://www.microsoft.com/msj/0498/hood0498.aspx
如今,导入库只有 API 和 DLL 名称,链接器知道如何生成导入它们所需的代码和元数据。
【讨论】:
以上是关于DLL 函数调用的间接跳转的主要内容,如果未能解决你的问题,请参考以下文章