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

Vi.*_*Vi. 58 c linux gdb stack-trace

使用这样的函数:

#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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我在输出中看到了print_trace的细节.

有什么其他方法可以做到这一点?

kar*_*lip 93

您在我的其他答案(现已删除)中提到您还想查看行号.从应用程序内部调用gdb时,我不确定如何做到这一点.

但是我将与您分享几种方法来打印带有函数名称的简单堆栈跟踪及其各自的行号而不使用gdb.其中大多数来自Linux Journal的一篇非常好的文章:

  • 方法#1:

第一种方法是使用打印和日志消息传播它,以便精确定位执行路径.在一个复杂的程序中,即使在一些GCC特定的宏的帮助下,它也可以简化一点,这个选项会变得繁琐乏味.例如,考虑调试宏,例如:

 #define TRACE_MSG fprintf(stderr, __FUNCTION__     \
                          "() [%s:%d] here I am\n", \
                          __FILE__, __LINE__)
Run Code Online (Sandbox Code Playgroud)

您可以通过剪切和粘贴它在整个程序中快速传播此宏.如果您不再需要它,只需将其定义为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 %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());
}
Run Code Online (Sandbox Code Playgroud)

此代码应编译为: 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
Run Code Online (Sandbox Code Playgroud)

更新2012/04/28最新的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\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);
}
Run Code Online (Sandbox Code Playgroud)

并初始化如下:

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());

}
Run Code Online (Sandbox Code Playgroud)

  • 除了使用“-rdynamic”之外,还要检查您的构建系统是否未添加“-fvisibility=hidden”选项!(因为它将完全丢弃`-rdynamic`的效果) (2认同)

Jim*_*ndy 8

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

那些不会显示参数值,源代码等,它们只适用于调用线程.但是,它们应该比以这种方式运行GDB快得多(也许可能不那么脆弱),所以它们有它们的位置.

  • 实际上我插入程序的片段首先用backtrace_symbols输出回溯,然后启动gdb为所有线程输出完全注释的堆栈跟踪.如果gdb失败,我仍然有`backtrace`的stacktrace. (2认同)

kar*_*lip 6

诺巴发布了一个很棒的答案.简而言之;

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

这是通过使用fork()在子进程中执行gdb,并在应用程序等待它完成时编写脚本来显示堆栈跟踪来完成的.这可以在不使用核心转储且不中止应用程序的情况下执行.

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

  • 查看问题中的示例代码。就是那个方法。我正在寻找其他不那么重量级的方式。addr2line-quality 的主要问题是它经常无法在 gdb 可以显示行号的地方。 (2认同)