Symbol Reslove

基本原理

链接器在处理目标文件时,需要对目标文件中的某些位置进行重定位,即将符号指向恰当的位置,确保程序正常执行。例如,当程序调用了一个函数时,相关的调用指令必须把控制流交给适当的目标执行地址。

在 ELF 文件中,对于每一个需要重定位的 ELF 节都有对应的重定位表,比如说 .text 节如果需要重定位,那么其对应的重定位表为 .rel.text。

举个例子,当一个程序导入某个函数时,.dynstr 就会包含对应函数名称的字符串,.dynsym 中就会包含一个具有相应名称的符号(Elf_Sym),在 .rel.plt 中就会包含一个指向这个符号的的重定位表项。即,这几者之间的引用关系是

image-20201029230806649

总的来说,plt 表项主要进行了如下的函数调用来解析目标函数

具体操作

_dl_runtime_resolve

32 位和 64 位具有不同的 _dl_runtime_resolve 函数,32 位的版本如下

其中

  • 以 cfi 开头的都是一些提示性信息,可以不用管。可参考

    • https://stackoverflow.com/questions/51962243/what-is-cfi-adjust-cfa-offset-and-cfi-rel-offset

    • https://sourceware.org/binutils/docs/as/CFI-directives.html

  • _CET_ENDBR 则与 Intel 的 CET 相关,标记着间接跳转的位置。如果程序中的间接跳转位置处没有这个指令,那就会出现问题。

因此这部分代码可以简化为

即,_dl_runtime_resolve 保存了 eax,ecx,edx 三个寄存器后,然后把 link_map 的地址放到 eax 中,把待解析的符号的偏移放到 edx 中。然后就去执行 _dl_fixup 函数。在函数执行返回后,会按照如下的顺序执行

  • 先恢复 edx 寄存器的值

  • 然后 恢复 ecx 的值

  • 然后把 _dl_fixup 函数的返回值放到当前的栈上

  • 然后恢复 eax 的值

  • 执行 ret $12,此时栈上为

    • 待解析的函数的地址

    • original eax

    • link_map 的地址

    • reloc_offset

64 位下的 _dl_runtime_resolve 与 32 位下类似,有几点主要的区别

  • 在刚进入函数时,会保存更多的信息

  • _dl_fixup 会使用 rdi 和 rsi 传参

  • 最后执行目标函数时使用的是 jmp 指令

_dl_fixup

_dl_runtime_resolve 中执行的最核心的函数就是 _dl_fixup 了,如下(这里也给出了一些相关的宏),需要注意的是,64 位下的 reloc_arg 就是 reloc_index。

参考

  • https://code.woboq.org/userspace/glibc/sysdeps/x86_64/dl-trampoline.h.html#60

  • https://stackoverflow.com/questions/46374907/what-does-the-f-prefix-of-some-gcc-command-line-options-mean

  • https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html

Last updated