使用这样的函数:
#include <stdio.h> #include <stdlib.h> #include <sys/wait.h> #include <unistd.h> void print_trace() { char pid_buf[30]; sprintf(pid_buf, "--pid=%d", getpid()); char name_buf[512]; name_buf[readlink("/proc/self/exe", name_buf, 511)]=0; int child_pid = fork(); if (!child_pid) { dup2(2,1); // redirect output to stderr fprintf(stdout,"stack trace for %s pid=%s\n",name_buf,pid_buf); execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "bt", name_buf, pid_buf, NULL); abort(); /* If gdb failed to start */ } else { waitpid(child_pid,NULL,0); } }
我在输出中看到了print_trace的详细信息。
还有什么其他方法可以做到?
您在我的另一个答案(现已删除)中提到,您还希望查看行号。从应用程序内部调用gdb时,我不确定该怎么做。
但是,我将与您分享几种 不使用gdb 即可打印具有函数名称及其相应行号的简单stacktrace的方法。其中大多数来自Linux Journal的一篇 非常好的 文章:
第一种方法是通过打印和日志消息进行传播,以查明执行路径。在复杂的程序中,即使借助某些特定于GCC的宏,此选项也可能变得繁琐而乏味。例如,考虑一个调试宏,例如:
#define TRACE_MSG fprintf(stderr, __FUNCTION__ \ "() [%s:%d] here I am\n", \ __FILE__, __LINE__)
您可以通过剪切和粘贴该宏在整个程序中快速传播。当您不再需要它时,只需将其定义为无操作即可将其关闭。
但是,获取堆栈回溯的一种更好的方法是使用glibc提供的某些特定支持功能。关键是backtrace(),它可以将堆栈帧从调用点导航到程序的开头,并提供返回地址数组。然后,您可以使用nm命令查看目标文件,从而将每个地址映射到代码中特定功能的主体。或者,您可以使用一种更简单的方法- 使用backtrace_symbols()。此函数将由backtrace()返回的返回地址列表转换为字符串列表,每个字符串包含函数内的函数名称偏移量和返回地址。字符串列表是从堆空间分配的(就像您调用了malloc()一样),因此一旦完成处理,就应该立即释放它。
我鼓励您阅读它,因为该页面有源代码示例。为了将地址转换为函数名,必须使用 -rdynamic 选项编译应用程序。
这项技术的一个更有用的应用是将堆栈回溯放入信号处理程序中,并让信号处理程序捕获程序可以接收的所有“不良”信号(SIGSEGV,SIGBUS,SIGILL,SIGFPE等)。这样,如果不幸的是程序崩溃了,并且您没有使用调试器运行它,则可以获取堆栈跟踪并知道错误发生在哪里。此技术还可以用于了解程序停止响应时在哪里循环
此处提供了此技术的实现。
我对方法3进行了一点改进,以打印行号。也可以将其复制以使用方法2。
基本上,我跟着一个尖端,它使用 addr2line 到
将地址转换为文件名和行号。
下面的源代码显示所有本地功能的行号。如果调用了另一个库中的函数,则可能会看到几个??:0而不是文件名。
??:0
#include <stdio.h> #include <signal.h> #include <stdio.h> #include <signal.h> #include <execinfo.h> void bt_sighandler(int sig, struct sigcontext ctx) { void *trace[16]; char **messages = (char **)NULL; int i, trace_size = 0; if (sig == SIGSEGV) printf("Got signal %d, faulty address is %p, " "from %p\n", sig, ctx.cr2, ctx.eip); else printf("Got signal %d\n", sig); trace_size = backtrace(trace, 16); /* overwrite sigaction with caller's address */ trace[1] = (void *)ctx.eip; messages = backtrace_symbols(trace, trace_size); /* skip first stack frame (points here) */ printf("[bt] Execution path:\n"); for (i=1; i<trace_size; ++i) { printf("[bt] #%d %s\n", i, messages[i]); /* find first occurence of '(' or ' ' in message[i] and assume * everything before that is the file name. (Don't go beyond 0 though * (string terminator)*/ size_t p = 0; while(messages[i][p] != '(' && messages[i][p] != ' ' && messages[i][p] != 0) ++p; char syscom[256]; sprintf(syscom,"addr2line %p -e %.*s", trace[i], p, messages[i]); //last parameter is the file name of the symbol system(syscom); } exit(0); } int func_a(int a, char b) { char *p = (char *)0xdeadbeef; a = a + b; *p = 10; /* CRASH here!! */ return 2*a; } int func_b() { int res, a = 5; res = 5 + func_a(a, 't'); return res; } int main() { /* Install our signal handler */ struct sigaction sa; sa.sa_handler = (void *)bt_sighandler; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_RESTART; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGUSR1, &sa, NULL); /* ... add any other signal here */ /* Do something */ printf("%d\n", func_b()); }
此代码应编译为: gcc sighandler.c -o sighandler -rdynamic
gcc sighandler.c -o sighandler -rdynamic
程序输出:
Got signal 11, faulty address is 0xdeadbeef, from 0x8048975 [bt] Execution path: [bt] #1 ./sighandler(func_a+0x1d) [0x8048975] /home/karl/workspace/stacktrace/sighandler.c:44 [bt] #2 ./sighandler(func_b+0x20) [0x804899f] /home/karl/workspace/stacktrace/sighandler.c:54 [bt] #3 ./sighandler(main+0x6c) [0x8048a16] /home/karl/workspace/stacktrace/sighandler.c:74 [bt] #4 /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe6) [0x3fdbd6] ??:0 [bt] #5 ./sighandler() [0x8048781] ??:0
为最新的Linux内核版本 更新2012/04/28 ,以上sigaction签名已过时。我还通过从此答案中获取可执行文件的名称来对其进行了一些改进。这是最新版本:
sigaction
char* exe = 0; int initialiseExecutableName() { char link[1024]; exe = new char[1024]; snprintf(link,sizeof link,"/proc/%d/exe",getpid()); if(readlink(link,exe,sizeof link)==-1) { fprintf(stderr,"ERRORRRRR\n"); exit(1); } printf("Executable name initialised: %s\n",exe); } const char* getExecutableName() { if (exe == 0) initialiseExecutableName(); return exe; } /* get REG_EIP from ucontext.h */ #define __USE_GNU #include <ucontext.h> void bt_sighandler(int sig, siginfo_t *info, void *secret) { void *trace[16]; char **messages = (char **)NULL; int i, trace_size = 0; ucontext_t *uc = (ucontext_t *)secret; /* Do something useful with siginfo_t */ if (sig == SIGSEGV) printf("Got signal %d, faulty address is %p, " "from %p\n", sig, info->si_addr, uc->uc_mcontext.gregs[REG_EIP]); else printf("Got signal %d\n", sig); trace_size = backtrace(trace, 16); /* overwrite sigaction with caller's address */ trace[1] = (void *) uc->uc_mcontext.gregs[REG_EIP]; messages = backtrace_symbols(trace, trace_size); /* skip first stack frame (points here) */ printf("[bt] Execution path:\n"); for (i=1; i<trace_size; ++i) { printf("[bt] %s\n", messages[i]); /* find first occurence of '(' or ' ' in message[i] and assume * everything before that is the file name. (Don't go beyond 0 though * (string terminator)*/ size_t p = 0; while(messages[i][p] != '(' && messages[i][p] != ' ' && messages[i][p] != 0) ++p; char syscom[256]; sprintf(syscom,"addr2line %p -e %.*s", trace[i] , p, messages[i] ); //last parameter is the filename of the symbol system(syscom); } exit(0); }
并像这样初始化:
int main() { /* Install our signal handler */ struct sigaction sa; sa.sa_sigaction = (void *)bt_sighandler; sigemptyset (&sa.sa_mask); sa.sa_flags = SA_RESTART | SA_SIGINFO; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGUSR1, &sa, NULL); /* ... add any other signal here */ /* Do something */ printf("%d\n", func_b()); }