C ++显示异常时的堆栈跟踪

C++ display stack trace on exception

如果抛出异常,我想有一种方法向用户报告堆栈跟踪。最好的方法是什么?它需要大量的额外代码吗?

回答问题:

如果可能的话,我希望它是便携式的。我希望弹出信息,以便用户可以复制堆栈跟踪,并在出现错误时将其发送给我。


这取决于哪个站台。

在GCC上,这是非常琐碎的,请参阅本文了解更多详细信息。

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

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


Andrew Grant的答案对获取抛出函数的堆栈跟踪没有帮助,至少对于gcc没有帮助,因为一个throw语句本身不会保存当前堆栈跟踪,而catch处理程序在该点将不再具有对堆栈跟踪的访问权。

使用gcc解决这个问题的唯一方法是确保在throw指令的点上生成一个堆栈跟踪,并将其保存到异常对象中。

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

更新日期:2017年7月11日:有关一些有用的代码,请查看cahit beyaz的答案,该答案指向http://stacktrace.sourceforge.net-我还没有使用过它,但看起来很有希望。


如果使用的是boost 1.65或更高版本,则可以使用boost::stacktrace:

1
2
3
4
#include <boost/stacktrace.hpp>

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


Unix:回溯

麦克:回溯

窗口:CaptureBacktrace


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

使用std::nested_exceptionstd::throw_with_nested

这不会让你放松,但在我看来,这是最好的选择。这里和这里的stackoverflow介绍了如何在不需要调试程序或繁琐的日志记录的情况下,通过编写一个适当的异常处理程序来获取代码中异常的回溯,该异常处理程序将重新引发嵌套的异常。

由于可以对任何派生的异常类执行此操作,因此可以向此类回溯添加大量信息!你也可以看看我在Github上的mwe,这里的回溯看起来像这样:

1
2
3
4
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"


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


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


由于在进入catch块时堆栈已经展开,所以在我的例子中,解决方案是不捕获某些异常,这些异常随后会导致sigabrt。在sigabrt i的信号处理程序中,fork()和execl()可以是gdb(在调试版本中)或google breakpads stackwalk(在发布版本中)。另外,我尝试只使用信号处理程序安全函数。

GDB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
static const char BACKTRACE_START[] ="<2>--- backtrace of entire stack ---
"
;
static const char BACKTRACE_STOP[] ="<2>--- backtrace finished ---
"
;

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

小型垃圾堆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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,我还必须添加以下内容:

1
2
3
4
5
6
7
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附加到崩溃的进程(也就是说,"及时"调试)


在带有g++的Linux上,检查这个lib

https://sourceforge.net/projects/libcsdbg

它为你做所有的工作


在Windows上,查看bugtrap。它不再在原来的链接,但它仍然在代码项目上可用。


我有一个类似的问题,虽然我喜欢可移植性,但我只需要GCC支持。在gcc中,execinfo.h和backtrace调用是可用的。为了解出函数名,宾曼先生有一段很好的代码。为了在异常上转储回溯,我创建了一个异常,它在构造函数中打印回溯。如果我希望它能在库中抛出异常的情况下工作,那么可能需要重新构建/链接,以便使用回溯异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/******************************************
#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
"
, e.what() );
    }
}

用GCC 4.84编译并运行此代码会产生一个非常好的未被忽略的C++函数名称的回溯:

1
2
3
4
5
6
7
8
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]


Poppy不仅可以收集堆栈跟踪,还可以收集参数值、局部变量等,所有这些都会导致崩溃。


以下代码在引发异常后立即停止执行。您需要设置一个Windows异常处理程序和一个终止处理程序。我用Mingw 32位测试了这个。

1
2
3
4
5
6
7
8
9
10
11
12
13
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


CPP工具Ex_diag-简单、多平台、最少的资源使用、简单和灵活的AT跟踪。