关于C++:如何在程序崩溃时自动生成堆栈跟踪

关于C++:如何在程序崩溃时自动生成堆栈跟踪

How to automatically generate a stacktrace when my program crashes

我正在使用gcc编译器在Linux上工作。当我的C++程序崩溃时,我希望它能自动生成堆栈跟踪。

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

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


对于Linux and I believe Mac Os X,if you're using GCC,or any compiler that use GLIBC,你可以在execinfo.h中使用Backtrace()functionsDocumentation can be found in the LIBC manual.

这是一个实例,说明安装SIGSEGV的程序,当该程序有缺陷时,手工制作并向stderr打印一份堆栈。函数在这里造成触发器操作员的故障:

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
#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:
"
, sig);
  backtrace_symbols_fd(array, size, STDERR_FILENO);
  exit(1);
}

void baz() {
 int *foo = (int*)-1; // make a bad pointer
  printf("%d
"
, *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.
}

把你的符号信息输出到你的输出中,GLIBC可以用来做一个很好的堆栈:

ZZU1

Executing this gets you this output:

1
2
3
4
5
6
7
8
9
10
$ ./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]

这显示了堆栈中每个帧的负载模块、偏移模块和函数。在这里,你可以看到在堆栈顶部的信号手柄和main之前的LIBC功能,加上mainfoobarbaz


亚麻

当使用后台函数(Execinfo.h)打印一个堆栈跟踪和优雅地输出时,当你得到一个分割缺陷时,已经被建议,我看不出有任何关于内在性的必要性,以确保后台的反馈点对故障的实际定位(至少对于某些建筑来说是X86&)。

当你进入信号手柄时,在堆栈帧链中的第一个入口包含在信号手柄内的一个回复地址和在LIBC内的一个标记()中。在信号(即故障位置)之前,最后一个函数的堆栈帧被呼叫失败。

代码

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#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 %p
"
,
  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) %s
"
, 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)
"
,
    SIGSEGV, strsignal(SIGSEGV));

  exit(EXIT_FAILURE);
 }

 foo1();

 exit(EXIT_SUCCESS);
}

输出

1
2
3
4
5
6
7
8
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]

在一个信号手柄的信号中呼叫后台()函数的所有危险,仍然存在,不应被过度探索,但我在这里描述的功能性在解体裂缝中的帮助。

重要的是要指出,我提供的实例是为X86开发/测试Linux。我还成功地用uc_mcontext.arm_pc实现了这一目标。

这是一个链接到文章,我从文章中了解到这一执行的详细情况:http://www.linuxjournal.com/article/6391


它甚至比"man backtrace"更容易,有一个小文档库(GNU特定的库)以libsegfault的形式与glibc一起分发,我认为它是由ulrich drepper编写的,以支持catchsegv程序(参见"man catchsegv")。

这给了我们3种可能性。而不是运行"程序-o hai":

  • 在CatchSegv中运行:

    1
    $ catchsegv program -o hai
  • 运行时与libsegfault链接:

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

    1
    2
    $ gcc -g1 -lSegFault -o program program.cc
    $ program -o hai
  • 在所有3种情况下,您将通过更少的优化(gcc-o0或-o1)和调试符号(gcc-g)获得更清晰的回溯。否则,您可能会得到一堆内存地址。

    您还可以通过如下方式捕获更多用于堆栈跟踪的信号:

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

    输出将如下所示(注意底部的回溯):

    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
    *** 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


    尽管已经提供了一个正确的答案,它描述了如何使用GNU LBC-EDCOX1,0个函数1和I提供了我自己的答案,它描述了如何确保从信号处理程序点到Burrt2的实际位置的回溯,但我没有看到任何从回溯中输出的DeangLink C++符号的提及。

    当从C++程序获得回溯时,输出可以通过EDCOX1×1×1来运行,从而使符号解散或直接使用EDCOX1×2×1。

    • 1个Linux和OS X注意,c++filt__cxa_demangle是gcc特有的
    • 2 Linux

    下面的C++ Linux示例使用与我其他答案相同的信号处理程序,并演示了如何使用EDCOX1(1)来对符号进行解散。

    代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class foo
    {
    public:
        foo() { foo1(); }

    private:
        void foo1() { foo2(); }
        void foo2() { foo3(); }
        void foo3() { foo4(); }
        void foo4() { crash(); }
        void crash() { char * p = NULL; *p = 0; }
    };

    int main(int argc, char ** argv)
    {
        // Setup signal handler for SIGSEGV
        ...

        foo * f = new foo();
        return 0;
    }

    输出(./test)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    signal 11 (Segmentation fault), address is (nil) from 0x8048e07
    [bt]: (1) ./test(crash__3foo+0x13) [0x8048e07]
    [bt]: (2) ./test(foo4__3foo+0x12) [0x8048dee]
    [bt]: (3) ./test(foo3__3foo+0x12) [0x8048dd6]
    [bt]: (4) ./test(foo2__3foo+0x12) [0x8048dbe]
    [bt]: (5) ./test(foo1__3foo+0x12) [0x8048da6]
    [bt]: (6) ./test(__3foo+0x12) [0x8048d8e]
    [bt]: (7) ./test(main+0xe0) [0x8048d18]
    [bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
    [bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]

    非扭曲输出(./test 2>&1 | c++filt):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    signal 11 (Segmentation fault), address is (nil) from 0x8048e07
    [bt]: (1) ./test(foo::crash(void)+0x13) [0x8048e07]
    [bt]: (2) ./test(foo::foo4(void)+0x12) [0x8048dee]
    [bt]: (3) ./test(foo::foo3(void)+0x12) [0x8048dd6]
    [bt]: (4) ./test(foo::foo2(void)+0x12) [0x8048dbe]
    [bt]: (5) ./test(foo::foo1(void)+0x12) [0x8048da6]
    [bt]: (6) ./test(foo::foo(void)+0x12) [0x8048d8e]
    [bt]: (7) ./test(main+0xe0) [0x8048d18]
    [bt]: (8) ./test(__libc_start_main+0x95) [0x42017589]
    [bt]: (9) ./test(__register_frame_info+0x3d) [0x8048981]

    以下内容基于我最初的答案中的信号处理程序,可以在上面的示例中替换信号处理程序,以演示如何使用abi::__cxa_demangle来对符号进行分解。此信号处理程序生成与上述示例相同的非扭曲输出。

    代码:

    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
    void crit_err_hdlr(int sig_num, siginfo_t * info, void * ucontext)
    {
        sig_ucontext_t * uc = (sig_ucontext_t *)ucontext;

        void * caller_address = (void *) uc->uc_mcontext.eip; // x86 specific

        std::cerr <<"signal" << sig_num
                  <<" (" << strsignal(sig_num) <<"), address is"
                  << info->si_addr <<" from" << caller_address
                  << std::endl << std::endl;

        void * array[50];
        int size = backtrace(array, 50);

        array[1] = caller_address;

        char ** messages = backtrace_symbols(array, size);    

        // skip first stack frame (points here)
        for (int i = 1; i < size && messages != NULL; ++i)
        {
            char *mangled_name = 0, *offset_begin = 0, *offset_end = 0;

            // find parantheses and +address offset surrounding mangled name
            for (char *p = messages[i]; *p; ++p)
            {
                if (*p == '(')
                {
                    mangled_name = p;
                }
                else if (*p == '+')
                {
                    offset_begin = p;
                }
                else if (*p == ')')
                {
                    offset_end = p;
                    break;
                }
            }

            // if the line could be processed, attempt to demangle the symbol
            if (mangled_name && offset_begin && offset_end &&
                mangled_name < offset_begin)
            {
                *mangled_name++ = '\0';
                *offset_begin++ = '\0';
                *offset_end++ = '\0';

                int status;
                char * real_name = abi::__cxa_demangle(mangled_name, 0, 0, &status);

                // if demangling is successful, output the demangled function name
                if (status == 0)
                {    
                    std::cerr <<"[bt]: (" << i <<")" << messages[i] <<" :"
                              << real_name <<"+" << offset_begin << offset_end
                              << std::endl;

                }
                // otherwise, output the mangled function name
                else
                {
                    std::cerr <<"[bt]: (" << i <<")" << messages[i] <<" :"
                              << mangled_name <<"+" << offset_begin << offset_end
                              << std::endl;
                }
                free(real_name);
            }
            // otherwise, print the whole line
            else
            {
                std::cerr <<"[bt]: (" << i <<")" << messages[i] << std::endl;
            }
        }
        std::cerr << std::endl;

        free(messages);

        exit(EXIT_FAILURE);
    }


    也许值得一看谷歌断裂,一个交叉平台断裂发生器和工具来处理倾销。


    您没有指定操作系统,因此这很难回答。如果您使用的是基于gnu libc的系统,那么您可能可以使用libc函数backtrace()

    GCC还有两个内置组件可以帮助您,但它们可能在您的体系结构上完全实现,也可能不完全实现,它们分别是__builtin_frame_address__builtin_return_address。两者都需要一个立即整数级别(我的意思是它不能是变量)。如果给定级别的__builtin_frame_address为非零,则可以安全地获取相同级别的返回地址。


    ulimit -c 设置Unix上的核心文件大小限制。默认情况下,核心文件大小限制为0。您可以通过ulimit -a看到您的ulimit值。

    另外,如果您从gdb中运行程序,它将在"分段违规"时停止您的程序(SIGSEGV,通常在您访问一块尚未分配的内存时),或者您可以设置断点。

    DDD和Nemiver是GDB的前端,这使得新手更容易使用它。


    libc的某些版本包含处理堆栈跟踪的函数;您可以使用它们:

    http://www.gnu.org/software/libc/manual/html_node/backtraces.html

    我记得很久以前使用libunwind获取堆栈跟踪,但在您的平台上可能不支持它。


    重要的是要注意,一旦生成了一个核心文件,就需要使用gdb工具来查看它。为了让gdb理解您的核心文件,您必须告诉gcc使用调试符号对二进制文件进行检测:为此,使用-g标志进行编译:

    1
    $ g++ -g prog.cpp -o prog

    然后,您可以设置"ulimit-c unlimited"让它转储一个内核,或者在gdb中运行您的程序。我更喜欢第二种方法:

    1
    2
    3
    4
    5
    6
    $ gdb ./prog
    ... gdb startup output ...
    (gdb) run
    ... program runs and crashes ...
    (gdb) where
    ... gdb outputs your stack trace ...

    我希望这有帮助。


    我一直在看这个问题。

    并在谷歌性能工具中燃烧

    http://code.google.com/p/google-perftools/source/browse/trunk/readme

    谈论自由风

    http://www.nongnu.org/libunwind/。

    我想听听这个图书馆的意见。

    RDYNAMIC的问题是,它可以在某些情况下增加二进制相对重要性的尺寸。


    感谢热情的极客们让我注意到addr2line实用程序。

    我编写了一个快速而脏的脚本来处理这里提供的答案的输出:(多亏了jschmier!)使用addr2line实用程序。

    脚本接受一个参数:包含jschmier实用程序输出的文件名。

    输出应该为跟踪的每个级别打印如下内容:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    BACKTRACE:  testExe 0x8A5db6b
    FILE:       pathToFile/testExe.C:110
    FUNCTION:   testFunction(int)
       107  
       108          
       109           int* i = 0x0;
      *110           *i = 5;
       111      
       112        }
       113        return i;

    代码:

    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
    #!/bin/bash

    LOGFILE=$1

    NUM_SRC_CONTEXT_LINES=3

    old_IFS=$IFS  # save the field separator          
    IFS=$'
    '
        # new field separator, the end of line          

    for bt in `cat $LOGFILE | grep '\[bt\]'`; do
       IFS=$old_IFS     # restore default field separator
       printf '
    '

       EXEC=`echo $bt | cut -d' ' -f3 | cut -d'(' -f1`  
       ADDR=`echo $bt | cut -d'[' -f3 | cut -d']' -f1`
       echo"BACKTRACE:  $EXEC $ADDR"
       A2L=`addr2line -a $ADDR -e $EXEC -pfC`
       #echo"A2L:        $A2L"

       FUNCTION=`echo $A2L | sed 's/\.*//' | cut -d' ' -f2-99`
       FILE_AND_LINE=`echo $A2L | sed 's/.* at //'`
       echo"FILE:       $FILE_AND_LINE"
       echo"FUNCTION:   $FUNCTION"

       # print offending source code
       SRCFILE=`echo $FILE_AND_LINE | cut -d':' -f1`
       LINENUM=`echo $FILE_AND_LINE | cut -d':' -f2`
       if ([ -f $SRCFILE ]); then
          cat -n $SRCFILE | grep -C $NUM_SRC_CONTEXT_LINES"^ *$LINENUM\>" | sed"s/ $LINENUM/*$LINENUM/"
       else
          echo"File not found: $SRCFILE"
       fi
       IFS=$'
    '
        # new field separator, the end of line          
    done

    IFS=$old_IFS     # restore default field separator

    忘记更改源代码,用backtrace()函数或宏进行一些黑客攻击——这些只是糟糕的解决方案。

    作为一个有效的解决方案,我建议:

  • 用"-g"标记编译程序,将调试符号嵌入到二进制文件中(不要担心这不会影响性能)。
  • 在Linux上运行下一个命令:"ulimit-c unlimited"——允许系统进行大崩溃转储。
  • 当程序崩溃时,在工作目录中您将看到文件"core"。
  • 运行下一个命令将backtrace打印到stdout:gdb-batch-ex"backtrace"。/your_program_exe./core
  • 这将以人类可读的方式(使用源文件名和行号)打印程序的正确可读回溯。此外,这种方法还可以让您自由地使系统自动化:有一个简短的脚本,检查进程是否创建了核心转储,然后通过电子邮件向开发人员发送回溯,或者将其记录到某个日志系统中。


    1
    ulimit -c unlimited

    是一个系统变量,wich将允许在应用程序崩溃后创建核心转储。在这种情况下,金额是无限的。在同一个目录中查找名为core的文件。确保编译代码时启用了调试信息!

    当做


    win:stackwalk64怎么样http://msdn.microsoft.com/en-us/library/ms680650.aspx


    你可以使用DeaHealther-TrimeC++类,它为你做任何事情,可靠。


    看:

    人3回溯

    还有:

    1
    2
    #include <exeinfo.h>
    int backtrace(void **buffer, int size);

    这些是GNU扩展。


    See the stack trace facility in ACE(apdative communication environment).这是为覆盖所有主要平台(和更多)而写的。图书馆是BSD-风格许可证,所以如果你不想使用ACE,你甚至可以拷贝/输入代码。


    我可以帮助Linux版本:可以使用函数backtrace、backtrace_symbols和backtrace_symbols_fd。请参阅相应的手册页。


    我发现@tganblin解决方案不完整。它不能处理stackoverflow。我认为,因为默认情况下,信号处理程序是用同一堆栈调用的,并且SIGSEGV被抛出两次。为了保护您需要为信号处理程序注册一个独立的堆栈。

    你可以用下面的代码来检查这个。默认情况下,处理程序失败。有了定义的宏堆栈溢出,一切正常。

    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
    #include <iostream>
    #include <execinfo.h>
    #include <signal.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <string>
    #include <cassert>

    using namespace std;

    //#define STACK_OVERFLOW

    #ifdef STACK_OVERFLOW
    static char stack_body[64*1024];
    static stack_t sigseg_stack;
    #endif

    static struct sigaction sigseg_handler;

    void handler(int sig) {
      cerr <<"sig seg fault handler" << endl;
      const int asize = 10;
      void *array[asize];
      size_t size;

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

      // print out all the frames to stderr
      cerr <<"stack trace:" << endl;
      backtrace_symbols_fd(array, size, STDERR_FILENO);
      cerr <<"resend SIGSEGV to get core dump" << endl;
      signal(sig, SIG_DFL);
      kill(getpid(), sig);
    }

    void foo() {
      foo();
    }

    int main(int argc, char **argv) {
    #ifdef STACK_OVERFLOW
      sigseg_stack.ss_sp = stack_body;
      sigseg_stack.ss_flags = SS_ONSTACK;
      sigseg_stack.ss_size = sizeof(stack_body);
      assert(!sigaltstack(&sigseg_stack, nullptr));
      sigseg_handler.sa_flags = SA_ONSTACK;
    #else
      sigseg_handler.sa_flags = SA_RESTART;  
    #endif
      sigseg_handler.sa_handler = &handler;
      assert(!sigaction(SIGSEGV, &sigseg_handler, nullptr));
      cout <<"sig action set" << endl;
      foo();
      return 0;
    }


    *NIX:您可以截获sigsegv(通常在崩溃前发出此信号)并将信息保存到文件中。(除了可以使用gdb进行调试的核心文件之外)。

    胜利:从msdn检查这个。

    你也可以查看谷歌的Chrome代码,看看它是如何处理崩溃的。它有一个很好的异常处理机制。


    我在这里看到了很多答案,执行信号处理程序然后退出。这是一种方法,但请记住一个非常重要的事实:如果您想为生成的错误获取核心转储,就不能调用exit(status)。打电话给abort()


    我将使用在可视泄漏检测器中为泄漏内存生成堆栈跟踪的代码。不过,这只适用于Win32。


    城里的新国王来了https://github.com/bobela/backward-cpp

    1个要放在代码中的头和1个要安装的库。

    我个人用这个函数来称呼它

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #include"backward.hpp"
    void stacker() {

    using namespace backward;
    StackTrace st;


    st.load_here(99); //Limit the number of trace depth to 99
    st.skip_n_firsts(3);//This will skip some backward internal function from the trace

    Printer p;
    p.snippet = true;
    p.object = true;
    p.color = true;
    p.address = true;
    p.print(st, stderr);
    }

    除了上面的答案外,下面介绍如何使DebianLinux操作系统生成核心转储

  • 在用户的主文件夹中创建"coredumps"文件夹
  • 转到""行下的/etc/security/limits.conf.,键入"Soft Core Unlimited",如果为根启用了核心转储,则键入"Root Soft Core Unlimited",以便为核心转储提供无限空间。
  • 注:"*软核无限"不包括根,这就是为什么必须在其自己的行中指定根。
  • 要检查这些值,请注销、重新登录,然后键入"ulimit-a"。"核心文件大小"应设置为无限制。
  • 检查.bashrc文件(用户和根文件,如果适用),确保没有在那里设置ulimit。否则,上述值将在启动时被覆盖。
  • 打开/etc/sysctl.conf。在底部输入以下内容:"kernel.core_pattern=/home//coredumps/%e_u%t.dump"。(%e是进程名,而%t是系统时间)
  • 退出并键入"sysctl-p"以加载新配置检查/proc/sys/kernel/core_模式,并验证它是否与您刚键入的内容匹配。
  • 通过在命令行("&;")上运行一个进程,然后用"kill-11"杀死它,可以测试堆芯卸载。如果核心转储成功,在分段故障指示后会看到"(核心转储)"。

  • 作为一个仅限Windows的解决方案,您可以使用Windows错误报告获得相当于堆栈跟踪(包含更多的信息)。只需几个注册表项,就可以将其设置为收集用户模式转储:

    Starting with Windows Server 2008 and Windows Vista with Service Pack 1 (SP1), Windows Error Reporting (WER) can be configured so that full user-mode dumps are collected and stored locally after a user-mode application crashes. [...]

    This feature is not enabled by default. Enabling the feature requires administrator privileges. To enable and configure the feature, use the following registry values under the HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps key.

    您可以从安装程序设置注册表项,安装程序具有所需的权限。

    与在客户机上生成堆栈跟踪相比,创建用户模式转储具有以下优势:

    • 它已经在系统中实现了。如果您需要对要转储的信息量进行更细粒度的控制,您可以使用上述WER,也可以自己调用minidumpwritedump。(确保从其他进程调用它。)
    • 比堆栈跟踪更完整。其中,它可以包含局部变量、函数参数、其他线程的堆栈、加载的模块等。数据量(以及大小)是高度可定制的。
    • 无需发送调试符号。这两者都大大减小了部署的大小,同时也使得对应用程序进行反向工程变得更加困难。
    • 很大程度上独立于所使用的编译器。使用WER甚至不需要任何代码。无论哪种方法,拥有一种获取符号数据库(PDB)的方法对于离线分析都非常有用。我相信GCC可以生成PDB,或者有工具将符号数据库转换为PDB格式。

    请注意,WER只能由应用程序崩溃触发(即系统由于未处理的异常终止进程)。可以随时调用MiniDumpWriteDump。如果需要转储当前状态以诊断崩溃以外的问题,这可能会有所帮助。

    强制阅读,如果要评估小型转储的适用性:

    • 有效小型垃圾场
    • 有效小型垃圾场(第2部分)

    在Linux/Unix/MacOSX上,使用核心文件(您可以使用ulimit或兼容的系统调用启用它们)。在Windows上使用Microsoft错误报告(您可以成为合作伙伴并访问应用程序崩溃数据)。


    我忘记了"apport"的gnome技术,但我对使用它知之甚少。它用于生成StackTraces和其他用于处理的诊断,并可以自动归档Bug。这当然值得一看。


    如果你仍然想像我一样独自行动,你可以链接到bfd,避免像我在这里所做的那样使用addr2line

    https://github.com/gnif/lookingglass/blob/master/common/src/crash.linux.c

    这将产生输出:

    1
    2
    3
    4
    5
    6
    7
    [E]        crash.linux.c:170  | crit_err_hdlr                  | ==== FATAL CRASH (a12-151-g28b12c85f4+1) ====
    [E]        crash.linux.c:171  | crit_err_hdlr                  | signal 11 (Segmentation fault), address is (nil)
    [E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (0) /home/geoff/Projects/LookingGlass/client/src/main.c:936 (register_key_binds)
    [E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (1) /home/geoff/Projects/LookingGlass/client/src/main.c:1069 (run)
    [E]        crash.linux.c:194  | crit_err_hdlr                  | [trace]: (2) /home/geoff/Projects/LookingGlass/client/src/main.c:1314 (main)
    [E]        crash.linux.c:199  | crit_err_hdlr                  | [trace]: (3) /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xeb) [0x7f8aa65f809b]
    [E]        crash.linux.c:199  | crit_err_hdlr                  | [trace]: (4) ./looking-glass-client(_start+0x2a) [0x55c70fc4aeca]

    它看起来像在最后一个C++Boost版本出现的库中提供精确的你想要的,可能代码会是多平台的。它是boost::stacktrace,可以像在boost示例中那样使用:

    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
    #include <filesystem>
    #include <sstream>
    #include <fstream>
    #include <signal.h>     // ::signal, ::raise
    #include <boost/stacktrace.hpp>

    const char* backtraceFileName ="./backtraceFile.dump";

    void signalHandler(int)
    {
        ::signal(SIGSEGV, SIG_DFL);
        ::signal(SIGABRT, SIG_DFL);
        boost::stacktrace::safe_dump_to(backtraceFileName);
        ::raise(SIGABRT);
    }

    void sendReport()
    {
        if (std::filesystem::exists(backtraceFileName))
        {
            std::ifstream file(backtraceFileName);

            auto st = boost::stacktrace::stacktrace::from_dump(file);
            std::ostringstream backtraceStream;
            backtraceStream << st << std::endl;

            // sending the code from st

            file.close();
            std::filesystem::remove(backtraceFileName);
        }
    }

    int main()
    {
        ::signal(SIGSEGV, signalHandler);
        ::signal(SIGABRT, signalHandler);

        sendReport();
        // ... rest of code
    }

    在Linux中,您编译上面的代码:

    1
    g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace

    从Boost文档复制的回溯示例:

    1
    2
    3
    4
    5
    6
    7
    0# bar(int) at /path/to/source/file.cpp:70
    1# bar(int) at /path/to/source/file.cpp:70
    2# bar(int) at /path/to/source/file.cpp:70
    3# bar(int) at /path/to/source/file.cpp:70
    4# main at /path/to/main.cpp:93
    5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
    6# _start

    推荐阅读