当前位置: 首页 > Linux

[问题分析]记得__do_global_ctors_aux引起的一次coredump

时间:2023-04-06 21:41:50 Linux

问题描述工作中一次C++代码测试,测试也反馈了一个严重的问题。我的代码在运行过程中会有100%的概率发生coredump。gdb解析core文件的结果如下(无关内容省略):#00x00007fcd165a1265inraise()from/lib64/libc.so.6#10x00007fcd165a2d10inabort()from/lib64/libc.so.6#20x00007fcd16e19d14在__gnu_cxx::__verbose_terminate_handler()()来自/usr/lib64/libstdc++.so.6#30x00007fcd16e17e16在??()来自/usr/lib64/libstdc++.so.6#40x00007fcd16e17e43instd::terminate()()来自/usr/lib64/libstdc++.so.6#50x00007fcd16e17f2ain__cxa_throw()from/usr/lib64/libstdc++.so.6#60x00007fcd17b7d27eingoogle::protobuf::internal::LogMessage::Finish()()来自/home/a/dms/dp2/lib/libprotobuf.so.7#70x00007fcd17b8e352ingoogle::protobuf::DescriptorPool::InternalAddGeneratedFile(voidconst*,int)()来自/home/a/dms/dp2/lib/libprotobuf.so.7#80x00007fcd0b97e524inecpm::proto::algolog::protobuf_AddDesc_proto_2falgo_5fcommon_2eproto()()来自/home/a/search/dmerger/var/inner_lib/2017_01_17_12_24_16/libdmerger_sn_handler.so#90x00007fcd0b97f326in__do_global_ctors_aux()from/home/a/search/dmerger/var/inner_lib/2017_01_17_12_24_16/libdmerger_sn_handler.so#100x00007fcd0b8fa0e3in_init()from/home/a/search/dmerger/var/inner_lib/2017_01_17_12_24_16/libdmerger_sn_handler.so#110x00007fcd0b8e3a14在??()来自/home/a/search/dmerger/var/inner_lib/2017_01_17_12_24_16/libdmerger_sn_handler.so#120x0000003f2940d3fbincall_init()from/lib64/ld-linux-x86-64.so.2#130x0000003nalnal_intern_d505init()来自/lib64/ld-linux-x86-64.so.2#140x0000003f29410ffeindl_open_worker()from/lib64/ld-linux-x86-64.so.2#150x0000003f2940d086in_dl_catch_error()from/lib64/ld-linux-x86-64.so.2#160x0000003f294107dcin_dl_open()from/lib64/ld-linux-x86-64.so.2#170x00007fcd1705cf9aindlopen_doit()from/lib64/libdl.so.2#180x000000d0f69在_dl_catch_error()来自/lib64/ld-linux-x86-64.so.2#190x00007fcd1705d50din_dleerror_run()from/lib64/libdl.so.2#200x00007fcd1705cf11indlopen@@GLIBC_2.2.5()from/lib64/libdl.so.2#210x00007fcd18a59e9dinecpm_summer2::0fbload::thisd,soPath=0xc0af88"/home/a/search/dmerger/var/inner_lib/2017_01_17_12_24_16/libdmerger_sn_handler.so")atbuild/release64/ecpm_summer2/SoManager.cpp:33#220x00007fcd18a5e465和ecStepmer::He和ecStepmer::=0xbfbc70,name=,flag=)atbuild/release64/ecpm_summer2/Step.cpp:166刚开始看到这个分析结果,本能的觉得可能哪里不对与代码注意错误使用指针,导致内存错误,但我检查了整篇文章的所有代码,并没有发现任何可能的错误。只好请来了从业多年的高手帮我看一下这个内核。大师就是大师,一眼就看出端倪,指明了解题方向。下面详细说说分析过程。分析过程1、从最后一行分析结果可以看出,核心是在一个叫做loadHandler的函数中启动的。这个loadHandler做了一件事,就是在进程中调用dlopen加载动态库(.so)。从第19行可以看出,使用dlopen加载动态库时出现了明显的错误。继续向上追查,发现在加载动态库的过程中,第9行提示调用__do_global_ctors_aux(),然后出现了protobuf相关的错误。相信大家都对protobuf有所了解。它是谷歌开源的一种数据结构。它可以根据开发者的定义生成相应的消息进行通信。在我们的代码中,protobufmessages是在代码中定义的,以静态库的形式加入到编译过程中。从应用层加载动态库的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.但是为什么程序在构造全局变量时会失败?这个问题让我很困惑。后来在同事的提示下,在公司里找到了一篇文章,指出了问题所在。原来,由于每个动态库都被看做是一个独立的库,所以在加载一个动态库的时候,必须先加载它里面的所有全局变量,这是完全没有问题的。但是有一种极端的情况,就是加载状态库的全局变量定义在一个依赖动态库的静态库中,而这个静态库又被主程序(即调用的程序)所依赖尝试加载动态库)。这样的极端情况会导致静态库中的全局变量symbols被连续两次加载到内存中,而且两次加载时symbols的名字完全一样,会导致加载过程中出现运行时错误。有了这个提示,我很快检查了我的代码库的依赖关系,发现主程序和加载的动态库都依赖于定义protobuf消息字段的静态库,这导致主程序尝试加载时出现protobuf消息动态库如果重复定义为全局变量,会报错退出。解决方案解决方案其实很简单,取消protobufmessage字段的全局属性,或者将定义protobufmessage字段的库声明为动态库。总结以下总结为个人猜测和理解,不一定完全正确,如有错误请指正。综上所述,这个错误的解决方案给我带来了以下收获:1、对动态库和静态库的区别有了更深刻的认识。静态库本身编译完成后,内部的符号名就暴露出来了,这些符号在主程序的编译过程中会用到,所有的静态库在链接链接的时候会一起编译成一个二进制文件。动态库是在主程序生成二进制文件后以特定的方式加载的。加载完成后,库中的符号名就会暴露给外部,主程序就可以调用库中的函数了。动态库可以理解为一个独立的项目,动态库之间的关系是“调用”,而静态库会“依附”到目标项目的二进制文件中。2.理解动态库的加载过程是在main函数执行前构造全局变量,main函数执行后销毁全局变量。3、工作中注意库依赖,避免多个相互调用的动态库依赖同一个定义全局变量的静态库。