《程序员的自我修养——链接、装载与库》读书笔记—— 7.4 延迟绑定

为什么需要延迟绑定

根据前文所述,动态链接的可执行程序在使用.so 中的符号时,依赖.got 中保存的地址,这个地址是链接器在程序运行时才去填充(或者说绑定)的。如果有大量的符号需要动态链接,这些符号查找地址重定位的工作必定会消耗可观的时间。并且,可能很多动态库中的函数在程序执行完时都不会被调用到,那么对这些函数的查找和重定位的工作就白白浪费了。

什么是PLT 延迟绑定

PLT(procesure linkage table) 延迟绑定的基本思路就是,在程序第一次被使用的时候,才去进行绑定(符号查找,填充.got 表)。

如何实现PLT

bar@plt:
jmp *(bar@GOT)
push n
push moduleID
jump _dl_runtime_resolve

这里根据书中内容简要概况一下延迟绑定的实现流程:

  1. 在第一次调用bar 函数时,此时*(bar@GOT) 被填充的是“push n” 这一行代码的地址,也就是会跳到这一行执行
  2. push n,以及push moduleID 是把bar 函数这个符号在.rel.plt 中的下标,以及函数所在的模块id入栈,也就是作为两个入参,跳到 _dl_runtime_resolve 运行。
  3. _dl_runtime_resolve 会将bar() 的真正地址填入 bar@GOT。
  4. 下次执行bar 函数时,会在jmp *(bar@GOT) 处直接跳到.so 中的bar函数处执行。

更详细的PLT 跳转过程分析

将如下代码编译并反汇编。

zqxl@ubuntu:~/work/practice/link_load_and_lib_learn/_7.4_plt$ cat plt.c 
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
	puts("This is a plt test.\n");
	exit(0);
}
zqxl@ubuntu:~/work/practice/link_load_and_lib_learn/_7.4_plt$ 
zqxl@ubuntu:~/work/practice/link_load_and_lib_learn/_7.4_plt$ gcc -m32 -no-pie -g -o plt plt.c
zqxl@ubuntu:~/work/practice/link_load_and_lib_learn/_7.4_plt$ 
zqxl@ubuntu:~/work/practice/link_load_and_lib_learn/_7.4_plt$ objdump -ds plt > plt.s

gcc时的参数含义:
-m32:表示编译成32位,
-no-pie:表示生成非地址无关可执行程序。pie的可执行程序可以被装载到任意虚拟地址,-no-pie 的可执行程序被装载的地址在ld 阶段就已经确定下来了。注意,这里的-no-pie 是指的编译出得可执行程序本身,这个可执行程序仍然可以使用动态库。
objdump 时的参数含义:
-d:仅反汇编代码段(-D会把非代码段反汇编,本来就不是代码反汇编成汇编代码有啥意义?)
-s:显示所有内容。因为我们要关注.got段的内容。
查看main 函数:
在这里插入图片描述
可以看到调用put 函数的位置,实际调用的是puts@plt,也就是 .plt段中的puts 函数。那就看一下puts@plt
在这里插入图片描述
可以看到puts@plt 会跳到一个地址上去,这个地址被保存在0x804a00c这个位置,那就看一下0x804a00c这个地址存了什么
在这里插入图片描述
可以看到0x804a00c这个地址在.got.plt段,其内容是0x08048306(objdump 显示16进制时,地址按照byte从左到右递增,因此上图红圈中的“06830408”其实是0x08048306)。也就是说plt.S的line296 会跳到line297 去执行。
接下来,line298 跳到line290,然后跳到*0x0804a008(注意此处仍然是以0x0804a008处保存的内容作为地址跳过去)
在这里插入图片描述
此时,从上边line130行可以看到,0x0804a008这个地址上是零,显然0地址不会有指令,那么这个地址肯定是在加载时填充的,下边通过gdb 验证一下。
在这里插入图片描述
果然,如上图所示,0x0804a008这个地址上已经有了一个地址了,看一下0xf7fead40 这个地址属于哪个模块
在这里插入图片描述
显然,这个地址是ld-2.27.so中的代码。也就是说,此时应该就是跳到了 _dl_runtime_resolve 去执行了。运行完puts@plt。此时再查看0x804a00c中保存的地址
在这里插入图片描述
发现这个地址在 /lib32/libc-2.27.so 中,应该就是直接跳到了真正的puts 函数去执行了。至此,我们已经通过实验验证了书中的描述了。

实验小节

  1. main 函数调用puts,其实是调到了 puts@plt
  2. puts@plt 的第一行代码会使用.got.plt 段中保存一个地址,准确来说是跳到那个地址去执行
  3. 第一次运行puts@plt 时,这个地址就是puts@plt的第二行
  4. puts@plt 后边的代码会跳到ld.so 中的 _dl_runtime_resolve 进行延迟绑定,会将.got.plt 段中的那个地址修改为libc.so 中真正的puts 所在地址
  5. 再次调用puts@plt 时,会在第一行直接跳转到 libc.so 中的puts 函数。

参考:
延迟绑定(PLT)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值