coredump

使用c++,难免coredump,coredump主要由以下几种:

segmentation fault

进程访问受限内存触发,OS会通过发信号SIGSEGV给进程,告知这个问题,如果进程没有自定义handler,用默认的,会终结掉进程。使用c/c++这种提供存在底层内存控制函数的语言,会导致这个问题。想java/rust一些高级语言会设计自己的机制,为用户屏蔽该问题,或者以更友好的方式让程序员更易于处理。通常的原因如下:

  • 访问不存在的内存空间或该进程外的地址空间
  • 访问本进程没有权限访问的内存,例如内核地址空间
  • 写只读内存空间,例如进程内存空间中的代码段空间

应用程序中常见的错误包括:

  • 使用指向非本进程内存空间的指针
  • 使用未初始化的指针
  • 使用被释放的指针
  • buffer overflow 或者 stack overflow
char *p1 = NULL;           // Null pointer
char *p2;                  // Wild pointer: not initialized at all.
char *p3  = malloc(10 * sizeof(char));  // Initialized pointer to allocated memory
                                        // (assuming malloc did not fail)
free(p3);                  // p3 is now a dangling pointer, as memory has been freed

使用空指针肯定会导致segfault,但是使用未初始化或被释放的指针可能导致随机的值,在程序中引起比较难查的问题。

segfault产生的core一般比较容易追查,直接按照常规手段进行debug即可。

throw exception

大部分core是由于segfault导致的,因为segfault包含的点较多,在使用内存过程中也较容易犯错。把exception单独拿出来的原因是它导致的core比较难追查,下面是之前遇到的一个core的堆栈信息:

coredump

默认的handler会发送SIGABRT给相应的进程,终止进程。导致上述进程coredump的原因可以从下面的标准输出看出来:

coredump2

可以看出来是调用stof传入字符串导致的exception。解决办法也比较常规,就是尽量捕获所有异常,不让应用程序内部的exception throw出去。但从core的堆栈看,是由于在程序因为异常退出时进行的回收操作,导致的内存访问问题,并不能说明真正出现异常的位置。(TODO 这里需要围绕brpc和c++运行库的异常处理机制扩展说明下)

应用代码出现exception,brpc中的bthread库不会catch,会抛出,处理方法和其他lib一致。但从图片的bt中看,应该是brpc在异常退出时,回收异常导致的。我在brpc的github上提出问题,并没有比较专业的回复,都是想让我把应用本身的问题查出来解决就行了。link。懂这个问题的同学可以联系我 adanteng@qq.com

coredump

该文件记录程序异常退出时的内存状态,程序的堆栈信息。可以在同进程中切换线程,查看stack信息。

gnu文档中关于coredump文件查看的内容比较少,可以参考 gnu doc 中描述的Stack和Data两章。日常分析主要用到如下命令:

  • bt 查看程序堆栈
  • f stackframe 切换到堆栈中的某个方法内部
  • p v 打印变量值,如果是指针,需要用到 p *ptr
  • info thread 可以查看当前进程中其他线程状态
  • pvector vec 当你使用第3个命令打印的时候,遇到stl中的容器,自己操作首尾指针比较麻烦,利用别人给出的.gdbinit文件会比较方便。link

注意coredump只是辅助,在很多情况下,还要依赖于你对于程序的理解,例如下图:

coredump3

图片中能反馈的信息有以下几点:

  • path变量被释放,从p path命令可以看到,path内部的数据都不合理,而程序中又试图传递合理的值
  • get_distance_from_gaode_map_thread是在线程中运行

光通过core信息,基本不可能弄清除到底发生了什么。还是要对上下文不断的推敲,例如:在pthread_create之后使用pthread_join等待该线程,为什么path还是会被销毁?最后其实是由于pthread_join使用的pid变量使用不当导致的。

再看一个例子:

coredump4

~OrderGroupDistrDisGat方法中对_getdsscore指针进行了释放,导致重复释放。综合上下文才知道是该变量在声明的时候没有初始化,导致~VirtualOrderGroupDistrDisGat在调用父类析构函数时释放了未初始化的指针。

所以,在process挂掉后,针对一个process的分析要是多方面的,通过core定位代码位置,通过log确定上下文场景,通过标准输出辅助确认异常错误(有些sdk或者运行库会打印到stderr中)。

stack

core是OS对于系统的一种保护方法,当基于c++运行库的编写的应用程序出现违规操作的时候,需要拒绝的并给出错误类型对应的信号。运行库要有自己默认的处理方法,一般都是退出。我们写的程序也是运行在别的程序设定的环境中,所以core不会让程序凭空消失,会留下痕迹,这就是coredump文件。那么coredump中的堆栈又是怎么能这么详细的?下面就简单介绍下栈在辅助程序运行,所做的一些事情。以下参考:link

------1

通过stack,运行库记录所有函数中的没有被销毁的变量,每个thread都有一片内存空间,用于存储在该thread内部发生的函数调用中各函数的堆栈,所以运行库当发生异常,只要回溯当前stack即可。

而当异常发生时,c++会Stack Unwind,就是运行库沿着stack查找各祖先函数体内是否又catch block,有就执行catch内逻辑,并继续执行下去。第二点中描述的core的bt,显示的部分,包含于栈回退过程中。当应用程序没有任何捕获机制时,会走__gnu_cxx::__verbose_terminate_handler导致程序最终退出。

coredump
Share this