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

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我的程序崩溃时如何自动生成堆栈跟踪相关的知识,希望对你有一定的参考价值。

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

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

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

答案

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

void baz() {
 int *foo = (int*)-1; // make a bad pointer
  printf("%d
", *foo);       // causes segfault
}

void bar() { baz(); }
void foo() { bar(); }


int main(int argc, char **argv) {
  signal(SIGSEGV, handler);   // install our handler
  foo(); // this will call foo, bar, and baz.  baz segfaults.
}

使用-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函数。

另一答案

我一直在看这个问题。

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

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

谈论libunwind

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

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

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

另一答案

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

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

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

另一答案

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

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

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

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

另一答案
ulimit -c unlimited

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

问候

另一答案

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

另一答案

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

另一答案

看着:

男人3回溯

和:

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

这些是GNU扩展。

另一答案

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

另一答案

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

另一答案

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

win:从msdn检查this

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

另一答案

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
", 
  sig_num, strsignal(sig_num), info->si_addr, 
  (void *)caller_address);

 size = backtrace(array, 50);

 /* overwrite sigaction with caller's address */
 array[1] = caller_address;

 messages = backtrace_symbols(array, size);

 /* skip first stack frame (points here) */
 for (i = 1; i < size && messages != NULL; ++i)
 {
  fprintf(stderr, "[bt]: (%d) %s
", i, messages[i]);
 }

 free(messages);

 exit(EXIT_FAILURE);
}

int crash()
{
 char * p = NULL;
 *p = 0;
 return 0;
}

int foo4()
{
 crash();
 return 0;
}

int foo3()
{
 foo4();
 return 0;
}

int foo2()
{
 foo3();
 return 0;
}

int foo1()
{
 foo2();
 return 0;
}

int main(int argc, char ** argv)
{
 struct sigaction sigact;

 sigact.sa_sigaction = crit_err_hdlr;
 sigact.sa_flags = SA_RESTART | SA_SIGINFO;

 if (sigaction(SIGSEGV, &sigact, (struct sigaction *)NULL) != 0)
 {
  fprintf(stderr, "error setting signal handler for %d (%s)
",
    SIGSEGV, strsignal(SIGSEGV));

  exit(EXIT_FAILURE);
 }

 foo1();

 exit(EXIT_SUCCESS);
}

产量

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_mcont

以上是关于我的程序崩溃时如何自动生成堆栈跟踪的主要内容,如果未能解决你的问题,请参考以下文章

如何让android打印掉崩溃系统应用程序的核心转储?

在 C# 中如何收集程序崩溃的堆栈跟踪

XCode:仪器有堆栈跟踪吗?

在应用程序崩溃/C++ 期间获取堆栈跟踪

当 gcc 中的应用程序在没有 gdb 的情况下崩溃时,如何生成堆栈转储和转储的寄存器值?

是否可以在使用 Fabric / Crashlytics 启动时查看先前崩溃的堆栈跟踪?