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

 美丽空间wo 发布于 2023-01-20 17:37

我对链接器有一个恼人的问题.我想将一些符号从共享库链接到静态库,但不导出它的符号(即,我不能简单地合并库或链接--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,但我想我的图书馆使用的定义foolibfoo.a,我想从函数libext.so调用的函数foolibext.so.换句话说,我想要我的库的编译时链接libfoo.a.

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

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

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

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

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

$> nm libmine.so
0000000000000a78 T bar

foo在编译时被链接并不会导出其符号.

最小的例子

您不需要阅读本文,但您可以使用它来玩游戏并找到解决方案.

bar.cpp:代表我没有代码的第三方应用:

#include 
extern "C" void foo(){ std::cerr << "old::foo" << std::endl; }
extern "C" void bar(){ std::cerr << "old::bar" << std::endl; foo(); }

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

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

trap.cpp:我的库中的代码,陷阱bar,调用新的foo和转发:

#include 
extern "C" {
  #include 
}
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

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

撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有