可以架在你而无需重定位的代码成为位置无关代码(Position-Independent Code,PIC)。用户对GCC使用-fpic选项指示GNU编译系统生成PIC代码。动态链接库的编译必须总是使用该选项。
在x86-64系统中,对同一个目标模块中符号的引用是不需要特殊处理使之成为PIC。可以用PC相对寻址来编译这些引用,构造目标文件时由静态链接器重定位。然而,对动态链接库定义的外部过程和对全局变量的引用需要一些技巧。
无论我们在内存中何处加载一个目标模块,数据段与代码段的距离总是保持不变。因此,代码段中任何指令和数据段中任何变量之间的距离都是一个运行时常量,与代码段和数据段的绝对内存位置是无关的。
所以想要生成对全局变量PIC引用,可以在数据段开始的地方构造一张全局偏移量(Global Offset Table,GOT)。在GOT中,每个被这个目标模块引用的全局数据都有一个由编译器生成的重定位记录。在加载时,动态链接库会重定位GOT中的每个条目,使它包含目标的正确的绝对地址。运行的时候,根据数据段和代码段之间距离保持不变的特点,使用PC相对引用得到GOT表中的绝对地址即可。
假设程序调用有一个动态链接库定义的函数。编译器无法预测这个函数的运行时地址,因为定义它的共享模块再运行时可以加载到任意位置。GNU编译系统使用一种称为延迟绑定的技术将过程地址的绑定推迟到第一次调用该过程时。
使用延迟绑定的动机是对于一个像libc.so这样的动态链接库中包含很多函数,而一个应用程序往往只会使用到其中的小部分。把函数地址的解析推迟到它实际被调用的地方,能避免动态链接库在加载时对没有使用的函数进行不必要的重定位。第一次调用过程运行时开销很大,但是其后的每次调用只会花费一条指令和一个间接的内存引用。
延迟绑定技术使用到两个数据结构:
上图展示了,延迟绑定的具体步骤:
参考书籍:《深入理解计算机系统(第三版)》