我对链接器有一个恼人的问题。我想将一些符号从共享库链接到静态库,但不导出它的符号(即,我不能简单地合并库或链接--whole-archive
)。我想要的是链接(如在链接可执行文件,解决未定义的符号)我的共享库到静态库并删除未定义的符号。
我正在寻找的东西可能只是一个链接器选项,但我不能把它放在它上面。
我会尝试尽我所能地描述问题(这不是那么容易),然后提供玩具最小的例子来玩。
编辑:问题已经解决,解决方案发布在问题的底部
简要说明:
我想使用LD_PRELOAD
技巧来捕获可执行文件中的一些函数调用。此可执行文件链接到第三方共享库,该库包含我要捕获的函数的函数定义。
这个第三方库还包含来自另一个库的符号,我也在我的库中使用它,但是使用了不同的(不兼容的)版本。
我想要做的是编译我的共享库并在编译时将其与最后(静态)库的定义链接,而不导出符号,以便我的共享库使用与我想要的版本不同的版本陷阱。
简化问题说明
我有一个名为libext.so
的第三方库,我没有源代码。这定义了一个函数bar
并使用了另一个库中的函数foo
,但符号都在那里定义:
$> nm libext.so
0000000000000a16 T bar
00000000000009e8 T foo
正如我所提到的,foo
是一个外部依赖,我想使用更新的版本。我有一个更新的库,我们称之为libfoo.a
:
$> nm libfoo.a
0000000000000000 T foo
现在的问题是我想创建一个重新定义bar
的动态库,但我希望我的库使用来自foo
的{{1}}的定义,我想要libfoo.a
中的函数从libext.so
调用函数foo
。换句话说,我希望将库的编译时链接到libext.so
。
我正在寻找的是定义一个使用libfoo.a
但不导出其符号的库。如果我将我的库链接到libfoo.a
,我会得到:
libfoo.a
这意味着我超载了$> nm libmine.so
0000000000000a78 T bar
0000000000000b2c T foo
和foo
(我不想覆盖bar
)。如果我没有将我的库链接到foo
,我会得到:
libfoo.a
所以我的图书馆将使用他们的$> nm libmine.so
0000000000000a78 T bar
U foo
版本,我也不想这样做。我想要的是:
foo
$> nm libmine.so
0000000000000a78 T bar
在编译时链接,其符号未导出。
最小范例
您不需要阅读本文,但您可以使用它来解决问题。
foo
:代表我没有代码的第三方应用:
bar.cpp
#include <iostream>
extern "C" void foo(){ std::cerr << "old::foo" << std::endl; }
extern "C" void bar(){ std::cerr << "old::bar" << std::endl; foo(); }
:表示我的lib和第三方使用的函数的较新版本:
foo.cpp
#include <iostream>
extern "C" void foo(){ std::cerr << "new::foo" << std::endl; }
:来自我的库的代码,它陷阱trap.cpp
,调用新的bar
并转发:
foo
#include <iostream>
extern "C" {
#include <dlfcn.h>
}
extern "C" void foo();
extern "C" void bar(){
std::cerr << "new::bar" << std::endl;
foo(); // Should be new::foo
void (*fwd)() = (void(*)())dlsym(RTLD_NEXT, "bar");
fwd(); // Should use old::foo
}
:一个用于调用exec.cpp
的虚拟可执行文件:
bar
extern "C" void bar();
int main(){
bar();
}
:仅限Unix,抱歉
Makefile
在这种情况下,default:
# The third party library
g++ -c -o bar.o bar.cpp -fpic
gcc -shared -Wl,-soname,libext.so -o libext.so bar.o
# The updated library
g++ -c -o foo.o foo.cpp -fPIC
ar rcs libfoo.a foo.o
# My trapping library
g++ -c -o trap.o trap.cpp -fPIC
gcc -shared -Wl,-soname,libmine.so -o libmine.so trap.o -ldl -L. -lfoo
# The dummy executable
g++ -o test exec.cpp -L. libext.so
会调用bar
;正常执行是:
foo
预加载我的图书馆拦截$> ./test
old::bar
old::foo
,调用我的bar
并转发foo
,当前的执行是:
bar
最后一行是错误的,所需的输出是:
$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
new::foo
的解决方案 的
1)正如在接受的答案中所指出的,我们可以使用链接器版本脚本将不需要的符号的范围从全局更改为本地:
$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
old::foo
使用链接器版本进行编译显示BAR {
global: bar;
local: *;
};
是本地的,程序现在按预期运行:
foo
2)另一种方法是使用属性$> gcc -shared -Wl,-soname,libmine.so -Wl,--version-script=libmine.version -o libmine.so trap.o -ldl -L. -lfoo
$> nm libmine.so
0000000000000978 T bar
0000000000000000 A BAR
0000000000000a2c t foo
$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
old::foo
重新编译libfoo.a
并链接。导出符号的可见性也是本地的,行为与上面相同。