如何在我的gcc C ++程序崩溃时自动生成堆栈跟踪

当我的C ++程序崩溃时,我希望它自动生成一个堆栈跟踪。

我的程序由许多不同的用户运行,它也运行在Linux,Windows和Macintosh上(所有版本都使用gcc )。

我希望我的程序能够在崩溃时生成堆栈跟踪,并且在用户下次运行堆栈跟踪时,它会询问他们是否可以将堆栈跟踪发送给我,以便我可以跟踪问题。 我可以处理发送给我的信息,但我不知道如何生成跟踪字符串。 有任何想法吗?


对于Linux,我相信Mac OS X,如果您使用gcc或任何使用glibc的编译器,则可以使用execinfo.h的backtrace()函数打印execinfo.h并在出现分段错误时正常退出。 文档可以在libc手册中找到。

这里有一个示例程序,它安装一个SIGSEGV处理程序,并在它发生段stderr时向stderr堆栈跟踪。 这里的baz()函数会导致触发处理程序的segfault:

#include <stdio.h>
#include <execinfo.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>


void handler(int sig) {
  void *array[10];
  size_t size;

  // get void*'s for all entries on the stack
  size = backtrace(array, 10);

  // print out all the frames to stderr
  fprintf(stderr, "Error: signal %d:n", sig);
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  exit(1);
}

void baz() {
 int *foo = (int*)-1; // make a bad pointer
  printf("%dn", *foo);       // causes segfault
}

void bar() { baz(); }
void foo() { bar(); }


int main(int argc, char **argv) {
  signal(SIGSEGV, handler);   // install our handler
  foo(); // this will call foo, bar, and baz.  baz segfaults.
}

使用-g -rdynamic进行编译可以在输出中获得符号信息,glibc可以使用它来创建一个不错的堆栈跟踪:

$ gcc -g -rdynamic ./test.c -o test

执行此操作可以获得以下输出:

$ ./test
Error: signal 11:
./test(handler+0x19)[0x400911]
/lib64/tls/libc.so.6[0x3a9b92e380]
./test(baz+0x14)[0x400962]
./test(bar+0xe)[0x400983]
./test(foo+0xe)[0x400993]
./test(main+0x28)[0x4009bd]
/lib64/tls/libc.so.6(__libc_start_main+0xdb)[0x3a9b91c4bb]
./test[0x40086a]

这显示堆栈中每个帧来自的加载模块,偏移量和函数。 在这里你可以看到在堆栈的顶部信号处理程序,以及libc函数之前main在除了mainfoobar ,和baz


Linux的

虽然已经建议在execinfo.h中使用backtrace()函数来打印堆栈跟踪并在出现分段错误时正常退出,但我没有提到必须确保所产生的回溯指向实际位置的错综复杂(至少对于某些架构 - x86和ARM)。

当您进入信号处理程序时,堆栈帧链中的前两个条目在信号处理程序中包含一个返回地址,而在libc中包含一个位于sigaction()内部的地址。 在信号之前调用的最后一个函数(它是故障的位置)的堆栈帧丢失。

#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#ifndef __USE_GNU
#define __USE_GNU
#endif

#include <execinfo.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ucontext.h>
#include <unistd.h>

/* This structure mirrors the one found in /usr/include/asm/ucontext.h */
typedef struct _sig_ucontext {
 unsigned long     uc_flags;
 struct ucontext   *uc_link;
 stack_t           uc_stack;
 struct sigcontext uc_mcontext;
 sigset_t          uc_sigmask;
} sig_ucontext_t;

void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
{
 void *             array[50];
 void *             caller_address;
 char **            messages;
 int                size, i;
 sig_ucontext_t *   uc;

 uc = (sig_ucontext_t *)ucontext;

 /* Get the address at the time the signal was raised */
#if defined(__i386__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.eip; // EIP: x86 specific
#elif defined(__x86_64__) // gcc specific
 caller_address = (void *) uc->uc_mcontext.rip; // RIP: x86_64 specific
#else
#error Unsupported architecture. // TODO: Add support for other arch.
#endif

 fprintf(stderr, "signal %d (%s), address is %p from %pn", 
  sig_num, strsignal(sig_num), info->si_addr, 
  (void *)caller_address);

 size = backtrace(array, 50);

 /* overwrite sigaction with caller's address */
 array[1] = caller_address;

 messages = backtrace_symbols(array, size);

 /* skip first stack frame (points here) */
 for (i = 1; i < size && messages != NULL; ++i)
 {
  fprintf(stderr, "[bt]: (%d) %sn", i, messages[i]);
 }

 free(messages);

 exit(EXIT_FAILURE);
}

int crash()
{
 char * p = NULL;
 *p = 0;
 return 0;
}

int foo4()
{
 crash();
 return 0;
}

int foo3()
{
 foo4();
 return 0;
}

int foo2()
{
 foo3();
 return 0;
}

int foo1()
{
 foo2();
 return 0;
}

int main(int argc, char ** argv)
{
 struct sigaction sigact;

 sigact.sa_sigaction = crit_err_hdlr;
 sigact.sa_flags = SA_RESTART | SA_SIGINFO;

 if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
 {
  fprintf(stderr, "error setting signal handler for %d (%s)n",
    SIGSEGV, strsignal(SIGSEGV));

  exit(EXIT_FAILURE);
 }

 foo1();

 exit(EXIT_SUCCESS);
}

产量

signal 11 (Segmentation fault), address is (nil) from 0x8c50
[bt]: (1) ./test(crash+0x24) [0x8c50]
[bt]: (2) ./test(foo4+0x10) [0x8c70]
[bt]: (3) ./test(foo3+0x10) [0x8c8c]
[bt]: (4) ./test(foo2+0x10) [0x8ca8]
[bt]: (5) ./test(foo1+0x10) [0x8cc4]
[bt]: (6) ./test(main+0x74) [0x8d44]
[bt]: (7) /lib/libc.so.6(__libc_start_main+0xa8) [0x40032e44]

在信号处理程序中调用backtrace()函数的所有危险仍然存在,不应该被忽略,但我发现我在这里描述的功能在调试崩溃时非常有用。

请注意,我提供的示例是在Linux for Linux上开发/测试的。 我也使用uc_mcontext.arm_pc而不是uc_mcontext.eip在ARM上成功实现了这一uc_mcontext.eip

这里有一篇文章的链接,我学习了这个实现的细节:http://www.linuxjournal.com/article/6391


它比“人回溯”更容易,还有一个小文档库(GNU特有),它以glibc作为libSegFault.so分发,我相信这是由Ulrich Drepper编写的,用于支持程序catchsegv(请参阅“man catchsegv”)。

这给了我们3种可能性。 而不是运行“程序 - 海”:

  • 在catchsegv中运行:

    $ catchsegv program -o hai
    
  • 在运行时与libSegFault链接:

    $ LD_PRELOAD=/lib/libSegFault.so program -o hai
    
  • 在编译时与libSegFault链接:

    $ gcc -g1 -lSegFault -o program program.cc
    $ program -o hai
    
  • 在所有这三种情况下,您都会得到更清晰的回溯,但优化较少(gcc -O0或-O1)和调试符号(gcc -g)。 否则,你可能会得到一堆内存地址。

    你也可以用类似的方式捕获更多的堆栈跟踪信号:

    $ export SEGFAULT_SIGNALS="all"       # "all" signals
    $ export SEGFAULT_SIGNALS="bus abrt"  # SIGBUS and SIGABRT
    

    输出看起来像这样(注意底部的回溯):

    *** Segmentation fault Register dump:
    
     EAX: 0000000c   EBX: 00000080   ECX:
    00000000   EDX: 0000000c  ESI:
    bfdbf080   EDI: 080497e0   EBP:
    bfdbee38   ESP: bfdbee20
    
     EIP: 0805640f   EFLAGS: 00010282
    
     CS: 0073   DS: 007b   ES: 007b   FS:
    0000   GS: 0033   SS: 007b
    
     Trap: 0000000e   Error: 00000004  
    OldMask: 00000000  ESP/signal:
    bfdbee20   CR2: 00000024
    
     FPUCW: ffff037f   FPUSW: ffff0000  
    TAG: ffffffff  IPOFF: 00000000  
    CSSEL: 0000   DATAOFF: 00000000  
    DATASEL: 0000
    
     ST(0) 0000 0000000000000000   ST(1)
    0000 0000000000000000  ST(2) 0000
    0000000000000000   ST(3) 0000
    0000000000000000  ST(4) 0000
    0000000000000000   ST(5) 0000
    0000000000000000  ST(6) 0000
    0000000000000000   ST(7) 0000
    0000000000000000
    
    Backtrace:
    /lib/libSegFault.so[0xb7f9e100]
    ??:0(??)[0xb7fa3400]
    /usr/include/c++/4.3/bits/stl_queue.h:226(_ZNSt5queueISsSt5dequeISsSaISsEEE4pushERKSs)[0x805647a]
    /home/dbingham/src/middle-earth-mud/alpha6/src/engine/player.cpp:73(_ZN6Player5inputESs)[0x805377c]
    /home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:159(_ZN6Socket4ReadEv)[0x8050698]
    /home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:413(_ZN12ServerSocket4ReadEv)[0x80507ad]
    /home/dbingham/src/middle-earth-mud/alpha6/src/engine/socket.cpp:300(_ZN12ServerSocket4pollEv)[0x8050b44]
    /home/dbingham/src/middle-earth-mud/alpha6/src/engine/main.cpp:34(main)[0x8049a72]
    /lib/tls/i686/cmov/libc.so.6(__libc_start_main+0xe5)[0xb7d1b775]
    /build/buildd/glibc-2.9/csu/../sysdeps/i386/elf/start.S:122(_start)[0x8049801]
    

    如果你想知道血淋淋的细节,最好的来源是不幸的来源:请参阅http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c及其父目录http://sourceware.org/git/?p=glibc.git;a=tree;f=debug

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

    上一篇: How to automatically generate a stacktrace when my gcc C++ program crashes

    下一篇: Why does Intel's compiler prefer NEG+ADD over SUB?