在 LLVM 的 MC(Machine Code)层中,Fixup(修复项)是用于处理指令编码过程中无法立即确定的符号引用或地址偏移的机制。当生成机器代码时,某些指令的操作数(如跳转目标地址、数据符号的偏移量等)可能在汇编阶段无法确定(例如,符号可能位于其他模块或需要链接时才能确定)。此时,LLVM 会生成一个 Fixup 记录,标记这些需要后续修正的位置,并在最终生成目标文件时完成地址计算或重定位。
Fixup 的核心概念
-
问题场景
- 例如,一条跳转指令的目标地址可能暂时未知(如跨函数的跳转或外部符号引用)。
- 汇编器无法在首次生成指令时确定正确的偏移量,但需要预留位置并记录如何修正。
-
Fixup 的作用
- 在指令编码过程中,标记需要后续修正的位置(指令中的偏移)和修正类型(如何计算正确的值)。
- 最终在生成目标文件时,通过重定位(Relocation)或链接器完成修正。
-
Fixup 与重定位(Relocation)的关系
- Fixup 是汇编器内部的概念,用于在生成机器码时记录需要修正的位置。
- 重定位 是目标文件(如 ELF、COFF)中的条目,指导链接器如何修正地址。Fixup 最终可能转换为重定位条目。
Fixup 的实现细节
-
MCFixup 类
- 表示一个具体的修复项,包含以下信息:
- Offset:指令中需要修正的位置(相对于指令起始的字节偏移)。
- Value:需要修正的符号表达式(如
Symbol + Offset
)。 - Kind:修复类型(
MCFixupKind
),决定如何计算修正值(例如,绝对地址、相对偏移、符号差等)。
- 表示一个具体的修复项,包含以下信息:
-
MCFixupKind
- 枚举类型,定义不同的修复语义。例如:
FK_Data_4
:修正为 4 字节的绝对地址。FK_PCRel_4
:修正为 4 字节的相对地址(PC-relative)。
- 不同目标架构可能定义自己的 Fixup 类型(如
X86::fixup_foo
)。
- 枚举类型,定义不同的修复语义。例如:
-
MCCodeEmitter 中的 Fixup 生成
- 当编码指令时,如果发现操作数需要延迟计算,代码发射器(
MCCodeEmitter
)会生成一个MCFixup
。 - 例如,编码一条
call
指令时,若目标函数地址未知,会生成一个 Fixup 记录偏移和类型:// 伪代码示例:在编码 call 指令时生成 Fixup void MyArchMCCodeEmitter::encodeCall(const MCInst &MI, raw_ostream &OS, SmallVectorImpl<MCFixup> &Fixups) { // 1. 写入临时的占位值(如全0) OS.write(0x00000000, 4); // 假设 call 的地址占4字节 // 2. 生成 Fixup,标记偏移和类型 Fixups.push_back(MCFixup::create(OS.tell() - 4, TargetExpr, MCFixupKind(X86::fixup_pcrel32))); }
- 当编码指令时,如果发现操作数需要延迟计算,代码发射器(
-
MCAsmBackend 与 Fixup 处理
MCAsmBackend
负责将 Fixup 转换为目标文件中的重定位条目(如 ELF 的R_X86_64_PC32
)。- 在指令松弛(Relaxation)过程中,可能需要调整 Fixup(例如,短跳转扩展为长跳转时,修正偏移和类型)。
Fixup 的典型应用场景
-
跳转指令的 PC 相对偏移
jmp foo # 目标符号 foo 的地址可能尚未确定
- 汇编器生成一个
FK_PCRel_4
Fixup,记录跳转指令的偏移位置。
- 汇编器生成一个
-
全局符号的绝对地址引用
movl $foo, %eax # 加载符号 foo 的绝对地址
- 生成
FK_Data_4
Fixup,后续转换为绝对地址重定位。
- 生成
-
跨段的数据引用
.long foo - bar # 符号差(符号地址的差值)
- 生成
FK_SecRel_4
Fixup,表示段内相对偏移。
- 生成
示例:x86 中的 Fixup
假设有以下 x86 汇编代码:
callq my_function
- 在编码
callq
时,my_function
的地址可能未知。 - 汇编器生成一个
MCFixup
,记录:- Offset:
callq
指令的操作数字段位置(例如指令的第 2 字节,假设操作码占1字节)。 - Value: 符号
my_function
。 - Kind:
X86::fixup_pcrel32
(32 位 PC 相对偏移)。
- Offset:
- 在生成目标文件时,
MCAsmBackend
将其转换为 ELF 重定位类型R_X86_64_PLT32
,指导链接器计算正确的偏移。
Fixup 的工作流程
- 编码阶段
- 代码发射器(
MCCodeEmitter
)生成指令的二进制占位值,并记录 Fixup。
- 代码发射器(
- 汇编阶段
- 汇编器(
MCAsmBackend
)收集所有 Fixup,生成重定位条目写入目标文件。
- 汇编器(
- 链接阶段
- 链接器根据重定位条目修正地址,填充正确的值。
总结
LLVM 的 MC Fixup 是处理机器代码生成过程中延迟地址计算的核心机制。它通过记录需要修正的位置和类型,支持跨模块符号引用、动态地址计算和指令优化(如分支松弛),最终通过重定位确保生成正确的可执行代码。理解 Fixup 是掌握 LLVM 汇编器和目标文件生成原理的关键。