Boost C++ 库

...世界上最受推崇和专业设计的 C++ 库项目之一。 Herb SutterAndrei Alexandrescu, C++ 编码标准

PrevUpHomeNext

入门指南

如何打印当前调用堆栈
更好的断言
处理 terminate
来自任意异常的堆栈跟踪
带有堆栈跟踪的异常
启用和禁用堆栈跟踪
按指定格式保存堆栈跟踪
从指针获取函数信息
全局控制堆栈跟踪输出格式

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
[Note] 注意

默认情况下,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_ASSERTBOOST_ASSERT_MSG 将在失败时调用我们的函数 assertion_failedassertion_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
[Warning] 警告

有人可能会想编写一个信号处理程序,以在 SIGSEGV 或 abort 时打印堆栈跟踪。 不幸的是,没有跨平台的方法可以做到这一点而没有死锁的风险。 并非所有平台都提供以异步信号安全方式获取堆栈跟踪的方法。

信号处理程序通常在单独的堆栈上调用,并且在尝试获取跟踪时返回垃圾!

通用建议是避免信号处理程序! 使用特定于平台的方式来存储和解码核心文件

有关更多信息,请参见“理论异步信号安全性”

[Warning] 警告

目前,该功能仅适用于某些流行的 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)

PrevUpHomeNext