記一次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符號






免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM