位置无关代码
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 了
参考资料:
- 地址无关代码 https://baike.baidu.com/item/%E5%9C%B0%E5%9D%80%E6%97%A0%E5%85%B3%E4%BB%A3%E7%A0%81/22702477
- 《深入理解计算机系统》P489
评论区