c - 如何生成stacktrace当我的gcc C app崩溃时

  显示原文与译文双语对照的内容

当我的C++ 应用程序崩溃时,我想生成一个 stacktrace 。

我已经问过了,但我想我需要澄清我的需求。

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

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

时间:

对于linux和我相信 Mac OS X, 如果你正在使用gcc,或任何使用glibc的编译器,你可以使用在 execinfo.h backtrace() 函数打印异常堆栈和优雅地退出当你得到一个段错误。 文档可以在libc手册中找到

下面是一个示例程序,它安装一个 SIGSEGV 处理程序,并在它运行时将stacktrace打印到 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("%dn", *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可以使用它来创建一个漂亮的stacktrace:


$ 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 除了 mainfoobarbaz

Linux

backtrace()的使用功能在 execinfo.h 打印异常堆栈和退出优雅地当你得到一段错误已经建议,我看到没有提到错综复杂的必要的,以确保生成的回溯点的实际位置故障( 至少对于某些架构- 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 %pn", 
 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) %sn", 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上开发/测试的。 我还在ARM上使用 uc_mcontext.arm_pc 而不是 uc_mcontext.eip 成功地实现了这个。

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

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

从 C++ 程序获取回溯时,输出可以通过 c++filt 运行 1 取消改编符号或者使用 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]

Demangled输出./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 如何用于取消对符号的取消改编。 这里信号处理程序生成与上面的示例相同的demangled输出。

代码:


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++ = '';
 *offset_begin++ = '';
 *offset_end++ = '';

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

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

GCC还有两个可以帮助你的构建,但是它们可能会在你的体系结构中完全实现,这些是 __builtin_frame_address__builtin_return_address 。 它们都需要一个立即整数级( 我的意思是,它不能是一个变量) 。 如果给定级别的__builtin_frame_address 为 non-zero,则获取相同级别的返回地址应该是安全的。

ulimit -c <value> 在unix上设置核心文件大小限制。 默认情况下,核心文件大小限制为 0. 你可以看到与 ulimit -aulimit 值。

还有,如果你运行你的程序在gdb,它会阻止你的节目"分段违规"( SIGSEGV, 通常当你访问分配一块内存,你没有)你也可以设置断点。

front-ends和nemiver是用于gdb的,使初学者更容易使用它。

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


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

然后,你可以设置"ulimit -c无限制"来让它转储一个内核,或者只在gdb中运行你的程序。 我更喜欢第二种方法:


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

我希望这可以帮到你。

...