记一次C++错误静态链接导致进程被Abort

静态链接符号重名导致的Abort

本质上来说,这就是编译时连接器ld没有发现符号名冲突,进而导致运行时链接器ld-linux-$(arch).so将同一个对象初始化了两遍,并且在该对象上调用了两遍析构函数,进而导致析构时引发double free

需要注意的是,只有 *nux 系统中才有这种问题,Windows 系统中并不存在该问题,因为 Windows 切实处理好了所有被静态链接的库,会在内存中加载相同的副本。

由于涉及到链接器的细节,所以我将仔细区分编译式链接器和运行式链接器。

在编译 gloggtest 时,你是否遇到过无端出现 Aborted的情况?根据退出消息,你的进程出现了 double free。然而,即使你把代码审查了个遍,仍然没有发现究竟在哪里发生了 double free。这时就需要考虑一下代码之外的问题,比如这可能是链接器导致的。

在编译时,你的主程序编译时链接了一个动态库。而你的主程序和动态库都编译时链接了一个静态库,而这个静态库又是由C++编写的,且拥有非static的、析构函数中需要调用free(void *)的变量,此时就有不小的发生 double free 以及内存泄漏的风险。而对于static变量,则只有内存泄露的问题,不过通常这种程度的泄露不会有任何问题。

为什么没有在编译时发现

编译时链接器进行链接时,如果是 .o 文件,那么当符号发生冲突时,编译时链接器将会报告链接错误。然而,如果是.a 或 .so 文件,编译时链接器会忽略第一个符号以后的其他符号。

原因

C/C++均使用符号作为启动时期定位全局变量内存地址的方式,在程序执行运行时动态链接时,运行时链接器会将变量名与地址空间相关联,同时会执行对应动态链接库的初始化函数,并且在程序结束时会执行库的卸载回调函数。

对于 c 语言库而言,如果不对函数使用特殊标记__attribute__((constructor)) / __attribute__((destructor)),那么就没有额外的初始化函数及拆卸回调要执行。但是对于 c++ 而言,在加载动态库以及程序退出时,均会执行全局变量的构造及析构函数,并且每一个动态库都会执行一次。然而,由于编译时链接器仅链接了第一个符号,两次构造也均会在同一个内存地址上进行构造和析构,发生两次构造,只会导致内存泄露。然而,发生两次析构则会导致 double free。

std::string为例,如果在cpp文件中写了全局变量std::string g_abort_str = "0123456789abcdef"运行时动态链接器会为每个so中的该符号执行一次构造函数,然而,对于导出符号而言,即使同名符号处于两个不同的动态链接库中,最终仍然会映射到内存当中的同一地址,该地址会被初始化两次,显然,第一次初始化的使用的内存发生了内存泄露。而当程序退出,运行时链接器执行清理时,同样会对该地址执行两次析构函数,第一次析构函数当然可以正常释放内存,而第二次析构函数执行时,由于该内存已经在第一次析构函数当中释放,再次尝试释放该内存则会导致 double free。注意字符串长度如果小于16个字节,则会因为SSO优化而不会有该问题。

如何避免

下面我列举几种可能的规避方式。正确使用以下的任何一种方法都可以规避该问题。

  1. 在编译选项中加入-Wl,--exclude-libs,<your_static_lib>

    -Wl为gcc 传给静态链接器ld的选项。gcc -Wl,aa,bbb,cde在gcc传给ld时,会变为ld aaa bbb cde这种形式。

    需要注意的是这里的lib名是需要lib前缀的。

    通常情况下,一个动态库链接静态库后,这个动态库也会继承静态库当中的导出符号(即非static符号),而使用该链接选项,则可以让动态库在链接静态库后将静态库中的导出符号改为非导出符号,相当链接时为静态库中的所有函数、变量均加上了 static。这样所有静态库符号都保存在本地,同样也不会被主程序或其他动态链接库劫持符号。

  2. 在编译选项中加入-Wl,-Bsymbolic。(不推荐)

    这会使共享库中的全局符号优先绑定到本地(locally),不会被主程序或其他共享库劫持。然而,这也会导致共享库出现一些其他的行为差异。

  3. 使用dlopen

    使用dlopen打开动态库,例如dlopen("libconflict.so", RTLD_LOCAL | RTLD_LAZY);RTLD_LAZY同样也会保证该 so 中的符号不被劫持。

  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2019-2025 Ytyan

请我喝杯咖啡吧~