在GNU/Linux編程中,我們可能會遇到程序因為內存訪問錯誤而崩潰
或類似的問題。一般情況下,我們借助程序崩潰後生成的core文件
來定位引起程序崩潰的位置。
但有時我們無法在現場調試,只能依靠用戶傳回的一些日志文件的
內容來定位程序錯誤的位置。如果這時日志中包含程序崩潰時的棧
調用信息,那麼對排除錯誤將會有些幫助。
使用backtrace函數和addr2line程序可以幫助我們實現這個願望!
文章最後的代碼是一個簡陋的實現。
編譯:
g++ -g -rdynamic -o mytest main.cpp
注意: 不加 -g, addr2line程序無法打印行號;
不加 -dynamic, backtrace無法打印完整信息,
可能會沒有和函數名稱相關的信息;
執行:
./mytest
不過程序總有bug,backtrace和addr2line也一樣,
不知為何行號都向下竄了一行?我用C語言程序試的時候
沒這個問題;
對於某些程序,可能無法打印出完整堆棧信息,這時改用core
方法,也無法得到完整堆棧信息。
程序在fedora11上打印出如下信息,注意那個static函數。
=================================
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]
./mytest [0x8048a55]
./mytest(_Z5foo_1i+0x1a) [0x8048a71]
./mytest [0x8048a55]
./mytest(_Z5foo_1i+0x1a) [0x8048a71]
./mytest [0x8048a55]
./mytest(_Z5foo_1i+0x1a) [0x8048a71]
./mytest [0x8048a55]
./mytest(main+0x86) [0x8048a3a]
/lib/libc.so.6(__libc_start_main+0xe6) [0x15da66]
./mytest [0x8048921]
src info:
dump_stack(_IO_FILE*)
/home/shuheng/temp_blog/main.cpp:59
segv_handle(int)
/home/shuheng/temp_blog/main.cpp:99
??
??:0
??
??:0
foo_1(int)
/home/shuheng/temp_blog/main.cpp:53
foo
/home/shuheng/temp_blog/main.cpp:44
foo_1(int)
/home/shuheng/temp_blog/main.cpp:48
foo
/home/shuheng/temp_blog/main.cpp:44
foo_1(int)
/home/shuheng/temp_blog/main.cpp:48
foo
/home/shuheng/temp_blog/main.cpp:44
foo_1(int)
/home/shuheng/temp_blog/main.cpp:48
foo
/home/shuheng/temp_blog/main.cpp:44
main
/home/shuheng/temp_blog/main.cpp:38
??
??:0
_start
??:0
=================================
main.cpp:
========================================
// 2012年 02月 06日 星期一 09:20:08 CST
// author: 李小丹(Li Shao Dan) 字 殊恆(shuheng)
// K.I.S.S
// S.P.O.T
// copy from my tst_execinfo.c
// XXX g++ -g -rdynamic -o mytest main.cpp
// man backtrace_symbols
// man addr2line
#include <cstdio>
#include <cstdlib>
#include <csignal>
#include <cstring>
#include <unistd.h>
#include <execinfo.h>
void dump_stack(FILE *);
void segv_handle(int);
static int foo(int);
int foo_1(int);
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);
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;
//crash
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, "==================\n");
fprintf(log, "Frame info:\n");
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) {
fprintf(log, "%s\n", infos[i]);
if(s > 0) {
len = snprintf(p, s, " %p", bufs[i]);
p += len;
s -= len;
}
}
fprintf(log, "src info:\n");
FILE *fp;
char buf[128];
if((fp = popen(cmd, "r"))) {
while(fgets(buf, sizeof(buf), fp))
fprintf(log, "%s", buf);
pclose(fp);
}
fprintf(log, "==================\n");
free(infos);
// same as:
//backtrace_symbols_fd(bufs, n, STDOUT_FILENO);
}
void segv_handle(int s)
{
dump_stack(stdout);
exit(127 + s);
}