即使共享对象包含静态版本的符号,也使用 C++ 动态 stdc++ 符号

问题描述 投票:0回答:1

我有一个针对 GCC 8.4 编译的(linux)共享对象,它静态链接在 stdc++fs 中。然后,在具有较新 stdc++ 运行时的其他系统上使用和运行此共享对象,它仅有时有效,其他时候它会崩溃

filesystem::~path

我使用这篇文章作为理解符号和部分发生的事情的指南,但是我不明白为什么它只是有时失败。

我可以通过更改工作程序在加载我的程序之前加载不同的 C++ 共享对象的方式来重现该问题。

工作

这是从使用 JNI 加载我的共享对象的 Java 程序中捕获的

binding file /lib64/libstdc++.so.6 [0] to /lib64/libstdc++.so.6 [0]: normal symbol `_ZNSt10filesystem4path14_M_split_cmptsEv' [GLIBCXX_3.4.26]
//later on
binding file libmyso.so [0] to libmyso.so [0]: normal symbol `_ZNSt10filesystem4path14_M_split_cmptsEv'

不工作

这是从同一个 Java 程序捕获的,但在 LLVM 上强制使用 LD_PRELOAD,尝试在到达我的库之前模拟加载此符号(据信在非功能情况下就是这种情况)

binding file /lib64/libstdc++.so.6 [0] to /lib64/libstdc++.so.6 [0]: normal symbol `_ZNSt10filesystem4path14_M_split_cmptsEv' [GLIBCXX_3.4.26]
//later on
binding file libmyso.so [0] to /lib64/libstdc++.so.6 [0]: normal symbol `_ZNSt10filesystem4path14_M_split_cmptsEv'

根据上面的链接帖子,我不得不相信崩溃的原因是由于此处列出的符号版本不兼容。我想要理解的是为什么我的符号在第二种情况下没有使用本地提供的符号(通过静态链接 stdc++fs)?

c++ gcc linker c++17
1个回答
0
投票

你明白工作和不工作的区别在于 共享库的加载顺序不同 你的程序取决于。导致加载顺序差异的原因是 超出了这个答案的范围,因为您显然认为它超出了范围 对于这个问题。

我想要理解的是为什么我的符号没有使用本地 在第二种情况下提供了一个(通过静态链接 stdc++fs)

看来您的印象是,如果 动态可见符号

sym
静态链接到共享库
liblate.so
那么
sym
本身内对
liblate.so
的引用将默认被绑定 在运行时它自己对
sym
的定义,甚至 如果动态链接器已经加载了另一个共享库
libfirst.so
,那就是
sym
的动态可见定义的第一个提供者。

事实并非如此。默认情况下,程序中对 sym

all
引用将 绑定到动态链接器找到的第一个定义 -
libfirst.so

提供的定义

这是一个基本的演示。看评论。

$ cat barcat.cpp
#include <string>

std::string bar()
{
    // bar() returns a string identifying the source file
    // from which it was compiled.
    return "bar from " __FILE_NAME__;
}

std::string cat(std::string const & lhs, std::string const & rhs)
{
    // cat() returns the concatenation of its argument strings.
    return lhs + rhs;
}


$ cat foobar.cpp
#include <string>

extern std::string cat(std::string const &, std::string const &);  

std::string bar()
{
    // bar() returns a string identifying the source file
    // from which it was compiled.
    return "bar from " __FILE_NAME__;
}

std::string foobar()
{
    // foobar() returns a string saying that it is calling bar(),
    // and does so.
    return cat("foobar calling ",bar());
}

$ cat main.cpp
#include <string>
#include <iostream>

extern std::string cat(std::string const &, std::string const &);
extern std::string foobar();

int main()
{
    std::cout << cat("main() calling ",foobar()) << std::endl;
    return 0;
}

是的,

barcat.cpp
foobar.cpp
都应该包含
bar
的定义。现在我们将编译源代码:

$ g++ -c -Wall -Wextra -Wpedantic -fPIC barcat.cpp foobar.cpp
$ g++ -c -Wall -Wextra -Wpedantic main.cpp

然后制作两个共享库:

$ g++ -shared -o libbarcat.so barcat.o
$ g++ -shared -o libfoobar.so foobar.o

libbarcat.so
静态链接动态可见的定义
bar
cat
libfoobar.so
静态链接动态可见
bar
的定义 - 再次! - 和
foobar
。正如我们所看到的:

$ readelf -W --dyn-syms libbarcat.so | c++filt | egrep .*(Symbol|Ndx| bar| cat) 
Symbol table '.dynsym' contains 45 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
    29: 00000000000024f9   165 FUNC    GLOBAL DEFAULT   14 bar[abi:cxx11]()
    31: 000000000000259e    88 FUNC    GLOBAL DEFAULT   14 cat(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)
    
$ readelf -W --dyn-syms libfoobar.so | c++filt | egrep .*(Symbol|Ndx| bar| foobar) 
Symbol table '.dynsym' contains 38 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
    24: 0000000000002439   165 FUNC    GLOBAL DEFAULT   14 bar[abi:cxx11]()
    35: 00000000000024de   257 FUNC    GLOBAL DEFAULT   14 foobar[abi:cxx11]()
    

静态链接例如:

$ g++ -shared -o libfoobar.so foobar.o

foobar.o
是静态库成员
libname.a(foobar.o)
完全相同 联动:

$ g++ -shared -o libfoobar.so -L /some/path -l name

其中

-l name
被解析为
/some/path/libname.a

现在我们将链接这样的程序:

$ g++ -o prog main.o -L . -l foobar -l barcat -Wl,-rpath=$(pwd)

检查

prog
的动态部分:

$ readelf --dynamic prog

Dynamic section at offset 0x3ce8 contains 32 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libfoobar.so]
 0x0000000000000001 (NEEDED)             Shared library: [libbarcat.so]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)            Library runpath: [/home/imk/develop/so/scrap]
 ...[cut]...

我们看到动态链接器已被告知先加载

libfoobar.so
,之前
libbarcat.so
(并且已被赋予非默认库路径,这样它就可以找到那些 未安装的库)。因此,我们预测动态链接器将解析
bar
符合
libfoobar.so
提供的定义。事实也是如此:

$ ./prog
main calling foobar calling bar from foobar.cpp

现在让我们像这样重新链接程序:

$ g++ -o prog main.o -L . -l barcat -l foobar -Wl,-rpath=$(pwd)

这具有颠倒

libfoobar.so
libbarcat.so
的加载顺序的效果:

$ readelf --dynamic prog

Dynamic section at offset 0x3ce8 contains 32 entries:
  Tag        Type                         Name/Value
 0x0000000000000001 (NEEDED)             Shared library: [libbarcat.so]
 0x0000000000000001 (NEEDED)             Shared library: [libfoobar.so]
 0x0000000000000001 (NEEDED)             Shared library: [libstdc++.so.6]
 0x0000000000000001 (NEEDED)             Shared library: [libgcc_s.so.1]
 0x0000000000000001 (NEEDED)             Shared library: [libc.so.6]
 0x000000000000001d (RUNPATH)            Library runpath: [/home/imk/develop/so/scrap]
 ...[cut]...
 

这将得到结果:

$ ./prog
main calling foobar calling bar from barcat.cpp

libfoobar.so
bar
的引用被解析为由
libbarcat.so
,尽管
libfoobar.so
提供了自己不同的动态 可见的定义。默认情况下,动态链接器将动态可见符号绑定到 它找到第一个定义并且不会寻找另一个定义。在这方面它就像 静态链接器。

但是,可以重新链接

libfoobar.so
- 不需要重新编译 - 以其自己的方式 无论如何,对
bar
的引用都将与其自己的定义绑定。来自男人ld: 来自
man ld

-B符号

创建共享库时,将对全局符号的引用绑定到定义中 在共享库中(如果有)。通常,程序可以链接到 共享库以覆盖共享库内的定义。这个选项是 仅在支持共享库的 ELF 平台上有意义。

因此我们可以重新链接

libfoobar.so
,例如:

$ g++ -shared -o libfoobar.so foobar.o -Wl,-Bsymbolic

之后:

$ g++ -o prog main.o -L . -l barcat -l foobar -Wl,-rpath=$(pwd)

将产生:

$ ./prog
main calling foobar calling bar from foobar.cpp

尽管

libbarcat.so
libfoobar.so

之前加载
© www.soinside.com 2019 - 2024. All rights reserved.