linux 下的动态链接是通过 PLT&GOT 来实现的,这里做一个实验,通过这个实验来理解
使用如下源代码 test.c:
依次使用下列命令进行编译:
这样除了原有的 test.c 还有个 test.o 以及可执行文件 test
通过 objdump -d test.o 可以查看反汇编
printf () 和函数是在 glibc 动态库里面的,只有当程序运行起来的时候才能确定地址,所以此时的 printf () 函数先用 fc ff ff ff 也就是有符号数的 -4 代替
运行时进行重定位是无法修改代码段的,只能将 printf 重定位到数据段,但是已经编译好的程序,调用 printf 的时候怎么才能找到这个地址呐?
链接器会额外生成一小段代码,通过这段代码来获取 printf () 的地址,像下面这样,进行链接的时候只需要对 printf_stub () 进行重定位操作就可以
总体来说,动态链接每个函数需要两个东西:
1、用来存放外部函数地址的数据段
2、用来获取数据段记录的外部函数地址的代码
对应有两个表,一个用来存放外部的函数地址的数据表称为全局偏移表(GOT, Global Offset Table),那个存放额外代码的表称为程序链接表(PLT,Procedure Link Table)
可执行文件里面保存的是 PLT 表的地址,对应 PLT 地址指向的是 GOT 的地址,GOT 表指向的就是 glibc 中的地址
那我们可以发现,在这里面想要通过 plt 表获取函数的地址,首先要保证 got 表已经获取了正确的地址,但是在一开始就进行所有函数的重定位是比较麻烦的,为此,linux 引入了延迟绑定机制
只有动态库函数在被调用时,才会地址解析和重定位工作,为此可以使用类似这样的代码来实现:
说明一下这段代码工作流程,一开始,printf@got 是 lookup_printf 函数的地址,这个函数用来寻找 printf () 的地址,然后写入 printf@got,lookup_printf 执行完成后会返回到 address_good,这样再 jmp 的话就可以直接跳到 printf 来执行了
也就是说这样的机制的话如果不知道 printf 的地址,就去找一下,知道的话就直接去 jmp 执行 printf 了
接下来,我们就来看一下这个 “找” 的工作是怎么实现的:
通过 objdump -d test > test.asm 可以看到其中 plt 表项有三条指令
ps. 这里 plt 表的第一项使用 objdump 的时候给没有符号名的一项自动改成了离他最近的一项,为了避免引起误会,改成了 common,而且随着不断深入,会发现,确实可以叫 common
其中除第一个表项以外,plt 表的第一条都是跳转到对应的 got 表项,而 got 表项的内容我们可以通过 gdb 来看一下,如果函数还没有执行的时候,这里的地址是对应 plt 表项的下一条命令,即 push 0x0
(说一下怎么查看,先 gdb test 然后 b main,再 run, 再 x/x jmp的那个地址 就可以)
还记得之前我们说的,在还没有执行过函数之前 printf@got 的内容是 lookup_printf 函数的地址吗,这就是要去找 printf 函数的地址了
现在要做的是:
接下来继续
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!