GNU/Linux 程式因記憶體存取錯誤崩潰時,通常依賴 core dump 檔案除錯。但無法在現場除錯時,若日誌中包含崩潰瞬間的堆疊呼叫資訊,將大幅加速定位問題。本文示範如何使用 backtrace 與 addr2line 在訊號處理器中自動產生可讀的堆疊日誌。
g++ -g -rdynamic -o mytest main.cpp
-g:編入除錯符號,addr2line 才能顯示行號。-rdynamic:將所有符號加入動態符號表,backtrace_symbols 才能解析函數名稱。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; ++i) {
if (s > 0) {
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);
}
int main() {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_handler = segv_handle;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGSEGV, &sa, 0) < 0) { perror("sigaction"); exit(1); }
foo(7); // 觸發遞迴最終導致 SIGSEGV
return 0;
}
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/shuheng/temp_blog/main.cpp:59
segv_handle(int) /home/shuheng/temp_blog/main.cpp:99
?? ??:0
foo_1(int) /home/shuheng/temp_blog/main.cpp:53
foo /home/shuheng/temp_blog/main.cpp:44
main /home/shuheng/temp_blog/main.cpp:38
...
gdb 分析。async-signal-safe 函數;本範例為示範簡單化,生產環境需謹慎處理 (如寫入預先開啟的檔案描述符)。