地址无关代码详解
00 前言
在计算机领域中,地址无关代码(position-independent code,PIC),又称地址无关可执行文件(position-independent executable,PIE),是指可在主存储器中任意位置正确地运行,而不受其绝对地址影响的一种机器码。PIC广泛使用于共享库,使得同一个库中的代码能够被加载到不同进程的地址空间中。PIC还用于缺少内存管理单元的计算机系统中,使得操作系统能够在单一的地址空间中将不同的运行程序隔离开来。
通过这种方式,无限多个进程可以共享一个共享模块的代码段的单一副本,用户可以对 GCC 使用 -fpic 选项指示 GNU 编译系统生成 PIC 代码。
01 PIC 数据引用
编译器在数据段开始的地方创建了一个表,叫做全局偏移量表(Global Offset Table, GOT) 。在 GOT 中,每个被这个目标模块引用的全局数据目标(过程或全局变量)都有一个 8 字节条目。编译器还为 GOT 中每个条目生成一个重定位记录。在加载时,动态链接器会重定位 GOT 中的每个条目,使得它包含目标的正确的绝对地址。每个引用全局目标的目标模块都有自己的 GOT。

02 PIC 函数调用
使用了“延迟绑定”解决这个问题,将过程地址的绑定推迟到了第一次调用该过程时
该技术的动机:
对于共享库,应用程序通常只会使用其中很少的一部分,因此该方法可以避免不需要的重定位。
第一次调用,进行绑定的时候开销很大,但是其后的每次调用都只会花费一条指令和一个间接的内存引用。
使用了两个数据结构:
GOT 全局偏移量表——数据段的一部分
GOT 是一个数组,每个条目都是 8 字节
PLT 过程链接表(Procedure Linkage Table)——代码段的一部分
PLT 是一个数组,每个条目都是 16 字节
每个被可执行程序调用的库函数都有他自己的 PLT 条目
PLT[0]跳转到动态链接器中PLT[1]调用系统启动函数__libc_start_main,初始化执行环境,调用main函数并处理其返回值PLT[2]开始,调用用户代码调用的函数

上图展示了调用 addvec 的原理:
程序调用进入
PLT[2]第一次跳转到下一条指令,将
addvec的 ID 压入栈中,然后跳转到PLT[0]跳转到动态链接器中,使用两个栈条目确定
addvec的运行时位置,重写GOT[4],再跳回addvec后续再调用进入
PLT[2]就可以直接跳转到addvec了
参考资料:
《深入理解计算机系统》P489
- 感谢你赐予我前进的力量

