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

[链接器的世界-实验篇05] 跨越文件的调用:合并代码,再填写位移

本实验把多个代码目标文件链接为一个可执行文件,使一个文件中的调用能够到达另一个文件中的定义。重定位公式、字段地址 P 和 call 的 -4 addend 见原理篇 04。

实现数值检查前,先读同章的位表示与扩展检查:它解释 64 位模运算、32 与 32S 的差别,以及为什么大的无符号地址也可能符合有符号字段的编码要求。

起点:class5 代码与测试,位于 private 仓库,需要访问权限。复用 class0–4,在 class5 crate 的库接口中实现三个接口。输入对象不可修改;CLI 已提供。

实现任务

任务接口要达到的结果
C5.1merge按输入顺序与对齐合并代码,记录每个 (file, section) 的贡献位置
C5.2apply验证并写入一个重定位字段;任何错误都保持目标缓冲区不变
C5.3link连接解析、全局选择、布局、地址换算、重定位和 ELF 写出

完整合同见 class5 README。merge 保留空贡献的身份,对齐零按一处理;重复身份非法,最终代码必须非空。贡献位置是合并代码内部的偏移,尚未加上输出头部或虚拟基址。

apply 支持 NONE、64、PC32、PLT32、32、32S。这里的 PLT32 使用静态直接绑定。offset 是目标切片内的位置,P 是字段的运行时地址,S 是选中符号的运行时值;不能用下一条指令地址代替 P。NONE 不访问字段。其他类型依次检查类型、字段边界、数值范围,验证完成后才写字节。

每个输入要求一张符号表;支持 PROGBITS/flags=6 的代码贡献,其他非空已分配节与 COMMON 不支持。RELA 必须关联本对象的符号表并指向输出代码贡献;非空 REL 和调试节重定位被拒绝。写入必须限制在单个贡献的切片中,不能越过它修改相邻代码。通常的符号引用允许节末尾标签,入口仍必须严格位于代码内。

把一次 PC32 写入具体化:若重定位字段的运行时地址 P = 0x4000b1,目标符号的运行时值 S = 0x4000c0,显式加数 A = -4,则写入值为 S + A - P = 0x0b。字段占 4 字节,下一条指令地址是 P + 4 = 0x4000b5,所以执行时 0x4000b5 + 0x0b = 0x4000c0。把 P 错写成下一条指令地址会少减 4,恰好形成最难发现的偏移错误。

验收及其含义

在克隆后的仓库根目录、原生 x86-64 Linux 上执行:

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

合同测试检查对齐、身份、负 addend、32 位扩展规则、64 位模运算以及失败时不改写字节。原生测试分别汇编入口、编译 C 函数,交换两份对象的输入顺序后链接并执行;还检查绝对地址调用、未解析 WEAK、缺失定义、禁止的数据和 CLI。

跨文件执行正确说明定义选择和地址换算能够配合;相邻贡献越界测试另外防止错误字段碰巧落在输出缓冲区内而被接受。交换输入顺序可以改变布局,验收要求行为正确,不要求产物字节完全相同。