从程序内部调用gdb打印堆栈跟踪的最佳方式是什么? [英] Best way to invoke gdb from inside program to print its stacktrace?
问题描述
使用这样的函数:
#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); //将输出重定向到stderr
fprintf(stdout,%s pid =%s\\\
的堆栈跟踪,name_buf,pid_buf);
execlp(gdb,gdb,--batch,-n,-ex,thread,-ex,bt,name_buf,pid_buf,NULL);
abort(); / *如果gdb无法启动* /
} else {
waitpid(child_pid,NULL,0);
}
}
我在输出中看到print_trace的细节。 / p>
有什么其他方法可以做到这一点?
我的另一个答案(现在删除),你也希望看到行号。我不确定如何从应用程序中调用gdb。
但我将与您分享几种打印简单堆栈跟踪的方法使用函数名称和它们各自的行号而不使用gdb 。他们大多数来自 Linux Journal 的非常漂亮的文章: / p>
第一种方法是按照
的顺序将print
分发给打印和日志消息以查明执行路径。在一个
复杂程序中,这个选项可能会变得繁琐乏味,即使
借助于某些GCC特定的
宏,它也可以被简化一些。
例如,考虑调试宏
,比如:
#define TRACE_MSG fprintf(stderr,__FUNCTION__ \
()[%s:%d] here I am \\\
,\
__FILE__,__LINE__)
$ code>
您可以通过剪切和
粘贴将整个宏快速传播
它。如果您不再需要
,只需将
定义为no-op就可以将其关闭。
- 方法2:(它没有提及任何关于行号的信息,但是我在方法4上做了)
但是,获得堆栈回溯的更好方法是使用
提供的某些
特定支持函数b glibc。关键之一是backtrace(),
将堆栈帧从
导入到程序的
开头,并提供一个
返回地址数组。然后,您可以通过
将
的每个地址映射到代码中
特定函数的主体,使用
nm命令查看目标文件。或者,您可以使用
更简单的方式 - 使用backtrace_symbols()。
该函数将由
backtrace()返回的
返回地址列表转换为字符串列表
,其中包含函数名称
offset函数和
返回地址。字符串列表是从你的堆空间分配的
(就好像
你叫做malloc()),所以你应该
free()它,只要你完成了
它。
我鼓励你阅读它,因为该页面有源代码的例子。为了将地址转换为函数名称,您必须使用 -rdynamic 选项编译您的应用程序。
- 方法#3:(做方法2的更好方法)
lockquote
对于
这种技术来说,一个更有用的应用是在信号处理程序中放置一个
的backtrace,后面的
可以捕获您的程序可以接收的所有坏
信号
(SIGSEGV,SIGBUS,SIGILL,SIGFPE和
等)。这样,如果你的程序
不幸崩溃,而你不是用
运行一个调试器,你可以
得到一个堆栈跟踪并知道
故障发生在哪里。这个技巧也可以用来理解你的
程序在循环中的位置,以防止
响应
该技术的实现可在此处。
$ b- 方法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(得到的信号%d,错误的地址是%p,
from%p \ n,sig,ctx .cr2,ctx.eip);
else
printf(Got signal%d \\\
,sig);
trace_size = backtrace(trace,16);
/ *用调用者的地址覆盖sigaction * /
trace [1] =(void *)ctx.eip;
messages = backtrace_symbols(trace,trace_size);
/ *跳过第一个堆栈帧(点在这里)* /
printf([bt] Execution path:\\\
);
for(i = 1; i< trace_size; ++ i)
{
printf([bt]#%d%s \ n,i,messages [i]) ;
/ *在消息[i]中首先发现'('或''并假定
*之前的所有内容都是文件名(不要超过0,尽管
*(string terminator)* /
size_t p = 0;
while(messages [i] [p]!='('&& messages [i] [p]!=' '
&&&&&&&&信息[i] [p]!= 0)
++ p;
$ b $ char syscom [256];
sprintf(syscom, addr2line%p -e%。* s,trace [i],p,messages [i]);
//最后一个参数是符号的文件名
system(syscom);
exit(0);
}
int func_a(int a,char b){
char * p =(char *)0xdeadbeef;
a = a + b;
* p = 10; / * CRASH here !! * /
返回2 * a;
}
int func_b(){
int res,a = 5;
res = 5 + func_a(a,'t');
return res;
}
int main(){
/ *安装我们的信号处理程序* /
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);
/ * ...在这里添加任何其他信号* /
/ *做一些事情* /
printf(%d\\\
,func_b());
}
这段代码应该编译为:
gcc sighandler。 c -o sighandler -rdynamic
程序输出:
得到信号11,错误的地址是0xdeadbeef,从0x8048975
[bt]执行路径:
[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]
$:
对于最近的Linux内核版本,上面的
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 \\\
);
exit(1);
}
printf(Executable name initialised:%s\\\
,exe);
$ b $ const char * getExecutableName()
{
if(exe == 0)
initialiseExecutableName();
返回exe;
}
/ *从ucontext.h获取REG_EIP * /
#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;
$ b $ *如果(sig == SIGSEGV)
printf(得到的信号%d,错误的地址是%p,$ b $,做一些对siginfo_t * /
有用的东西bfrom%p \\\
,sig,info-> si_addr,
uc-> uc_mcontext.gregs [REG_EIP]);
else
printf(Got signal%d \\\
,sig);
trace_size = backtrace(trace,16);
/ *用调用者的地址覆盖sigaction * /
trace [1] =(void *)uc-> uc_mcontext.gregs [REG_EIP];
messages = backtrace_symbols(trace,trace_size);
/ *跳过第一个堆栈帧(点在这里)* /
printf([bt] Execution path:\\\
);
for(i = 1; i< trace_size; ++ i)
{
printf([bt]%s \ n,messages [i]);
/ *在消息[i]中首先发现'('或''并假定
*之前的所有内容都是文件名(不要超过0,尽管
*(string terminator)* /
size_t p = 0;
while(messages [i] [p]!='('&& messages [i] [p]!=' '
&&&&&&&&信息[i] [p]!= 0)
++ p;
$ b $ char syscom [256];
sprintf(syscom, addr2line%p -e%。* s,trace [i],p,messages [i]);
//最后一个参数是符号的文件名
system(syscom);
$ b exit(0);
}
并初始化如下:
pre $ int b
$ *安装我们的信号处理程序* /
struct sigaction sa;
$ b $ 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);
/ * ...在这里添加任何其他信号* /
/ *做些什么* /
printf(%d \ n,func_b() );
}
Using a function like this:
#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); } }
I see the details of print_trace in the output.
What are other ways to do it?
解决方案You mentioned on my other answer (now deleted) that you also want to see line numbers. I'm not sure how to do that when invoking gdb from inside your application.
But I'm going to share with you a couple of ways to print a simple stacktrace with function names and their respective line numbers without using gdb. Most of them came from a very nice article from Linux Journal:
- Method #1:
The first method is to disseminate it with print and log messages in order to pinpoint the execution path. In a complex program, this option can become cumbersome and tedious even if, with the help of some GCC-specific macros, it can be simplified a bit. Consider, for example, a debug macro such as:
#define TRACE_MSG fprintf(stderr, __FUNCTION__ \ "() [%s:%d] here I am\n", \ __FILE__, __LINE__)
You can propagate this macro quickly throughout your program by cutting and pasting it. When you do not need it anymore, switch it off simply by defining it to no-op.
- Method #2: (It doesn't say anything about line numbers, but I do on method 4)
A nicer way to get a stack backtrace, however, is to use some of the specific support functions provided by glibc. The key one is backtrace(), which navigates the stack frames from the calling point to the beginning of the program and provides an array of return addresses. You then can map each address to the body of a particular function in your code by having a look at the object file with the nm command. Or, you can do it a simpler way--use backtrace_symbols(). This function transforms a list of return addresses, as returned by backtrace(), into a list of strings, each containing the function name offset within the function and the return address. The list of strings is allocated from your heap space (as if you called malloc()), so you should free() it as soon as you are done with it.
I encourage you to read it since the page has source code examples. In order to convert an address to a function name you must compile your application with the -rdynamic option.
- Method #3: (A better way of doing method 2)
An even more useful application for this technique is putting a stack backtrace inside a signal handler and having the latter catch all the "bad" signals your program can receive (SIGSEGV, SIGBUS, SIGILL, SIGFPE and the like). This way, if your program unfortunately crashes and you were not running it with a debugger, you can get a stack trace and know where the fault happened. This technique also can be used to understand where your program is looping in case it stops responding
An implementation of this technique is available here.
- Method #4:
A small improvement I've done on method #3 to print line numbers. This could be copied to work on method #2 also.
Basically, I followed a tip that uses addr2line to
convert addresses into file names and line numbers.
The source code below prints line numbers for all local functions. If a function from another library is called, you might see a couple of
??:0
instead of file names.#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()); }
This code should be compiled as:
gcc sighandler.c -o sighandler -rdynamic
The program outputs:
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
Update 2012/04/28 for recent linux kernel versions, the above
sigaction
signature is obsolete. Also I improved it a bit by grabbing the executable name from this answer. Here is an up to date version: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); }
and initialise like this:
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()); }
这篇关于从程序内部调用gdb打印堆栈跟踪的最佳方式是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
- 方法#3:(做方法2的更好方法)