【问题标题】:Stack trace after catching exception捕获异常后的堆栈跟踪
【发布时间】:2016-12-04 19:21:33
【问题描述】:

当然,我也希望在使用调试器引发异常后查看堆栈跟踪。通常,当从未捕获到异常时,调试器会在收到SIGABRT 后停止程序,我可以看到整个堆栈跟踪并识别异常的原因。

但是捕获异常后如何诊断异常原因呢?

#include <iostream>
#include <stdexcept>

void foo() {
    throw std::runtime_error("An error message");
}

int main() {
    try {
        foo();
    } catch (const std::exception &e) {
        std::cerr << e.what(); // add breakpoint here
    }        
    return 0;
}

在 catch 部分添加断点自然会在捕获异常后停止程序,但是堆栈跟踪不包含 foo() 调用,因此无法诊断异常的原因。

请注意,该示例非常简单。对于复杂和嵌套的调用,try 部分中某处发生异常的信息实际上是无用的。而且我不能简单地避免捕获异常,因为如果我没有捕获它,那么它就会被我正在使用的框架捕获并且堆栈跟踪会丢失。

【问题讨论】:

  • 这是特定于调试器的。对于 Visual Studio 的 GDB 类型 catch throw,它默认启用。否则,请阅读调试器附带的文档。
  • @Drop 我主要对 GDB 感兴趣。不像在 IDE 中放置断点那么简单,但基本上可以。它停在每个 throw 上,所以不是 100% 我想要的,但它非常接近,除非有人有更好的解决方案,否则我可以接受这个作为答案。
  • 还有catch catch 和其他catchpoints (src)。看起来这是你需要的。虽然不包括带有回溯的部分。

标签: c++ exception stack-trace


【解决方案1】:

但是如何在捕获异常后诊断异常原因?

你不能。堆栈已经在捕获点展开。如果您想诊断抛出的原因,您需要在抛出点将它们包含在异常中(如果您愿意,这可能包括完整的堆栈跟踪,但这将取决于系统)。

您可能需要考虑的其他事项:

a) 在 API 调用的调用点(不能修改调用中的代码)添加更多的前置条件检查

b) 在您控制的函数和方法中添加前置条件检查(断言或抛出)。如果性能有问题,您可以在发布版本中删除它们。

c) 在抛出异常的消息中包含抛出的全部原因。

【讨论】:

    【解决方案2】:

    我自己从未使用过它,但可能值得一看:

    https://www.codeproject.com/articles/11132/walking-the-callstack

    尤其是段落

    显示异常的调用栈

    使用此 StackWalker,您还可以在 异常处理程序。您只需要编写一个过滤器功能 堆栈遍历。

    【讨论】:

    • 那是 windows 和 MSVC 特定的答案。我不会接受它,因为我无法在 Linux 上检查它,但可能对阅读此主题的其他人有用。
    【解决方案3】:

    如果您正在处理自己的异常,那么我的libexcept 库可能对您有用。这个库提供了一些类。基类能够在实例化时收集堆栈跟踪。 99% 的时间,这为您提供了调试问题所需的所有信息。如果您只知道异常发生在哪里,而不知道它是如何被调用的,那么知道如何解决问题可能会更加困难。 (是的!因为同一个函数可能会以 20 种不同的方式被调用,所以仅仅确切地知道发生了哪个异常通常是不够的。)

    在一个只有当非常糟糕事情发生时才抛出异常的项目中,这甚至可以保留在你的发布版本中。然后,您可以在客户端日志中记录堆栈跟踪,并使调试远程/终端客户端崩溃变得容易数百倍。

    我有一个binary (pre-compiled version) for Ubuntu 16.04 on Launchpad(在您的 apt 列表中安装 PPA,然后安装 libexcept_1.0.5.0~xenial_amd64.deb 或任何当前版本。)

    要使用,派生自所提供的两个类之一:

    class my_exception : public libexcept::logic_exception_t
    {
       ...
    };
    

    在您的catch() 中,您可以使用get_stack_trace() 打印堆栈跟踪以获取字符串向量。在我们的例子中,我们实际上将它发送到我们的日志中。

    try
    {
        ...
        throw my_exception("what happened?!");
        ...
    }
    catch(my_exception const & e)
    {
        stack_trace_t const & stack(e.get_stack_trace());
        for(auto s : stack)
        {
            SNAP_LOG_ERROR(*s);
        }
    }
    

    显然,如果您要在许多地方记录堆栈跟踪,您可能会想要一个函数。

    但是,对于您不生成的异常,您实际上无能为力。但是,如果您想编写一个包装器,您可以使用自己的异常重新抛出,以便至少缩小异常的来源。

    假设您正在使用第 3 方 library 并期望 library 偶尔抛出一次......

    try
    {
        library::func();   // may throw
    }
    catch(library::exception const & e)
    {
        throw my_exception(e.what());   // converted exception
    }
    

    现在你至少有一个来自这个低级捕获的堆栈跟踪。

    【讨论】:

      【解决方案4】:

      这个问题比较老了,但我想补充一点,可以在标准 C++11 中进行这样的跟踪:

      使用std::nested_exceptionstd::throw_with_nested

      它在 StackOverflow herehere 上进行了描述,您可以在代码中获取异常的回溯,而无需调试器或繁琐的日志记录,只需编写适当的异常将重新抛出嵌套异常的处理程序。

      由于您可以对任何派生的异常类执行此操作,因此您可以向此类回溯添加大量信息! 在生成回溯的相应catch-statement 中添加断点应该可以正常工作。 你也可以看看我的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"
      

      或我的"trace" library,与this answer 中给出的类似,但跨平台。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-22
        • 2013-02-21
        • 1970-01-01
        • 2010-12-20
        • 1970-01-01
        • 2011-01-05
        • 2010-11-23
        • 1970-01-01
        相关资源
        最近更新 更多