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

c++ gcc crash stack-trace assert

317526 观看

30回复

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

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

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

作者: KPexEA 的来源 发布者: 2019 年 8 月 7 日

回应 (30)


468

决定

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

这是一个示例程序,它安装SIGSEGV处理程序并stderr在segfaults时打印堆栈跟踪。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]

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

作者: Todd Gamblin 发布者: 16.09.2008 09:30

119

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 %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而不是在ARM上成功实现了这个uc_mcontext.eip

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

作者: jschmier 发布者: 18.12.2009 12:05

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
    

在所有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

作者: jhclark 发布者: 06.07.2011 03:51

80

即使提供了正确的答案,描述了如何使用GNU libc backtrace()函数1,我提供了自己的答案,描述了如何确保从信号处理程序的回溯指向故障的实际位置2,我看不到任何提及从回溯输出的demangling C ++符号。

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

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

Demangled Output./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);
}
作者: jschmier 发布者: 26.03.2010 07:52

33

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

作者: Simon Steele 发布者: 17.09.2008 11:31

21

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

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

作者: Brian Mitchell 发布者: 16.09.2008 08:52

12

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

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

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

作者: Joseph 发布者: 16.09.2008 09:23

11

感谢热心人士将我的注意力吸引到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 
作者: arr_sea 发布者: 04.04.2013 03:21

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

我希望这有帮助。

作者: Benson 发布者: 16.09.2008 08:57

10

我一直在看这个问题。

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

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

谈论libunwind

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

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

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

作者: Gregory 发布者: 18.09.2008 02:55

9

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

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

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

作者: Stephen Deken 发布者: 16.09.2008 08:51

9

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

作者: markhor 发布者: 01.03.2013 06:38

9

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

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

  1. 使用“-g”标志编译程序,以便将调试符号嵌入到二进制文件中(不要担心这不会影响您的性能)。
  2. 在linux上运行下一个命令:“ulimit -c unlimited” - 允许系统进行大型崩溃转储。
  3. 当您的程序崩溃时,在工作目录中您将看到文件“core”。
  4. 运行下一个命令将backtrace打印到stdout:gdb -batch -ex“backtrace”./ your_program_exe ./core

这将以人类可读的方式打印程序的正确可读回溯(具有源文件名和行号)。此外,这种方法可让您自由地自动化系统:使用一个简短的脚本来检查进程是否创建了核心转储,然后通过电子邮件向开发人员发送回溯,或将其记录到某些日志记录系统中。

作者: loopzilla 发布者: 06.07.2016 12:37

8

ulimit -c unlimited

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

问候

作者: mana 发布者: 16.09.2008 08:47

8

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

作者: Roskoto 发布者: 16.09.2008 09:24

6

看着:

男人3回溯

和:

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

这些是GNU扩展。

作者: Stéphane 发布者: 16.09.2008 08:55

6

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

作者: Adam Mitz 发布者: 17.09.2008 12:07

5

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

作者: terminus 发布者: 16.09.2008 09:01

4

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

win:从msdn 检查一下

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

作者: INS 发布者: 16.09.2008 09:09

4

I found that @tgamblin solution is not complete. It cannot handle with stackoverflow. I think because by default signal handler is called with the same stack and SIGSEGV is thrown twice. To protect you need register an independent stack for the signal handler.

You can check this with code below. By default the handler fails. With defined macro STACK_OVERFLOW it's all right.

#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;
} 
作者: Daneel S. Yaitskov 发布者: 20.03.2014 11:59

3

I would use the code that generates a stack trace for leaked memory in Visual Leak Detector. This only works on Win32, though.

作者: Jim Buck 发布者: 16.09.2008 09:00

3

I have seen a lot of answers here performing a signal handler and then exiting. That's the way to go, but remember a very important fact: If you want to get the core dump for the generated error, you can't call exit(status). Call abort() instead!

作者: jard18 发布者: 12.12.2013 11:23

3

The new king in town has arrived https://github.com/bombela/backward-cpp

1 header to place in your code and 1 library to install.

Personally I call it using this function

#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);
}
作者: Roy 发布者: 31.12.2016 12:19

2

In addition to above answers, here how you make Debian Linux OS generate core dump

  1. Create a “coredumps” folder in the user's home folder
  2. Go to /etc/security/limits.conf. Below the ' ' line, type “ soft core unlimited”, and “root soft core unlimited” if enabling core dumps for root, to allow unlimited space for core dumps.
  3. NOTE: “* soft core unlimited” does not cover root, which is why root has to be specified in its own line.
  4. To check these values, log out, log back in, and type “ulimit -a”. “Core file size” should be set to unlimited.
  5. Check the .bashrc files (user, and root if applicable) to make sure that ulimit is not set there. Otherwise, the value above will be overwritten on startup.
  6. Open /etc/sysctl.conf. Enter the following at the bottom: “kernel.core_pattern = /home//coredumps/%e_%t.dump”. (%e will be the process name, and %t will be the system time)
  7. Exit and type “sysctl -p” to load the new configuration Check /proc/sys/kernel/core_pattern and verify that this matches what you just typed in.
  8. Core dumping can be tested by running a process on the command line (“ &”), and then killing it with “kill -11 ”. If core dumping is successful, you will see “(core dumped)” after the segmentation fault indication.
作者: enthusiasticgeek 发布者: 08.05.2013 07:13

2

As a Windows-only solution, you can get the equivalent of a stack trace (with much, much more information) using Windows Error Reporting. With just a few registry entries, it can be set up to collect user-mode dumps:

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.

You can set the registry entries from your installer, which has the required privileges.

Creating a user-mode dump has the following advantages over generating a stack trace on the client:

  • It's already implemented in the system. You can either use WER as outlined above, or call MiniDumpWriteDump yourself, if you need more fine-grained control over the amount of information to dump. (Make sure to call it from a different process.)
  • Way more complete than a stack trace. Among others it can contain local variables, function arguments, stacks for other threads, loaded modules, and so on. The amount of data (and consequently size) is highly customizable.
  • No need to ship debug symbols. This both drastically decreases the size of your deployment, as well as makes it harder to reverse-engineer your application.
  • Largely independent of the compiler you use. Using WER does not even require any code. Either way, having a way to get a symbol database (PDB) is very useful for offline analysis. I believe GCC can either generate PDB's, or there are tools to convert the symbol database to the PDB format.

Take note, that WER can only be triggered by an application crash (i.e. the system terminating a process due to an unhandled exception). MiniDumpWriteDump can be called at any time. This may be helpful if you need to dump the current state to diagnose issues other than a crash.

Mandatory reading, if you want to evaluate the applicability of mini dumps:

作者: IInspectable 发布者: 01.03.2018 01:00

1

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

作者: Kasprzol 发布者: 16.09.2008 09:16

0

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

作者: Joseph 发布者: 16.09.2008 09:24

0

它看起来像最后一个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
作者: Grzegorz Bazior 发布者: 29.01.2019 06:55

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]
作者: Geoffrey 发布者: 11.04.2019 06:45

0

如果您使用的是C ++并且不希望/不能使用Boost,则可以使用以下代码[链接到原始站点]打印带有demangled名称的回溯。

注意,此解决方案特定于Linux。它使用GNU的libc函数backtrace()/ backtrace_symbols()(来自execinfo.h)来获取回溯,然后使用__cxa_demangle()(来自cxxabi.h)来解码回溯符号名称。

// stacktrace.h (c) 2008, Timo Bingmann from http://idlebox.net/
// published under the WTFPL v2.0

#ifndef _STACKTRACE_H_
#define _STACKTRACE_H_

#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <cxxabi.h>

/** Print a demangled stack backtrace of the caller function to FILE* out. */
static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames = 63)
{
    fprintf(out, "stack trace:\n");

    // storage array for stack trace address data
    void* addrlist[max_frames+1];

    // retrieve current stack addresses
    int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));

    if (addrlen == 0) {
    fprintf(out, "  <empty, possibly corrupt>\n");
    return;
    }

    // resolve addresses into strings containing "filename(function+address)",
    // this array must be free()-ed
    char** symbollist = backtrace_symbols(addrlist, addrlen);

    // allocate string which will be filled with the demangled function name
    size_t funcnamesize = 256;
    char* funcname = (char*)malloc(funcnamesize);

    // iterate over the returned symbol lines. skip the first, it is the
    // address of this function.
    for (int i = 1; i < addrlen; i++)
    {
    char *begin_name = 0, *begin_offset = 0, *end_offset = 0;

    // find parentheses and +address offset surrounding the mangled name:
    // ./module(function+0x15c) [0x8048a6d]
    for (char *p = symbollist[i]; *p; ++p)
    {
        if (*p == '(')
        begin_name = p;
        else if (*p == '+')
        begin_offset = p;
        else if (*p == ')' && begin_offset) {
        end_offset = p;
        break;
        }
    }

    if (begin_name && begin_offset && end_offset
        && begin_name < begin_offset)
    {
        *begin_name++ = '\0';
        *begin_offset++ = '\0';
        *end_offset = '\0';

        // mangled name is now in [begin_name, begin_offset) and caller
        // offset in [begin_offset, end_offset). now apply
        // __cxa_demangle():

        int status;
        char* ret = abi::__cxa_demangle(begin_name,
                        funcname, &funcnamesize, &status);
        if (status == 0) {
        funcname = ret; // use possibly realloc()-ed string
        fprintf(out, "  %s : %s+%s\n",
            symbollist[i], funcname, begin_offset);
        }
        else {
        // demangling failed. Output function name as a C function with
        // no arguments.
        fprintf(out, "  %s : %s()+%s\n",
            symbollist[i], begin_name, begin_offset);
        }
    }
    else
    {
        // couldn't parse the line? print the whole line.
        fprintf(out, "  %s\n", symbollist[i]);
    }
    }

    free(funcname);
    free(symbollist);
}

#endif // _STACKTRACE_H_

HTH!

作者: sundeep singh 发布者: 07.08.2019 01:01
32x32