plt&got
一些前置
怕自己忘记,写点前置知识
静态链接的函数地址在编译或者链接期间就确定了。动态链接的函数地址需要在运行时确定。
静态链接是把库直接链接到可执行文件,跟自己写的代码没什么区别。地址空间在用户空间。不同应用用同一份库,会各自复制一份。库函数的符号地址固定。不需要重新加载。
动态链接是放到系统空间,各应用共用一份,动态库的符号表需要重新计算,加载位置可能每次都不一样。
plt&got
1 |
|
1 | $ gcc test.c -o test -m32 |
demo函数:
1 | 000011cd <demo>: |
因为打印了以\n
结尾的字符串,所以printf
自动优化成了puts
。
demo
函数调用的puts
函数,因为put
函数位于libc动态库内,所以必须在程序运行起来之后,才能知道puts
函数的加载地址。
Q.当进程运行之后,glibc动态库装载之后,我们上面的call怎么修改地址呢?
- 现代操作系统不允许修改代码段,只能修改数据段
- 如果demo函数在一个动态库里,修改了代码段,就无法做到系统内所有进程共享同一个动态库。
所以puts函数只能在运行时写到数据段内。
链接阶段是将一个或者多个中间文件(.o文件)通过链接器将它们链接成一个可执行文件。
- 各个中间文件(
.o
文件)之间的同名section合并 - 对代码段,数据段以及各符号进行地址分配
- 链接时重定位修正(调用puts函数的地址会在链接时进行修正,这个过程就被称为链接时重定位)
除了重定位过程,其它动作是无法修改中间文件中函数体内指令的,而重定位过程也只能是修改指令中的操作数,换句话说,链接过程无法修改编译过程生成的汇编指令。
如果puts
函数在普通的.o
文件中定义,那么在链接阶段,它的地址就会被确定。但是如果在动态链接库中,则无法进行链接时重定位。
前面说过,程序运行时不能用重定位修改代码段,所以链接器会生成一段代码,通过这段代码获取动态库的链接地址,并完成调用。
1 | .text |
动态链接需要用来存放外部函数的数据段,还有获取数据段地址的代码。
存放外部函数地址的段就是GOT(Global Offset Table),而那段获取地址的代码就是PLT。
之前有个疑问是,为什么需要plt,而不能直接返回到got。现在来看其实是got是数据段,没有执行权限,必须有plt代码段进行jmp执行。然后got表存储在数据段,不会影响程序执行的效率,有些不需要使用的函数不会被调用。
.got.plt 是GOT(Global Offset Table)的一部分,另一部分是 .got。
.got 存放全局变量引用地址。.got.plt 存放函数引用地址,也就是与PLT表一起起作用的辅助部分,对于外部函数的引用全部被分离出来放到了 .got.plt 中
最后
感谢海枫师傅的文章,虽然只看了一章,但是受益匪浅