LLVM后端通过目标特定的Lowering逻辑,在Reloc::PIC模式下将GlobalAddressSDNode等节点转为GOT/PLT访问或PC-relative指令序列,如x86-64生成movq foo@GOTPCREL(%rip), %rax,而非绝对地址加载。

LLVM后端如何生成 PIC 指令序列
LLVM 后端本身不直接“支持”-fPIC这种编译选项,而是通过目标特定的代码生成逻辑,在指令选择、地址计算和重定位模型中配合前端传递的 PIC 模式(如 TargetMachine::getRelocationModel() == Reloc::PIC)来生成符合位置无关语义的机器指令。
关键在于:后端必须为每个目标架构实现正确的 PC 相对寻址模式、全局偏移表(GOT)访问序列、以及对符号引用的重定位类型标注(如 R_X86_64_GOTPCREL、R_AARCH64_PREL64 等)。
- 当
Reloc::PIC被启用时,后端在 loweringGlobalAddressSDNode或ExternalSymbolSDNode时,会避开直接生成绝对地址加载,转而插入 GOT 查找或 PC-relative load 序列 - 例如在 X86-64 上,
movq foo@GOTPCREL(%rip), %rax这类指令由 SelectionDAG 的LowerGlobalAddress方法生成,而非movq $foo, %rax - ARM64 使用
adrp+add组合实现 PC-relative 地址计算;RISC-V 使用auipc+addi,这些都需在目标 Lowering 函数中显式支持
SelectionDAG 中的 PIC lowering 必须覆盖哪些节点
不是所有全局引用都会被自动转成 PIC 形式。后端需在 TargetLowering::LowerOperation 或专用 lowering 函数(如 LowerGlobalAddress、LowerConstantPool、LowerBlockAddress)中显式处理以下节点类型:
-
GlobalAddressSDNode:对应全局变量、函数地址取址(如&printf)——必须生成 GOT 入口访问或 PC-relative load -
ExternalSymbolSDNode:对应未定义外部符号(如调用malloc)——需生成 PLT stub 调用或 GOT+call 序列 -
ConstantPoolSDNode:常量池项(如字符串字面量)——在 PIC 模式下不能硬编码地址,需通过 GOT 或 PC-relative offset 访问 -
JumpTableSDNode:跳转表地址——必须使用 PC-relative 加载,否则无法在任意基址运行
漏掉任一节点,就可能导致生成非法的绝对地址引用,链接时报 relocation R_X86_64_32 against <code>xxx can not be used when making a shared object 错误。
寄存器分配与调度对 PIC 正确性的影响
PIC 指令序列往往比普通指令更长(例如多一条 lea 或额外 mov),且依赖特定寄存器(如 x86-64 的 %rip、AArch64 的 x29 常作 frame pointer,但 GOT 访问可能需临时寄存器)。这直接影响寄存器分配和指令调度行为:
- 某些目标(如 ARM)要求 PIC 序列中不能破坏
lr(link register),否则 PLT 调用返回失败;后端需在TargetRegisterInfo中将该寄存器标记为clobbered或预留 - GOT 访问通常引入额外的数据依赖链(load addr → load from addr),若调度器错误地重排,可能造成冒险;需在
SchedPredicate或SubtargetInfo::getSchedModel中为 GOT 相关指令指定正确延迟和资源 - 寄存器分配器若将 PIC 序列中的临时地址寄存器(如 x86-64 的
%rax用于 GOT 查找)分配给其他活跃值,会导致地址被覆盖——必须确保 lowering 后的 MachineInstr 显式声明def和use关系
测试 PIC 生成是否真正生效的实操方法
光看汇编输出不够,必须验证最终产物是否真的不含非法重定位、能否被 dlopen 正确加载:
- 编译时加
-fPIC -shared,然后用readelf -d libxxx.so | grep TEXTREL—— 若输出为空,说明无运行时文本重定位(TEXTREL 是 PIC 失败的典型标志) - 用
objdump -dr libxxx.so | grep -E "(R_[A-Z0-9_]+_32|R_[A-Z0-9_]+_64)"检查是否存在非 GOT/PLT 类型的绝对重定位(如R_X86_64_32) - 写一个 minimal C 文件调用该库函数,用
gcc -pie main.c -L. -lxxx -o main链接,再运行./main—— 若 segfault 或报cannot make segment writable for relocation,说明 PIC 生成有缺陷 - 对自研后端,可在
SelectionDAGBuilder中插入断点,检查GlobalAddressSDNode是否被 Lower 成ISD::LOAD+ISD::ADD(GOT)或ISD::ADD+ISD::Constant(PC-rel),而非直接ISD::Constant
最易忽略的一点:很多后端只实现了函数调用的 PLT 支持,却忘了全局变量取址的 GOT 支持——这两者在 lowering 层是完全独立的路径,必须分别验证。

















