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符号





    原创不易,转载请注明出处,谢谢
  • 相关阅读:
    Constants and Variables
    随想
    C#基础篇之语言和框架介绍
    Python基础19 实例方法 类方法 静态方法 私有变量 私有方法 属性
    Python基础18 实例变量 类变量 构造方法
    Python基础17 嵌套函数 函数类型和Lambda表达式 三大基础函数 filter() map() reduce()
    Python基础16 函数返回值 作用区域 生成器
    Python基础11 List插入,删除,替换和其他常用方法 insert() remove() pop() reverse() copy() clear() index() count()
    Python基础15 函数的定义 使用关键字参数调用 参数默认值 可变参数
    Python基础14 字典的创建修改访问和遍历 popitem() keys() values() items()
  • 原文地址:https://www.cnblogs.com/Keeping-Fit/p/14251144.html
Copyright © 2011-2022 走看看