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

回答问题:

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


当前回答

Andrew Grant的回答并不能帮助获得抛出函数的堆栈跟踪,至少在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

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

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

使用std::nested_exception和std::throw_with_nested

这不会给你一个堆栈unwind,但在我看来是次好的事情。 StackOverflow在这里和这里都有描述,如何在不需要调试器或繁琐的日志记录的情况下,通过简单地编写一个适当的异常处理程序重新抛出嵌套异常,就可以在代码中对异常进行回溯。

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

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"

由于在进入捕获块时堆栈已经展开,因此在我的案例中,解决方案是不捕获某些异常,这些异常随后会导致SIGABRT。在SIGABRT的信号处理程序中,我然后fork()和execl() gdb(在调试构建中)或谷歌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;
}

编辑:为了让它为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附加到崩溃的进程(也就是“即时”调试)

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