boost::stacktrace::stacktrace 包含用于处理调用堆栈/回溯/堆栈跟踪的方法。这是一个小例子
#include <boost/stacktrace.hpp> // ... somewhere inside the `bar(int)` function that is called recursively: std::cout << boost::stacktrace::stacktrace();
在那个例子中
boost::stacktrace:: 是包含所有用于处理堆栈跟踪的类和函数的命名空间stacktrace() 是默认构造函数调用;构造函数将当前的函数调用序列存储在 stacktrace 类中。上面的代码会输出类似这样的内容
0# bar(int) at /path/to/source/file.cpp:70 1# bar(int) at /path/to/source/file.cpp:70 2# bar(int) at /path/to/source/file.cpp:70 3# bar(int) at /path/to/source/file.cpp:70 4# main at /path/to/main.cpp:93 5# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6 6# _start
![]() |
注意 |
|---|---|
默认情况下,Stacktrace 库在解码堆栈跟踪的方法方面非常保守。如果你的输出不像上面例子中那么花哨,请参阅 "配置和构建" 部分,以启用库的高级功能。 |
通常情况下,断言提供的信息不足以定位问题。例如,你可能会在越界访问时看到以下消息
../../../boost/array.hpp:123: T& boost::array<T, N>::operator[](boost::array<T, N>::size_type) [with T = int; long unsigned int N = 5ul]: Assertion '(i < N)&&("out of range")' failed. Aborted (core dumped)
在没有调试器的情况下,这不足以定位问题。在真实世界的例子中可能有成千上万行代码,以及上百个可能发生该断言的地方。让我们尝试改进断言,使其更具信息量
// BOOST_ENABLE_ASSERT_DEBUG_HANDLER is defined for the whole project #include <stdexcept> // std::logic_error #include <iostream> // std::cerr #include <boost/stacktrace.hpp> namespace boost { inline void assertion_failed_msg(char const* expr, char const* msg, char const* function, char const* /*file*/, long /*line*/) { std::cerr << "Expression '" << expr << "' is false in function '" << function << "': " << (msg ? msg : "<...>") << ".\n" << "Backtrace:\n" << boost::stacktrace::stacktrace() << '\n'; std::abort(); } inline void assertion_failed(char const* expr, char const* function, char const* file, long line) { ::boost::assertion_failed_msg(expr, 0 /*nullptr*/, function, file, line); } } // namespace boost
我们为整个项目定义了 BOOST_ENABLE_ASSERT_DEBUG_HANDLER 宏。现在,所有 BOOST_ASSERT 和 BOOST_ASSERT_MSG 在失败时都会调用我们的函数 assertion_failed 和 assertion_failed_msg。在 assertion_failed_msg 中,我们输出了断言宏提供的以及 boost::stacktrace::stacktrace 提供的信息
Expression 'i < N' is false in function 'T& boost::array<T, N>::operator[](boost::array<T, N>::size_type) [with T = int; long unsigned int N = 5ul; boost::array<T, N>::reference = int&; boost::array<T, N>::size_type = long unsigned int]': out of range. Backtrace: 0# boost::assertion_failed_msg(char const*, char const*, char const*, char const*, long) at ../example/assert_handler.cpp:39 1# boost::array<int, 5ul>::operator[](unsigned long) at ../../../boost/array.hpp:124 2# bar(int) at ../example/assert_handler.cpp:17 3# foo(int) at ../example/assert_handler.cpp:25 4# bar(int) at ../example/assert_handler.cpp:17 5# foo(int) at ../example/assert_handler.cpp:25 6# main at ../example/assert_handler.cpp:54 7# 0x00007F991FD69F45 in /lib/x86_64-linux-gnu/libc.so.6 8# 0x0000000000401139
现在我们知道了导致断言的步骤,可以在没有调试器的情况下找到错误。
std::terminate 调用有时会发生在程序中。程序员通常希望在这些事件中获得尽可能多的信息,因此拥有堆栈跟踪可以显著改善调试和修复。
下面是如何编写一个转储堆栈跟踪的终止处理程序
#include <cstdlib> // std::abort #include <exception> // std::set_terminate #include <iostream> // std::cerr #include <boost/stacktrace.hpp> void my_terminate_handler() { try { std::cerr << boost::stacktrace::stacktrace(); } catch (...) {} std::abort(); }
下面是如何注册它
std::set_terminate(&my_terminate_handler);
现在,在调用 std::terminate 时,我们会得到以下输出
Previous run crashed: 0# my_terminate_handler(int) at ../example/terminate_handler.cpp:37 1# __cxxabiv1::__terminate(void (*)()) at ../../../../src/libstdc++-v3/libsupc++/eh_terminate.cc:48 2# 0x00007F3CE65E5901 in /usr/lib/x86_64-linux-gnu/libstdc++.so.6 3# bar(int) at ../example/terminate_handler.cpp:18 4# foo(int) at ../example/terminate_handler.cpp:22 5# bar(int) at ../example/terminate_handler.cpp:14 6# foo(int) at ../example/terminate_handler.cpp:22 7# main at ../example/terminate_handler.cpp:84 8# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6 9# 0x0000000000402209
![]() |
警告 |
|---|---|
|
有人可能会想编写一个信号处理程序,在 信号处理程序通常在单独的堆栈上调用,并且在尝试获取跟踪时会返回垃圾! 通用建议是 避免信号处理程序!使用 特定平台的 核心文件 存储和解码方法。 有关更多信息,请参阅 "理论异步信号安全"。 |
![]() |
警告 |
|---|---|
目前,此功能仅适用于 POSIX 系统上具有 libbacktrace 的一些流行 C++ 运行时以及 Windows。通过运行一些测试来确保你的平台受支持。 |
该库提供了一种从异常中获取堆栈跟踪的方法,就好像堆栈跟踪是在异常被抛出时捕获的。即使异常是从第三方二进制库抛出的,也能正常工作。
链接 boost_stacktrace_from_exception 库(或只是 LD_PRELOAD 它!)并调用 boost::stacktrace::stacktrace::from_current_exception() 来获取跟踪信息
#include <iostream> #include <stdexcept> #include <string_view> #include <boost/stacktrace.hpp> void foo(std::string_view key); void bar(std::string_view key); int main() { try { foo("test1"); bar("test2"); } catch (const std::exception& exc) { boost::stacktrace::stacktrace trace = boost::stacktrace::stacktrace::from_current_exception(); // <--- std::cerr << "Caught exception: " << exc.what() << ", trace:\n" << trace; } }
上述示例的输出可能如下
Caught exception: std::map::at, trace: 0# get_data_from_config(std::string_view) at /home/axolm/basic.cpp:600 1# bar(std::string_view) at /home/axolm/basic.cpp:6 2# main at /home/axolm/basic.cpp:17
通过上述技术,开发人员无需调试器即可定位抛出异常的源文件和函数。这对于在容器中进行测试(github CI、其他 CI)特别有用,因为开发人员无法直接访问测试环境,并且重现问题很复杂。
请注意,链接 boost_stacktrace_from_exception 可能会增加应用程序的内存消耗,因为异常现在会额外存储跟踪信息。
在运行时,boost::stacktrace::this_thread::set_capture_stacktraces_at_throw() 允许禁用/启用在抛出时捕获和存储异常中的跟踪信息。
要禁用 boost_stacktrace_from_exception 库,请在构建时设置 boost.stacktrace.from_exception=off 选项,例如 ./b2 boost.stacktrace.from_exception=off。
你可以通过将堆栈跟踪嵌入异常来提供更多信息。有很多方法可以做到这一点,下面是如何使用 Boost.Exception 来实现
boost::error_info 类型定义,它保存堆栈跟踪#include <boost/stacktrace.hpp> #include <boost/exception/all.hpp> typedef boost::error_info<struct tag_stacktrace, boost::stacktrace::stacktrace> traced;
template <class E> void throw_with_trace(const E& e) { throw boost::enable_error_info(e) << traced(boost::stacktrace::stacktrace()); }
throw_with_trace(E); 而不是简单的 throw E;if (i >= 4) throw_with_trace(std::out_of_range("'i' must be less than 4 in oops()")); if (i <= 0) throw_with_trace(std::logic_error("'i' must be greater than zero in oops()"));
try { foo(5); // testing assert handler } catch (const std::exception& e) { std::cerr << e.what() << '\n'; const boost::stacktrace::stacktrace* st = boost::get_error_info<traced>(e); if (st) { std::cerr << *st << '\n'; } }
上面的代码会输出
'i' must not be greater than zero in oops() 0# void throw_with_trace<std::logic_error>(std::logic_error const&) at ../example/throwing_st.cpp:22 1# oops(int) at ../example/throwing_st.cpp:38 2# bar(int) at ../example/throwing_st.cpp:54 3# foo(int) at ../example/throwing_st.cpp:59 4# bar(int) at ../example/throwing_st.cpp:49 5# foo(int) at ../example/throwing_st.cpp:59 6# main at ../example/throwing_st.cpp:76 7# 0x00007FAC113BEF45 in /lib/x86_64-linux-gnu/libc.so.6 8# 0x0000000000402ED9
有时会出现需要轻松启用/禁用整个项目堆栈跟踪的需求。这可以很容易地实现。
只需为整个项目定义 BOOST_STACKTRACE_LINK。现在你可以通过链接不同的库来启用/禁用堆栈跟踪
boost_stacktrace_noop 来禁用回溯boost_stacktrace_* 库有关更多信息,请参阅 "配置和构建" 部分。
boost::stacktrace::stacktrace 提供了对堆栈跟踪的各个 帧 的访问,因此你可以按照自己的格式保存堆栈跟踪信息。考虑以下保存每个帧的函数地址的例子
#include <boost/stacktrace.hpp> #include <iostream> // std::cout namespace bs = boost::stacktrace; void dump_compact(const bs::stacktrace& st) { for (bs::frame frame: st) { std::cout << frame.address() << ','; } std::cout << std::endl; }
上面的代码会输出
0x7fbcfd17f6b5,0x400d4a,0x400d61,0x400d61,0x400d61,0x400d61,0x400d77,0x400cbf,0x400dc0,0x7fbcfc82d830,0x400a79,
boost::stacktrace::frame 提供了关于函数的信息。你可以从函数指针构造该类并在运行时获取函数名
#include <signal.h> // ::signal #include <boost/stacktrace/frame.hpp> #include <iostream> // std::cerr #include <cstdlib> // std::exit void print_signal_handler_and_exit() { typedef void(*function_t)(int); function_t old_signal_function = ::signal(SIGSEGV, SIG_DFL); boost::stacktrace::frame f(old_signal_function); std::cout << f << std::endl; std::exit(0); }
上面的代码会输出
my_signal_handler(int) at boost/libs/stacktrace/example/debug_function.cpp:21
你可以通过定义 Boost.Config 中的宏 BOOST_USER_CONFIG 指向一个类似下面的文件来覆盖默认堆栈跟踪输出运算符的行为
#ifndef USER_CONFIG_HPP #define USER_CONFIG_HPP #include <boost/stacktrace/stacktrace_fwd.hpp> #include <iosfwd> namespace boost { namespace stacktrace { template <class CharT, class TraitsT, class Allocator> std::basic_ostream<CharT, TraitsT>& do_stream_st(std::basic_ostream<CharT, TraitsT>& os, const basic_stacktrace<Allocator>& bt); template <class CharT, class TraitsT> std::basic_ostream<CharT, TraitsT>& operator<<(std::basic_ostream<CharT, TraitsT>& os, const stacktrace& bt) { return do_stream_st(os, bt); } }} // namespace boost::stacktrace #endif // USER_CONFIG_HPP
do_stream_st 的实现可以是以下内容
namespace boost { namespace stacktrace { template <class CharT, class TraitsT, class Allocator> std::basic_ostream<CharT, TraitsT>& do_stream_st(std::basic_ostream<CharT, TraitsT>& os, const basic_stacktrace<Allocator>& bt) { const std::streamsize w = os.width(); const std::size_t frames = bt.size(); for (std::size_t i = 0; i < frames; ++i) { os.width(2); os << i; os.width(w); os << "# "; os << bt[i].name(); os << '\n'; } return os; } }} // namespace boost::stacktrace
上面的代码会输出
Terminate called: 0# bar(int) 1# foo(int) 2# bar(int) 3# foo(int)