我正在使用GCC编译器在Linux上工作。当我的C ++程序崩溃时,我希望它自动生成一个堆栈跟踪。
我的程序由许多不同的用户运行,并且还可以在Linux,Windows和Macintosh上运行(所有版本都使用进行编译gcc
)。
我希望我的程序能够在崩溃时生成堆栈跟踪,并在用户下次运行时生成一个堆栈跟踪,它将询问他们是否可以将堆栈跟踪发送给我,这样我就可以找到问题所在。我可以处理向我发送信息,但是我不知道如何生成跟踪字符串。有任何想法吗?
我正在使用GCC编译器在Linux上工作。当我的C ++程序崩溃时,我希望它自动生成一个堆栈跟踪。
我的程序由许多不同的用户运行,并且还可以在Linux,Windows和Macintosh上运行(所有版本都使用进行编译gcc
)。
我希望我的程序能够在崩溃时生成堆栈跟踪,并在用户下次运行时生成一个堆栈跟踪,它将询问他们是否可以将堆栈跟踪发送给我,这样我就可以找到问题所在。我可以处理向我发送信息,但是我不知道如何生成跟踪字符串。有任何想法吗?
Answers:
对于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
,除了main
,foo
,bar
,和baz
。
sigaction()
在libc中可能包含一个返回地址。尽管您的回溯似乎是正确的,但我有时发现有必要采取其他步骤来确保故障的实际位置出现在回溯中,因为它可以被sigaction()
内核覆盖。
catchsegv
这不是OP所需要的,但是对于捕获分段错误并获取所有信息非常有用。
它甚至比“ man backtrace”更容易,还有一个文件很少的库(特定于GNU)与glibc一起发行,名为libSegFault.so,我相信它是由Ulrich Drepper编写的,以支持catchsegv程序(请参阅“ man catchsegv”)。
这给了我们3种可能性。而不是运行“程序-o hai”:
在catchsegv中运行:
$ catchsegv program -o hai
在运行时与libSegFault链接:
$ LD_PRELOAD=/lib/libSegFault.so program -o hai
在编译时与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
-Wl,--no-as-needed
到编译器标志。否则,ld
实际上不会链接到libSegFault
,因为它会识别二进制文件不使用其任何符号。
虽然已经建议在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
-rdynamic
以指示链接程序将所有符号(不仅是用过的符号)添加到动态符号表中。这允许backtrace_symbols()
将地址转换为函数名称
addr2line
命令以某种方式获取崩溃发生的确切行吗?
glibc
uc_mcontext
,不包含名为的字段eip
。现在有一个需要索引的数组,它uc_mcontext.gregs[REG_EIP]
是等效的。
即使提供了描述如何使用GNU libc 函数1的正确答案,并且我提供了自己的答案,该答案描述了如何确保从信号处理程序返回的轨迹指向故障2的实际位置,但我看不到提及拆解backtrace()
回溯输出的C ++符号的。
从C ++程序获取回溯信息时,输出可以通过c++filt
1进行运行以对符号进行解拆,也可以直接使用1进行输出。abi::__cxa_demangle
c++filt
并且__cxa_demangle
是GCC特定的下面的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);
}
std::cerr
,free()
并且exit()
所有违反反对呼吁POSIX系统的非异步信号安全的调用限制。 如果你的过程,任何调用,如失败,此代码就会死锁free()
,malloc()
new
或detete
。
值得一看的是Google Breakpad,它是一个跨平台的崩溃转储生成器和用于处理转储的工具。
感谢热情爱好者吸引我注意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
ulimit -c <value>
在UNIX上设置核心文件大小限制。默认情况下,核心文件大小限制为0。您可以使用来查看ulimit
值ulimit -a
。
另外,如果您从gdb中运行程序,则会因“分段违规”(SIGSEGV
通常是在您访问未分配的内存时)而暂停程序,也可以设置断点。
ddd和nemiver是gdb的前端,对于新手来说,使用它非常容易。
重要的是要注意,一旦生成核心文件,就需要使用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 ...
我希望这有帮助。
gdb
从崩溃的程序中直接调用。用于将调用gdb的SIGSEGV,SEGILL,SIGBUS,SIGFPE的设置处理程序。详细信息:stackoverflow.com/questions/3151779/… 优点是您可以像一样获得漂亮的,带注释的回溯bt full
,也可以获得所有线程的堆栈跟踪。
我一直在研究这个问题已有一段时间了。
并深入埋入Google Performance Tools README中
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来获取堆栈跟踪,但是您的平台可能不支持它。
您可以使用DeathHandler-小型C ++类,可为您做所有可靠的事情。
execlp()
执行addr2line调用...很高兴完全留在自己的程序中(可以通过以某种形式包含addr2line代码来实现)
忘了更改源代码,并使用backtrace()函数或宏进行一些改动-这些都是较差的解决方案。
作为一个可以正常工作的解决方案,我建议:
这将以人类可读的方式(带有源文件名和行号)打印程序的正确可读回溯。此外,这种方法将为您提供自动化系统的自由:编写一个简短的脚本,检查进程是否创建了核心转储,然后通过电子邮件向开发人员发送回溯记录,或将其记录到某些日志记录系统中。
ulimit -c unlimited
是一个系统变量,它允许您在应用程序崩溃后创建核心转储。在这种情况下,数量不受限制。在同一目录中查找名为core的文件。确保在启用调试信息的情况下编译代码!
问候
limit coredumpsize unlimited
看着:
男子3回溯
和:
#include <exeinfo.h>
int backtrace(void **buffer, int size);
这些是GNU扩展。
看起来在最新的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
* nix:您可以拦截SIGSEGV(通常在崩溃前会发出此信号)并将信息保存到文件中。(例如,可以使用gdb调试的核心文件除外)。
赢:检查这从MSDN。
您还可以查看Google的Chrome浏览器代码,了解其如何处理崩溃。它具有很好的异常处理机制。
我发现@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;
}
镇上的新国王已经到来 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);
}
我将使用为Visual Leak Detector中的泄漏内存生成堆栈跟踪的代码。不过,这仅适用于Win32。
作为仅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
可以随时调用。如果您需要转储当前状态以诊断崩溃以外的问题,这可能会有所帮助。
必读,如果您想评估小型转储的适用性:
除了上述答案之外,还介绍了如何使Debian Linux OS生成核心转储。
如果您仍然想像我一样独自使用它,可以链接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]
我忘记了GNOME的“ appport”技术,但是我对使用它并不了解。它用于生成堆栈跟踪和其他诊断以进行处理,并且可以自动归档错误。当然值得检查。