求助:Meson未链接指定的“冗余”共享库
我最近遇到一个Meson链接的问题,想请教大家:我写了一个用来覆盖libc函数的库,打算把它链接到我的二进制程序里,库和二进制都定义在同一个meson.build里。这个库必须排在链接列表的最前面,这样它的函数才能优先于libc的函数被调用。因为静态还是动态库的选择权在打包者手里,所以我用了Meson的library()函数。
我是这样定义这个库的:
libemilua_libc_service = library( 'emilua-libc-service', libc_service_src, override_options : ['b_lundef=false'], dependencies : [ boost ], include_directories : include_directories(incdir), implicit_include_directories : false, version : meson.project_version(), install : true, ) libemilua_libc_service_dep = declare_dependency( dependencies : [ libemilua_dep ], link_with : [libemilua_libc_service], )
然后在可执行文件里引用这个依赖(特意放在最前面):
emilua_bin = executable( 'emilua', ['src/main.cpp'], dependencies : [ # libc_service must be linked first to override glibc functions libemilua_libc_service_dep, libemilua_dep, libemilua_main_dep, ], export_dynamic : get_option('enable_plugins'), include_directories : include_directories(incdir), implicit_include_directories : false, install : true, )
但奇怪的是,运行ldd emilua检查链接情况时,完全看不到libemilua-libc-service的影子,实际测试也发现程序用的还是glibc的标准函数,根本没用到我写的覆盖函数。
不过如果我用meson configure -Ddefault_library=static改成静态链接重新编译,这个库就被正常嵌入到二进制里了,测试也能正常覆盖libc函数,功能完全没问题。
补充上下文
先给大家解释下项目结构:libemilua是一个可以嵌入C程序的Lua运行时库,/usr/bin/emilua就是基于这个库构建的。考虑到不是所有C开发者都能接受会替换glibc函数的库,我把这个替换行为单独抽成了libemilua-libc-service库——只有当程序主动链接这个库时才会生效(它还覆盖了libemilua的几个符号,让libemilua能检测到是否可以假设libc函数已被替换)。而且libemilua完全不依赖libemilua-libc-service的任何符号。
这个库的作用是配合沙箱机制(比如Linux的seccomp、FreeBSD的capsicum)禁用环境权限:当进程处于沙箱模式时,我覆盖的libc函数会把请求通过匿名UNIX套接字转发到沙箱外的策略管理进程,由它来执行操作或拒绝请求,再把结果返回。代码本身是没问题的——静态链接或者用LD_PRELOAD强制加载动态库时,功能都正常,就是Meson这边动态链接时出了问题。
问题分析与解决办法
后来我搞明白了,问题出在链接器的默认优化上:
问题根源
Meson底层用的链接器(比如GCC的ld)默认会对动态链接做符号依赖优化:如果你的可执行文件(以及它直接依赖的库)没有用到某个动态库的任何符号,链接器就会自动跳过链接这个库——这就是为什么静态链接时没问题(静态链接不会做这种优化,会把整个库打包进去),动态链接时库就“消失”了。
针对这个场景,有几种适合Linux和FreeBSD的解决方式(你提到可以暂时忽略Windows):
1. 给链接器传递--no-as-needed参数
这个参数告诉链接器不要跳过那些看起来“没用”的动态库。在Meson里,可以修改依赖声明,精准控制这个参数的作用范围:
libemilua_libc_service_dep = declare_dependency( dependencies : [ libemilua_dep ], link_with : [libemilua_libc_service], link_args : ['-Wl,--no-as-needed', '-lemilua-libc-service', '-Wl,--as-needed'], )
这里用-Wl,--as-needed把后续的链接恢复默认行为,避免影响其他库的优化。
2. 在库中添加“锚点符号”,让可执行文件引用它
可以在libemilua-libc-service里加一个空的无意义函数,然后在主程序里引用它,这样链接器就会认为这个库是被使用的:
// 在libemilua-libc-service的某个源文件中添加 void __emilua_libc_service_anchor() {}
// 在src/main.cpp中添加 extern void __emilua_libc_service_anchor(); static void __attribute__((used)) dummy() { __emilua_libc_service_anchor(); }
__attribute__((used))是GCC/Clang的属性,确保编译器不会把这个空函数优化掉。
3. 使用Meson的link_whole选项
如果是静态库,link_whole会强制链接整个库;对于动态库,它的效果类似--no-as-needed。可以直接修改依赖声明:
libemilua_libc_service_dep = declare_dependency( dependencies : [ libemilua_dep ], link_whole : [libemilua_libc_service], )
不过要注意,这个选项在不同平台的链接器上行为可能略有差异,但在Linux和FreeBSD上是有效的。
推荐方案
如果你的项目主要在Linux和FreeBSD上运行,方案1是最直接的——不需要修改代码,只需要调整Meson配置。方案2的侵入性稍大,但兼容性更好(如果以后要支持更多平台)。方案3比较简洁,但要注意link_whole的行为细节。
备注:内容来源于stack exchange,提问作者vinipsmaker

