嵌入式/生產環境常禁用 Core 轉儲,或無法現場調試。在信號處理函數中捕獲堆棧並輸出到日誌,配合 addr2line 事後解析,是定位崩潰的利器。
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
...
backtrace、malloc(backtrace_symbols 內部調用)、fprintf、popen 均不安全。生產代碼應僅記錄原始地址,延遲到主流程或單獨進程解析。-rdynamic 僅對可執行文件有效,動態庫符號需 -fvisibility=default 或 dlopen 配合。addr2line -C 解碼(GCC/Clang)。MSVC 環境用 StackWalk64 + SymFromAddr。coredumpctl 分析。