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

时间:2009-03-27 22:42:46

标签: c++ exception exception-handling stack-trace

如果抛出异常,我想有办法向用户报告堆栈跟踪。做这个的最好方式是什么?是否需要大量的额外代码?

回答问题:

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

16 个答案:

答案 0 :(得分:69)

这取决于哪个平台。

在GCC上,它非常简单,详见this post

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

您必须找出将此功能集成到您的应用中的最佳方式,但您需要编写的代码量应该很少。

答案 1 :(得分:45)

Andrew Grant的回答 帮助获取投掷函数的堆栈跟踪,至少不是GCC,因为throw语句确实如此不能保存当前的堆栈跟踪,并且catch处理程序将不再能够访问堆栈跟踪。

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

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

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

答案 2 :(得分:34)

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

#include <boost/stacktrace.hpp>

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

答案 3 :(得分:13)

Unix:backtrace

Mac:backtrace

Windows:CaptureBackTrace

答案 4 :(得分:4)

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

答案 5 :(得分:4)

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

答案 6 :(得分:4)

我想添加标准库选项(即跨平台)如何生成异常回溯, C ++ 11

使用std::nested_exceptionstd::throw_with_nested

这不会给你一个堆叠放松,但在我看来是下一个最好的事情。 它在StackOverflow herehere中进行了描述,您可以通过简单地编写一个正确的代码来在代码中获得异常的回溯,而无需调试器或繁琐的日志记录异常处理程序,它将重新抛出嵌套异常。

由于您可以对任何派生的异常类执行此操作,因此可以向此类回溯添加大量信息! 您还可以查看我的MWE on 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"

答案 7 :(得分:3)

在Windows上,查看BugTrap。它不再是原始链接,但仍可在CodeProject上使用。

答案 8 :(得分:3)

使用g ++在linux上的

检查这个lib

https://sourceforge.net/projects/libcsdbg

它为你完成所有工作

答案 9 :(得分:3)

由于进入catch块时堆栈已经解卷,因此我的解决方案是不捕获某些异常,然后导致SIGABRT。然后在SIGABRT的信号处理程序中,我使用fork()和execl()要么是gdb(在调试版本中),要么是Google Breakpads stackwalk(在发行版本中)。另外,我尝试仅使用信号处理程序安全功能。

GDB:

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

编辑:要使其适用于防摔垫,我还必须添加以下内容:

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

来源:How to get a stack trace for C++ using gcc with line number information?Is it possible to attach gdb to a crashed process (a.k.a "just-in-time" debugging)

答案 10 :(得分:2)

Poppy不仅可以收集堆栈跟踪,还可以收集参数值,局部变量等 - 导致崩溃的一切。

答案 11 :(得分:2)

我有类似的问题,虽然我喜欢可移植性,但我只需要gcc支持。在gcc中,execinfo.h和backtrace调用可用。为了解码函数名,Bingmann先生有a nice piece of code.为了转储异常的回溯,我创建了一个在构造函数中打印回溯的异常。如果我希望这可以使用库中抛出的异常,则可能需要重建/链接以便使用回溯异常。

/******************************************
#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]

答案 12 :(得分:1)

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

答案 13 :(得分:1)

以下代码在抛出异常后立即停止执行。您需要设置一个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

答案 14 :(得分:1)

如果您使用的是C ++,并且不想/不能使用Boost,则可以使用以下代码[link to the original site]打印带有反斜线名称的回溯。

注意,此解决方案特定于Linux。它使用GNU的libc函数backtrace()/ backtrace_symbols()(来自execinfo.h)来获取回溯,然后使用__cxa_demangle()(来自cxxabi.h)来取消回溯符号名称。

// 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!

答案 15 :(得分:0)

OSX的工作示例(目前已在Catalina 10.15上进行了测试)。显然不能移植到linux / windows。可能对某人有用。

在“ Mew-exception”字符串中,您可以使用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;
}