lld启用--gc-sections时会删除未被显式引用的注册代码,因其依赖定义即触发机制;可通过--undefined、--retain-symbols-file或__attribute__((used))等方式强制保留。

lld 链接时为什么注册代码会被 GC 掉
当你用 --gc-sections 启用段级垃圾回收时,lld 会扫描所有符号的引用关系,把**没有被任何活代码显式引用**的函数或数据段直接丢弃。而很多注册机制(比如全局构造器、插件注册表、静态初始化列表)依赖“定义即触发”,靠编译器自动生成的 .init_array 或 .ctors 段来调用注册函数——这些函数本身在 IR 或目标文件里可能完全没被其他代码 call 过,lld 就会误判为“死代码”。
用 --undefined 强制保留注册函数
这是最轻量、最直接的方式:告诉链接器“这个符号我明确要用,哪怕现在没看到引用”。
- 适用于已知注册函数名的情况,例如
register_plugin_a()、__attribute__((constructor))函数(注意:这类函数名由编译器生成,需用nm查看实际符号名) - 命令写法:
lld -flavor gnu --undefined=register_plugin_a --gc-sections ... - 多个函数可重复加:
--undefined=foo --undefined=bar - 注意:符号名区分大小写,C++ 函数要加
extern "C"或用c++filt确认 mangling 后的名字
用 --retain-symbols-file 批量保护注册符号
当注册函数分散、数量多、或来自第三方库且无法改源码时,比逐个 --undefined 更可控。
- 新建一个文本文件(如
keep-syms.txt),每行写一个要保留的符号名:register_module_x init_driver_y __cxx_global_var_init
- 链接时传入:
lld -flavor gnu --retain-symbols-file=keep-syms.txt --gc-sections ... - 该选项不检查符号是否存在,只起“白名单”作用;若符号不存在,也不报错
- 适合 CI 流水线中固化保护规则,避免漏加
用 .section + .used 属性让编译器参与保护
从源头防止符号被优化掉,比链接期补救更可靠,但需要改源码。
- 在定义注册函数时加
__attribute__((used)):void __attribute__((used)) register_thing() { ... } - 或对全局变量(如插件结构体)也加:
static const PluginDesc desc __attribute__((used)) = { ... }; - Clang 和 GCC 都支持;
__attribute__((used))会强制生成符号并阻止 LTO 删除 - 注意:
__attribute__((used))不影响函数内联决策,仅保符号和段 - 如果用了 LTO,还需确保链接时不加
-flto=thin(thin LTO 可能绕过部分used语义)
真正容易被忽略的是:注册代码是否跨编译单元。如果注册函数定义在 a.cpp,但只在 b.cpp 的静态初始化中被间接调用(比如通过函数指针数组),--gc-sections 依然可能删掉它——因为链接器看不到跨 TU 的间接引用。这时候必须用 --undefined 或 --retain-symbols-file 显式声明,不能依赖源码属性。











