我的链接器有一个烦人的问题。我想将一些符号从共享库链接到静态库,但不导出它的符号(即,我不能简单地合并库或链接--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
from的定义,libfoo.a
并且我希望函数 fromlibext.so
调用函数foo
from libext.so
。换句话说,我希望我的库的编译时链接到libfoo.a
.
我正在寻找的是定义一个使用libfoo.a
但不导出其符号的库。如果我将我的图书馆链接到libfoo.a
,我会得到:
$> nm libmine.so
0000000000000a78 T bar
0000000000000b2c T foo
这意味着我都超载了foo
and bar
(我不想覆盖foo
)。如果我不将我的图书馆链接到libfoo.a
,我会得到:
$> nm libmine.so
0000000000000a78 T bar
U foo
所以我的图书馆将使用他们的版本foo
,我也不想要。我想要的是:
$> nm libmine.so
0000000000000a78 T bar
Wherefoo
在编译时被链接并且它的符号没有被导出。
最小的例子
您不需要阅读此内容,但您可以使用它来玩转并找到解决方案。
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(); }
foo.cpp
: 代表我的库和第三方都使用的函数的更新版本:
#include <iostream>
extern "C" void foo(){ std::cerr << "new::foo" << std::endl; }
trap.cpp
:我的库中的代码,它捕获bar
,调用 newfoo
并转发:
#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();
}
Makefile
: 仅限 Unix,对不起
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
最后一行是错误的,所需的输出是:
$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
old::foo
解决方案
1)正如在接受的答案中指出的那样,我们可以使用链接器版本脚本将不需要的符号的范围从全局更改为本地:
BAR {
global: bar;
local: *;
};
使用链接器版本进行编译显示foo
是本地的,程序现在按预期运行:
$> 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
2)另一种方法是使用属性重新编译-fvisibility=hidden
并链接到该属性。导出符号的可见性也是本地的,行为与上述相同。