如何从程序中调用gdb来打印它的堆栈跟踪?
现在我正在使用这样的function:
#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 。 他们大部分来自Linux Journal的一篇非常不错的文章:
- 方法#1:
第一种方法是通过打印和日志消息来传播它,以查明执行path。 在一个复杂的程序中,即使在一些GCC特定的macros的帮助下,这个选项也可能会变得繁琐和繁琐。 例如,考虑一个debuggingmacros,例如:
#define TRACE_MSG fprintf(stderr, __FUNCTION__ \ "() [%s:%d] here I am\n", \ __FILE__, __LINE__)
你可以通过剪切和粘贴来快速传播这个macros。 当你不再需要它时,只需将其定义为no-op即可closures它。
- 方法二:(它没有提及任何关于行号的信息,但是我使用方法4)
但是,获得堆栈回溯的更好方法是使用glibc提供的一些特定支持函数。 关键是backtrace(),它将栈帧从调用点导航到程序的开始处,并提供一个返回地址数组。 然后,您可以通过使用nm命令查看目标文件,将每个地址映射到代码中特定函数的主体。 或者,你可以做一个更简单的方法 – 使用backtrace_symbols()。 该函数将backtrace()返回的返回地址列表转换为一个string列表,每个string都包含该函数内的函数名称偏移量和返回地址。 string列表是从你的堆空间分配的(就像你叫做malloc()),所以你应该尽快释放()它。
我鼓励你阅读它,因为该页面有源代码示例。 为了将地址转换为函数名称,您必须使用-rdynamic选项编译您的应用程序。
- 方法3 🙁做方法2的更好方法)
对于这种技术来说,更有用的应用是在信号处理程序中放置一个栈回溯,并让后者捕获程序可以接收的所有“坏”信号(SIGSEGV,SIGBUS,SIGILL,SIGFPE等)。 这样,如果你的程序不幸崩溃,而你没有用debugging器运行它,你可以得到一个堆栈跟踪,并知道故障发生的地方。 这种技术也可以用来了解你的程序在循环停止响应的情况下
这个技术的实现可以在这里find 。
- 方法#4:
我在方法#3上做了一些小的改进来打印行号。 这也可以复制到方法#2上。
基本上,我遵循了一个使用addr2line 的提示
将地址转换成文件名和行号。
下面的源代码打印所有本地函数的行号。 如果调用另一个函数库中的函数,则可能会看到几个??: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
程序输出:
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
签名已经过时了。 此外,我通过从这个答案中获取可执行文件的名称来改进它。 这是一个最新的版本 :
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()); }
如果你使用的是Linux,标准的C库包含一个名为backtrace
的函数,该函数用一个帧的返回地址填充一个数组,另一个函数叫做backtrace_symbols
,它将从backtrace
获取地址并查找相应的函数名。 这些logging在GNU C库手册中 。
那些不会显示参数值,源代码行等,它们只适用于调用线程。 但是,它们比以这种方式运行GDB的速度要快得多(也许更less),所以它们有它们的位置。
我已经通知nobar他应该在这里发表他的梦幻般的答案 ,但是既然他没有动手指,我会为你们做:
所以你想要一个独立的函数,打印一个堆栈跟踪与所有的function, GDB堆栈跟踪具有,并不会终止您的应用程序。 答案是以非交互模式自动启动gdb来执行你想要的任务。
这是通过在subprocess中执行gdb来完成的,使用fork(),并在您的应用程序等待完成时脚本化它来显示堆栈跟踪。 这可以在不使用核心转储的情况下执行,也不需要中止应用程序。
我相信这就是你要找的@Vi
不是abort()
更简单?
这样,如果发生在这个领域,客户可以发送给你核心文件(我不知道有很多用户在我的应用程序中足够的参与,希望我强制他们进行debugging)。