当我的程序崩溃时如何自动生成堆栈跟踪

689

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

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

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


4
backtrace和backtrace_symbols_fd函数不是异步信号安全的。在信号处理程序中不应使用这些函数。 - Parag Bafna
13
backtrace_symbols调用malloc,因此不能在信号处理程序中使用。另外两个函数(backtrace和backtrace_symbols_fd)没有这个问题,并且通常在信号处理程序中使用。 - cmccabe
3
@cmccabe那是不正确的,backtrace_symbols_fd通常不会调用malloc,但如果在其catch_error块中发生错误,则可能会调用。 - Sam Saffron
7
“可能”是指没有POSIX规范涉及backtrace_symbols_fd(或任何backtrace); 然而,GNU / Linux的backtrace_symbols_fd被指定为永远不会调用malloc,如http://linux.die.net/man/3/backtrace_symbols_fd所述。因此,可以安全地假设在Linux上它永远不会调用malloc。 - codetaku
1
在2021年,有没有更好的解决方案来解决这个问题?我只想像Java或Python一样打印堆栈跟踪。 - stackoverflowuser2010
显示剩余2条评论
31个回答

594

对于Linux和我相信的Mac OS X,如果您使用gcc或任何使用glibc的编译器,您可以使用execinfo.h中的backtrace()函数来打印堆栈跟踪并在发生分段错误时优雅地退出。您可以在libc手册中找到文档

这是一个示例程序,它安装了一个SIGSEGV处理程序,并在发生段错误时将堆栈跟踪打印到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("%d\n", *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之前的foobarbaz


58
还有一个/lib/libSegFault.so,你可以使用LD_PRELOAD来加载它。 - CesarB
7
看起来你的回溯输出的前两个条目包含了一个信号处理程序内部的返回地址,以及可能包含在libc中sigaction()内部的一个返回地址。虽然你的回溯似乎是正确的,但我有时发现需要额外的步骤来确保故障实际位置出现在回溯中,因为它可能被内核用sigaction()覆盖。 - jschmier
9
如果崩溃是由malloc内部引起的,会发生什么?那么你会持有一个锁,然后在"backtrace"尝试分配内存时被卡住吗? - Mattias Nilsson
8
catchsegv并不是OP需要的,但它非常适合捕获分段错误并获取所有信息。 - Matt Clarkson
13
对于ARM,我还需要使用-funwind-tables选项进行编译。否则我的堆栈深度始终为1(空的)。 - jfritz42
显示剩余18条评论

175
比使用"man backtrace"还要简单,有一个鲜为人知的库(只适用于GNU)作为libSegFault.so与glibc一起发布,我相信是由Ulrich Drepper编写来支持程序catchsegv(请参见"man catchsegv")。
这给了我们三种可能性,而不是运行"program -o hai":
  1. 在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


    1
    "可能性3. 在编译时与libSegFault链接"无效。 - HHK
    5
    @crafter: 你所说的“不起作用”是什么意思?你尝试了什么,使用了什么语言/编译器/工具链/发行版/硬件?它是否无法编译?是否无法捕获错误?是否根本没有产生输出?还是生成难以使用的输出?感谢提供详细信息,这将有助于大家。 - Stéphane Gourichon
    2
    “最好的来源不幸是源代码”...希望有一天,catchsegv的man页面实际上会提到SEGFAULT_SIGNALS。在那之前,可以参考这个答案。 - greggo
    13
    要连接到libSegFault,您需要将-Wl,--no-as-needed添加到编译器标志中。否则,由于识别出二进制文件未使用任何其符号,ld确实不会链接到libSegFault - Phillip
    4
    catchsegvlibSegFault 已经在 glibc 2.35 中被移除:https://savannah.gnu.org/forum/forum.php?forum_id=10111 看起来 gdb -ex=r --args <command> 可以部分替代。 - Will Chen
    显示剩余2条评论

    130

    Linux

    虽然在execinfo.h中使用backtrace()函数打印堆栈跟踪并优雅地退出程序以避免段错误的建议已被提出, 但我没有看到有关确保结果堆栈跟踪指向故障实际位置所需的细节说明(至少对于某些体系结构 - x86和ARM)。

    当进入信号处理程序时,堆栈帧链中的前两个条目包含信号处理程序内的返回地址和libc中sigaction()内的一个返回地址。信号发生前调用的最后一个函数的堆栈帧(即故障位置)丢失了。

    Code

    #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;
     ucontext_t        *uc_link;
     stack_t           uc_stack;
     sigcontext_t      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\n", 
      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\n", 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上为x86开发/测试的。我还成功地在ARM上实现了这个功能,使用uc_mcontext.arm_pc代替uc_mcontext.eip。
    这是一篇文章的链接,我从中了解到了这个实现的细节: http://www.linuxjournal.com/article/6391

    11
    在使用GNU ld的系统上,记得用-rdynamic编译来指示连接器将所有符号添加到动态符号表中,而不仅仅是使用的符号。这使得backtrace_symbols()函数能够将地址转换为函数名称。 - jschmier
    2
    此外,您需要在GCC的命令行中添加“-mapcs-frame”选项,以在ARM平台上生成堆栈帧。 - qehgt
    3
    或许已经有些晚了,但我们能否使用addr2line命令来获取崩溃发生的确切行号? - enthusiasticgeek
    5
    在较新版本的glibc中,uc_mcontext不再包含名为eip的字段。现在有一个需要索引的数组,uc_mcontext.gregs[REG_EIP]是相当于这个字段的内容。 - mmlb
    7
    对于ARM,我的回溯信息一直只有1层,直到我向编译器添加了“-funwind-tables”选项。 - jfritz42
    显示剩余6条评论

    95

    虽然已经提供了一个正确答案,描述了如何使用GNU libc的backtrace()函数1,我也提供了我的答案,描述了如何确保从信号处理程序获取的回溯指向故障的实际位置2,但我没有看到任何提到解码从回溯输出的C++符号。

    在从C++程序获取回溯时,可以通过运行输出来进行解码符号c++filt1,或者直接使用abi::__cxa_demangle1

    • 1 Linux和OS X 请注意,c++filt__cxa_demangle是GCC特定的
    • 2 Linux

    下面是一个C++ Linux示例,使用与我的其他答案相同的信号处理程序,并演示了如何使用c++filt来解除符号名称。 代码:
    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):

    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):

    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来解构符号。该信号处理程序产生与上述示例相同的解构输出。 代码:
    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);
    }
    

    1
    谢谢你的分享,jschmier。我创建了一个小的bash脚本来将此输出输入到addr2line实用程序中。 请参见:stackoverflow.com/a/15801966/1797414 - arr_sea
    6
    别忘了加入 #include <cxxabi.h>。 - Bamaco
    1
    自2008年以来,这里发布了良好的文档和简单的头文件... http://panthema.net/2008/0901-stacktrace-demangled/ 与您的方法非常相似 :) - Kevin
    2
    使用 std::cerrfree()exit() 都违反了在 POSIX 系统上调用非异步信号安全调用的限制。如果您的进程在任何调用中失败,如 free()malloc()newdelete,则此代码将死锁。 - Andrew Henle
    @MikeMoreton 所以,如果在某些情况下它可能会导致程序死锁,那也没什么大不了的,因为程序已经失败了。太好了 - 现在你的关键进程挂起了,无法重新启动。哇,这真是强大。您正在倡导一种非常低的可靠性标准。每次像那样编写代码时,都会降低系统的可靠性。当您在数千行代码中编写垃圾代码时,您将获得垃圾产品。但是,如果这对您来说足够好...... - Andrew Henle
    显示剩余5条评论

    34

    这个可能值得看一下:Google Breakpad,它是一个跨平台的崩溃转储生成器,以及处理这些转储的工具。


    它可以报告一些像分段错误之类的问题,但是对于未处理的C++异常并不提供任何信息。 - DBedrenko

    22

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

    GCC还有两个内置函数可以帮助您,但这些函数在您的架构上可能完全实现或部分实现,它们是__builtin_frame_address__builtin_return_address。这两个函数都需要一个立即整数级别(我指的是它不能是变量)。如果给定级别的__builtin_frame_address不为零,则可以安全地获取同一级别的返回地址。


    14

    感谢enthusiasticgeek提醒我注意addr2line工具。

    我编写了一个快速脚本来处理这里提供的答案的输出(非常感谢jschmier!)使用addr2line实用程序。

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

    每个追踪级别的输出应该打印类似于以下内容:

    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;
    

    代码:

    #!/bin/bash
    
    LOGFILE=$1
    
    NUM_SRC_CONTEXT_LINES=3
    
    old_IFS=$IFS  # save the field separator           
    IFS=$'\n'     # new field separator, the end of line           
    
    for bt in `cat $LOGFILE | grep '\[bt\]'`; do
       IFS=$old_IFS     # restore default field separator 
       printf '\n'
       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/\<at\>.*//' | 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=$'\n'     # new field separator, the end of line           
    done
    
    IFS=$old_IFS     # restore default field separator 
    

    14

    ulimit -c <value> 用于设置 Unix 上核心文件大小的限制。默认情况下,核心文件大小限制为 0。您可以使用 ulimit -a 命令查看 ulimit 的值。

    此外,如果您在 gdb 中运行程序,则会在 "段违规"(一般发生在访问未分配内存块时)或者您可以设置断点时中断程序。

    ddd 和 nemiver 是 gdb 的前端,使初学者更容易使用它。


    6
    核心转储比栈跟踪更加有用,因为您可以在调试器中加载核心转储并查看程序及其数据在崩溃点的整个状态。 - Adam Hawes
    1
    其他人建议的回溯工具可能比没有好,但它非常基础——甚至不提供行号。另一方面,使用核心转储可以让您在应用程序崩溃时追溯整个状态(包括详细的堆栈跟踪)。尝试在现场调试中使用此功能可能存在实际问题,但它绝对是分析开发过程中崩溃和断言的更强大工具(至少在Linux上)。 - Brent Bradburn

    13

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

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

    然后,您可以设置 "ulimit -c unlimited" 来转储核心文件,或者在 gdb 中运行程序。 我更喜欢第二种方法:


    Then, you can either set "ulimit -c unlimited" to enable core dumping or run your program inside gdb. I prefer the latter approach.
    $ gdb ./prog
    ... gdb startup output ...
    (gdb) run
    ... program runs and crashes ...
    (gdb) where
    ... gdb outputs your stack trace ...
    

    希望这可以帮到你。


    5
    你还可以从崩溃的程序中直接调用 gdb。设置 SIGSEGVSEGILLSIGBUSSIGFPE 的处理程序,以调用gdb。详情请参阅:https://dev59.com/BXA75IYBdhLWcg3wsraD。优点是你可以获得像 bt full 一样美观、注释齐全的回溯信息,而且你还可以获得所有线程的堆栈跟踪信息。 - Vi.
    你还可以比答案更轻松地获取回溯:gdb -silent ./prog core --eval-command=backtrace --batch -它将显示回溯并关闭调试器。 - baziorek

    13

    看起来在最新的C++ Boost版本中出现了提供您所需功能的库,代码可能是跨平台的。 它就是boost::stacktrace,您可以像在boost示例中那样使用它:

    #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中,您编译上面的代码:
    g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace
    

    以下是从Boost文档中复制的示例回溯:

    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
    

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接