它是如何最好从程序调用GDB打印它的堆栈跟踪? [英] How it's better to invoke gdb from program to print its stacktrace?

查看:259
本文介绍了它是如何最好从程序调用GDB打印它的堆栈跟踪?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

现在我使用的功能是这样的:

 的#include<&stdio.h中GT;
#包括LT&;&stdlib.h中GT;
#包括LT&; SYS / wait.h>
#包括LT&;&unistd.h中GT;无效print_trace(){
    CHAR pid_buf [30];
    sprintf的(pid_buf,--pid =%d个,GETPID());
    焦炭name_buf [512];
    name_buf [的readlink(的/ proc /自/ EXE,name_buf,511)] = 0;
    INT child_pid = fork()的;
    如果(!child_pid){
        dup2(2,1); //输出重定向到stderr
        fprintf中(标准输出,%s的PID =%s的\\ n堆栈跟踪,name_buf,pid_buf);
        execlp(GDB,GDB,--batch,-n,-ex,线,-ex,胜,name_buf,pid_buf,NULL);
        中止(); / *如果GDB启动失败* /
    }其他{
        waitpid函数(child_pid,NULL,0);
    }
}

和我看到的print_trace细节输出。

什么其他的方法来做到这一点?


解决方案

您对我的其他答案(现已删除),您也想看看行号提及。我不知道如何从应用程序中调用GDB时做的。

不过,我会与大家分享几种方法来打印一个简单的堆栈跟踪与功能名称及其各自的行号的没有用gdb 。他们大多是从的非常不错的通过 Linux杂志文章:


  • 方法1:


  

第一种方法是将它传播
  ,以便打印和日志消息
  要找准执行路径。在一个
  复杂的程序,这个选项可以
  成为累赘即使乏味,
  与一些GCC-具体的帮助
  宏,它可以简化一点。
  考虑,例如,一个调​​试宏
  如:


 的#define TRACE_MSG fprintf中(标准错误,__FUNCTION__ \\
                          ()[%S:%d个]我在这里的\\ n,\\
                          __FILE__,__LINE__)


  

您可以快速地传播这个宏
  在整个切割程序和
  粘贴。当你不需要它
  了,通过简单地将其关闭
  它定义为无操作。



  • 方法2:(它没有说关于行号什么,但我做的方法4)


  

有一个更好的方式来获得一个堆栈跟踪,
  然而,使用一些的
  通过提供具体的支持功能
  glibc的。关键的是回溯()
  它的导航堆栈帧从
  主叫点的开始
  程序并提供的阵列
  返回地址。然后,您可以映射
  每个地址的一个身体
  在code特定功能由
  在看看与目标文件
  nm命令。或者,你可以做它
  更简单的方法 - 使用backtrace_symbols()。
  这个函数转换的列表
  返回地址,通过返回的
  回溯(),成字符串列表,
  每个包含函数名
  功能和内偏移
  退货地址。字符串列表是
  从分配的堆空间(好像
  你叫的malloc()),所以你应该
  free()的它,只要你与完成
  吧。


我鼓励你,因为页面有<一读它href=\"http://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/063/6391/6391l1.html\">source code 例子。为了将地址转换为函数名,您必须编译在 -rdynamic 选项您的应用程序。


  • 方法3:(这样做的更好的方式方法2)


  

对于一个更加有用的应用程序
  该技术是把一个堆栈
  一个信号处理器内部,并回溯
  具有后捕获所有的坏
  信号你的程序可以接收
  (SIGSEGV,SIGBUS,SIGILL,SIGFPE和
   类似)。这样,如果你的程序
  不幸崩溃,你不在。
  用调试器来运行它,你可以
  得到一个堆栈跟踪并知道在哪里
  故障发生了。这种技术还
  可以用来了解你在哪里
  程序的情况下,循环停止
  响应


该技术的实现可<一个href=\"http://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/063/6391/6391l2.html\">here.


  • 方法4:

一个小的改善我的方法#3完成打印行号。这可以被复制到在方法#2也工作。

基本上,我followed使用尖端 addr2line


  

转换地址转换为文件名和
  行号。


源$ C ​​$ C下面打印行号为所有本地功能。如果从另一个库中的函数被调用时,您可能会看到一对夫妇的 ??:0 而不是文件名

 的#include&LT;&stdio.h中GT;
#包括LT&;&signal.h中GT;
#包括LT&;&stdio.h中GT;
#包括LT&;&signal.h中GT;
#包括LT&;&execinfo.h GT;无效bt_sighandler(INT SIG,结构sigcontext CTX){  void *的跟踪[16];
  焦炭**消息=(字符**)NULL;
  INT I,trace_size = 0;  如果(SIG == SIGSEGV)
    的printf(得到信号%D,有故障的地址是%P
           从%P \\ N,SIG,ctx.cr2,ctx.eip);
  其他
    的printf(得到信号%d个\\ N,SIG);  trace_size =回溯(追踪,16);
  / *覆盖的sigaction与来电者的地址* /
  追溯[1] =(无效*)ctx.eip;
  消息= backtrace_symbols(跟踪,trace_size);
  / *跳过第一的栈帧(点这里)* /
  的printf([BT]执行路径:\\ n);
  对于(i = 1; I&LT; trace_size ++ I)
  {
    的printf([BT]#%d个%S \\ n,我,消息[I]);    / *找到'('或''第一次出现在消息[i]和假设
     *在这之前的一切是文件名。 (不超越0虽然
     *(字符串结束)* /
    为size_t p值= 0;
    而(消息由[i] [P] ='('和;!&放大器;!消息由[i] [P] =''
            &功放;&安培;消息由[i] [P]!= 0)
        ++磷;    焦炭SYSCOM [256];
    的sprintf(SYSCOM。addr2line%P -e%*的,痕量[I],P,消息由[i]);
        //最后一个参数是所述符号的文件名
    系统(SYSCOM);
  }  出口(0);
}
INT func_a(int类型的,字符B){  的char * p =(字符*)0xdeadbeef;  一个= A + B;
  * p值= 10; / * CRASH在这里! * /  返回2 * A;
}
INT func_b(){  中期业绩,1 = 5;  RES = 5 + func_a(一,'T');  返回水库;
}
诠释主(){  / *安装我们的信号处理* /
  结构sigaction的SA;  sa.sa_handler =(无效*)bt_sighandler;
  sigemptyset(安培; sa.sa_mask);
  sa.sa_flags = SA_RESTART;  的sigaction(SIGSEGV,&安培; SA,NULL);
  的sigaction(SIGUSR1,&安培; SA,NULL);
  / * ...在这里添加任何其他信号* /  /* 做一点事 */
  的printf(%d个\\ N,func_b());
}

这code应该被编译为:的gcc -o sighandler.c 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]
??:0


更新2012/04/28 作为最近的Linux内核版本,上面的的sigaction 签名已经过时了。此外,我从抓住可执行文件的名称这个答案提高了一点。下面是一个<一个href=\"http://www.linuxjournal.com/files/linuxjournal.com/linuxjournal/articles/063/6391/6391l3.html\">up最新版本:

 的char *的exe = 0;INT initialiseExecutableName()
{
    焦炭链接[1024];
    exe文件=新的char [1024];
    的snprintf(链路,链路的sizeof的/ proc /%D / EXE,GETPID());
    如果(的readlink(链接,EXE,sizeof的链接)== - 1){
        fprintf中(标准错误,ERRORRRRR \\ n);
        出口(1);
    }
    的printf(可执行名称初始化:%S \\ n,EXE);
}为const char * getExecutableName()
{
    如果(EXE == 0)
        initialiseExecutableName();
    返回exe文件;
}/ *从ucontext.h得到REG_EIP * /
#定义__USE_GNU
#包括LT&;&ucontext.h GT;无效bt_sighandler(INT SIG,siginfo_t *信息,
                   void *的秘密){  void *的跟踪[16];
  焦炭**消息=(字符**)NULL;
  INT I,trace_size = 0;
  ucontext_t * UC =(ucontext_t *)秘密;  / *做一些与siginfo_t有用的* /
  如果(SIG == SIGSEGV)
    的printf(得到信号%D,有故障的地址是%P
           从%P \\ N,SIG,信息 - &GT; si_addr,
           UC-&GT; uc_mcontext.gregs [REG_EIP]);
  其他
    的printf(得到信号%D#92; \\ N,SIG);  trace_size =回溯(追踪,16);
  / *覆盖的sigaction与来电者的地址* /
  追溯[1] =(无效*)UC-&GT; uc_mcontext.gregs [REG_EIP]  消息= backtrace_symbols(跟踪,trace_size);
  / *跳过第一的栈帧(点这里)* /
  的printf([BT]执行路径:#92; \\ n);
  对于(i = 1; I&LT; trace_size ++ I)
  {
    的printf([BT]%S#92; \\ N,消息[I]);    / *找到'('或''第一次出现在消息[i]和假设
     *在这之前的一切是文件名。 (不超越0虽然
     *(字符串结束)* /
    为size_t p值= 0;
    而(消息由[i] [P] ='('和;!&放大器;!消息由[i] [P] =''
            &功放;&安培;消息由[i] [P]!= 0)
        ++磷;    焦炭SYSCOM [256];
    的sprintf(SYSCOM。addr2line%P -e%*的,痕量[I],P,消息由[i]);
           //最后一个参数是所述符号的文件名
    系统(SYSCOM);  }
  出口(0);
}

和初始化是这样的:

  INT的main(){  / *安装我们的信号处理* /
  结构sigaction的SA;  sa.sa_sigaction =(无效*)bt_sighandler;
  sigemptyset(安培; sa.sa_mask);
  sa.sa_flags = SA_RESTART | SA_SIGINFO;  的sigaction(SIGSEGV,&安培; SA,NULL);
  的sigaction(SIGUSR1,&安培; SA,NULL);
  / * ...在这里添加任何其他信号* /  /* 做一点事 */
  的printf(%d个#92; N,func_b());}

Now I'm using 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);
    }
}

And I see details of print_trace in the output.

What are the 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#92;\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:#92;\n");
  for (i=1; i<trace_size; ++i)
  {
    printf("[bt] %s#92;\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#92;n", func_b());

}

这篇关于它是如何最好从程序调用GDB打印它的堆栈跟踪?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆