刚写的程序经常崩溃怎么办,设置断点当然是一个方法,但是如果是跨平台调试,又没有对应的gdb工具,过程就会变得十分痛苦,将程序崩溃栈打印出来是一种很好的方式。
glibc中的backtrace
#include <execinfo.h>
/*
* 功能: 获取当前线程的调用堆栈并存放在buffer中(指向字符串数组的指针)
* @param buffer: 存放当前线程的调用堆栈
* @param size: 指定buffer中可以保存多少个 void* 元素(void* 元素实际上是从堆栈中获取的返回地址)
* @return 实际返回的 void* 元素个数
* */
int backtrace(void **buffer, int size);
/*
* 功能: 将backtrace函数获取的信息转化为一个字符串数组
* @param buffer: backtrace获取的堆栈指针
* @param size: backtrace返回值
* @return: 一个指向字符串数组的指针, 包含 size 个 char* 元素, 每个元素包含了一个相对于buffer中对应元素的可打印信息(函数名、函数偏移地址和实际返回地址)
* */
char **backtrace_symbols(void *const *buffer, int size);
/*
* 功能: 与backtrace_symbols函数功能相同, 但是不会malloc内存, 而是将结果写入文件描述符为fd的文件中
* */
void backtrace_symbols_fd(void *const *buffer, int size, int fd);
backtrace
的实现依赖于栈指针(fp寄存器),在gcc编译过程中任何非零的优化等级(-On
参数)或加入了栈指针优化参数-fomit-frame-pointer
后可能不能正确得到程序栈信息backtrace_symbols
的实现需要符号名称的支持,在gcc编译时需要加入-rdynamic
参数- 内联函数没有栈帧,它在编译过程中被展开在调用位置
- 尾调用优化
Tail-Call Optimization
将复用当前函数栈而不再生成新的函数栈,这将导致栈信息不能被正确获取
API使用
/*
* dump 堆栈信息
* */
void DumpTraceback(int signal) {
const int size = 200;
void *buffer[size];
char **strings;
int nptrs = backtrace(buffer, size);
printf("backtrace() returned %d address\n", nptrs);
// backtrace_symbols函数不可重入, 可以使用backtrace_symbols_fd替换
strings = backtrace_symbols(buffer, nptrs);
if (strings) {
for (int i = 0; i < nptrs; ++i) {
printf("%s\n", strings[i]);
}
free(strings);
}
}
g3log中提供的崩溃栈打印函数
/// Generate stackdump. Or in case a stackdump was pre-generated and non-empty just use that one
/// i.e. the latter case is only for Windows and test purposes
std::string stackdump(const char* rawdump) {
if (nullptr != rawdump && !std::string(rawdump).empty()) {
return { rawdump };
}
const size_t max_dump_size = 50;
void* dump[max_dump_size];
size_t size = backtrace(dump, max_dump_size);
//获取崩溃栈信息
char** messages = backtrace_symbols(dump, static_cast<int>(size)); // overwrite sigaction with caller's address
// dump stack: skip first frame, since that is here
//格式化到输出流中
std::ostringstream oss;
for (size_t idx = 1; idx < size && messages != nullptr; ++idx) {
char* mangled_name = 0, *offset_begin = 0, *offset_end = 0;
// find parantheses and +address offset surrounding mangled name
for (char* p = messages[idx]; *p; ++p) {
if (*p == '(') {
mangled_name = p;
}
else if (*p == '+') {
offset_begin = p;
}
else if (*p == ')') {
offset_end = p;
break;
}
}
// if the line could be processed, attempt to demangle the symbol
if (mangled_name && offset_begin && offset_end &&
mangled_name < offset_begin) {
*mangled_name++ = '\0';
*offset_begin++ = '\0';
*offset_end++ = '\0';
int status;
//使用cxx编译器功能,将函数地址翻译为函数名
char* real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);
// if demangling is successful, output the demangled function name
if (status == 0) {
oss << "\n\tstack dump [" << idx << "] " << messages[idx] << " : " << real_name << "+";
oss << offset_begin << offset_end << std::endl;
}// otherwise, output the mangled function name
else {
oss << "\tstack dump [" << idx << "] " << messages[idx] << mangled_name << "+";
oss << offset_begin << offset_end << std::endl;
}
free(real_name); // mallocated by abi::__cxa_demangle(...)
}
else {
// no demangling done -- just dump the whole line
oss << "\tstack dump [" << idx << "] " << messages[idx] << std::endl;
}
} // END: for(size_t idx = 1; idx < size && messages != nullptr; ++idx)
free(messages);
return oss.str();
}