GCC ABI炸弹

GCC ABI炸弹

GCC想要解决std::string/list等的实现缺陷,但是又没有办法直接做到旧版本兼容,于是引入了_GLIBCXX_USE_CXX11_ABI编译选择,设置为0就使用旧版本的ABI,设置为1就使用新版本。这会要求一个应用程序以及它依赖的所有动态库,同时使用相同的ABI标记。如果不是,最轻的就是在编译时报错,比如出现报错:undefined reference to std::__cxx11::basic_string。严重的就是在运行时莫名其妙就跪。

我就遇到了莫名其妙跪的情况,看coredump信息还没有cxx11相关的符号:

复制代码
#0  0x0000ffff48a23d30 in char32_t std::(anonymous namespace)::read_utf8_code_point<char>(std::(anonymous namespace)::range<char const, true>&, unsigned long) () from /root/.seekdb/cache/py3.9/1.0.0/libseekdb_python.so
#1  0x0000ffff48a25ccc in std::codecvt_base::result std::(anonymous namespace)::utf16_in<char, char16_t>(std::(anonymous namespace)::range<char const, true>&, std::(anonymous namespace)::range<char16_t, true>&, unsigned long, std::codecvt_mode, std::(anonymous namespace)::surrogates) () from /root/.seekdb/cache/py3.9/1.0.0/libseekdb_python.so
#2  0x0000ffff48a25e0c in std::codecvt<char16_t, char, __mbstate_t>::do_in(__mbstate_t&, char const*, char const*, char const*&, char16_t*, char16_t*, char16_t*&) const () from /root/.seekdb/cache/py3.9/1.0.0/libseekdb_python.so
#3  0x0000ffff48a8f0c4 in std::ostream& std::ostream::_M_insert<unsigned long>(unsigned long) ()
   from /root/.seekdb/cache/py3.9/1.0.0/libseekdb_python.so

问了下AI,它说还是有可能是ABI不一致导致的。

我这里出现问题的场景是在Python中同时使用torch包和pylibseekdb包,pylibseekdb是我们的包,torch是一个很流行的python包。很多与AI相关的Python包,比如sentence-transformers,都会依赖torch,因此torch还是避不开的。可以使用下面的命令判断torch使用哪种ABI编译的:

python 复制代码
>>> python3 -c 'import torch; print(f"PyTorch compiled with CXX11 ABI: {torch.compiled_with_cxx11_abi()}")'
PyTorch compiled with CXX11 ABI: True

torch编译时使用了 _GLIBCXX_USE_CXX11_ABI=1

由于pylibseekdb有很多依赖包,很难在短时间内把所有的依赖包都是用 _GLIBCXX_USE_CXX11_ABI=1 重新编译,只能另寻出路了。

我们知道Linux的程序在查找动态库引用的符号时,会优先在进程空间内寻找,然后再自己的动态库内寻找。这就导致pylibseekdb.so加载后,查找stdlibc++中 ABI=0 的符号时,先从进程空间查找,也就是 torch 引用的 ABI=1 的符号了,名字相同而实现不同。这里的符号包含了函数和全局变量。这种冲突也就导致了出现莫名其妙的coredump。

想要解决这个问题,一种方法是pylibseekdb也改成使用ABI=1来编译,一种方法是符号查找时优先从动态库本身查找,比如使用隐藏符号的方法。Linux上隐藏符号不好整,就从另一种方法着手尝试吧。

如果程序在使用 dlopen 打开动态库时指定了 RTLD_LOCAL 标识的话,那这个动态库的符号会先从动态库内部查找。否则就是使用默认搜索顺序。不过我们是没有办法修改python程序本身的代码来修改这个标识符的。

另一种方法是在编译动态库的时候处理,告诉程序我们的符号先从自己的动态库中查找,就是在编译时增加一个链接选项 -Bsymbolic告诉编译器动态库查找符号时优先从当前库中查找。

当然要解决这个问题还得静态链接C++的库: -static-libstdc++

最后再吐槽一下Linux上的动态库符号问题。Linux的动态库符号默认都是全局可见的,而且没有简便的方法来处理将符号隐藏在动态库内部。这个设计还是Windows的动态库做的好,默认就是隐藏符号,暴露哪些需要明确说明。

一些参考资料

Option -Bsymbolic 会导致严重副作用
关于Bsymbolic
gcc 编译参数:-Wl,-Bsymbolic 与 - Bsymbolic
ELF interposition and -Bsymbolic
GCC 关于CXX ABI的说明

相关推荐
Savior`L2 小时前
字符串哈希
c++·算法·哈希算法·散列表
十五年专注C++开发2 小时前
librf: 一款基于 C++11/14/17 标准实现的轻量级无栈协程库
开发语言·c++·分布式·异步io
JH灰色2 小时前
【大模型】-AutoGen Studio的搭建
python·语言模型
趁月色小酌***2 小时前
JAVA 知识点总结3
java·开发语言·python
程序猿编码2 小时前
手动清理 TCP TIME-WAIT 套接字:Linux 内核模块的实现与原理
linux·网络·tcp/ip·linux内核·套接字
pwn蒸鱼2 小时前
buuctf中的ciscn_2019_es_2(栈迁移)
linux·安全
Smile丶凉轩2 小时前
C++实现主从Reactor模型实现高并发服务器面试题总结
服务器·开发语言·c++
牛奶咖啡132 小时前
Linux的实用技巧——终端安全会话、命令提示工具安装使用、端口连通性测试与rm命令无法使用解决方案
linux·tmux·linux实现后台安全运行会话·linux的端口连通性测试·linux的命令提示工具·rm命令无法使用解决方法·tldr
智航GIS2 小时前
6.1 for循环
开发语言·python·算法