--trace-symbol可明确显示符号定义位置(如defined in main.o)和引用关系(如referenced by util.o),若无referenced by则该符号因无引用被gc移除;配合--verbose=sections可进一步确认段级存活状态,二者结合才能准确定位丢弃根因。

用 --trace-symbol 查符号定义和引用链
LLD 本身不提供“符号溯源”的图形化视图,但 --trace-symbol 是最直接、最可靠的命令行手段。它会明确告诉你:该符号在哪个 .o 文件中定义、被哪些其他目标文件引用、最终是否被保留(或因无引用被 GC 掉)。
常见错误现象是:编译通过,但运行时报 undefined symbol: _Z3foov;或者加了 --gc-sections 后函数莫名消失——这时不查引用链,光看源码根本找不到原因。
- 必须用 mangled 名(如
_Z3foov),C 函数可用原名(如printf),但 C++ 函数一定要 demangle 后再 mangling 回去,否则 LLD 找不到 - 命令写法:
ld.lld -o main main.o util.o --trace-symbol=_Z3foov - 输出里关键字段:
defined in main.o表示定义处;referenced by util.o表示谁引用了它;若没出现referenced by,说明该符号是孤岛,GC 阶段会被删
--verbose=sections 辅助判断段级存活状态
--trace-symbol 告诉你符号级引用关系,但有时问题出在段(section)层面:比如你写了自定义段 .myinit,却没被任何符号引用,或者链接脚本里漏掉了 KEEP(),结果整个段被静默丢弃。
--verbose=sections 会逐个列出所有输入 section 的来源、大小、是否参与 GC 判定——这是定位“段消失”类问题的唯一有效方式。
- 输出中每行含:
[section name] from [file] size [hex] [gc status],其中gc status为keep或discard - 注意:显式写在链接脚本里的
/DISCARD/ { *(.comment) }不会出现在此输出中,那是 link script 行为,不属于 GC 范畴 - 若发现某个段显示
discard,但你确认有符号引用它,就要检查该符号 linkage 是否为default(而非internal或hidden)
为什么 --print-gc-sections 不够用
--print-gc-sections 只回答“哪些段被删了”,不解释“为什么删”。它输出像这样:
ld: removing .text._Z3foov from main.o ld: removing .data._ZZ3foovE1x from main.o
看起来很清晰,但你无法知道:是 main.o 没被其他模块引用?还是 util.o 里调用它的 call 指令被优化掉了?抑或链接脚本里 *(.text) 没覆盖到这个 subsection?
-
--print-gc-sections是事后快照,不是因果分析工具 - 它对自定义段(如
.init_array、.mysec)完全不敏感,除非你显式把它加入 GC 判定范围(如--gc-sections+*(.mysec)在脚本中) - 真正要定位根因,必须和
--trace-symbol或--verbose=sections配合使用
容易被忽略的 linkage 和 visibility 陷阱
即使符号名、引用关系都对,LLD 仍可能“看不见”某个引用——根源常在前端生成的 IR 或汇编层级。
C++ 模板实例化、static inline 函数、__attribute__((visibility("hidden"))) 这些都会导致符号不可见于链接器外部,--trace-symbol 就查不到引用链。
- 检查 IR:用
llvm-dis a.bc -o - | grep -A5 "define.*foo"看 linkage 类型,必须是default或external - 检查汇编:
objdump -t main.o | grep foov,输出中第二列应为g(global),而不是l(local) - Clang 编译时加
-fvisibility=default可绕过默认 hidden 行为(尤其在构建共享库时)
实际调试时,先跑 --trace-symbol 看定义和引用是否完整;若有疑问,立刻补上 --verbose=sections 确认段级行为;最后回溯 IR 和 objdump 输出,核对 linkage 和 visibility。三者缺一不可,少一步就可能卡在“明明写了调用,链接器却说没引用”这种玄学问题里。











