Boost C++ 库

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

入门 - Boost C++ 函数库
PrevUpHomeNext

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 调用有时会发生在程序中。程序员通常希望在此类事件中获得尽可能多的信息,因此拥有堆栈跟踪可以显著改善调试和修复。

这是编写一个转储堆栈跟踪的终止处理程序的方法

#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] 警告

目前,此功能仅适用于 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 可能会增加应用程序的内存消耗,因为异常现在还额外存储了跟踪信息。

在运行时,switch 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 提供对堆栈跟踪的各个 frame 的访问,因此您可以以自己的格式保存堆栈跟踪信息。考虑这个示例,它只保存每个 frame 的函数地址。

#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