lld默认不报告丢弃的section,启用--gc-sections后配合--print-gc-sections可输出被GC移除的.text/.data等段;结合--trace-symbol和--verbose=sections能定位丢弃原因,但显式链接脚本/DISCARD规则不在此列。

lld 链接时如何确认哪些 section 被丢弃了
默认情况下,lld 不会主动告诉你哪些 .text、.data 或自定义段被丢弃——它只在静默中优化掉未引用的符号和 section。但你确实能查到,关键不是“看日志”,而是让链接器把决策过程暴露出来。
--print-gc-sections 是最直接的开关
这个选项会让 lld 在标准输出里逐行打印所有被垃圾回收(GC)机制移除的 section。注意:它只对启用了 --gc-sections 的构建有效,且仅作用于可重定位目标文件(.o),不适用于已链接好的 .so 或最终可执行文件。
常见用法:
- 编译时加
-ffunction-sections -fdata-sections,确保每个函数/数据单独成段 - 链接时加
-Wl,--gc-sections -Wl,--print-gc-sections - 如果用
clang,整条命令类似:clang -ffunction-sections -fdata-sections main.o -Wl,--gc-sections -Wl,--print-gc-sections -o main
输出示例(实际会更长):
ld: removing .text._Z3foov from main.o ld: removing .data._ZZ3foovE1x from main.o
这里 _Z3foov 是 foo() 的 mangled 名,说明该函数及其关联的局部静态变量段被 GC 掉了。
--trace-symbol 和 --verbose=sections 可交叉验证
--print-gc-sections 告诉你“被删了”,但不解释“为什么”。要定位根源,得结合符号追踪:
-
-Wl,--trace-symbol=_Z3foov:显示该符号在哪个.o中定义、被谁引用、最终是否保留 -
-Wl,--verbose=sections:输出所有输入 section 的来源、大小、是否参与 GC 判定,适合排查自定义段(如.myinit)为何消失 - 二者一起用,能快速判断是前端没生成引用、链接脚本误排除、还是
--gc-sections过激了
特别注意:如果你用了 linker script 并显式写了 /DISCARD/ { *(.comment) },这类段不会出现在 --print-gc-sections 输出里——那是显式丢弃,不属于 GC 范畴。
调试自定义段丢弃问题的典型陷阱
很多用户卡在“我明明写了 __attribute__((section(".mysec"))) void my_init() {},但最终二进制里找不到”,原因往往不在 lld 本身:
- Clang/GCC 默认启用
-ffunction-sections时,my_init确实进了.mysec,但若该函数完全没被调用或取地址,--gc-sections会连段一起删 - 链接脚本里漏写
.mysec : { *(.mysec) },导致段被归入/DISCARD/或根本没映射到输出 - 用了
static修饰,编译器可能在 LTO 阶段提前内联或优化掉,使得符号在.o层就不可见 -
lld的--gc-sections对.init_array、.fini_array等特殊段有例外处理,但自定义段不享受此待遇
真正难排查的,是 GC 和链接脚本行为叠加后产生的“幽灵丢弃”——比如某段因未被引用被 GC,而链接脚本又恰好没声明它,结果既没报错也没提示。这时候必须靠 --print-gc-sections + --verbose=sections 两路输出对齐时间点才能定位。

















