从程序内部调用gdb打印堆栈跟踪的最佳方式是什么?

使用这样的函数:

#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=%sn",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:
  • 第一种方法是使用打印和日志消息来传播它以查明执行路径。 在一个复杂的程序中,即使在某些GCC特定的宏的帮助下,该选项可能会变得繁琐乏味,但可能会稍微简化一些。 例如,考虑一个调试宏,例如:

     #define TRACE_MSG fprintf(stderr, __FUNCTION__     
                              "() [%s:%d] here I amn", 
                              __FILE__, __LINE__)
    

    您可以通过剪切并粘贴它来在整个程序中快速传播此宏。 当你不再需要它时,只需将其定义为no-op即可关闭它。

  • 方法2 :(它没有提及任何关于行号的信息,但是我在方法4上做了)
  • 但是,获得堆栈回溯的更好方法是使用glibc提供的某些特定支持功能。 关键之一是backtrace(),它将堆栈帧从调用点导航到程序的开始处,并提供返回地址数组。 然后,您可以通过使用nm命令查看目标文件,将每个地址映射到代码中特定函数的主体。 或者,您可以更简单地使用它 - 使用backtrace_symbols()。 该函数将backtrace()返回的返回地址列表转换为字符串列表,每个字符串都包含函数内的函数名称偏移量和返回地址。 字符串列表是从堆空间分配的(就像你调用malloc())一样,所以你应该尽快释放()它。

    我鼓励你阅读它,因为该页面有源代码示例。 为了将地址转换为函数名称,您必须使用-rdynamic选项编译您的应用程序。

  • 方法#3 :(做方法2的更好方法)
  • 对于这种技术来说,更有用的应用是在信号处理程序中放置栈回溯,并让后者捕获程序可以接收的所有“坏”信号(SIGSEGV,SIGBUS,SIGILL,SIGFPE等)。 这样,如果你的程序不幸崩溃,而你没有用调试器运行它,你可以得到一个堆栈跟踪,并知道故障发生的位置。 这种技术也可以用来了解程序在循环停止响应的情况下的位置

    这种技术的实现可以在这里找到。

  • 方法#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 %pn", sig, ctx.cr2, ctx.eip);
      else
        printf("Got signal %dn", 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 %sn", 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("%dn", 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,"ERRORRRRRn");
            exit(1);
        }
        printf("Executable name initialised: %sn",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 %pn", sig, info->si_addr, 
               uc->uc_mcontext.gregs[REG_EIP]);
      else
        printf("Got signal %dn", 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] %sn", 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("%dn", func_b());
    
    }
    

    如果您使用的是Linux,标准C库包含一个名为backtrace的函数,该函数使用框架的返回地址填充数组,另一个函数称为backtrace_symbols ,它将从backtrace获取地址并查找相应的函数名称。 这些记录在GNU C库手册中。

    那些不会显示参数值,源代码行等,它们只适用于调用线程。 但是,它们比以这种方式运行GDB的速度要快很多(也许不那么片面),所以它们有它们的位置。


    nobar发布了一个梦幻般的答案。 简而言之;

    所以你需要一个独立的函数来打印一个堆栈跟踪,其中包含所有gdb堆栈跟踪具有的功能,并且不会终止你的应用程序。 答案是以非交互模式自动启动gdb以执行您所需的任务。

    这是通过在子进程中执行gdb,使用fork()以及在您的应用程序等待完成时脚本化它来显示堆栈跟踪来完成的。 这可以在不使用核心转储的情况下执行,也不需要中止应用程序。

    我相信这就是你要找的@Vi

    链接地址: http://www.djcxy.com/p/15091.html

    上一篇: Best way to invoke gdb from inside program to print its stacktrace?

    下一篇: Optimizing for 3D imaging processes in C++