Exception(带 stack trace 的异常基类)
- 前置
- Exception
- CurrentThread::stackTrace()
前置
ABI:
- Application Binary Interface,应用程序二进制接口,可以参考:细谈ABI
RTTI type_info:
- RTTI:Run Time Type Identification,运行时类型识别
mangle 和 demangle :
- mangle: c++为了实现重载和重写等,对符号进行了mangling,见文章:重载的函数名部分 例如func变为_Z4funcidPi
- demangle:还原被mangled的名字
statcktrace:
- 栈回溯,系统自主打印进程调用栈的行为
backtrace: ———— 参考:backtrace函数
- 标准库中有三个函数包含在头文件<execinfo.h>下:
1.int backtrace(void **buffer, int size);
该函数获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针数组,参数size用来指定buffer中可以保存多少个void元素 函数的返回值是实际返回的void元素个数 buffer中的void*元素实际是从堆栈中获取的返回地址
2.char **backtrace_symbols(void *const *buffer, int size);
该函数将backtrace函数获取的信息转化为一个字符串数组,参数buffer是backtrace获取的堆栈指针,size是backtrace返回值。函数返回值是一个指向字符串数组的指针,它包含char*元素个数为size 每个字符串包含了一个相对于buffer中对应元素的可打印信息,包括函数名、函数偏移地址和实际返回地址(backtrace_symbols生成的字符串占用的内存是malloc出来的,但是是一次性malloc出来的,释放是只需要一次性释放返回的二级指针即可)
返回值大概是这样的:./testexception() [0x40144f] /lib64/libc.so.6(__libc_start_main+0xf3) [0x7f5c17b90493] ./testexception() [0x4012be]
编译时加上–rdynamic选项可以打印出括号里的函数名称
3.void backtrace_symbols_fd(void *const *buffer, int size, int fd);
该函数与backtrace_symbols函数功能相同,只是它不会malloc内存,而是将结果写入文件描述符为fd的文件中,每个函数对应一行 该函数可重入
abi::__cxa_demangle:
char* abi::__cxa_demangle (const char* mangled_name, char* output_buffer, size_t* length, int* status)
mangled_name:
- 顾名思义
output_buffer:
- 内存区域,使用 malloc 分配,*length 字节,其中存储了 demangled 名称。 如果 output_buffer 不够长,则使用 realloc 对其进行扩展。 output_buffer 可以改为 NULL; 在这种情况下,demangled 名称被放置在用 malloc 分配的内存区域中
length:
- 在output_biffer中demangled的名称个数
status:
- 0:操作成功
- -1内存分配错误
- -2非有效mangle_name
- -3有传入的参数无效
返回值:成功返回以NULL结尾的指向demangle_name的字符数组指针,失败返回NULL,
注意:调用者应负责次此处的free()
Exception
最新C++11版muduo将fillStackTrace()和自定义demangle函数整合到了CurrentThread::stackTrace函数中
类图:
- what():打印一条自定义消息
- stackTrace():返回stack_.c_str()
- 在构造函数,调用CurrentThread::stackTrace,进行demangle,填充字符串stack
CurrentThread::stackTrace()
string stackTrace(bool demangle)//是否需要进行demangle { string stack; const int max_frames = 200;//最多有200个栈地址 void* frame[max_frames];//void **指针数组 int nptrs = ::backtrace(frame, max_frames);//栈地址个数 char** strings = ::backtrace_symbols(frame, nptrs); //strings是一个指向字符串数组的指针,它包含char*元素个数为nptrs 每个字符串包含了一个相对于frame中对应元素的可打印信息 //通常是这样: ./testexception(main+0xc2) [0x401a5f] /lib64/libc.so.6(__libc_start_main+0xf3) [0x7f49991cf493] if (strings) { size_t len = 256; char* demangled = demangle ? static_cast<char*>(::malloc(len)) : nullptr;//用来存储demangle后的函数名 for (int i = 1; i < nptrs; ++i) //跳过0(0是当前函数) { if (demangle)//如果需要进行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';截取'('到'+'部分的mangled_name int status = 0; char* ret = abi::__cxa_demangle(left_par+1, demangled, &len, &status);//进行demangle *plus = '+';//还原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);//一次性释放backtrace_symbols返回的二级指针 } return stack; }
逻辑就是将demangle和fillstacktrace整合到一起,见注释,分别从strings中取出来,一一demangle
下面是测试用例Exception_test.cc的运行结果:
- Stack:
- Stack inside lambda:
- Stack inside std::function:
- Stack inside std::bind:
- 原stack trace: