linux C++ 崩溃堆栈抓取全过程

1. 崩溃信号处理

当程序崩溃时,操作系统会发送一个信号给程序,通知它发生了异常。在 C++中,可以通过 signal 函数来注册一个信号处理程序,使程序能够在接收到该信号时执行自定义的代码。

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void signal_handler(int sig) {
    // print out all the info to a log file
    _exit(1); // exit the program
}

int main() {
    // register signal handler
    signal(SIGSEGV, signal_handler);

    // your code here

    return 0;
}

有这个回调函数就解决了崩溃是否能够被观测的问题。

接下来就是获取崩溃信息的途径。

2. 打印崩溃信息

一般察看函数运行时堆栈的方法是使用GDB之类的外部调试器。但是,实际运行环境中,我们不可能让程序一直在调试状态下运行。所以,在正常运行期间出错时,打印出函数的调用堆栈是非常有用的。backtrace函数就给我们提供了一个很好的接口。

在头文件"execinfo.h"中声明了三个函数用于获取当前线程的函数调用堆栈。

int backtrace(void **buffer, int size)

该函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,最大不超过size大小,在buffer中的指针实际是从堆栈中获取的返回地址,每一个堆栈框架有一个返回地址,注意某些编译器的优化选项对获取正确的调用堆栈有干扰,另外内联函数没有堆栈框架;删除框架指针也会使无法正确解析堆栈内容。

char ** backtrace_symbols (void *const *buffer, int size)

backtrace_symbols将从backtrace函数获取的信息转化为一个字符串数组。参数buffer应该是从backtrace函数获取的数组指针,size是该数组中的元素个数(backtrace的返回值)。函数返回值是一个指向字符串数组的指针,它的大小同buffer相同。每个字符串包含了一个相对于buffer中对应元素的可打印信息。它包括函数名,函数的偏移地址,和实际的返回地址。

该函数的返回值是通过malloc函数申请的空间,因此调用这必须使用free函数来释放指针.
注意:如果不能为字符串获取足够的空间函数的返回值将会为NULL

void backtrace_symbols_fd (void *const *buffer, int size, int fd)

backtrace_symbols_fd与backtrace_symbols 函数具有相同的功能,不同的是它不会给调用者返回字符串数组,而是将结果写入文件描述符为fd的文件中,每个函数对应一行。它不需要调用malloc函数,因此适用于有可能调用该函数会失败的情况。

下面是一个使用backtrace捕获异常并打印函数调用堆栈的例子:

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void signal_handler(int sig) {
    void *array[10];
    size_t size;

    // get void*'s for all entries on the stack
    size = backtrace(array, 10);

    // print out all the frames to a log file
    FILE* fp = fopen("crash.log", "w");
    if (fp != NULL) {
        fprintf(fp, "Error: signal %d\n", sig);
        backtrace_symbols_fd(array, size, fileno(fp));
        fclose(fp);
    }

    _exit(1); // exit the program
}

int main() {
    // register signal handler
    signal(SIGSEGV, signal_handler);

    // your code here

    return 0;
}

崩溃堆栈:

  • 函数调用地址:表示该函数在程序内存中的位置。
  • 函数名和参数:表示该函数的名称和参数列表。如果无法获取函数名,则显示为"???"。
  • 文件名和行号:表示该函数在源代码中的位置。如果无法获取文件名和行号,则显示为"(unknown)"。 
Error: signal 11
[0] ./myprogram(+0x1139) [0x563a21a8f139]
[1] ./myprogram(+0x1298) [0x563a21a8f298]
[2] /lib/x86_64-linux-gnu/libc.so.6(+0x3ef20) [0x7f9c1d069f20]
[3] ./myprogram(+0xe75) [0x563a21a8ee75]
[4] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7f9c1cf9e0b3]
[5] ./myprogram(+0x992) [0x563a21a8e992]

符号表(Symbol Table)是一个存储程序函数名、变量名和其他符号信息的数据库,通常包含函数名、函数的起始地址、函数的大小、文件名、行号等信息。

在Linux下,可以使用GNU Binutils提供的addr2line命令来查询符号表,从而将函数地址和行号转换为源代码中的具体位置。

以下是一个使用addr2line命令查询符号表并输出对应代码位置的示例代码:
 

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void signal_handler(int sig) {
    void *array[10];
    size_t size;

    // get void*'s for all entries on the stack
    size = backtrace(array, 10);

    // print out all the frames to a log file
    FILE* fp = fopen("crash.log", "w");
    if (fp != NULL) {
        fprintf(fp, "Error: signal %d\n", sig);
        for (int i = 0; i < size; i++) {
            fprintf(fp, "[%d] ", i);
            if (array[i] != NULL) {
                char addr2line_cmd[256];
                sprintf(addr2line_cmd, "addr2line -f -e ./myprogram %p", array[i]);
                FILE* addr2line_fp = popen(addr2line_cmd, "r");
                if (addr2line_fp != NULL) {
                    char line[256];
                    while (fgets(line, sizeof(line), addr2line_fp) != NULL) {
                        fprintf(fp, "%s", line);
                    }
                    pclose(addr2line_fp);
                }
            } else {
                fprintf(fp, "(unknown)\n");
            }
        }
        fclose(fp);
    }

    _exit(1); // exit the program
}

int main() {
    // register signal handler
    signal(SIGSEGV, signal_handler);

    // your code here

    return 0;
}

通过addr2line转换之后:

Error: signal 11
[0] ./myprogram(foo+0x1d) [0x563a21a8f139]
    at example.cpp:27
[1] ./myprogram(bar+0x18) [0x563a21a8f298]
    at example.cpp:36
[2] /lib/x86_64-linux-gnu/libc.so.6(__GI___libc_read+0x10) [0x7f9c1d069f20]
    at ../sysdeps/unix/syscall-template.S:185
[3] ./myprogram(main+0x25) [0x563a21a8ee75]
    at example.cpp:45
[4] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf3) [0x7f9c1cf9e0b3]
    at ../csu/libc-start.c:308
[5] ./myprogram(_start+0x2a) [0x563a21a8e992]
    at ???:0

参考:
C++ 捕获异常崩溃全流程_c++捕获所有异常-CSDN博客

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 在Linux平台上,使用C++的std::thread时可能会遇到崩溃的问题。这通常是由于没有启用多线程支持导致的。在引用中的情况中,错误信息显示“Enable multithreading to use std::thread: Operation not permitted”,提示需要启用多线程才能使用std::thread。为了解决这个问题,需要确保在编译和链接程序时启用多线程支持。在使用gcc编译器时,可以通过添加"-pthread"选项来启用多线程支持。例如,可以使用以下命令编译程序: ``` g++ -pthread -o program program.cpp ``` 这样就会在编译和链接过程中启用多线程支持,从而解决std::thread崩溃的问题。另外,还需要注意在使用std::thread时,确保在异常情况下调用join()或detach()来避免悬挂线程或资源泄漏。引用和中的代码示例展示了如何使用RAII机制和detach()函数来确保线程的正确管理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [C++11 std::thread线程库](https://blog.csdn.net/code_peak/article/details/118200082)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [[C++]记在Linux下使用std::thread的错误](https://blog.csdn.net/sixdaycoder/article/details/81910816)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值