KPe*_*xEA 561 c++ crash gcc assert stack-trace
我正在使用GCC编译器在Linux上工作.当我的C++程序崩溃时,我希望它能自动生成一个堆栈跟踪.
我的程序由许多不同的用户运行,它也可以在Linux,Windows和Macintosh上运行(所有版本都使用编译gcc
).
我希望我的程序能够在崩溃时生成堆栈跟踪,并且在用户下次运行它时,它会询问他们是否可以将堆栈跟踪发送给我,以便我可以追踪问题.我可以处理向我发送信息,但我不知道如何生成跟踪字符串.有任何想法吗?
Tod*_*lin 484
对于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.
}
Run Code Online (Sandbox Code Playgroud)
编译时-g -rdynamic
会在输出中获取符号信息,glibc可以使用它来创建一个很好的堆栈跟踪:
$ gcc -g -rdynamic ./test.c -o test
Run Code Online (Sandbox Code Playgroud)
执行此操作可以获得此输出:
$ ./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]
Run Code Online (Sandbox Code Playgroud)
这显示了堆栈中每个帧的加载模块,偏移量和函数.在这里你可以看到在堆栈的顶部信号处理程序,以及libc函数之前main
除了main
,foo
,bar
,和baz
.
jhc*_*ark 119
它比"man backtrace"更容易,有一个小文档库(GNU特定的)与glibc一起分发为libSegFault.so,我相信这是由Ulrich Drepper编写的,用于支持程序catchsegv(参见"man catchsegv").
这给了我们三种可能性.而不是运行"程序-o hai":
在catchsegv中运行:
$ catchsegv program -o hai
Run Code Online (Sandbox Code Playgroud)在运行时与libSegFault链接:
$ LD_PRELOAD=/lib/libSegFault.so program -o hai
Run Code Online (Sandbox Code Playgroud)在编译时链接libSegFault:
$ gcc -g1 -lSegFault -o program program.cc
$ program -o hai
Run Code Online (Sandbox Code Playgroud)在所有3种情况下,您将获得更清晰的回溯,优化更少(gcc -O0或-O1)和调试符号(gcc -g).否则,您最终可能会得到一堆内存地址.
您还可以通过以下方式捕获更多堆栈跟踪信号:
$ export SEGFAULT_SIGNALS="all" # "all" signals
$ export SEGFAULT_SIGNALS="bus abrt" # SIGBUS and SIGABRT
Run Code Online (Sandbox Code Playgroud)
输出看起来像这样(注意底部的回溯):
*** 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]
Run Code Online (Sandbox Code Playgroud)
如果您想了解血腥细节,最好的来源是遗憾的来源:请参阅http://sourceware.org/git/?p=glibc.git;a=blob;f=debug/segfault.c及其父目录http://sourceware.org/git/?p=glibc.git;a=tree;f=debug
jsc*_*ier 118
虽然已经建议在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);
}
Run Code Online (Sandbox Code Playgroud)
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]
Run Code Online (Sandbox Code Playgroud)
在信号处理程序中调用backtrace()函数的所有危险仍然存在,不应该被忽视,但我发现我在这里描述的功能在调试崩溃时非常有用.
值得注意的是,我提供的示例是在Linux for x86上开发/测试的.我也使用uc_mcontext.arm_pc
而不是在ARM上成功实现了这个uc_mcontext.eip
.
以下是该文章的链接,其中我了解了此实现的详细信息:http: //www.linuxjournal.com/article/6391
jsc*_*ier 81
即使提供了正确的答案,描述了如何使用GNU libc backtrace()
函数1,我提供了自己的答案,描述了如何确保从信号处理程序的回溯指向故障的实际位置2,我看不到任何提及从回溯输出的demangling 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;
}
Run Code Online (Sandbox Code Playgroud)
输出(./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]
Run Code Online (Sandbox Code Playgroud)
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]
Run Code Online (Sandbox Code Playgroud)
以下内容基于我原始答案中的信号处理程序,可以替换上例中的信号处理程序,以演示如何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);
}
Run Code Online (Sandbox Code Playgroud)
Bri*_*ell 21
您没有指定您的操作系统,因此很难回答.如果您使用的是基于gnu libc的系统,则可以使用libc函数backtrace()
.
GCC还有两个可以帮助你的内置版本,但是你的架构可能会或者可能不会完全实现,而且那些是__builtin_frame_address
和__builtin_return_address
.两者都想要一个立即的整数级别(通过立即,我的意思是它不能是一个变量).如果__builtin_frame_address
给定级别非零,则应该可以安全地获取相同级别的返回地址.
小智 12
ulimit -c <value>
在unix上设置核心文件大小限制.默认情况下,核心文件大小限制为0.您可以使用以下内容查看ulimit
值ulimit -a
.
另外,如果你从gdb中运行你的程序,它将停止你的程序"分段违规"(SIGSEGV
通常当你访问一块你没有分配的内存时)或者你可以设置断点.
ddd和nemiver是gdb的前端,这使新手更容易使用它.
arr*_*sea 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;
Run Code Online (Sandbox Code Playgroud)
码:
#!/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
Run Code Online (Sandbox Code Playgroud)
baz*_*rek 11
看起来在最后一个 c++ boost 版本中出现的库中提供了你想要的东西,可能代码是多平台的。它是boost::stacktrace,您可以像在 boost sample 中一样使用它:
#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
}
Run Code Online (Sandbox Code Playgroud)
在 Linux 中你编译上面的代码:
g++ --std=c++17 file.cpp -lstdc++fs -lboost_stacktrace_backtrace -ldl -lbacktrace
Run Code Online (Sandbox Code Playgroud)
从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
Run Code Online (Sandbox Code Playgroud)
Ben*_*son 10
重要的是要注意,一旦生成核心文件,您将需要使用gdb工具来查看它.要让gdb理解你的核心文件,你必须告诉gcc使用调试符号来检测二进制文件:为此,使用-g标志进行编译:
$ g++ -g prog.cpp -o prog
Run Code Online (Sandbox Code Playgroud)
然后,你可以设置"ulimit -c unlimited"让它转储核心,或者只是在gdb中运行你的程序.我更喜欢第二种方法:
$ gdb ./prog
... gdb startup output ...
(gdb) run
... program runs and crashes ...
(gdb) where
... gdb outputs your stack trace ...
Run Code Online (Sandbox Code Playgroud)
我希望这有帮助.
Gre*_*ory 10
我一直在看这个问题.
并深深埋藏在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来获取堆栈跟踪,但是你的平台可能不支持它.
您可以使用DeathHandler - 小型C++类,它可以为您提供一切可靠的服务.
小智 9
忘记改变你的来源并使用backtrace()函数或宏来做一些黑客 - 这些只是糟糕的解决方案.
作为一个正常工作的解决方案,我建议:
这将以人类可读的方式打印程序的正确可读回溯(具有源文件名和行号).此外,这种方法可让您自由地自动化系统:使用一个简短的脚本来检查进程是否创建了核心转储,然后通过电子邮件向开发人员发送回溯,或将其记录到某些日志记录系统中.
镇上新王来了 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);
}
Run Code Online (Sandbox Code Playgroud)
ulimit -c unlimited
Run Code Online (Sandbox Code Playgroud)
是一个系统变量,它将允许在应用程序崩溃后创建核心转储.在这种情况下无限量.在同一目录中查找名为core的文件.确保在启用调试信息的情况下编译代码!
问候
看着:
男人3回溯
和:
#include <exeinfo.h>
int backtrace(void **buffer, int size);
Run Code Online (Sandbox Code Playgroud)
这些是GNU扩展.
作为仅适用于 Windows 的解决方案,您可以使用Windows Error Reporting获得等效于堆栈跟踪(包含更多、更多信息)。只需几个注册表项,就可以将其设置为收集用户模式转储:
从 Windows Server 2008 和带有 Service Pack 1 (SP1) 的 Windows Vista 开始,可以配置 Windows 错误报告 (WER),以便在用户模式应用程序崩溃后在本地收集和存储完整的用户模式转储。[...]
默认情况下不启用此功能。启用该功能需要管理员权限。要启用和配置该功能,请使用HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps项下的以下注册表值。
您可以从具有所需权限的安装程序设置注册表项。
与在客户端生成堆栈跟踪相比,创建用户模式转储具有以下优点:
请注意,WER 只能由应用程序崩溃(即系统由于未处理的异常而终止进程)触发。MiniDumpWriteDump
可以随时调用。如果您需要转储当前状态以诊断崩溃以外的问题,这可能会有所帮助。
必读,如果要评估mini dumps的适用性:
归档时间: |
|
查看次数: |
332623 次 |
最近记录: |