我的程序崩溃时如何自动生成stacktrace


590

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

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

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


4
backtrace和backtrace_symbols_fd不是异步信号安全的。您不应在信号处理程序中使用这些功能
Parag Bafna 2012年

10
backtrace_symbols调用malloc,因此不得在信号处理程序中使用。其他两个函数(backtrace和backtrace_symbols_fd)不存在此问题,通常在信号处理程序中使用。
cmccabe 2012年

3
@cmccabe错误的backtrace_symbols_fd通常不会调用malloc,但如果在catch_error块中出了问题,可能会发生问题
Sam Saffron

6
从某种意义上说,“可能”没有backtrace_symbols_fd(或任何回溯)的POSIX规范;但是,根据linux.die.net/man/3/backtrace_symbols_fd,已将 GNU / Linux的backtrace_symbols_fd指定为从不调用malloc 。因此,可以安全地假设它永远不会在Linux上调用malloc。
codetaku 2014年

Answers:


509

对于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


53
还有/lib/libSegFault.so,可以将其与LD_PRELOAD一起使用。
CesarB

6
看起来在backtrace输出中的前两个条目在信号处理程序中包含一个返回地址,sigaction()在libc中可能包含一个返回地址。尽管您的回溯似乎是正确的,但我有时发现有必要采取其他步骤来确保故障的实际位置出现在回溯中,因为它可以被sigaction()内核覆盖。
jschmier 2010年

9
如果崩溃来自malloc内部会发生什么?然后,您会不会持有一个锁,然后在“ backtrace”尝试分配内存时被卡住?
Mattias Nilsson '04年

7
catchsegv这不是OP所需要的,但是对于捕获分段错误并获取所有信息非常有用。
马特·克拉克森

8
对于ARM,我还必须使用-funwind-tables进行编译。否则,我的堆栈深度始终为1(空)。
jfritz42

128

它甚至比“ man backtrace”更容易,还有一个文件很少的库(特定于GNU)与glibc一起发行,名为libSegFault.so,我相信它是由Ulrich Drepper编写的,以支持catchsegv程序(请参阅“ man catchsegv”)。

这给了我们3种可能性。而不是运行“程序-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

在所有这三种情况下,通过较少的优化(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:您的意思是“不起作用”。您尝试过什么语言,编译器/工具链/发行版/硬件?编译失败了吗?捕捉错误?要产生产出吗?产生难以使用的输出?感谢您提供详细信息,它将对大家有所帮助。
斯特凡纳·古里科

1
“不幸的是,最好的源是源”。希望有一天,catchsegv的手册页实际上会提到SEGFAULT_SIGNALS。在此之前,这里有要参考的答案。
greggo 2014年

我真不敢相信我已经用C语言编程了5年了,从未听说过:/
DavidMFrey

6
@StéphaneGourichon@HansKratz要链接到libSegFault,您必须添加-Wl,--no-as-needed到编译器标志。否则,ld实际上不会链接到libSegFault,因为它会识别二进制文件不使用其任何符号。
菲利普(Phillip)

122

的Linux

虽然已经建议在execinfo.h中使用backtrace()函数来打印堆栈跟踪并在遇到分段错误时正常退出,但我看不到要确保生成的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代替uc_mcontext.eip

这是文章的链接,我在其中学习了此实现的详细信息:http : //www.linuxjournal.com/article/6391


11
在使用GNU ld的系统上,切记进行编译-rdynamic以指示链接程序将所有符号(不仅是用过的符号)添加到动态符号表中。这允许backtrace_symbols()将地址转换为函数名称
jschmier 2010年

1
另外,您需要在GCC的命令行中添加“ -mapcs-frame”选项以在ARM平台上生成堆栈帧
qehgt 2012年

3
这可能为时已晚,但是我们可以使用addr2line命令以某种方式获取崩溃发生的确切行吗?
狂热爱好者

4
在的较新版本中glibc uc_mcontext,不包含名为的字段eip。现在有一个需要索引的数组,它uc_mcontext.gregs[REG_EIP]是等效的。
mmlb 2012年

6
对于ARM,在向编译器添加-funwind-tables选项之前,回溯始终具有深度1。
jfritz42

84

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

从C ++程序获取回溯信息时,输出可以通过c++filt1进行运行以对符号进行解拆,也可以直接使用1进行输出。abi::__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]

拆线输出./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 2013年

4
不要忘记#include <cxxabi.h>
Bamaco

1
自2008年以来,良好的文档和直观的头文件已在此处发布... panthema.net/2008/0901-stacktrace-demangled非常类似于您的方法:)
kevinf 2014年

abi :: __ cxa_demangle似乎不是异步信号安全的,因此信号处理程序可能会死锁在malloc中的某个位置。
Orcy 2015年

使用的std::cerrfree()并且exit()所有违反反对呼吁POSIX系统的非异步信号安全的调用限制。 如果你的过程,任何调用,如失败,此代码就会死锁free()malloc() newdetete
安德鲁·亨利


21

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

海湾合作委员会也有两个内建命令,可以帮助你,但是这可能会或可能不会完全在你的架构来实现,而这些都是__builtin_frame_address__builtin_return_address。两者都需要立即整数级别(即时,我的意思是它不能是变量)。如果__builtin_frame_address给定级别的非零值,则可以安全地获取同一级别的返回地址。


13

感谢热情爱好者吸引我注意addr2line实用程序。

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

该脚本接受一个参数:包含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 

12

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

另外,如果您从gdb中运行程序,则会因“分段违规”(SIGSEGV通常是在您访问未分配的内存时)而暂停程序,也可以设置断点。

ddd和nemiver是gdb的前端,对于新手来说,使用它非常容易。


6
核心转储比堆栈跟踪更有用,因为您可以在调试器中加载核心转储,并在崩溃时查看整个程序的状态及其数据。
亚当·霍斯

1
其他人建议的回溯功能可能总比没有好,但它是非常基本的-它甚至没有给出行号。另一方面,使用核心转储,您可以追溯查看应用程序崩溃时的整个状态(包括详细的堆栈跟踪)。有可能是实际问题,试图利用这个现场调试,但它绝对是分析崩溃了更强大的工具和开发过程中断言(至少在Linux上)。
nobar 2010年

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

我希望这有帮助。


4
您也可以gdb从崩溃的程序中直接调用。用于将调用gdb的SIGSEGV,SEGILL,SIGBUS,SIGFPE的设置处理程序。详细信息:stackoverflow.com/questions/3151779/… 优点是您可以像一样获得漂亮的,带注释的回溯bt full,也可以获得所有线程的堆栈跟踪。
六。

您也可以比在答案中更容易获得回溯:gdb -silent ./prog core --eval-command = backtrace --batch-它将显示回溯并关闭调试器
baziorek

10

我一直在研究这个问题已有一段时间了。

并深入埋入Google Performance Tools README中

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

谈论libunwind

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

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

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


2
在x86 / 64上,我还没有看到-rdynamic增加二进制大小的多少。添加-g会使增加更大。

1
我注意到libunwind不具有获取行号的功能,我猜(未测试)unw_get_proc_name返回了函数符号(对于重载等混淆),而不是原始名称。
赫伯特2014年

1
没错 正确执行此操作非常棘手,但是我在gaddr2line上取得了出色的成功,这里有很多实用信息blog.bigpixel.ro/2010/09/stack-unwinding-stack-trace-with-gcc
Gregory


9

您可以使用DeathHandler-小型C ++类,可为您做所有可靠的事情。


1
不幸的是,它用来execlp()执行addr2line调用...很高兴完全留在自己的程序中(可以通过以某种形式包含addr2line代码来实现)
例如

9

忘了更改源代码,并使用backtrace()函数或宏进行一些改动-这些都是较差的解决方案。

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

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

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


它给出了错误的行号。可以改善吗?
HeyJude

7
ulimit -c unlimited

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

问候


5
用户没有要求核心转储。他正在请求堆栈跟踪。参见delorie.com/gnu/docs/glibc/libc_665.html
Todd Gamblin

1
崩溃时,核心转储将包含调用堆栈,不是吗?
密苏里州

3
您假设他在Unix上,并且正在使用Bash。
Paul Tomblin

2
如果您使用的是tcsh,则必须做limit coredumpsize unlimited
sivabudh 2010年


6

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


链接似乎已消失。
tglas

5

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


5

看起来在最新的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文档复制的示例backtrace :

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

4

* nix:您可以拦截SIGSEGV(通常在崩溃前会发出此信号)并将信息保存到文件中。(例如,可以使用gdb调试的核心文件除外)。

赢:检查从MSDN。

您还可以查看Google的Chrome浏览器代码,了解其如何处理崩溃。它具有很好的异常处理机制。


SEH不能帮助产生堆栈跟踪。虽然这可能是一个解决方案的一部分,该解决方案更难实现,并提供在披露有关您的应用程序比更多信息的费用较少的信息真正的解决办法:写一个小型转储。并将Windows设置为自动为您执行此操作。
IInspectable '18

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

4

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

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

我个人使用这个功能来称呼它

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

哇!最终,这就是应该做的事情!我只是通过自己的解决方案放弃了对这一解决方案的支持。
tglas

3

我将使用为Visual Leak Detector中的泄漏内存生成堆栈跟踪的代码。不过,这仅适用于Win32。


并要求您在代码中附带调试符号。通常是不可取的。编写一个小型转储,并将Windows设置为在未处理的异常时自动为您执行此操作。
IInspectable '18

3

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


3

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

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

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

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

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

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

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

必读,如果您想评估小型转储的适用性:


2

除了上述答案之外,还介绍了如何使Debian Linux OS生成核心转储。

  1. 在用户的主文件夹中创建一个“ coredumps”文件夹
  2. 转到/etc/security/limits.conf。在“”行下方,键入“ soft core unlimited”和“ root soft core unlimited”(如果为root启用了核心转储),以便为核心转储提供无限的空间。
  3. 注意:“ *软核无限制”不包括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. 可以通过在命令行(“&”)上运行一个进程,然后用“ kill -11”将其杀死来测试核心转储。如果核心转储成功,则在分段故障指示之后您将看到“(核心转储)”。

2

如果您仍然想像我一样独自使用它,可以链接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]

1

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


0

我忘记了GNOME的“ appport”技术,但是我对使用它并不了解。它用于生成堆栈跟踪和其他诊断以进行处理,并且可以自动归档错误。当然值得检查。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.