当前位置: 首页 > 面试题库 >

从程序内部调用gdb以打印其stacktrace的最佳方法?

鲁宏爽
2023-03-14
问题内容

使用这样的函数:

#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 即可打印具有函数名称及其相应行号的简单stacktrace的方法。其中大多数来自Linux
Journal的
一篇 非常好的
文章:

  • 方法1:

第一种方法是通过打印和日志消息进行传播,以查明执行路径。在复杂的程序中,即使借助某些特定于GCC的宏,此选项也可能变得繁琐而乏味。例如,考虑一个调试宏,例如:

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

您可以通过剪切和粘贴该宏在整个程序中快速传播。当您不再需要它时,只需将其定义为无操作即可将其关闭。

  • 方法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());
}

此代码应编译为: 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());

}


 类似资料:
  • 问题内容: 我有一个风俗习惯。此行中的每一行都有一个图标和一些文本。这些图标在后台下载,缓存并使用回调(分别替换为)进行下载。每次getView()运行都会触发从缓存或下载中获取缩略图的逻辑。 现在,根据Romain Guy所说: “绝对不能保证的调用顺序和次数。 我已经看到这种情况的发生,因为大小为2的一行getView()被调用了6次! 如何更改代码以避免重复的缩略图获取请求并处理视图回收?

  • 问题内容: 我想知道打印2D阵列的最佳方法是什么。这是我的一些代码,我只是想知道这是否是一种好习惯。如果发现任何其他错误,请更正我在此代码中犯的任何其他错误。谢谢! 问题答案: 你可以用简单的方式打印。 在下面使用以打印2D阵列 在下面使用以打印一维阵列

  • 问题内容: 为了娱乐并更好地学习Go,我正在尝试在Go中重新实现抗原。 问题是:是shell内置函数,所以我不能用 function 调用它,因为它希望在中有可执行文件。 我怎样才能做到这一点?并且,是否有可能使go程序内部的程序影响用户shell? 问题答案: 您可以直接在终端设备中编写命令。但是,要做到这一点,首先您需要知道哪个设备正在使用该用户。执行程序的脚本可能是解决方案。 然后,程序必须

  • 问题内容: 我对gdb无法正确打印变量有疑问。通过以下方式构建简单程序: 然后执行gdb: 这是我下一步要做的: 您可以看到在检查’p’变量时存在Python异常,而在显示’i’值时完全没有3。怎么了 ? 这是我去的版本 和gdb配置 问题答案: 添加到@AlexAtNet的答案中,Go 1.2.x之后的所有内容都中断了GDB支持,因此请使用go 1.2.x进行调试或使用gccgo(请记住,gcc

  • 问题内容: 我以前没有构建过Java Web应用程序,但是我已经完成了足够的测试,Maven正在构建我的WAR文件。它是一个多模块Maven项目,从属模块在WAR的WEB-INF / lib目录中均具有其JAR文件。 所以一切似乎都很好,但是如何调试呢?我知道如何(从命令行)在我的机器上的Tomcat中运行WAR。我也认为我也知道如何从命令行设置和运行Maven Jetty插件。但是,如何最好地调

  • 如何从主方法调用 bubbleSort 方法以打印排序的列表数组。我已经将10个随机数生成到一个数组中,但我还没有弄清楚如何调用bubbleSort并打印结果。我在这里错过了什么? 公共类 Bubblesort{ 公共静态void main(String[] args) { } }