c++在异常时显示堆栈跟踪

我希望有一种方法在抛出异常时向用户报告堆栈跟踪。最好的方法是什么?它是否需要大量额外的代码?

回答问题:

如果可能的话,我希望它是便携的。我想让信息弹出,这样用户就可以复制堆栈跟踪,并在出现错误时通过电子邮件发送给我。

298150 次浏览

这取决于哪个平台。

在GCC上,这是非常琐碎的,更多细节请参见这篇文章

在MSVC上,你可以使用StackWalker库来处理Windows所需的所有底层API调用。

你必须找出将此功能集成到应用程序中的最佳方法,但你需要编写的代码量应该是最小的。

在Windows上,检查小炮艇。它不再在原来的链接中,但在CodeProject中仍然可用。

AFAIK libunwind非常便携,到目前为止我还没有找到更容易使用的东西。

cp -tool ex_diag -重量轻,多平台,资源使用最少,简单灵活的跟踪。

Unix: 回溯

麦克:回溯

Windows: CaptureBackTrace

在使用g++的Linux上检查这个库

https://sourceforge.net/projects/libcsdbg

它为你做了所有的工作

罂粟不仅可以收集堆栈跟踪,还可以收集参数值、局部变量等——所有导致崩溃的东西。

安德鲁·格兰特的回答使<强> < / >强不帮助获得函数的堆栈跟踪,至少在GCC中不是这样,因为throw语句本身不会保存当前堆栈跟踪,并且catch处理程序在那时将不再访问堆栈跟踪。

解决这个问题的唯一方法(使用GCC)是确保在抛出指令的点生成堆栈跟踪,并将其与异常对象一起保存。

当然,此方法要求抛出异常的每个代码都使用特定的exception类。

2017年7月11日更新:对于一些有用的代码,看看Cahit beyaz的答案,它指向http://stacktrace.sourceforge.net -我还没有使用它,但它看起来很有前途。

下面的代码在抛出异常后立即停止执行。您需要设置一个windows_exception_handler和一个终止处理程序。我在MinGW 32bits中进行了测试。

void beforeCrash(void);


static const bool SET_TERMINATE = std::set_terminate(beforeCrash);


void beforeCrash() {
__asm("int3");
}


int main(int argc, char *argv[])
{
SetUnhandledExceptionFilter(windows_exception_handler);
...
}
检查windows_exception_handler函数的以下代码: http://www.codedisqus.com/0ziVPgVPUk/exception-handling-and-stacktrace-under-windows-mingwgcc.html < / p >

我也有类似的问题,尽管我喜欢可移植性,但我只需要gcc支持。在gcc中,execinfo.h和回溯调用是可用的。为了在异常上转储回溯,我创建了一个异常,在构造函数中打印回溯。如果我期望这与库中抛出的异常一起工作,则可能需要重新构建/链接,以便使用回溯异常。

/******************************************
#Makefile with flags for printing backtrace with function names
# compile with symbols for backtrace
CXXFLAGS=-g
# add symbols to dynamic symbol table for backtrace
LDFLAGS=-rdynamic
turducken: turducken.cc
******************************************/


#include <cstdio>
#include <stdexcept>
#include <execinfo.h>
#include "stacktrace.h" /* https://panthema.net/2008/0901-stacktrace-demangled/ */


// simple exception that prints backtrace when constructed
class btoverflow_error: public std::overflow_error
{
public:
btoverflow_error( const std::string& arg ) :
std::overflow_error( arg )
{
print_stacktrace();
};
};




void chicken(void)
{
throw btoverflow_error( "too big" );
}


void duck(void)
{
chicken();
}


void turkey(void)
{
duck();
}


int main( int argc, char *argv[])
{
try
{
turkey();
}
catch( btoverflow_error e)
{
printf( "caught exception: %s\n", e.what() );
}
}

使用gcc 4.8.4编译和运行这个函数会产生一个反向跟踪,其中包含了非常整洁的c++函数名:

stack trace:
./turducken : btoverflow_error::btoverflow_error(std::string const&)+0x43
./turducken : chicken()+0x48
./turducken : duck()+0x9
./turducken : turkey()+0x9
./turducken : main()+0x15
/lib/x86_64-linux-gnu/libc.so.6 : __libc_start_main()+0xf5
./turducken() [0x401629]

我推荐http://stacktrace.sourceforge.net/项目。它支持Windows, Mac OS和Linux

如果你使用的是Boost 1.65或更高版本,你可以使用boost::加:

#include <boost/stacktrace.hpp>


// ... somewhere inside the bar(int) function that is called recursively:
std::cout << boost::stacktrace::stacktrace();

我想添加一个标准库选项(即跨平台)如何生成异常回溯,这已经在c++ 11中可用:

使用std::nested_exceptionstd::throw_with_nested

这不会给你一个堆栈unwind,但在我看来是次好的事情。 StackOverflow 在这里在这里中描述了如何在代码中对异常进行回溯,而不需要调试器或繁琐的日志记录,只需编写一个适当的异常处理程序,即可重新抛出嵌套异常 因为你可以用任何派生的异常类来做这件事,你可以在这样的回溯中添加很多信息! 你也可以看一下我的MWE在GitHub上,其中的回溯是这样的:

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

由于堆栈在进入catch块时已经展开,在我的情况下的解决方案是无法捕捉某些异常,然后导致SIGABRT。在SIGABRT的信号处理程序中,我然后fork()和execl() gdb(在调试构建中)或谷歌breakpads stackwalk(在发布构建中)。此外,我尽量只使用信号处理程序安全的函数。

广东发展银行:

static const char BACKTRACE_START[] = "<2>--- backtrace of entire stack ---\n";
static const char BACKTRACE_STOP[] = "<2>--- backtrace finished ---\n";


static char *ltrim(char *s)
{
while (' ' == *s) {
s++;
}
return s;
}


void Backtracer::print()
{
int child_pid = ::fork();
if (child_pid == 0) {
// redirect stdout to stderr
::dup2(2, 1);


// create buffer for parent pid (2+16+1 spaces to allow up to a 64 bit hex parent pid)
char pid_buf[32];
const char* stem = "                   ";
const char* s = stem;
char* d = &pid_buf[0];
while (static_cast<bool>(*s))
{
*d++ = *s++;
}
*d-- = '\0';
char* hexppid = d;


// write parent pid to buffer and prefix with 0x
int ppid = getppid();
while (ppid != 0) {
*hexppid = ((ppid & 0xF) + '0');
if(*hexppid > '9') {
*hexppid += 'a' - '0' - 10;
}
--hexppid;
ppid >>= 4;
}
*hexppid-- = 'x';
*hexppid = '0';


// invoke GDB
char name_buf[512];
name_buf[::readlink("/proc/self/exe", &name_buf[0], 511)] = 0;
ssize_t r = ::write(STDERR_FILENO, &BACKTRACE_START[0], sizeof(BACKTRACE_START));
(void)r;
::execl("/usr/bin/gdb",
"/usr/bin/gdb", "--batch", "-n", "-ex", "thread apply all bt full", "-ex", "quit",
&name_buf[0], ltrim(&pid_buf[0]), nullptr);
::exit(1); // if GDB failed to start
} else if (child_pid == -1) {
::exit(1); // if forking failed
} else {
// make it work for non root users
if (0 != getuid()) {
::prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
}
::waitpid(child_pid, nullptr, 0);
ssize_t r = ::write(STDERR_FILENO, &BACKTRACE_STOP[0], sizeof(BACKTRACE_STOP));
(void)r;
}
}

minidump_stackwalk:

static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded)
{
int child_pid = ::fork();
if (child_pid == 0) {
::dup2(open("/dev/null", O_WRONLY), 2); // ignore verbose output on stderr
ssize_t r = ::write(STDOUT_FILENO, &MINIDUMP_STACKWALK_START[0], sizeof(MINIDUMP_STACKWALK_START));
(void)r;
::execl("/usr/bin/minidump_stackwalk", "/usr/bin/minidump_stackwalk", descriptor.path(), "/usr/share/breakpad-syms", nullptr);
::exit(1); // if minidump_stackwalk failed to start
} else if (child_pid == -1) {
::exit(1); // if forking failed
} else {
::waitpid(child_pid, nullptr, 0);
ssize_t r = ::write(STDOUT_FILENO, &MINIDUMP_STACKWALK_STOP[0], sizeof(MINIDUMP_STACKWALK_STOP));
(void)r;
}
::remove(descriptor.path()); // this is not signal safe anymore but should still work
return succeeded;
}

编辑:为了让它为breakpad工作,我还必须添加这个:

std::set_terminate([]()
{
ssize_t r = ::write(STDERR_FILENO, EXCEPTION, sizeof(EXCEPTION));
(void)r;
google_breakpad::ExceptionHandler::WriteMinidump(std::string("/tmp"), dumpCallback, NULL);
exit(1); // avoid creating a second dump by not calling std::abort
});

来源:如何获得堆栈跟踪的c++使用gcc与行号信息?是否可以将gdb附加到一个崩溃的进程(即“及时”进程);调试)

如果你正在使用c++并且不想要/不能使用Boost,你可以使用下面的代码[链接至原址]打印带有要求名称的反向跟踪。

注意,这个解决方案是特定于Linux的。它使用GNU的libc函数backtrace()/backtrace_symbols() (from execinfo.h)来获取反向跟踪,然后使用__cxa_demangle() (from cxxabih)来获取反向跟踪符号名称。

// stacktrace.h (c) 2008, Timo Bingmann from http://idlebox.net/
// published under the WTFPL v2.0


#ifndef _STACKTRACE_H_
#define _STACKTRACE_H_


#include <stdio.h>
#include <stdlib.h>
#include <execinfo.h>
#include <cxxabi.h>


/** Print a demangled stack backtrace of the caller function to FILE* out. */
static inline void print_stacktrace(FILE *out = stderr, unsigned int max_frames = 63)
{
fprintf(out, "stack trace:\n");


// storage array for stack trace address data
void* addrlist[max_frames+1];


// retrieve current stack addresses
int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));


if (addrlen == 0) {
fprintf(out, "  <empty, possibly corrupt>\n");
return;
}


// resolve addresses into strings containing "filename(function+address)",
// this array must be free()-ed
char** symbollist = backtrace_symbols(addrlist, addrlen);


// allocate string which will be filled with the demangled function name
size_t funcnamesize = 256;
char* funcname = (char*)malloc(funcnamesize);


// iterate over the returned symbol lines. skip the first, it is the
// address of this function.
for (int i = 1; i < addrlen; i++)
{
char *begin_name = 0, *begin_offset = 0, *end_offset = 0;


// find parentheses and +address offset surrounding the mangled name:
// ./module(function+0x15c) [0x8048a6d]
for (char *p = symbollist[i]; *p; ++p)
{
if (*p == '(')
begin_name = p;
else if (*p == '+')
begin_offset = p;
else if (*p == ')' && begin_offset) {
end_offset = p;
break;
}
}


if (begin_name && begin_offset && end_offset
&& begin_name < begin_offset)
{
*begin_name++ = '\0';
*begin_offset++ = '\0';
*end_offset = '\0';


// mangled name is now in [begin_name, begin_offset) and caller
// offset in [begin_offset, end_offset). now apply
// __cxa_demangle():


int status;
char* ret = abi::__cxa_demangle(begin_name,
funcname, &funcnamesize, &status);
if (status == 0) {
funcname = ret; // use possibly realloc()-ed string
fprintf(out, "  %s : %s+%s\n",
symbollist[i], funcname, begin_offset);
}
else {
// demangling failed. Output function name as a C function with
// no arguments.
fprintf(out, "  %s : %s()+%s\n",
symbollist[i], begin_name, begin_offset);
}
}
else
{
// couldn't parse the line? print the whole line.
fprintf(out, "  %s\n", symbollist[i]);
}
}


free(funcname);
free(symbollist);
}


#endif // _STACKTRACE_H_

HTH !

OSX的一个工作示例(现在在Catalina 10.15上测试)。显然不能移植到linux/windows。也许它会对某人有用。

在“喵例外”;字符串,可以使用backtrace和/或backtrace_symbols函数

#include <stdexcept>
#include <typeinfo>
#include <dlfcn.h>


extern "C" void __cxa_throw(void *thrown_object, std::type_info *tinfo, void (*dest)(void *));
static void (*__cxa_throw_orig)(void *thrown_object, std::type_info *tinfo, void (*dest)(void *));
extern "C" void luna_cxa_throw(void *thrown_object, std::type_info *tinfo, void (*dest)(void *))
{
printf("Mew-exception you can catch your backtrace here!");
__cxa_throw_orig(thrown_object, tinfo, dest);
}




//__attribute__ ((used))
//__attribute__ ((section ("__DATA,__interpose")))
static struct replace_pair_t {
void *replacement, *replacee;
} replace_pair = { (void*)luna_cxa_throw, (void*)__cxa_throw };


extern "C" const struct mach_header __dso_handle;
extern "C" void dyld_dynamic_interpose(const struct mach_header*,
const replace_pair_t replacements[],
size_t count);


int fn()
{
int a = 10; ++a;
throw std::runtime_error("Mew!");
}


int main(int argc, const char * argv[]) {
__cxa_throw_orig = (void (*)(void *thrown_object, std::type_info *tinfo, void (*dest)(void *)))dlsym(RTLD_DEFAULT, "__cxa_throw");


dyld_dynamic_interpose(&__dso_handle, &replace_pair, 1);


fn();
return 0;
}