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
调用有时会在程序中发生。 程序员通常希望在此类事件中获得尽可能多的信息,因此拥有堆栈跟踪可以显着改善调试和修复。
以下是如何编写一个转储堆栈跟踪的 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
![]() |
警告 |
---|---|
有人可能会想编写一个信号处理程序,以在 信号处理程序通常在单独的堆栈上调用,并且在尝试获取跟踪时返回垃圾! 通用建议是避免信号处理程序! 使用特定于平台的方式来存储和解码核心文件。 有关更多信息,请参见“理论异步信号安全性”。 |
![]() |
警告 |
---|---|
目前,该功能仅适用于某些流行的 C++ 运行时,对于具有 libbacktrace 的 POSIX 系统和 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
typedef,它保存堆栈跟踪#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)