浅析动态链接中GOT与PLT的工作方式

前言 动态链接是一种高效且节省空间的程序间共享代码方式。若程序使用静态链接方式,则程序所有代码都将集成到同一个二进制文件中,其优点在于无依赖关系,可以在不同运行环境的OS下运行。但是缺点也十分明显,由于二进制文件中包含全部代码,所以所占空间较大;如果多次运行同一个程序,则OS可能会对某个库函数进行多次重复 的加载,占用了不必要的内存;若某个公用的库函数产生了更新,则需要重新编译所有使用了该库的程序,工作量较大。 静态链接的一个典型的例子就是Golang,其默认所有程序都是使用静态链接的方式,包含有所有使用到的Golang库函数,因此使用Golang编写的程序因为具有优秀的可移植性和开箱即用受到较多好评。但较为直观的也能看见上面所说的缺点:Linux x86_64下,一个Golang编写的HelloWorld二进制文件占用空间为1.7MB。 而为了解决静态链接存在的重复加载、重复编译等问题,引入了动态链接的方式。使用动态链接的程序不包含库函数的代码,库函数通过动态链接库(.so)的形式独立存在。当程序开始运行并产生外部函数调用时,动态链接器将承担加载动态链接库和重定位函数地址、变量地址的工作,在运行时确定外部函数地址和变量的值,也叫惰性加载。动态链接能够减少程序的启动时间(程序占用空间变小),且动态链接器也不会产生较多额外的性能开销,因此动态链接还是如今比较广泛应用的一种链接方式。 为了支撑动态链接这一工作过程,在ELF文件中有4个Section与之相关: .got:全局偏移表(Global Offset Table),用于存储外部符号的绝对地址,由链接器进行填充。 .plt:过程链接表(Procedure Linkage Table),存有从.got.plt中查找外部函数地址的代码,若是第一次调用该函数,则会触发链接器解析函数地址并填充在.got.plt相应的位置;若函数地址已经存储在.got.plt中则直接跳转到对应地址继续执行。 .got.plt:GOT中专用于PLT存储外部函数地址的部分,是属于GOT的一部分。 .plt.got:不知道干啥用的,可能只是为了名字的对称…… 下面将对基于GOT和PLT来进行外部符号地址重定向的工作方式进行分析。为了便于演示过程,编写了两个C文件,一个编译为共享的动态链接库,另一个是可执行程序。代码和编译命令如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 // main.c // gcc -g -m32 -no-pie -L. main.c lib.so -o main #include <stdio.h> static int a; extern int b; extern void external(); void internal() { printf("[*] INT\n"); } int main(void) { printf("a = %d, b = %d\n", a, b); internal(); external(); return 0; } // lib....

Aug. 12, 2022 · 5 min · 960 words