首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

Linux 程序崩潰自動捕獲:回溯堆棧與 addr2line 符號解析實戰

     編輯:關於C語言

背景:無 Core 文件時的現場診斷

嵌入式/生產環境常禁用 Core 轉儲,或無法現場調試。在信號處理函數中捕獲堆棧並輸出到日誌,配合 addr2line 事後解析,是定位崩潰的利器。

核心 API

  • backtrace(void **buffer, int size):獲取調用堆棧地址列表。
  • backtrace_symbols(void *const *buffer, int size):將地址轉為可讀字符串(含模糊符號)。
  • addr2line -ifC -e <exe> <addr>...:地址→文件:行號+函數名(需編譯帶 -g -rdynamic)。

編譯關鍵選項

g++ -g -rdynamic -o mytest main.cpp
# -g: 生成調試信息(行號)
# -rdynamic: 導出所有符號到動態符號表,供 backtrace_symbols 解析

完整實現代碼

// main.cpp
#include 
#include 
#include 
#include 
#include 
#include 

void dump_stack(FILE *log);
void segv_handle(int);

static int foo(int);
int foo_1(int);

int main() {
    struct sigaction sa = {0};
    sa.sa_handler = segv_handle;
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGSEGV, &sa, 0) < 0) {
        perror("sigaction"); exit(1);
    }
    foo(7);
    return 0;
}

static int foo(int a) { return foo_1(a - 1); }

int foo_1(int a) {
    if (a > 0) return foo(a - 1);
    char *p = 0;
    memcpy(p, "hello", 5); // 故意崩潰
    return 0;
}

void dump_stack(FILE *log) {
    void *bufs[100];
    int n = backtrace(bufs, 100);
    char **infos = backtrace_symbols(bufs, n);
    if (!infos) exit(1);

    fprintf(log, "==================\nFrame info:\n");
    for (int i = 0; i < n; ++i)
        fprintf(log, "%s\n", infos[i]);

    // 構建 addr2line 命令
    char cmd[512];
    int len = snprintf(cmd, sizeof(cmd), "addr2line -ifC -e ./mytest");
    char *p = cmd + len;
    size_t s = sizeof(cmd) - len;
    for (int i = 0; i < n && s > 0; ++i) {
        len = snprintf(p, s, " %p", bufs[i]);
        p += len; s -= len;
    }

    fprintf(log, "src info:\n");
    FILE *fp = popen(cmd, "r");
    if (fp) {
        char buf[128];
        while (fgets(buf, sizeof(buf), fp))
            fprintf(log, "%s", buf);
        pclose(fp);
    }
    fprintf(log, "==================\n");
    free(infos);
}

void segv_handle(int s) {
    dump_stack(stdout);
    exit(127 + s);
}

輸出示例

Frame info:
./mytest(_Z10dump_stackP8_IO_FILE+0x1f) [0x8048abb]
./mytest(_Z11segv_handlei+0x13) [0x8048cba]
[0x709400]
/lib/libc.so.6(memcpy+0x5a) [0x1c22ba]
./mytest(_Z5foo_1i+0x3e) [0x8048a95]
...
src info:
dump_stack(_IO_FILE*) /home/.../main.cpp:59
segv_handle(int)    /home/.../main.cpp:99
??                  ??:0
foo_1(int)          /home/.../main.cpp:53
foo                 /home/.../main.cpp:44
...

注意事項與避坑指南

  1. 信號處理函數中只能調用 Async-Signal-Safe 函數。backtrace、malloc(backtrace_symbols 內部調用)、fprintf、popen 均不安全。生產代碼應僅記錄原始地址,延遲到主流程或單獨進程解析。
  2. -rdynamic 僅對可執行文件有效,動態庫符號需 -fvisibility=default 或 dlopen 配合。
  3. 內聯函數、尾調用優化可能導致堆棧幀缺失。
  4. C++ 名稱修飾需 addr2line -C 解碼(GCC/Clang)。MSVC 環境用 StackWalk64 + SymFromAddr。
  5. 遞歸深度超過緩衝區大小(示例 100)會截斷,按需調大。

進階替代方案

  • libunwind:更健壯、跨平台、支持非信號上下文遍歷。
  • Google Breakpad / Crashpad:成熟崩潰報告框架,支持斷點上傳、符號服務器。
  • systemd-coredump:現代 Linux 發行版默認收集器,配合 coredumpctl 分析。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved