Linker的核心任务是解析ELF/COFF格式并处理重定位,而非简单拼接二进制;需依赖libelf等成熟库解析目标文件,正确执行符号解析、地址分配与重定位填空,手工实现易出错且不可靠。

Linker 的核心任务不是“写个程序”,而是理解 ELF/COFF 格式与重定位表
你无法用几行 C++ 从零写出生产级 linker(如 ld 或 lld),因为 linker 不是简单拼接二进制——它必须解析目标文件格式(Linux 下主要是 ELF)、读取符号表(.symtab)、处理重定位条目(.rela.text, .rela.data)、解决外部引用、分配虚拟地址、填充 GOT/PLT,并生成可执行头。直接手写等于重实现 binutils 的一半逻辑。
如果你的目标是理解 linker 行为或做轻量实验,正确路径是:复用现有解析库 + 实现关键重定位逻辑,而非从头造轮子。
用 libelf 和 libbfd 解析目标文件比手撕二进制靠谱得多
手动 fread 读 ELF header、section header、symbol table 容易错位(比如忽略 e_ident 字节序、漏掉 sh_offset 偏移计算、混淆 st_value 是绝对地址还是 section 相对偏移)。真实项目都依赖成熟库:
-
libelf(来自 elfutils):轻量、POSIX 友好,适合只读解析;需用elf_begin()、elf_getscn()、gelf_getsym()逐层访问 -
libbfd(来自 binutils):支持 ELF/COFF/Mach-O,但 ABI 不稳定、文档差;bfd_openr()+bfd_check_format()+bfd_canonicalize_symtab()是基础链 - 不推荐用
std::ifstream+ 自定义 struct 读 ELF:Elf64_Shdr对齐、padding、动态节区顺序都会导致sizeof(Elf64_Shdr)≠ 实际磁盘长度
重定位的本质是“填空”:找到指令中的占位符并写入真实地址
例如 x86-64 下一条 call func@PLT 编译后生成的机器码里,调用目标地址是 0(占位),对应一个 R_X86_64_PLT32 重定位项;linker 必须:
① 查出 func 的最终运行时地址(可能来自另一个 .o 文件的 .text 段起始 + 符号偏移)
② 按该重定位类型公式计算填入值(如 S + A - P,其中 S=符号地址, A=加数, P=重定位点地址)
③ 把结果按字节序写回目标文件的 .text 数据块指定偏移处
常见陷阱:
- 忽略重定位类型差异:
R_X86_64_64(写 8 字节)和R_X86_64_PC32(写 4 字节,带符号扩展)不能混用 - 符号未定义(
UND)却强行解析:必须先做符号合并(STB_GLOBAL跨文件覆盖规则)、弱符号(STB_WEAK)处理 - 段地址未对齐就写入:若输出段
.text要求 16 字节对齐,但重定位点落在第 3 字节,直接写会破坏对齐约束
真正能跑通的最小可行 linker 需要至少三类输入:.o 文件、符号定义列表、内存布局描述
你可以用 C++ 写一个“链接脚本解释器 + 重定位执行器”,但必须依赖外部信息:
- 输入:一组
.o(用libelf解析),一个简易链接脚本(如SECTIONS { . = 0x400000; .text : { *(.text) } }) - 过程:遍历所有
.o的.symtab合并全局符号 → 按脚本分配各段基址 → 扫描所有.rela.*节区 → 对每个重定位项查符号地址、算值、覆写目标段数据 - 输出:构造新 ELF 文件头、程序头(
PT_LOAD)、写入拼合后的.text/.data内容 —— 这步最容易出错,因为e_phoff、e_shoff、sh_offset全要手工计算且严格对齐
哪怕只支持两个不含外部库调用的 .o 文件(比如一个定义 add(int,int),一个调用它),你也得处理符号索引重映射、重定位偏移修正、节区字符串表(.shstrtab)重建 —— 这些细节在 readelf -S 和 objdump -dr 输出里藏得很深,不对照看根本没法调试。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











