打印程序崩溃栈信息

打印程序崩溃栈信息

Louis 1022 2023-04-15

刚写的程序经常崩溃怎么办,设置断点当然是一个方法,但是如果是跨平台调试,又没有对应的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();
        }

参考文档

C++打印堆栈的几种方法 - 知乎 (zhihu.com)

GitHub - KjellKod/g3log