1. 问题

一个 C++ 程序,如果 throw 了 exception ,但是又没有 catch,那么一般会产生 coredump, 问题是,在 gcc 4.x 版本产生的 coredump 文件中,没有 throw 时候的堆栈信息,导致不知道是哪里 throw 的,没法查问题。

原因是 gcc 4.x 的 /libstdc++-v3/src/c++11/thread.cc:92 里面有个 catch(…),所以 stack unwind 了,就没了 throw 时候的 stack 。

1
2
3
4
5
6
7
void * execute_native_thread_routine(){
    try {
     ...   
    }catch(...){
        std::terminate();
    }
}

https://abcdabcd987.com/libstdc++-bug/

一个解决办法是可以升级 GCC 7 ,或者可以用更简单的办法:

2.代码 hook __cxa_throw ,让 coredump 带上堆栈

一个解决办法是通过改代码,hook __cxa_throw() 让每次生成的 coredump 都带上堆栈:

https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/libsupc%2B%2B/cxxabi.h#L616

1
2
// Throw the exception.
void __cxa_throw(void*, std::type_info*, void (_GLIBCXX_CDTOR_CALLABI *) (void *)) __attribute__((__noreturn__));

__cxa_throw() 是 libstdc++/libc++ 用于实现 throw 的函数。

https://libcxxabi.llvm.org/spec.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#include <iostream>
#include <stdexcept>
#include <thread>

extern "C" { //加这3行代码,通过 hook __cxa_throw,直接 abort,可以避免 stack unwind。
   void __cxa_throw(void* ex, void* info, void (*dest)(void*)) { ::abort(); }
}

void func(){
   throw std::runtime_error("die");
}

int main() {
   std::thread t(func);
   t.join();
   return 0;
}

效果如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
(gdb) bt
#0  __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:51
#1  0x00007ffff7264801 in __GI_abort () at abort.c:79
#2  0x0000555555554f53 in __cxa_throw (ex=0x7ffff0000ba0, info=0x555555756ce0 <typeinfo for std::runtime_error@@GLIBCXX_3.4>, 
   dest=0x7ffff7af4140 <std::runtime_error::~runtime_error()>) at test.cpp:6
#3  0x0000555555554f8f in func () at test.cpp:10
#4  0x0000555555555371 in std::__invoke_impl<void, void (*)()> (__f=@0x555555769e78: 0x555555554f53 <func()>)
   at /usr/include/c++/7/bits/invoke.h:60
#5  0x000055555555517e in std::__invoke<void (*)()> (__fn=@0x555555769e78: 0x555555554f53 <func()>) at /usr/include/c++/7/bits/invoke.h:95
#6  0x000055555555584c in std:: thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul> (this=0x555555769e78) at /usr/include/c++/7/thread:234
#7  0x0000555555555808 in std:: thread::_Invoker<std::tuple<void (*)()> >::operator() (this=0x555555769e78) at /usr/include/c++/7/thread:243
#8  0x00005555555557d8 in std:: thread::_State_impl<std:: thread::_Invoker<std::tuple<void (*)()> > >::_M_run (this=0x555555769e70)
   at /usr/include/c++/7/thread:186
#9  0x00007ffff7b096ef in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#10 0x00007ffff761c6db in start_thread (arg=0x7ffff6e85700) at pthread_create.c:463
#11 0x00007ffff734588f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95

3. gdb catch throw

如果是对已有的二进制,或者已经在运行的进程:

gdb 里面输入 catch throw 然后运行,gdb 就会在任何 throw 的时候暂停,即可看到 throw 时候的栈。