问题描述
在工作中一次C++代码提测中,测试同时反馈了一个严重的问题,我的代码在运行过程中会100%概率的出现coredump,gdb解析core文件结果如下(不相关内容被省略):
#0 0x00007fcd165a1265 in raise () from /lib64/libc.so.6
#1 0x00007fcd165a2d10 in abort () from /lib64/libc.so.6
#2 0x00007fcd16e19d14 in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib64/libstdc++.so.6
#3 0x00007fcd16e17e16 in ?? () from /usr/lib64/libstdc++.so.6
#4 0x00007fcd16e17e43 in std::terminate() () from /usr/lib64/libstdc++.so.6
#5 0x00007fcd16e17f2a in __cxa_throw () from /usr/lib64/libstdc++.so.6
#6 0x00007fcd17b7d27e in google::protobuf::internal::LogMessage::Finish() () from /home/a/dms/dp2/lib/libprotobuf.so.7
#7 0x00007fcd17b8e352 in google::protobuf::DescriptorPool::InternalAddGeneratedFile(void const*, int) () from /home/a/dms/dp2/lib/libprotobuf.so.7
#8 0x00007fcd0b97e524 in ecpm::proto::algolog::protobuf_AddDesc_proto_2falgo_5fcommon_2eproto() () from /home/a/search/dmerger/var/inner_lib/2017_01_17_12_24_16/libdmerger_sn_handler.so
#9 0x00007fcd0b97f326 in __do_global_ctors_aux () from /home/a/search/dmerger/var/inner_lib/2017_01_17_12_24_16/libdmerger_sn_handler.so
#10 0x00007fcd0b8fa0e3 in _init () from /home/a/search/dmerger/var/inner_lib/2017_01_17_12_24_16/libdmerger_sn_handler.so
#11 0x00007fcd0b8e3a14 in ?? () from /home/a/search/dmerger/var/inner_lib/2017_01_17_12_24_16/libdmerger_sn_handler.so
#12 0x0000003f2940d3fb in call_init () from /lib64/ld-linux-x86-64.so.2
#13 0x0000003f2940d505 in _dl_init_internal () from /lib64/ld-linux-x86-64.so.2
#14 0x0000003f29410ffe in dl_open_worker () from /lib64/ld-linux-x86-64.so.2
#15 0x0000003f2940d086 in _dl_catch_error () from /lib64/ld-linux-x86-64.so.2
#16 0x0000003f294107dc in _dl_open () from /lib64/ld-linux-x86-64.so.2
#17 0x00007fcd1705cf9a in dlopen_doit () from /lib64/libdl.so.2
#18 0x0000003f2940d086 in _dl_catch_error () from /lib64/ld-linux-x86-64.so.2
#19 0x00007fcd1705d50d in _dlerror_run () from /lib64/libdl.so.2
#20 0x00007fcd1705cf11 in dlopen@@GLIBC_2.2.5 () from /lib64/libdl.so.2
#21 0x00007fcd18a59e9d in ecpm_summer2::SoManager::load (this=0xbfbd00, soPath=0xc0af88 "/home/a/search/dmerger/var/inner_lib/2017_01_17_12_24_16/libdmerger_sn_handler.so") at build/release64/ecpm_summer2/SoManager.cpp:33
#22 0x00007fcd18a5e465 in ecpm_summer2::Step::loadHandler (this=0xbfbc70, name=<value optimized out>, flag=<value optimized out>) at build/release64/ecpm_summer2/Step.cpp:166
刚开始看到这个解析结果,我本能的想到可能是代码中那个地方不小心错误的使用了指针,导致了内存错误,但是通篇检查了所有代码,也没有发现可能出错的地方。无奈,只好请来工作了多年的大神来帮我看一下这个core,大神不亏是大神,一眼就看出了端倪,指出了解决方向,下面就来详细说下分析过程。
分析过程
1、从解析结果最后一行可以看出,core开始的位置是在一个名叫loadHandler
的函数中,这个loadHandler
做了一件事,就是在进程中调用dlopen
加载动态库(.so)。从第19行来看,明显是在使用dlopen
加载动态库时出现了错误。继续向上追溯,发现在加载动态库的过程中,第9行提示了调用了__do_global_ctors_aux ()
,而后就出现了和protobuf
相关的错误。protobuf
相信大家都有所了解,是Google开源的一个数据结构,可以根据开发者的定义生成对应的消息,用于通信。在我们的代码中,protobuf
消息是在代码中定义并以静态库的形式加入编译环节的。可以看出从应用层的loadHandler
函数加载动态库开始,程序进入了glibc
,然后调用了__do_global_ctors_aux ()
函数,最后回到了应用层的protobuf
,如果应用层代码没有缺陷的话,那么一定是在执行__do_global_ctors_aux ()
时出了问题。
2、那么__do_global_ctors_aux ()
做了什么呢?
通过查阅资料得知,__do_global_ctors_aux ()
并不是glibc中的函数,而是来自于gcc编译器中的crtbegin.o。原来在进行动态连接时,并非所有函数都来自于glibc,也有一部分来源于gcc,这是因为在加载动态库的时候,需要提供一些和语言相关的支持函数。在加载动态库的时候,gcc会用__do_global_ctors_aux ()
和__do_global_dtors_aux ()
来构造和析构被加载的动态库中的全局变量(全局变量显然应该在库的函数加载前加载,在函数被析构后被析构)。
3、但是为什么程序会在构造全局变量时失败呢?
这个问题令我百思不得其解,后来在同事的提示下,我找到了公司内部的一篇文章,上面指出了问题所在。原来,由于每一个动态库都被当做一个独立的库,因此在加载动态库的时候要首先加载其内部的所有的全局变量,这原本没什么问题。但有一种极端情况,就是被加载态库的全局变量被定义在被该动态库依赖的一个静态库中,而这个静态库又被主程序(即尝试加载动态库的程序)依赖。这样的极端情况会导致静态库中的全局变量符号被先后两次加载到内存当中,两次加载时符号的命名完全一致,这会导致加载过程中的运行时错误。
有了这个提示,我赶快检查了我的代码库的依赖关系,发现主程序和被加载的动态库都会依赖定义protobuf
消息字段的静态库,这就导致当主程序尝试加载动态库时,protobuf
消息作为全局变量被重复定义,报错退出。
解决方法
解决方法其实很简单,取消protobuf
消息字段的全局属性,或者将定义protobuf
消息字段的库声明为动态库。
总结
以下总结属个人猜测和理解,不一定完全正确,如有纰漏请指正。
总结看来,本次错误地解决让我有如下收获:
1、更加深入了解了动态库与静态库的区别。
静态库在自身编译好以后,暴露出内部符号命名,在主程序的编译过程中会使用这些符号,并且在链接环节将所有静态库一起编译成为一个二进制文件。
动态库是在主程序生成二进制文件以后,通过特定方式进行加载,当加载完成以后,库内部的符号命名才会对外暴露,这时主程序才能调用库中的函数。
动态库都可以理解为是一个独立的工程,动态库之间都是“调用”关系,而静态库则会“依附”进目标工程的二进制中。
2、了解了动态库的加载流程是在main
函数执行前先构造全局变量,main
函数执行后析构全局变量。
3、工作上注意库的依赖关系,避免相互调用的多个动态库依赖同一个定义有全局变量的静态库。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。