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

rlb*_*ond 183 c++ exception-handling exception stack-trace

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

回答问题:

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

And*_*ant 69

这取决于哪个平台.

在海湾合作委员会这是非常微不足道的,请看这篇文章了解更多细节.

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

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

  • 您链接的帖子主要指向从段错误生成跟踪,但提问者特别提到异常,这是完全不同的野兽. (64认同)
  • 我同意@Shep - 这个答案并没有真正帮助在GCC上获得抛出代码的堆栈跟踪.请参阅我的答案以获得可能的解决方 (6认同)
  • 这个答案有误导性。该链接指向特定于“Linux”而不是“gcc”的答案。 (3认同)

Tho*_*ann 52

安德鲁·格兰特的回答并不能帮助得到的堆栈跟踪投掷功能,至少不会与海湾合作委员会,因为throw语句不保存自身当前的堆栈跟踪和捕捉处理程序将无法访问的堆栈跟踪那一点.

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

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

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

  • 不幸的是,链接已失效。你能提供一些其他的吗? (2认同)
  • 而且archive.org也不知道.该死的.好吧,程序应该是明确的:抛出一个自定义类'对象,它在抛出时记录堆栈跟踪. (2认同)
  • 令人遗憾的是答案是"不,你不能从C++异常中获得堆栈跟踪",唯一的选择是抛出自己的类,在构造时生成堆栈跟踪.如果你被困在使用像C++ std :: library的任何部分这样的东西,那你就不走运了.对不起,很难成为你. (2认同)

vas*_*sek 36

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

#include <boost/stacktrace.hpp>

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

  • [boost docs](http://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.handle_terminates_aborts_and_seg)不仅解释了捕获堆栈跟踪,还解释了如何执行它用于例外和断言.好东西. (5认同)
  • 此 stacktrace() 是否会打印 GettingStarted 指南中给出的源文件和行号? (2认同)

bob*_*obo 13

Unix: 回溯

Mac: 回溯

Windows:CaptureBackTrace

  • Mac 回溯链接已失效。 (2认同)

GPM*_*ler 8

我想添加一个标准的库选项(即跨平台)来生成异常回溯,C ++ 11已经提供了该选项:

使用std::nested_exceptionstd::throw_with_nested

这不会给您带来很多麻烦,但我认为下一件好事。它在此处此处的 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"
Run Code Online (Sandbox Code Playgroud)

  • 我已经有一段时间没有对 C++ 如何破坏如此简单的概念感到震惊了。感谢您的提醒。 (9认同)

Nic*_*sky 6

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


Bl0*_*und 6

由于进入 catch 块时堆栈已经展开,因此我的解决方案是不捕获某些异常,从而导致 SIGABRT。在 SIGABRT 的信号处理程序中,我然后 fork() 和 execl() gdb(在调试版本中)或 Google 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

小型转储_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;
}
Run Code Online (Sandbox Code Playgroud)

编辑:为了使其适用于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
});
Run Code Online (Sandbox Code Playgroud)

来源:如何使用 gcc 和行号信息获取 C++ 的堆栈跟踪?是否可以将 gdb 附加到崩溃的进程(又名“即时”调试)


小智 6

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

请注意,此解决方案特定于 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_
Run Code Online (Sandbox Code Playgroud)

哼!


cah*_*yaz 5

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

  • 在其主页上,我看到“抛出 stack_runtime_error”。我是否正确地推断出该库仅适用于从该类派生的异常,而不适用于“std::exception”或来自第三方库的异常? (5认同)

归档时间:

查看次数:

194634 次

最近记录:

6 年,1 月 前