_GLIBCXX_USE_CXX11_ABI 定义不一致带来的宕机问题


很久没有写文章了记录点什么了,这次问题还算印象深刻

ABI 是什么

我们看看wiki定义: 应用二进制接口(英语:application binary interface,缩写为ABI)是指两程序模块间的接口,一个ABI定义了机器代码如何访问数据结构与运算程序,此处所定义的界面相当低端并且相依于硬件。而类似概念的API则在源代码定义这些,则较为高端,并不直接相依于硬件,通常会是人类可阅读的代码。一个ABI常见的样貌即是调用约定:资料怎么成为计算程序的输入或者从中得到输出;x86的调用约定即是一个ABI的例子

_GLIBCXX_USE_CXX11_ABI 是什么

一个宏定义.

In the GCC 5.1 release libstdc++ introduced a new library ABI that includes new implementations of std::string and std::list. These changes were necessary to conform to the 2011 C++ standard which forbids Copy-On-Write strings and requires lists to keep track of their size.

完整文章
https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html

大意是c++ 11 的新提案修改了 std::string 和 std::list 的 ABI接口,但是直到 GCC 5.1才发布.

截取 gcc-9.3.0/include/c++/9.3.0/x86_64-pc-linux-gnu/bits/c++config.h 中的一段代码

#if _GLIBCXX_USE_CXX11_ABI
namespace std
{
  inline namespace __cxx11 __attribute__((__abi_tag__ ("cxx11"))) { }
}
namespace __gnu_cxx
{
  inline namespace __cxx11 __attribute__((__abi_tag__ ("cxx11"))) { }
}
# define _GLIBCXX_NAMESPACE_CXX11 __cxx11::
# define _GLIBCXX_BEGIN_NAMESPACE_CXX11 namespace __cxx11 {
# define _GLIBCXX_END_NAMESPACE_CXX11 }
# define _GLIBCXX_DEFAULT_ABI_TAG _GLIBCXX_ABI_TAG_CXX11
#else
# define _GLIBCXX_NAMESPACE_CXX11
# define _GLIBCXX_BEGIN_NAMESPACE_CXX11
# define _GLIBCXX_END_NAMESPACE_CXX11
# define _GLIBCXX_DEFAULT_ABI_TAG
#endif

string 和 list 简化一下实现大概应该是这个样子

#if _GLIBCXX_USE_CXX11_ABI
namespace std {
	inline namespace __cxx11 {
		class string {};
		class list {};
	};
};
#else
namespace std {
	class string {};
	class list {};
}
#endif

命名空间前加 inline 关键字是c++11 的新特性 “内联命名空间”,有兴趣的自己去了解一下,这个不是今天讨论的重点

使用 GCC 5.1+ 的编译器,默认 _GLIBCXX_USE_CXX11_ABI =1也就是默认使用新版ABI的string 和 list,如果要禁用c++11新版ABI, 只需要在Makefile中定义 -D _GLIBCXX_USE_CXX11_ABI=0就可继续使用旧版本ABI顺利编译通过

一段示例

典型的使用了老版ABI库的错误提示

tLogger.cpp:55: undefined reference to `log4cxx::WriterAppender::getEncoding[abi:cxx11]() const'
tLogger.cpp:58: undefined reference to `log4cxx::Level::toString[abi:cxx11]() const'  objs/bin/tLogger.o: In function `libtse::tLogger::createDailyRollingLogger(libtse::tLoggerAttribute const&)':
tLogger.cpp:117: undefined reference to `log4cxx::Logger::getLogger(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
tLogger.cpp:122: undefined reference to `log4cxx::WriterAppender::setEncoding(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
tLogger.cpp:123: undefined reference to `log4cxx::Level::toLevel(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
tLogger.cpp:138: undefined reference to `log4cxx::DailyRollingFileAppender::setDatePattern(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
tLogger.cpp:141: undefined reference to `log4cxx::PatternLayout::setConversionPattern(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
tLogger.cpp:154: undefined reference to `log4cxx::WriterAppender::setEncoding(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)'
tLogger.cpp:155: undefined reference to `log4cxx::Level::toLevel(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)' objs/bin/tLogger.o: In function `libtse::tLogger::setlevel(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)':
tLogger.cpp:180: undefined reference to `log4cxx::Level::toLevel(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)' objs/bin/tLogger.o: In function `libtse::tLogger::trace(char const*, ...)':
tLogger.cpp:192: undefined reference to `log4cxx::Logger::trace(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const' objs/bin/tLogger.o: In function `libtse::tLogger::debug(char const*, ...)':
tLogger.cpp:204: undefined reference to `log4cxx::Logger::debug(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const' objs/bin/tLogger.o: In function `libtse::tLogger::info(char const*, ...)':
tLogger.cpp:216: undefined reference to `log4cxx::Logger::info(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const' objs/bin/tLogger.o: In function `libtse::tLogger::warn(char const*, ...)':
tLogger.cpp:228: undefined reference to `log4cxx::Logger::warn(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const' objs/bin/tLogger.o: In function `libtse::tLogger::error(char const*, ...)':
tLogger.cpp:240: undefined reference to `log4cxx::Logger::error(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const' objs/bin/tLogger.o: In function `libtse::tLogger::fatal(char const*, ...)':
tLogger.cpp:252: undefined reference to `log4cxx::Logger::fatal(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const'
collect2: error: ld returned 1 exit status

踩坑

– 情形1

情形描述

  • 库LibA 指定采用旧版ABI编译( _GLIBCXX_USE_CXX11_ABI = 0)
  • 库LibB 采用默认编译选项 (_GLIBCXX_USE_CXX11_ABI = 1)
  • 库LibB 引用了 LibA
  • 程序C依赖LibA和LibB,采用默认编译选项 (_GLIBCXX_USE_CXX11_ABI = 1)

程序会莫名奇妙的宕机!!

疑问:为什么LibA使用旧版本ABI,而程序使用新版ABI却可以编译通过?
解答:程序C中没有引用到LibA内有关string 和 list的接口

– 情形2

情形描述

  • 库LibA 采用默认编译选项 _GLIBCXX_USE_CXX11_ABI = 1
  • 库LibB 指定采用旧版ABI编译 (_GLIBCXX_USE_CXX11_ABI = 0)
  • 库LibB 引用了 LibA
  • 程序C依赖LibA和LibB,由于LIbB使用了旧版ABI,需要指定 _GLIBCXX_USE_CXX11_ABI = 0才行

程序任然会莫名奇妙的宕机!!

宕机的位置

AddressSanitizer:DEADLYSIGNAL
=================================================================
==3730==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000010 (pc 0x7f5edb0c60d5 bp 0x7fffc90c5240 sp 0x7fffc90c5228 T0)
==3730==The signal is caused by a READ memory access.
==3730==Hint: address points to the zero page.
    #0 0x7f5edb0c60d4 in __pthread_cond_signal (/lib64/libpthread.so.0+0xc0d4)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib64/libpthread.so.0+0xc0d4) in __pthread_cond_signal

结论

– 怎么发现的

  1. 最近升级了gcc9高版本,修改了Makefile
  2. 这问题断断续续我查了好几天时间,觉得代码是不是有什么隐藏bug,完全没有想到是ABI定义不一致问题造成的
  3. 由于我还有一台云主机,上面也有工程代码,在这上面编译运行却一切正常

接下来验证一下自己的猜想

– 情形1

  • 库LibA 指定采用旧版ABI编译 (_GLIBCXX_USE_CXX11_ABI = 0)
  • 库LIbB 指定采用旧版ABI编译 (_GLIBCXX_USE_CXX11_ABI = 0)
  • 程序C 指定采用旧版ABI编译 (_GLIBCXX_USE_CXX11_ABI = 0)

运行正常

– 情形2

  • 库LibA 指定采用新版ABI编译 (_GLIBCXX_USE_CXX11_ABI = 1)
  • 库LIbB 指定采用新版ABI编译 (_GLIBCXX_USE_CXX11_ABI = 1)
  • 程序C 指定采用新版ABI编译 (_GLIBCXX_USE_CXX11_ABI = 1)

运行正常

– 总结

  • 首先一个观点,在实际项目中使用老版ABI的库是再正常不过的事情,如果升级了gcc 5.1+ 并且引用了库中string或者list相关接口,势必需要指定 (_GLIBCXX_USE_CXX11_ABI = 0)
  • 将引用到的老版本ABI库使用新gcc重新编译,如果没有源码或者编译三方库非常麻烦,确保引用的库中间不要混合使用新版ABI和旧版ABI

环境

系统环境

CentOS Linux release 7.7.1908 (Core)

编译器环境

COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/local/gcc-9.3.0/libexec/gcc/x86_64-pc-linux-gnu/9.3.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ../gcc-9.3.0/configure --prefix=/usr/local/gcc-9.3.0/ --enable-checking=release --enable-languages=c,c++ --disable-multilib
Thread model: posix
gcc version 9.3.0 (GCC) 

引用

  1. https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html
  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值