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

时间:2008-09-16 20:58:35

标签: c++ gcc crash stack-trace assert

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

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

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

29 个答案:

答案 0 :(得分:462)

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

这是一个示例程序,它安装SIGSEGV处理程序,并在它发生段错误时将堆栈跟踪打印到stderr。这里的baz()函数会导致触发处理程序的段错误:

#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]

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

答案 1 :(得分:118)

的Linux

虽然在execinfo.h中使用backtrace()函数来打印堆栈跟踪并在遇到分段错误时正常退出already been suggested,但我没有提到确保产生的回溯点所需的复杂性到故障的实际位置(至少对于某些架构 - 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 %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 for x86上开发/测试的。我还使用uc_mcontext.arm_pc而不是uc_mcontext.eip在ARM上成功实现了此功能。

以下是该文章的链接,其中我了解了此实现的详细信息: http://www.linuxjournal.com/article/6391

答案 2 :(得分:117)

它比“man backtrace”更容易,有一个小文档库(GNU特定的)与glibc一起分发为libSegFault.so,我相信这是由Ulrich Drepper编写的,用于支持程序catchsegv(参见“man catchsegv”) )。

这为我们提供了三种可能性。而不是运行“程序-o hai”:

  1. 在catchsegv中运行:

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

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

    $ gcc -g1 -lSegFault -o program program.cc
    $ program -o hai
    
  4. 在所有3种情况下,您将获得更清晰的回溯,优化更少(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

答案 3 :(得分:80)

尽管提供了一个correct answer来描述如何使用GNU libc backtrace()函数 1 ,我提供了my own answer来描述如何确保从信号处理程序回溯到故障 2 的实际位置,我没有看到从回溯中输出demangling C ++符号的任何内容。

从C ++程序获取回溯时,可以通过c++filt 1 运行输出以解码符号或使用abi::__cxa_demangle 1 直接

  • 1 Linux&amp; OS X. 请注意,c++filt__cxa_demangle是特定于GCC的
  • 2 Linux

以下C ++ Linux示例使用与other answer相同的信号处理程序,并演示如何使用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]

以下内容构建于original answer的信号处理程序之上,可以替换上例中的信号处理程序,以演示如何使用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);
}

答案 4 :(得分:33)

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

答案 5 :(得分:21)

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

GCC还有两个内置版可以为您提供帮助,但可能会或可能不会在您的架构上完全实现,而__builtin_frame_address__builtin_return_address。两者都想要一个立即的整数级别(通过立即,我的意思是它不能是一个变量)。如果给定级别的__builtin_frame_address非零,那么获取相同级别的返回地址应该是安全的。

答案 6 :(得分:12)

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

另外,如果你从gdb中运行你的程序,它会停止你的程序“违反分段”(SIGSEGV,一般当你访问一块你没有分配的内存时),或者你可以设置断点。

ddd和nemiver是gdb的前端,这使新手更容易使用它。

答案 7 :(得分:11)

感谢热心人士关注addr2line实用程序。

我编写了一个快速而又脏的脚本来处理提供的答案的输出here: (非常感谢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 

答案 8 :(得分:10)

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

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

然后,你可以设置“ulimit -c unlimited”让它转储核心,或者只是在gdb中运行你的程序。我更喜欢第二种方法:

$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...

我希望这会有所帮助。

答案 9 :(得分:10)

我一直在看这个问题。

深深埋藏在Google Performance Tools自述文件中

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

谈论libunwind

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

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

-rdynamic的问题在于,它可以在某些情况下相对显着地增加二进制文件的大小

答案 10 :(得分:9)

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

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

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

答案 11 :(得分:9)

忘记更改你的资源并使用backtrace()函数或宏来做一些黑客 - 这些只是糟糕的解决方案。

作为一个正常运作的解决方案,我建议:

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

答案 12 :(得分:8)

ulimit -c unlimited

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

问候

答案 13 :(得分:8)

答案 14 :(得分:8)

您可以使用DeathHandler - 小型C ++课程,它可以为您提供一切可靠的服务。

答案 15 :(得分:6)

请参阅ACE(自适应通信环境)中的堆栈跟踪工具。它已经编写为涵盖所有主要平台(以及更多)。该库是BSD风格的许可证,因此如果您不想使用ACE,您甚至可以复制/粘贴代码。

答案 16 :(得分:6)

看看:

man 3 backtrace

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

这些是GNU扩展。

答案 17 :(得分:5)

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

答案 18 :(得分:4)

* nix中: 你可以拦截SIGSEGV(通常在崩溃之前引发此信号)并将信息保存到文件中。 (除了可以用于使用gdb进行调试的核心文件)。

赢: 从msdn。

检查this

您还可以查看Google的Chrome代码,了解它如何处理崩溃问题。它有一个很好的异常处理机制。

答案 19 :(得分:4)

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

您可以使用下面的代码进行检查。默认情况下,处理程序失败使用已定义的宏STACK_OVERFLOW,它可以。

#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;
} 

答案 20 :(得分:3)

我会在Visual Leak Detector中使用为泄漏内存生成堆栈跟踪的代码。但这仅适用于Win32。

答案 21 :(得分:3)

我在这里看到很多答案,执行信号处理程序然后退出。 这是要走的路,但请记住一个非常重要的事实:如果您想为生成的错误获取核心转储,则无法调用exit(status)。请改为呼叫abort()

答案 22 :(得分:3)

镇上的新国王已经到了 https://github.com/bombela/backward-cpp

要在代码中放置1个标头,要安装1个库。

我个人称之为使用此功能

[stephen@blackbox tmp]$ java InterruptTest 
I found, it is: 855
I found, it is: 3051
[stephen@blackbox tmp]$ java InterruptTest 
I found, it is: 2875
I found, it is: 5008
I found, it is: 1081
I found, it is: 8527
[stephen@blackbox tmp]$ java InterruptTest 
I found, it is: 2653
I found, it is: 5377
I found, it is: 1092
[stephen@blackbox tmp]$ java InterruptTest 
I found, it is: 255
I found, it is: 9095
I found, it is: 6983
I found, it is: 3777

答案 23 :(得分:2)

除了上面的答案,这里是如何使Debian Linux OS生成核心转储

  1. 在用户的主文件夹中创建“coredumps”文件夹
  2. 转到/etc/security/limits.conf。如果为root启用核心转储,则在''行下方键入“soft core unlimited”和“root soft core unlimited”,以允许无限的核心转储空间。
  3. 注意:“* soft core unlimited”不包括root,这就是必须在自己的行中指定root的原因。
  4. 要检查这些值,请注销,重新登录,然后键入“ulimit -a”。 “核心文件大小”应设置为无限制。
  5. 检查.bashrc文件(用户和root,如果适用)以确保未在那里设置ulimit。否则,上面的值将在启动时被覆盖。
  6. 打开/etc/sysctl.conf。 在底部输入以下内容:“kernel.core_pattern = /home//coredumps/%e_%t.dump”。 (%e将是进程名称,%t将是系统时间)
  7. 退出并键入“sysctl -p”以加载新配置 检查/ proc / sys / kernel / core_pattern并验证它与您刚输入的内容相匹配。
  8. 可以通过在命令行(“&amp;”)上运行进程来测试核心转储,然后使用“kill -11”将其杀死。如果核心转储成功,您将在分段故障指示后看到“(core dumped)”。

答案 24 :(得分:2)

作为仅限Windows的解决方案,您可以使用Windows Error Reporting获得相当于堆栈跟踪(包含更多信息)。只需几个注册表项,就可以设置为collect user-mode dumps

  

从Windows Server 2008和带有Service Pack 1(SP1)的Windows Vista开始,可以配置Windows错误报告(WER),以便在用户模式应用程序崩溃后收集并存储本地完整的用户模式转储。 [...]

     

默认情况下不启用此功能。启用该功能需要管理员权限。要启用和配置该功能,请在 HKEY_LOCAL_MACHINE \ SOFTWARE \ Microsoft \ Windows \ Windows错误报告\ LocalDumps 键下使用以下注册表值。

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

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

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

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

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

答案 25 :(得分:1)

在Linux / unix / MacOSX上使用核心文件(您可以使用ulimit或compatible system call启用它们)。在Windows上使用Microsoft错误报告(您可以成为合作伙伴并访问应用程序崩溃数据)。

答案 26 :(得分:0)

我忘记了“apport”的GNOME技术,但我对使用它并不了解。它用于生成堆栈跟踪和其他诊断以进行处理,并可自动提交错误。这当然值得一试。

答案 27 :(得分:0)

似乎在最后一个c ++ boost版本中出现了一个库,它提供了您想要的内容,代码可能是多平台的。 它是boost::stacktrace,您可以像as in boost sample一样使用它:

#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 documentation复制的示例回溯:

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

答案 28 :(得分:0)

如果您仍然想像我一样独自一人,则可以链接到bfd,并避免像我在这里那样使用addr2line

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

这将产生输出:

[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]