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 函数调用的间接跳转的主要内容,如果未能解决你的问题,请参考以下文章

汇编语言中的函数调用

函数的啥调用是一个函数直接或间接地调用它自身

objdump 输出中的直接和间接调用/跳转

C语言中,函数是不是可以直接或间接调用自己!!求大神!!求解析!!!

如何从一个动态链接库DLL中查看接口函数

[Effective JavaScript 笔记]第17条:间接调用eval函数优于直接调用