链接器的世界/ 实验篇/ 16 篇
6 分钟阅读公开阅读

[链接器的世界-实验篇09] 不知道加载地址时怎么链接:自重定位的静态 PIE

本实验生成自重定位的静态 PIE。Linux 可以把主映像放到不同基址;提供的启动代码在用户入口之前修正数据指针,程序不依赖 libc 或动态加载器。位移与绝对地址的区别见原理篇 04,加载偏移、目标类别与启动责任见原理篇 06。

起点:class9 代码与测试,位于 private 仓库,需要访问权限。复用 class8::prepare 的归档选择、符号解析和 GC,以及 class6 的布局。startup.S、构建脚本和 CLI 已提供。构建与测试在原生 x86-64 Linux 上执行,需要 GNU as 和 objcopy。

实现任务

任务接口要达到的结果
C9.1patch应用静态补丁,或将加载期指针字段清零并返回 Relative
C9.2encode_relative按目标 RVA 排序输出 RELA,拒绝重叠与越界写入范围
C9.3emit校验保留区域与入口,填写启动描述符,输出 ET_DYN
C9.4link从 Prepared 统计运行时记录、预留空间、布局、修补并输出 RVA map

完整类型、支持矩阵和检查规则见 class9 README。Value 区分 Absolute 与 ImageRva,即使数值相同也不能互换;Fixup.offset 是贡献切片内的位置,place_rva 是这个字段在映像中的 RVA。class6 的地址转成 RVA 时减去 BASE,ABS 常量不做这项转换。

本章只允许在已初始化 Data 中修正加载期指针;只读内容、BSS 和段间隙不是合法目的地。NONE 无条件不操作,其他失败必须保持原字节;同时违反多条规则时,patch 不规定错误优先级。

BOOT 是项目提供的 160 字节启动块,后 32 字节保存四个小端 u64:BOOT RVA、RELA RVA、记录条数和用户入口 RVA。具体范围与保留贡献的布局要求在 README 中。启动代码据此恢复实际基址、应用记录并跳到用户入口,保留初始 rsp、清零 rdx。这份自定义描述符不属于通用 ELF ABI。

实现时保持的一条不变量链

把实现拆成四个阶段,并让每个阶段只改变自己负责的坐标:Prepared 仍保存输入身份;class6 的布局结果用固定 BASE 表示;patch 在贡献切片内处理静态值或把可随装载基址变化的字段变成 Relative;encode_relative 只负责确定性的 RELA 字节;emit 最后才把 BOOT、RELA 和入口描述写入 ET_DYN。只要把 RVA、贡献内偏移和文件偏移混在一个整数里,低地址样例仍可能通过而 ASLR 或跨贡献边界会失败。

验收中的指针初始化、BSS/COMMON、非 PIC 拒绝分别覆盖这条链的三种分支:静态值、运行时相对值和不允许的绝对引用。先在 patch 的独立测试里验证原子性,再看 emit 的保留区域和真实执行;这样可以判断错误来自算术、存储位置还是启动代码。

从任务到测试

任务关键测试测试所证明的性质
C9.1 patchstatic_displacements_and_absolute_values、runtime_pointer_and_failure_atomicity静态值直接写入;运行时指针只产生 Relative;失败不会留下半次修改。
C9.2 encode_relativerelative_record_encoding、relative_record_rejections记录按 RVA 编码,重叠、越界和非法预留都会被拒绝。
C9.3 emitpie_headers_and_descriptor、emission_rejects_bad_destinations输出是无解释器的 ET_DYN,启动描述符与保留区域一致,写入目的地属于可写 Data。
C9.4 linkruntime_addresses_vary_with_aslr、archive_calls_and_initial_stack同一镜像可在不同基址启动,并保留归档选择、初始栈和入口约定。

先读合同测试确认局部不变量,再读原生测试确认这些不变量在真实执行中仍成立;只通过其中一组不能替代另一组。

验收及其含义

在克隆后的仓库根目录执行:

cargo test --locked -p class9
cargo test --locked -p class9 --release
python3 scripts/grade.py class9

合同测试检查目标类别、补丁原子性、记录排序和重叠、描述符、程序头与合法目的地。原生测试检查代码和数据指针、BSS/COMMON、ABS/WEAK、负加数、归档与 GC、初始栈,以及非 PIC 引用和只读补丁的拒绝。

ASLR 用例执行 12 次,读取程序报告的入口地址,要求出现至少两个不同地址;并不要求每次都不同。该用例要求系统启用 ASLR。结构检查要求 ET_DYN、无 PT_INTERP/PT_DYNAMIC,并保持既定段权限。通过这些检查说明提供的程序在实际不同基址下可运行,不意味着支持任意动态链接程序或共享库。

如果在 macOS 或非 x86-64 Linux 上执行,build.rs 会在编译阶段主动返回 class9 requires native x86-64 Linux。这是启动汇编和原生执行验收的环境合同,不是 patch、emit 或链接逻辑的测试失败;应把仓库放到原生 x86-64 Linux(可通过 SSH 连接)后再运行本节命令,不能用交叉编译结果替代这项验收。