ARM平台GCC编译C++程序:Boost ASIO弱符号疑问及移除方法
我用gcc 9.2.0编译面向ARM平台的C++程序,依赖boost 1.71.0(主要使用asio模块)。最终可执行文件运行正常,但即使经过strip处理,执行readelf -W prog --dyn-syms仍能看到不少符号条目。这些符号的Ndx并非UND(未定义),说明它们没必要留在符号表中,且这些条目占用约140kB,我希望将其移除。
当前使用的编译/链接选项:
-fvisibility=hidden-fvisibility-inlines-hidden-flto-Wl,--exclude-libs,ALL-Wl,-r,--discard-all-Wl,-flto
未使用这些选项时,二进制文件大小超过1.3MB。
我的两个问题:
- 这些符号为何是WEAK类型?它们来自模板(头文件),并非外部库,我认为无需定义为WEAK。
- 如何将这些符号从符号表中移除?
注:我不想也无法手动修改boost的可见性设置,升级后重复操作过于繁琐,此方案不可行。
编辑:使用objcopy的-x、--localize-hidden、--strip-unneeded等选项均无效,符号表未被修改,推测这些函数/方法正在被使用,可能被链接器ld特殊标记,无法移除。
1. WEAK符号的成因
模板实例化生成WEAK符号是C++标准与GCC实现共同作用的结果:
- 模板属于头文件级代码,多个编译单元都可能实例化同一个模板。为了避免链接时出现重复定义错误,GCC会将模板实例化的符号标记为WEAK——链接器遇到多个相同的WEAK符号时,会自动保留其中一个、丢弃其余,且不会抛出错误。
- Boost Asio大量使用模板(比如
io_context、异步操作的handler包装等),这些模板实例化后就会生成这类WEAK符号,哪怕代码是本地实例化而非来自外部库,GCC依然会遵循该规则处理。
2. 移除WEAK符号的可行方案
既然无法修改Boost源码,且常规strip/objcopy选项无效,可以尝试以下方法:
调整链接器选项,精准清理未使用符号
在现有链接选项基础上,添加-Wl,--gc-sections,配合已有的-flto和--discard-all使用:
-Wl,--gc-sections,--discard-all
--gc-sections会触发链接器的垃圾回收机制,清理未被引用的代码段;结合LTO(-flto),链接器能更精准识别哪些WEAK符号是真正未被使用的,从而将其从动态符号表中移除。
用objcopy弱化符号后再清理
先通过objcopy将所有WEAK符号转为弱化状态,再用strip清理:
objcopy --weaken-symbol='*' prog prog.tmp strip --strip-unneeded prog.tmp prog
注意:如果存在需要动态引用的WEAK符号(Asio一般不会有这类场景),此操作可能导致程序异常,需先做测试验证。
升级GCC版本(可选)
GCC 9对-fvisibility=hidden结合模板实例化的符号处理不够完善,GCC 10及以上版本对这类场景的可见性控制更精准,能自动隐藏更多非必要的WEAK符号,无需额外操作。如果项目允许升级编译器,这是一劳永逸的解决办法。
为何之前的objcopy选项无效?
你提到的--localize-hidden无效,大概率是因为这些WEAK符号的可见性并非hidden——Boost模板默认可见性为default,即便你添加了-fvisibility=hidden,部分模板实例化符号可能被Boost内部宏(比如BOOST_SYMBOL_VISIBLE)强制设为可见,导致--localize-hidden无法处理。而--strip-unneeded仅会移除未被引用的UND符号,对于已定义且被链接器判定为“被引用”的WEAK符号,不会进行移除。
内容的提问来源于stack exchange,提问作者Jędrzej Dudkiewicz

