关于C#:从内部程序调用gdb打印其堆栈跟踪的最佳方法?

Best way to invoke gdb from inside program to print its stacktrace?

使用如下函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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
"
,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);
    }
}

我在输出中看到了打印跟踪的详细信息。

还有其他的方法吗?


你在我的另一个回答(现在已删除)中提到你还想看到行号。当从应用程序内部调用gdb时,我不确定如何做到这一点。

但是,我将与您分享一些方法,用函数名和它们各自的行号打印一个简单的stacktrace,而不使用gdb。他们中的大多数来自于Linux期刊上一篇非常好的文章:

  • 方法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:

1
2
3
4
 #define TRACE_MSG fprintf(stderr, __FUNCTION__     \
                         "() [%s:%d] here I am

", \
                         __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.

  • 方法2:(它对行号没有任何说明,但我对方法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.

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

  • 方法3:(更好的方法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

此技术的一个实现在这里可用。

  • 方法4:

我在打印行号的方法3上做了一个小的改进。这也可以复制到方法2上。

基本上,我遵循了使用addr2行

convert addresses into file names and
line numbers.

下面的源代码打印所有本地函数的行号。如果调用另一个库中的函数,您可能会看到几个??:0而不是文件名。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#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
"
, sig, ctx.cr2, ctx.eip);
  else
    printf("Got signal %d
"
, 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:
"
);
  for (i=1; i<trace_size; ++i)
  {
    printf("[bt] #%d %s
"
, 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
"
, func_b());
}

此代码应编译为:gcc sighandler.c -o sighandler -rdynamic

程序输出:

1
2
3
4
5
6
7
8
9
10
11
12
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

更新2012/04/28对于最新的Linux内核版本,上述sigaction签名已过时。另外,我通过从这个答案中获取可执行文件名对它做了一些改进。以下是最新版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
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);
}

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
"
, sig, info->si_addr,
           uc->uc_mcontext.gregs[REG_EIP]);
  else
    printf("Got signal %d
"
, 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:
"
);
  for (i=1; i<trace_size; ++i)
  {
    printf("[bt] %s
"
, 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);
}

然后这样初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
"
, func_b());

}


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

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


诺巴给出了一个极好的答案。简而言之;

So you want a stand-alone function that prints a stack trace with all of the features that gdb stack traces have and that doesn't terminate your application. The answer is to automate the launch of gdb in a non-interactive mode to perform just the tasks that you want.

This is done by executing gdb in a child process, using fork(), and scripting it to display a stack-trace while your application waits for it to complete. This can be performed without the use of a core-dump and without aborting the application.

我相信这就是你要找的,@vi


难道abort()不简单吗?

这样,如果它发生在字段中,客户可以向您发送核心文件(我不知道有多少用户在我的应用程序中有足够的参与,希望我强制他们调试它)。