llvm-readelf是gnu readelf的llvm兼容替代品,专为处理llvm生成的elf文件(含lto、自定义section等)设计,不依赖bfd库,对非标准elf更鲁棒;查看segments用-l(仅对可执行文件/共享库有效),sections用-s,重定位用-r,符号表用-s,并需注意目标文件无program header、static符号默认不显示、未初始化变量为common类型等关键行为。

用 llvm-readelf 替代 readelf 查看段信息
LLVM 工具链自带的 llvm-readelf 是 GNU readelf 的兼容替代品,但更适配 LLVM 生成的 ELF(尤其含 LTO、ThinLTO 或自定义 section 时)。它不依赖 BFD 库,对非标准或裁剪过的 ELF 更鲁棒。
查看程序头(segments)用:llvm-readelf -l file.o;查看节区(sections)用:llvm-readelf -S file.o。注意:目标文件(.o)通常没有 program header(-l 输出为空),这是正常现象——只有可执行文件或共享库才含加载语义的 segments。
常见误操作:
- 对
.o文件执llvm-readelf -l后发现没输出,误以为命令失败 —— 实际是预期行为 - 混淆
-S(section headers)和-l(program headers):前者用于链接,后者用于加载 - 未加
--wide导致长 section 名被截断,看不到.llvm_addrsig或.note.gnu.property等新 section
llvm-readelf -r 解析重定位表的要点
重定位表(relocation table)只存在于目标文件(.o)和部分可执行文件(启用 PIE 时)。用 llvm-readelf -r file.o 可列出所有待链接器填地址的“坑”。
关键字段含义:
-
Offset:该重定位项在所在 section(如.text)内的字节偏移 -
Type:重定位类型,如R_X86_64_PC32(相对调用)、R_X86_64_64(绝对地址)——类型决定链接器如何计算最终值 -
Symbol:引用的符号名,若为UND表示未定义,需外部提供 -
Addend:预加常量,参与最终地址计算(例如lea rax, [rip + foo@GOTPCREL]中的 offset)
容易踩的坑:
- 编译时加了
-fPIE但没加-pie链接,导致.o里出现大量R_X86_64_REX_GOTPCRELX类型,而链接器无法处理(需用ld.lld或新版 GNU ld) - 用
clang -c -O2编译后,某些重定位被优化掉(如内联函数调用),-r输出变少 —— 不代表没重定位,只是被编译器消除了
为什么有时 llvm-readelf -s 看不到全局变量符号?
llvm-readelf -s file.o 显示符号表,但你会发现像 static int x; 或未初始化的 int y; 可能不出现,或类型为 NOTYPE / COMMON。
原因在于符号可见性与存储类别:
-
static符号默认作用域为本文件,llvm-readelf -s默认只显示全局/弱符号;加--dyn-syms也看不到,因为它们不进动态符号表 - 未初始化全局变量(
int z;)在目标文件中常以COMMON类型存在,Value字段为 0,Size字段才是实际占位大小 - 若用了
-fvisibility=hidden,即使非static,符号也可能被标记为HIDDEN,llvm-readelf -s仍显示,但链接时不可导出
验证方式:llvm-readelf -s file.o | grep -E "(z|COMMON)",再对比 nm -C file.o 输出是否一致。
LLVM 工具链下重定位分析的典型工作流
从源码到重定位诊断,推荐按此顺序查:
- 先确认文件类型:
file file.o→ 输出含ELF 64-bit LSB relocatable才是目标文件 - 看节区布局和属性:
llvm-readelf -S --wide file.o | grep -E "(text|data|bss|rela)" - 定位具体引用位置:
llvm-readelf -r file.o | grep "func_name",结合llvm-objdump -d file.o查反汇编中 call 指令的 offset - 检查符号定义来源:
llvm-readelf -s file.o | grep "func_name",注意Ndx列是否为UND(未定义)或数字(定义在某 section)
最易忽略的一点:LLVM 默认生成的重定位项可能带 @PLT 或 @GOTPCREL 后缀(如 printf@PLT),这表示调用走 PLT 表,不是直接跳转——调试时若断点设在 printf 符号上却没命中,很可能是因为实际跳转发生在 PLT stub 里。











