将静态库链接到共享库并隐藏导出的符号

Thi*_*aut 13 c++ linker static-linking ld-preload

我对链接器有一个恼人的问题.我想将一些符号从共享库链接到静态库,但不导出它的符号(即,我不能简单地合并库或链接--whole-archive).我想要的是链接(如在链接可执行文件,解决未定义的符号)我的共享库到静态库并删除未定义的符号.

我正在寻找的东西可能只是一个链接器选项,但我无法指责它.

我会尝试尽我所能地描述问题(这不是那么容易),然后提供玩具最小的例子来玩.

编辑:问题已经解决,解决方案发布在问题的底部

简要说明:

我想使用这个LD_PRELOAD技巧来捕获可执行文件中的一些函数调用.此可执行文件链接到第三方共享库,该库包含我要捕获的函数的函数定义.

这个第三方库还包含来自另一个库的符号,我也在我的库中使用它,但是使用不同的(不兼容的)版本.

我想要做的是编译我的共享库,并在编译时将其与最后(静态)库的定义链接,而不导出符号,以便我的共享库使用与我想要捕获的版本不同的版本.

简化问题描述

我有一个第三方库libext.so,我没有源代码.这定义了一个函数bar并使用了foo另一个库中的函数,但符号都在那里定义:

$> nm libext.so
0000000000000a16 T bar
00000000000009e8 T foo
Run Code Online (Sandbox Code Playgroud)

正如我所提到的,foo是一个外部依赖,我想使用更新的版本.我有一个更新的库,让我们称之为libfoo.a:

$> nm libfoo.a
0000000000000000 T foo
Run Code Online (Sandbox Code Playgroud)

现在的问题是,我想创造出重新定义了一个动态库bar,但我想我的图书馆使用的定义foolibfoo.a,我想从函数libext.so调用的函数foolibext.so.换句话说,我想要我的库的编译时链接libfoo.a.

我正在寻找的是定义一个使用libfoo.a但不导出其符号的库.如果我链接我的库libfoo.a,我得到:

$> nm libmine.so
0000000000000a78 T bar
0000000000000b2c T foo
Run Code Online (Sandbox Code Playgroud)

这意味着我既超载foobar(我不希望重写foo).如果我没有链接我的图书馆libfoo.a,我得到:

$> nm libmine.so
0000000000000a78 T bar
                 U foo
Run Code Online (Sandbox Code Playgroud)

所以我的图书馆将使用他们的版本foo,我也不想要.我想要的是:

$> nm libmine.so
0000000000000a78 T bar
Run Code Online (Sandbox Code Playgroud)

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(); }
Run Code Online (Sandbox Code Playgroud)

foo.cpp:表示我的lib和第三方使用的函数的较新版本:

#include <iostream>
extern "C" void foo(){ std::cerr << "new::foo" << std::endl; }
Run Code Online (Sandbox Code Playgroud)

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
}
Run Code Online (Sandbox Code Playgroud)

exec.cpp:要调用的虚拟可执行文件bar:

extern "C" void bar();

int main(){
  bar();
}
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

在这种情况下,bar电话foo; 正常执行是:

$> ./test
old::bar
old::foo
Run Code Online (Sandbox Code Playgroud)

预加载我的库拦截bar,调用我foo和前进bar,当前执行是:

$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
new::foo
Run Code Online (Sandbox Code Playgroud)

最后一行是错误的,所需的输出是:

$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
old::foo
Run Code Online (Sandbox Code Playgroud)

1)正如在接受的答案中所指出的,我们可以使用链接器版本脚本将不需要的符号的范围从全局更改为本地:

BAR {
  global: bar;
  local: *;
};
Run Code Online (Sandbox Code Playgroud)

使用链接器版本进行编译显示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
Run Code Online (Sandbox Code Playgroud)

2)另一种方法是libfoo.a使用属性-fvisibility=hidden和链接重新编译.导出符号的可见性也是本地的,行为与上面相同.

Emp*_*ian 4

您想要使用链接器版本脚本,它导出您想要的符号(bar此处)并隐藏其他所有内容。

例子在这里

  • 那行得通。奇怪的是,没有链接器选项可以在链接时直接降低链接依赖项的可见性。 (2认同)