简体   繁体   English

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

[英]link a static library to a shared library and hide exported symbols

I am having an annoying problem with the linker. linker 有一个烦人的问题。 I want to link some symbols from a shared library to a static library, but not export its symbols (ie, I cannot simply merge the libraries or link with --whole-archive ).我想将共享库中的一些符号链接到 static 库,但不导出其符号(即,我不能简单地合并库或与--whole-archive链接)。 What I want is link (as in, like linking an executable, solving undefined symbols) my shared library to a static one and remove the undefined symbols.我想要的是链接(如链接可执行文件,解决未定义的符号)我的共享库到一个 static 并删除未定义的符号。

The thing I am looking for is probably just a linker option but I can't put my finger on it.我正在寻找的东西可能只是一个 linker 选项,但我不能指望它。

I'll try to describe the problem the best I can (it's not that easy) and then provide a toy minimal example to play with.我会尽力描述这个问题(这并不容易),然后提供一个玩具最小的例子来玩。

Quick description:快速说明:

I want to use the LD_PRELOAD trick to trap some function calls in an executable.我想使用LD_PRELOAD技巧来捕获可执行文件中的一些 function 调用。 This executable is linked against a third party shared library, which contains the function definition of the functions I want to trap.此可执行文件链接到第三方共享库,其中包含我要捕获的函数的 function 定义。

This third party library also contains symbols from yet another library, which I am also using in my library, but with a different (non-compatible) version.这个第三方库还包含来自另一个库的符号,我也在我的库中使用它,但版本不同(不兼容)。

What I want to do is compile my shared library and link it at compile time with the definitions of the last (static) library, without exporting the symbols, so that my shared library uses a different version from the one I want to trap.我想要做的是编译我的共享库并在编译时将它与最后一个(静态)库的定义链接,而不导出符号,以便我的共享库使用与我想要捕获的版本不同的版本。

Simplified problem description简化的问题描述

I have a third party library called libext.so , for which I don't have the source code.我有一个名为libext.so的第三方库,我没有它的源代码。 This defines a function bar and uses a function foo from another library, but the symbols are both defined there:这定义了一个 function bar并使用来自另一个库的 function foo ,但符号都在那里定义:

$> nm libext.so
0000000000000a16 T bar
00000000000009e8 T foo

As I mentioned, foo is an external dependency, for which I want to use a newer version.正如我所提到的, foo是一个外部依赖项,我想为此使用更新的版本。 I have an updated library for it, let's call it libfoo.a :我有一个更新的库,我们称之为libfoo.a

$> nm libfoo.a
0000000000000000 T foo

Now the problem is that I want to create a dynamic library which re-defines bar , but I want my library to use the the definition of foo from libfoo.a and i want the functions from libext.so to call the function foo from libext.so .现在的问题是我想创建一个重新定义bar的动态库,但我希望我的库使用libfoo.afoo的定义,并且我希望 libext.so 中的函数从libext.so调用libext.so foo libext.so In other words, I want a compile time linkage of my library to libfoo.a .换句话说,我想要我的库与libfoo.a的编译时链接。

What I am looking for is define a library which uses libfoo.a but doesn't export its symbols.我正在寻找的是定义一个使用libfoo.a但不导出其符号的库。 If I link my library to libfoo.a , I get:如果我将我的库链接到libfoo.a ,我会得到:

$> nm libmine.so
0000000000000a78 T bar
0000000000000b2c T foo

Which means I overload both foo and bar (i don't want to override foo ).这意味着我重载了foobar (我不想覆盖foo )。 If i don't link my library to libfoo.a , I get:如果我不将我的库链接到libfoo.a ,我会得到:

$> nm libmine.so
0000000000000a78 T bar
                 U foo

So my library will use their version of foo , which I don't want either.所以我的图书馆将使用他们的foo版本,我也不想要。 What I want is:我想要的是:

$> nm libmine.so
0000000000000a78 T bar

Where foo is linked at compile time and its symbol not exported.其中foo在编译时链接并且其符号未导出。

Minimal example最小的例子

You don't need to read this, but you can use it to play around and find a solution.您不需要阅读此内容,但您可以使用它来玩转并找到解决方案。

bar.cpp : represents the third party app I don't have the code for: 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 : represents a newer version of a function used by both my lib and the third party: foo.cpp :代表我的库和第三方都使用的 function 的较新版本:

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

trap.cpp : the code from my library, it traps bar , calls the new foo and forwards: 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 : a dummy executable to call bar : exec.cpp :调用bar的虚拟可执行文件:

extern "C" void bar();

int main(){
  bar();
}

Makefile : Unix only, sorry 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

In this case, bar calls foo ;在这种情况下, bar调用foo the normal execution is:正常的执行是:

$> ./test
old::bar
old::foo

Preloading my library intercepts bar , calls my foo and forwards bar , the current execution is:预加载我的库拦截bar ,调用我的foo并转发bar ,当前执行是:

$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
new::foo

The last line is wrong, the desired output is:最后一行错了,想要的 output 是:

$> LD_PRELOAD=libmine.so ./test
new::bar
new::foo
old::bar
old::foo

You want to use a linker version script, which exports the symbol(s) you want ( bar here) and hides everything else. 您想使用链接器版本脚本,该脚本导出您想要的符号(在此处bar )并隐藏其他所有内容。

Example here . 这里的例子。

As pointed out in the accepted answer, we can use a linker version script to change the scope of the undesired symbols from global to local:正如在接受的答案中指出的那样,我们可以使用 linker 版本脚本将不需要的符号的 scope 从全局更改为本地:

BAR {
  global: bar;
  local: *;
};

Compiling with the linker version shows foo is local and the program now behaves as expected:使用 linker 版本编译显示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

An alternative is to re-compile libfoo.a with the attribute -fvisibility=hidden and link against that.另一种方法是使用属性-fvisibility=hidden重新编译libfoo.a并链接到该属性。 The visibility of the exported symbols is then local too, and the behavior is the same as above.导出符号的可见性也是本地的,行为与上述相同。


Note: This answer was originally written by OP (Thibaut) in the question.注意:此答案最初由问题中的 OP (Thibaut) 编写。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM