本文源码基于muduo v2.0.2分析
UML
继承自std::exception。如何使用该类可以看Exception_test.cc文件。 函数重点关注如何打印堆栈信息
backtrace打印堆栈
Exception::Exception(string msg)
: message_(std::move(msg)),
stack_(CurrentThread::stackTrace(/*demangle=*/false))
{
}
调用Exception的构造函数时会打印当前的堆栈信息。
CurrentThread::stackTrace(bool)
string stackTrace(bool demangle)
{
string stack;
const int max_frames = 200;
void* frame[max_frames];
//打印堆栈地址
int nptrs = ::backtrace(frame, max_frames);
//将地址转换成可以阅读的符号信息
char** strings = ::backtrace_symbols(frame, nptrs);
if (strings)
{
size_t len = 256;
//下面这块是由于编译器会将函数名字转换,通过abi::__cxa_demangle给转换回来的过程。具体见下文
char* demangled = demangle ? static_cast<char*>(::malloc(len)) : nullptr;
for (int i = 1; i < nptrs; ++i) // skipping the 0-th, which is this function
{
if (demangle)
{
// https://panthema.net/2008/0901-stacktrace-demangled/
// bin/exception_test(_ZN3Bar4testEv+0x79) [0x401909]
char* left_par = nullptr;
char* plus = nullptr;
for (char* p = strings[i]; *p; ++p)
{
if (*p == '(')
left_par = p;
else if (*p == '+')
plus = p;
}
if (left_par && plus)
{
*plus = '\0';
int status = 0;
char* ret = abi::__cxa_demangle(left_par+1, demangled, &len, &status);
*plus = '+';
if (status == 0)
{
demangled = ret; // ret could be realloc()
stack.append(strings[i], left_par+1);
stack.append(demangled);
stack.append(plus);
stack.push_back('\n');
continue;
}
}
}
// Fallback to mangled names
stack.append(strings[i]);
stack.push_back('\n');
}
free(demangled);
free(strings);
}
return stack;
}
backtrace和backtrace_symbols
#include <execinfo.h>
int backtrace(void **buffer, int size);
char **backtrace_symbols(void *const *buffer, int size);
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
backtrace返回调用者的堆栈信息,指向buffer指针数组。每个buffer的成员是void*类型,为栈帧的地址。size为地址的最大个数,如果超过了该值,则保留最近的size个栈帧,其余截断。函数返回值为地址个数。
backtrace_symbols()将地址转换成描述地址符号的string数组。该函数内部调用了malloc并返回,所以使用完毕需要free掉返回值。函数调用失败返回NULL。
backtrace_symbols_fd同backtrace_symbols,只不过将结果写入了fd中。
__cxa_demangle
至此,虽然已经通过backtrace_symbols将地址转换成可阅读的符号,但是因为编译器会对C++的符号名称进行转换,打印出的信息仍然是不可阅读的。
下面是截取的一段exception_test输出。
//转换后的堆栈信息
Stack inside std::bind:
./exception_test(Bar::callback()+0x12) [0x401f62]
./exception_test(foo()+0x13f) [0x401ebf]
./exception_test(main+0xb) [0x401c9b]
/usr/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f546b93b555]
./exception_test() [0x401ccf]
//未转换的打印
reason: oops
stack trace:
./exception_test(_ZN5muduo9ExceptionC1ENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE+0x50) [0x4021c0]
./exception_test() [0x401b7c]
./exception_test(main+0xb) [0x401c9b]
/usr/lib64/libc.so.6(__libc_start_main+0xf5) [0x7f546b93b555]
./exception_test() [0x401ccf]
可以看到我们并不知道_ZN5muduo9ExceptionC1ENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE+0x50
代表了什么。通过__cxa_demangle,可以还原编译器对符号的转换。
/*
Parameters:
mangled_name A NUL-terminated character string containing the name to be demangled.
output_buffer A region of memory, allocated with malloc, of *length bytes, into which the demangled name is stored. If output_buffer is not long enough, it is expanded using realloc. output_buffer may instead be NULL; in that case, the demangled name is placed in a region of memory allocated with malloc.
length If length is non-NULL, the length of the buffer containing the demangled name is placed in *length.
status *status is set to one of the following values:
0: The demangling operation succeeded.
-1: A memory allocation failiure occurred.
-2: mangled_name is not a valid name under the C++ ABI mangling rules.
-3: One of the arguments is invalid.
Returns:
A pointer to the start of the NUL-terminated demangled name, or NULL if the demangling fails. The caller is responsible for deallocating this memory using free.
*/
char* abi::__cxa_demangle ( const char * mangled_name,
char * output_buffer,
size_t * length,
int * status
)
Reference
C++ Code Snippet - Print Stack Backtrace Programmatically with Demangled Function Names