zoukankan      html  css  js  c++  java
  • 记一次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符号





    原创不易,转载请注明出处,谢谢
  • 相关阅读:
    selenium 资料
    SpringMVC上传文件总结
    java 获取当天(今日)零点零分零秒
    存储过程实例基于postgersql
    为webService添加Interceptor(拦截器)
    spring+redis实例(二)
    hibernate字段映射枚举类型
    WordPress 在Ubuntu下安装插件、主题输入FTP后无法创建目录
    spring + redis 实例(一)
    mybatis字段映射枚举类型
  • 原文地址:https://www.cnblogs.com/Keeping-Fit/p/14251144.html
Copyright © 2011-2022 走看看