Fixup是LLVM后端对“待修补位置”的内部抽象,当MCCodeEmitter遇到无法立即求值的符号表达式(如sym+4)时,生成MCFixup记录偏移、重定位类型和表达式,由AsmPrinter触发并最终写入目标文件的.rela.*节。

LLVM 后端处理 Fixup 和重定位,本质是把“地址未定”的指令或数据引用,在目标文件生成阶段打上标记,留待链接器(如 lld)或加载器填入最终地址。这不是后端一次性算出地址,而是分阶段协作:后端负责识别哪些地方需要修补、生成何种重定位类型、写进目标文件的 .rela.* 节;链接器负责解析符号、计算地址、执行修补。
什么是 Fixup?它在后端里怎么出现
Fixup 是 LLVM 后端内部对“待修补位置”的抽象,不是目标文件格式里的标准术语,而是 MCExpr / MCFixup 机制中的一环。当你在 MCInst 指令里用到一个尚未确定地址的符号(比如函数名、全局变量),MCCodeEmitter 无法直接编码成机器码,就会生成一个 MCFixup 实例,附着在该指令的操作数上。
-
MCFixup记录了三要素:要修补的字节偏移、要应用的重定位类型(如R_CPU0_32)、原始表达式(如sym + 4) - 它通常由
AsmPrinter::EmitInstruction()触发,在调用MCCodeEmitter::encodeInstruction()时,遇到无法立即求值的MCExpr就创建MCFixup - 同一个指令可能有多个
Fixup,例如一条取地址指令同时含符号和偏移:lw $t0, sym+8($zero)
重定位类型怎么定义和使用
重定位类型(如 R_X86_64_PC32、R_AARCH64_ABS64)必须与目标架构严格匹配,由后端在 TargetInfo 子系统中注册,并在 MCCodeEmitter 或 ELFObjectWriter 中映射到具体语义。
- 定义位置通常在
include/llvm/BinaryFormat/ELF.h(通用)或lib/Target/Cpu0/MCTargetDesc/Cpu0FixupKinds.h(自定义) - 每个
Fixup必须绑定一个FK_*枚举值(如FK_CPU0_32),再由Cpu0ELFObjectWriter::getRelocType()映射为 ELF 标准重定位码 - 错误常见于:自定义后端漏实现
getRelocType(),或传入了不支持的FixupKind,导致llc -filetype=obj报错unhandled fixup kind - 向量指令、跳转范围受限指令(如 RISC-V 的
beq)常需特殊重定位类型来支持短跳转优化或 relax 处理
后端如何把 Fixup 写进目标文件
当 llc 以 -filetype=obj 运行时,AsmPrinter 使用 MCObjectStreamer,最终交由 MCObjectWriter(如 ELFObjectWriter)落盘。此时所有 Fixup 被转换为重定位表项(Rela 条目),写入 .rela.text 或 .rela.data 等节。
- 每个
Rela条目含:被修补地址(r_offset)、符号索引(r_info)、加数(r_addend) -
r_addend来自MCFixup原始表达式的常量部分,例如sym + 12的12就进r_addend,而非硬编码在指令流里 - 若后端启用了
MCAsmBackend::shouldForceRelocation()返回true(如处理弱符号、TLS),即使看似可立即编码的位置也会强制生成Fixup - 注意:
llc -filetype=asm不触发Fixup处理,所有符号以伪操作(如.quad sym)形式输出,留给汇编器(as)二次处理
真正容易被忽略的是 Fixup 的生命周期——它只存在于 MCInst → ObjectFile 这一瞬;一旦写入 .o,就变成静态的重定位表项,后端代码再也无法干预。这意味着所有符号解析逻辑、地址计算假设(如是否 PIC、是否小内存模型)必须在 MCCodeEmitter 和 AsmPrinter 阶段就完全确定,不能指望链接器“反过来改指令”。

















