我有一个程序会在某处引发未捕获的异常。我得到的只是一个异常被抛出的报告,并且没有关于它被抛出的位置的信息。编译为包含调试符号的程序不通知我在我的代码中生成异常的位置似乎不合逻辑。
如果没有在 gdb 中设置“catch throw”并为每个抛出的异常调用回溯,有什么方法可以告诉我的异常来自哪里?
原文由 alexgolec 发布,翻译遵循 CC BY-SA 4.0 许可协议
我有一个程序会在某处引发未捕获的异常。我得到的只是一个异常被抛出的报告,并且没有关于它被抛出的位置的信息。编译为包含调试符号的程序不通知我在我的代码中生成异常的位置似乎不合逻辑。
如果没有在 gdb 中设置“catch throw”并为每个抛出的异常调用回溯,有什么方法可以告诉我的异常来自哪里?
原文由 alexgolec 发布,翻译遵循 CC BY-SA 4.0 许可协议
您可以将代码中的主要紧凑位置标记为 noexcept
来定位异常,然后使用 libunwind (只需将 -lunwind
添加到链接器参数)(使用 clang++ 3.6
测试) :
demagle.hpp:
#pragma once
char const *
get_demangled_name(char const * const symbol) noexcept;
demangle.cpp:
#include "demangle.hpp"
#include <memory>
#include <cstdlib>
#include <cxxabi.h>
namespace
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
std::unique_ptr< char, decltype(std::free) & > demangled_name{nullptr, std::free};
#pragma clang diagnostic pop
}
char const *
get_demangled_name(char const * const symbol) noexcept
{
if (!symbol) {
return "<null>";
}
int status = -4;
demangled_name.reset(abi::__cxa_demangle(symbol, demangled_name.release(), nullptr, &status));
return ((status == 0) ? demangled_name.get() : symbol);
}
回溯.hpp:
#pragma once
#include <ostream>
void
backtrace(std::ostream & _out) noexcept;
回溯.cpp:
#include "backtrace.hpp"
#include <iostream>
#include <iomanip>
#include <limits>
#include <ostream>
#include <cstdint>
#define UNW_LOCAL_ONLY
#include <libunwind.h>
namespace
{
void
print_reg(std::ostream & _out, unw_word_t reg) noexcept
{
constexpr std::size_t address_width = std::numeric_limits< std::uintptr_t >::digits / 4;
_out << "0x" << std::setfill('0') << std::setw(address_width) << reg;
}
char symbol[1024];
}
void
backtrace(std::ostream & _out) noexcept
{
unw_cursor_t cursor;
unw_context_t context;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
_out << std::hex << std::uppercase;
while (0 < unw_step(&cursor)) {
unw_word_t ip = 0;
unw_get_reg(&cursor, UNW_REG_IP, &ip);
if (ip == 0) {
break;
}
unw_word_t sp = 0;
unw_get_reg(&cursor, UNW_REG_SP, &sp);
print_reg(_out, ip);
_out << ": (SP:";
print_reg(_out, sp);
_out << ") ";
unw_word_t offset = 0;
if (unw_get_proc_name(&cursor, symbol, sizeof(symbol), &offset) == 0) {
_out << "(" << get_demangled_name(symbol) << " + 0x" << offset << ")\n\n";
} else {
_out << "-- error: unable to obtain symbol name for this frame\n\n";
}
}
_out << std::flush;
}
backtrace_on_terminate.hpp:
#include "demangle.hpp"
#include "backtrace.hpp"
#include <iostream>
#include <type_traits>
#include <exception>
#include <memory>
#include <typeinfo>
#include <cstdlib>
#include <cxxabi.h>
namespace
{
[[noreturn]]
void
backtrace_on_terminate() noexcept;
static_assert(std::is_same< std::terminate_handler, decltype(&backtrace_on_terminate) >{});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wglobal-constructors"
#pragma clang diagnostic ignored "-Wexit-time-destructors"
std::unique_ptr< std::remove_pointer_t< std::terminate_handler >, decltype(std::set_terminate) & > terminate_handler{std::set_terminate(backtrace_on_terminate), std::set_terminate};
#pragma clang diagnostic pop
[[noreturn]]
void
backtrace_on_terminate() noexcept
{
std::set_terminate(terminate_handler.release()); // to avoid infinite looping if any
backtrace(std::clog);
if (std::exception_ptr ep = std::current_exception()) {
try {
std::rethrow_exception(ep);
} catch (std::exception const & e) {
std::clog << "backtrace: unhandled exception std::exception:what(): " << e.what() << std::endl;
} catch (...) {
if (std::type_info * et = abi::__cxa_current_exception_type()) {
std::clog << "backtrace: unhandled exception type: " << get_demangled_name(et->name()) << std::endl;
} else {
std::clog << "backtrace: unhandled unknown exception" << std::endl;
}
}
}
std::_Exit(EXIT_FAILURE); // change to desired return code
}
}
关于这个问题有 一篇很好的文章。
原文由 Tomilov Anatoliy 发布,翻译遵循 CC BY-SA 3.0 许可协议
3 回答2k 阅读✓ 已解决
2 回答3.9k 阅读✓ 已解决
2 回答3.2k 阅读✓ 已解决
1 回答3.2k 阅读✓ 已解决
1 回答2.7k 阅读✓ 已解决
3 回答3.4k 阅读
1 回答1.6k 阅读✓ 已解决
以下是一些 可能 对调试您的问题有用的信息
如果未捕获异常,则自动调用特殊库函数
std::terminate()
。 Terminate 实际上是一个指向函数的指针,默认值是标准 C 库函数std::abort()
。如果未对未捕获的异常进行清理† ,则实际上 可能 有助于调试此问题,因为没有调用析构函数。†在调用
std::terminate()
之前是否展开堆栈是实现定义的。调用
abort()
通常可用于生成可分析以确定异常原因的核心转储。确保通过ulimit -c unlimited
(Linux) 启用核心转储。您可以使用
std::set_terminate()
安装自己的terminate()
功能。您应该能够在 gdb 中的终止函数上设置断点。您 可能 能够从terminate()
函数生成堆栈回溯,并且此回溯 可能 有助于识别异常的位置。Bruce Eckel 的 Thinking in C++, 2nd Ed 中对 未捕获的异常 进行了简短的讨论,这也可能会有所帮助。
由于
terminate()
默认调用abort()
(这将导致SIGABRT
默认信号),您 可以 设置一个SIGABRT
然后 从信号处理程序中打印堆栈回溯。此回溯 可能 有助于识别异常的位置。注意: 我说 可能 是因为 C++ 通过使用语言结构将错误处理和报告代码与普通代码分开来支持非本地错误处理。 catch 块可以并且通常位于与抛出点不同的函数/方法中。在评论中还向我指出(感谢 Dan ),在调用
terminate()
之前是否展开堆栈是由实现定义的。更新: 我将一个名为的 Linux 测试程序放在一起,该程序在
terminate()
通过set_terminate()
设置的函数和另一个在SIGABRT
的信号处理程序中生成回溯。两个回溯都正确显示了未处理异常的位置。更新 2: 感谢有关 在 terminate 中捕获未捕获异常 的博客文章,我学到了一些新技巧;包括在终止处理程序中重新抛出未捕获的异常。需要注意的是,自定义终止处理程序中的空
throw
语句可与 GCC 一起使用,并且不是可移植的解决方案。代码:
输出: