记一次C++ ABI不兼容问题

toc

背景

公司项目使用到了阿里云的智能语音交互SDK,分布式文件系统Ceph,系统ubuntu,g++版本gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)

问题发现过程

根据需求对比各家语音转文SDK后,选择了阿里云智能语音交互,随即使用SDK附带的demo进行准确率测试,测试OK后接入项目,出现链接错误:

[100%] Linking CXX executable AudioToText
CMakeFiles/AudioToText.dir/ConfigManager.cpp.o: In function `TransConfig::Config::GenerateNewTokenWhenTokenExpire[abi:cxx11]()':
/home/yjk/projects/AudioToText/ConfigManager.cpp:36: undefined reference to `AlibabaNlsCommon::NlsToken::setAccessKeyId(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
/home/yjk/projects/AudioToText/ConfigManager.cpp:37: undefined reference to `AlibabaNlsCommon::NlsToken::setKeySecret(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
CMakeFiles/AudioToText.dir/SpeechTranslator.cpp.o: In function `Translation::SpeechTranslator::ParseCallBack(AlibabaNls::NlsEvent*)':
/home/yjk/projects/AudioToText/SpeechTranslator.cpp:199: undefined reference to `AlibabaNls::NlsEvent::getSentenceWordsList[abi:cxx11]()'
collect2: error: ld returned 1 exit status
CMakeFiles/AudioToText.dir/build.make:484: recipe for target 'AudioToText' failed
make[2]: *** [AudioToText] Error 1
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/AudioToText.dir/all' failed
make[1]: *** [CMakeFiles/AudioToText.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2
终端进程“/bin/bash '-c', 'cmake --build ./ --target all --'”已终止,退出代码: 2。

以为是项目CMakeLists.txt有问题,检查文件发现确实有指定连接路径以及库名



随即比对SDK附带的demo中的CMakeLists.txt,发现添加了一个宏定义


把此语句添加到项目项目CMakeLists.txt,编译成功通过
究其原因,是因为
在GCC5.1发布的同时,为libstdc++添加了新的特性,其中也包括了std::stringstd::list的新实现。这个新的实现使得两者符合了c++11的标准,具体来说是取消了Copy-On-Write。那么,这样子虽然符合了c++11的标注,旧版不就无法兼容了吗。为了避免上述混乱,对于旧版而言,GCC5.1添加了__cxx11命名空间,GCC5.1或者说c++11规范下的string和list,实际上是std::__cxx11::stringstd::__cxx11::list,所以我们一般的using namespace std就会变成形如using namespace std::__cxx11的样子。也就是说,有旧版(c++03规范)的libstdc++.so,和新版(c++11规范)的libstdc++.so两个库同时存在。

为了避免两个库到底选择哪一个的麻烦,GCC5.1就引入了-D_GLIBCXX_USE_CXX11_ABI来控制编译器到底链接哪一个libstdc++.so

  • D_GLIBCXX_USE_CXX11_ABI=0 链接旧版库
  • D_GLIBCXX_USE_CXX11_ABI=1 链接新版库

引用自_GLIBCXX_USE_CXX11_ABI有什么作用
明白了这个之后,添加宏定义之前的连接错误就可以解释了:
连接器在链接时,寻找的符号是

`AlibabaNlsCommon::NlsToken::setKeySecret(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'

通过nm命令查看下动态库导出的符号,使用c++filt恢复下函数签名

得到导出的符号

`AlibabaNlsCommon::NlsToken::setAccessKeyId(std::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)`

符号不匹配,链接器找不到需要的符号,链接报错

另一个库引入的新问题

接入Ceph的库librados.so后,又发生了链接错误

[100%] Linking CXX executable AudioToText
CMakeFiles/AudioToText.dir/CephClient.cpp.o: In function `FileSystem::CephWrapper::GetAllPool(std::string&)':
/home/yjk/projects/AudioToText/CephClient.cpp:42: undefined reference to `librados::Rados::pool_list(std::list<std::string, std::allocator<std::string> >&)'
CMakeFiles/AudioToText.dir/CephClient.cpp.o: In function `FileSystem::CephWrapper::ReadObjectToFile(std::string const&, std::string const&, std::string&, std::string&)':
/home/yjk/projects/AudioToText/CephClient.cpp:58: undefined reference to `librados::IoCtx::getxattr(std::string const&, char const*, ceph::buffer::list&)'
/home/yjk/projects/AudioToText/CephClient.cpp:62: undefined reference to `librados::IoCtx::getxattr(std::string const&, char const*, ceph::buffer::list&)'
/home/yjk/projects/AudioToText/CephClient.cpp:73: undefined reference to `librados::IoCtx::aio_read(std::string const&, librados::AioCompletion*, ceph::buffer::list*, unsigned long, unsigned long)'
CMakeFiles/AudioToText.dir/CephClient.cpp.o: In function `FileSystem::CephWrapper::WriteObject(std::string const&, std::string const&, char const*, unsigned int, std::string&)':
/home/yjk/projects/AudioToText/CephClient.cpp:97: undefined reference to `librados::IoCtx::write(std::string const&, ceph::buffer::list&, unsigned long, unsigned long)'
/home/yjk/projects/AudioToText/CephClient.cpp:105: undefined reference to `librados::IoCtx::setxattr(std::string const&, char const*, ceph::buffer::list&)'
/home/yjk/projects/AudioToText/CephClient.cpp:112: undefined reference to `librados::IoCtx::aio_read(std::string const&, librados::AioCompletion*, ceph::buffer::list*, unsigned long, unsigned long)'
/home/yjk/projects/AudioToText/CephClient.cpp:118: undefined reference to `librados::IoCtx::getxattr(std::string const&, char const*, ceph::buffer::list&)'
collect2: error: ld returned 1 exit status
CMakeFiles/AudioToText.dir/build.make:484: recipe for target 'AudioToText' failed
make[2]: *** [AudioToText] Error 1
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/AudioToText.dir/all' failed
make[1]: *** [CMakeFiles/AudioToText.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2
终端进程“/bin/bash '-c', 'cmake --build ./ --target all --'”已终止,退出代码: 2。

检查CMakeLists.txt后,将目光锁定到了之前添加的宏定义

将其修改为


发现依赖的ceph的库librados.so链接成功,阿里云的库链接失败

[100%] Linking CXX executable AudioToText
CMakeFiles/AudioToText.dir/ConfigManager.cpp.o: In function `TransConfig::Config::GenerateNewTokenWhenTokenExpire[abi:cxx11]()':
/home/yjk/projects/AudioToText/ConfigManager.cpp:36: undefined reference to `AlibabaNlsCommon::NlsToken::setAccessKeyId(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
/home/yjk/projects/AudioToText/ConfigManager.cpp:37: undefined reference to `AlibabaNlsCommon::NlsToken::setKeySecret(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
CMakeFiles/AudioToText.dir/SpeechTranslator.cpp.o: In function `Translation::SpeechTranslator::ParseCallBack(AlibabaNls::NlsEvent*)':
/home/yjk/projects/AudioToText/SpeechTranslator.cpp:199: undefined reference to `AlibabaNls::NlsEvent::getSentenceWordsList[abi:cxx11]()'
collect2: error: ld returned 1 exit status
CMakeFiles/AudioToText.dir/build.make:484: recipe for target 'AudioToText' failed
make[2]: *** [AudioToText] Error 1
CMakeFiles/Makefile2:67: recipe for target 'CMakeFiles/AudioToText.dir/all' failed
make[1]: *** [CMakeFiles/AudioToText.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2
终端进程“/bin/bash '-c', 'cmake --build ./ --target all --'”已终止,退出代码: 2。

修改之前。指定的D_GLIBCXX_USE_CXX11_ABI=0,连接器去寻找std::string与std::list时不带命名空间__cxx11,然而ceph的库librados.so导出的符号带了__cxx11
就拿pool_list函数来说,链接器链接时,试图链接

`librados::Rados::pool_list(std::list<std::string, std::allocator<std::string> >&)'
//相当于连接了 (std::string 就是 std::basic_string<char>)
`librados::Rados::pool_list(std::list<std::basic_string<char, std::char_traits<char>, std::allocator<char> >>&)'

然而librados.so导出的却是

这时阿里云的库是链接成功了,但是ceph的库缺链接失败了,两个库的ABI是不兼容的,不管指定D_GLIBCXX_USE_CXX11_ABI为0还是1,总有一个会链接失败!!!!!!!!!!!!!!!!!!!!!!!!!!!
目前还在寻找解决办法,先记一笔,避免遗忘

经验教训

  1. 当项目需要依赖多个三方库时,在选择库时
    • 提前通过nm命令与c++filt组合查看下导出的符号,避免导出符号不兼容的情况发生而引起后续的折腾
    • 查看动态库的 .comment段中存储的编译器版本信息(不一定有)
      readelf -p .comment <lib-name>
      objdump -s --section=.comment <lib-name>


    以GCC5.1版本为分界线检查,避免ABI不兼容

2.制作库时,尽量别导出STL符号





原创不易,转载请注明出处,谢谢
原文地址:https://www.cnblogs.com/Keeping-Fit/p/14251144.html