线上服务出现了重启告警,而且重启后继续。排查线上出现了一堆core,core信息显示如下:
这个是一个线程抛出的异常,但是看不到线程本身的代码,只可看到core文件堆栈栈钉出现在libstdc 库上。唯一业务代码在第6个栈帧。catch方块里的throw e代码上。
这里有几个问题。这里的异常实际上是在线程调度的函数抛出了异常。也就是thread->m_Runner->Execute()的时候,当时在异常抛出的时候,我们其实想要看到的是那个时候的堆栈内存快照。但是这个core发生的时候,已经退出了那个Execute函数,这个实际函数已经退出了,当时core点的堆栈已经展开了。然后函数跳过Execute函数,走到catch模块,到throw e位置,由于外部再无补货这个exception e的函数,函数在此结束。core信息反应的是此时throw e的内存快照。
1. 问题
1.1 c 的throw或者abort机制
这里引入了一个知识点就是:C 程序,如果 代码throw了 exception ,但是外部又没有 catch,那么一般会产生 coredump。而如果外面catch住了异常,这个就不会产生coredump,但是你也可以继续选择向上级抛异常,也可以忽略打点异常日志出来e.what()。 gcc有个宏_GLIBCXX_THROW_OR_ABORT定义你的行为是throw还是abort
我记得谷歌c 规范也是建议大家不写异常。但是如果团队内有人不遵守怎么办,我们可以在makefile加上(-gcc的 -fno-exceptions ,在编译阶段禁用异常机制。这样写了异常的代码不会通过。
gcc有个宏_GLIBCXX_THROW_OR_ABORT
的宏,該宏定義為
#ifndef _GLIBCXX_THROW_OR_ABORT
# if __cpp_exceptions
# define _GLIBCXX_THROW_OR_ABORT(_EXC) (throw (_EXC))
# else
# define _GLIBCXX_THROW_OR_ABORT(_EXC) (__builtin_abort())
# endif
#endif
1.2 线程执行机制
有了第一个问题的铺垫,我们知道上层代码如果catch住了调用函数的异常,会导致core信息显示的不是调用函数本身的堆栈。所以要么函数外面不cacth,要么让抛__cxa_throw的函数直接挂掉。
本文的第一个问题正是,在 gcc 4.x 版本(gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC) )产生的 coredump 文件中,没有 throw 时候的堆栈信息,导致不知道是哪里 throw 的,没法查问题。
我们继续看gcc 4.x 的 /libstdc -v3/src/c 11/thread.cc:92 的代码就发现原因。这个gcc 4.x的线程执行的实现里面有个 catch(…),所以 stack unwind 了,就没了 throw 时候的 stack 。
以下是gcc4.x-6.x的实现
而gcc7.x以上已经重新实现了,以下是gcc 7.x以上的实现
这里就没有了cache。https://abcdabcd987.com/libstdc -bug/
二、解决办法
2.1 升级到gcc7以上
一个解决办法是可以升级 GCC 7 ,
2.2 强行改写throw方法
因为不是所有的项目都方便切换到新版本的gcc,历史代码不好兼容新版本gcc,可以用更简单的办法:
1.代码 hook __cxa_throw
,
一个解决办法是通过改代码,hook __cxa_throw()
让每次生成的 coredump 都带上堆栈:
https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/cxxabi.h#L616
vim /usr/include/c /4.8.2/cxxabi.h 看到__cxa_throw的声明。__cxa_throw() 是 libstdc /libc 用于实现 throw 的函数。
https://libcxxabi.llvm.org/spec.html
我们可以重写(或者叫hook)
这个函数要加到你调用线程的那个文件里。比如我这里的Util/CThread.cpp(项目代码中线程执行函数)。
效果如下: