├── test ├── cmake_subdir_test │ ├── main.cpp │ └── CMakeLists.txt ├── test_trivial.cpp ├── cmake_install_test │ ├── main.cpp │ └── CMakeLists.txt ├── CMakeLists.txt ├── torture.cpp ├── test_from_exception_none.cpp ├── thread_safety_checking.cpp ├── test_noop.cpp ├── test_impl.hpp ├── test_void_ptr_cast.cpp ├── test_num_conv.cpp ├── test_impl.cpp ├── appveyor.yml ├── test_from_exception.cpp └── test.cpp ├── meta └── libraries.json ├── include └── boost │ ├── stacktrace │ ├── detail │ │ ├── pop_options.h │ │ ├── collect_noop.ipp │ │ ├── try_dec_convert.hpp │ │ ├── collect_msvc.ipp │ │ ├── frame_noop.ipp │ │ ├── safe_dump_noop.ipp │ │ ├── push_options.h │ │ ├── to_dec_array.hpp │ │ ├── unwind_base_impls.hpp │ │ ├── void_ptr_cast.hpp │ │ ├── to_hex_array.hpp │ │ ├── safe_dump_posix.ipp │ │ ├── safe_dump_win.ipp │ │ ├── addr_base_msvc.hpp │ │ ├── addr_base.hpp │ │ ├── frame_unwind.ipp │ │ ├── collect_unwind.ipp │ │ ├── frame_decl.hpp │ │ ├── location_from_symbol.hpp │ │ ├── libbacktrace_impls.hpp │ │ ├── addr2line_impls.hpp │ │ └── frame_msvc.ipp │ ├── stacktrace_fwd.hpp │ ├── this_thread.hpp │ ├── frame.hpp │ ├── safe_dump_to.hpp │ └── stacktrace.hpp │ └── stacktrace.hpp ├── src ├── noop.cpp ├── windbg.cpp ├── basic.cpp ├── addr2line.cpp ├── backtrace.cpp ├── windbg_cached.cpp ├── exception_headers.h └── from_exception.cpp ├── .github └── dependabot.yml ├── boost-stacktrace-features.jam ├── index.html ├── example ├── debug_function.cpp ├── user_config.cpp ├── trace_addresses.cpp ├── assert_handler.cpp ├── user_config.hpp ├── throwing_st.cpp └── terminate_handler.cpp ├── README.md ├── doc └── Jamfile.v2 ├── .gitattributes └── CMakeLists.txt /test/cmake_subdir_test/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Peter Dimov 2 | // Copyright 2022-2025 Antony Polukhin 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // https://www.boost.org/LICENSE_1_0.txt 5 | 6 | #include 7 | #include 8 | 9 | int main() 10 | { 11 | std::cout << boost::stacktrace::stacktrace() << std::endl; 12 | } 13 | -------------------------------------------------------------------------------- /meta/libraries.json: -------------------------------------------------------------------------------- 1 | { 2 | "key": "stacktrace", 3 | "name": "Stacktrace", 4 | "authors": [ 5 | "Antony Polukhin" 6 | ], 7 | "maintainers": [ 8 | "Antony Polukhin " 9 | ], 10 | "description": "Gather, store, copy and print backtraces.", 11 | "std": [ "c++23" ], 12 | "category": [ 13 | "System", "Correctness" 14 | ], 15 | "cxxstd": "11" 16 | } 17 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/pop_options.h: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | // No include guards! Intentionally. 8 | 9 | #ifdef BOOST_STACKTRACE_FUNCTION 10 | # undef BOOST_STACKTRACE_FUNCTION 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /src/noop.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2020. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #define BOOST_STACKTRACE_INTERNAL_BUILD_LIBS 8 | #define BOOST_STACKTRACE_LINK 9 | #define BOOST_STACKTRACE_USE_NOOP 10 | #include 11 | #include 12 | -------------------------------------------------------------------------------- /test/test_trivial.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2022-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | // See https://github.com/boostorg/stacktrace/issues/116 8 | 9 | #include 10 | 11 | #include 12 | 13 | int main() { 14 | std::cout << boost::stacktrace::stacktrace() << std::endl; 15 | } 16 | -------------------------------------------------------------------------------- /src/windbg.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2020. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef _GNU_SOURCE 8 | # define _GNU_SOURCE 9 | #endif 10 | 11 | #define BOOST_STACKTRACE_INTERNAL_BUILD_LIBS 12 | #define BOOST_STACKTRACE_LINK 13 | #include 14 | #include 15 | -------------------------------------------------------------------------------- /src/basic.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2020. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #define BOOST_STACKTRACE_INTERNAL_BUILD_LIBS 8 | #define BOOST_STACKTRACE_LINK 9 | 10 | #ifndef _GNU_SOURCE 11 | # define _GNU_SOURCE 12 | #endif 13 | 14 | #include 15 | #include 16 | -------------------------------------------------------------------------------- /src/addr2line.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2020. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #define BOOST_STACKTRACE_INTERNAL_BUILD_LIBS 8 | #define BOOST_STACKTRACE_USE_ADDR2LINE 9 | #define BOOST_STACKTRACE_LINK 10 | 11 | #ifndef _GNU_SOURCE 12 | # define _GNU_SOURCE 13 | #endif 14 | 15 | #include 16 | #include 17 | -------------------------------------------------------------------------------- /src/backtrace.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2020. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #define BOOST_STACKTRACE_INTERNAL_BUILD_LIBS 8 | #define BOOST_STACKTRACE_USE_BACKTRACE 9 | #define BOOST_STACKTRACE_LINK 10 | 11 | #ifndef _GNU_SOURCE 12 | # define _GNU_SOURCE 13 | #endif 14 | 15 | #include 16 | #include 17 | -------------------------------------------------------------------------------- /src/windbg_cached.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2020. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef _GNU_SOURCE 8 | # define _GNU_SOURCE 9 | #endif 10 | 11 | #define BOOST_STACKTRACE_INTERNAL_BUILD_LIBS 12 | #define BOOST_STACKTRACE_LINK 13 | #define BOOST_STACKTRACE_USE_WINDBG_CACHED 14 | #include 15 | #include 16 | -------------------------------------------------------------------------------- /test/cmake_install_test/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Peter Dimov 2 | // Copyright 2022-2025 Antony Polukhin 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // https://www.boost.org/LICENSE_1_0.txt 5 | 6 | #include 7 | #include 8 | 9 | int main() 10 | { 11 | std::cout << boost::stacktrace::stacktrace() << std::endl; 12 | try { 13 | throw 42; 14 | } catch (...) { 15 | std::cout << "From current excption:\n" << boost::stacktrace::stacktrace::from_current_exception() << std::endl; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /include/boost/stacktrace.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_HPP 8 | #define BOOST_STACKTRACE_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #endif // BOOST_STACKTRACE_HPP 21 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2020 Peter Dimov 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | include(BoostTest OPTIONAL RESULT_VARIABLE HAVE_BOOST_TEST) 6 | 7 | if(NOT HAVE_BOOST_TEST) 8 | return() 9 | endif() 10 | 11 | boost_test(TYPE run SOURCES test.cpp test_impl.cpp LINK_LIBRARIES Boost::stacktrace Boost::core) 12 | boost_test(TYPE run SOURCES test_noop.cpp test_impl.cpp LINK_LIBRARIES Boost::stacktrace_noop Boost::core) 13 | 14 | boost_test(TYPE run SOURCES test_trivial.cpp LINK_LIBRARIES Boost::stacktrace Boost::core) 15 | -------------------------------------------------------------------------------- /test/cmake_install_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2021 Peter Dimov 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # https://www.boost.org/LICENSE_1_0.txt 4 | 5 | cmake_minimum_required(VERSION 3.8...4.20) 6 | 7 | project(cmake_install_test LANGUAGES CXX) 8 | 9 | find_package( 10 | Boost 11 | REQUIRED stacktrace ${BOOST_STACKTRACE_IMPL_BACKEND} stacktrace_from_exception 12 | CONFIG 13 | ) 14 | add_executable(main main.cpp) 15 | target_link_libraries(main Boost::${BOOST_STACKTRACE_IMPL_BACKEND}) 16 | 17 | add_executable(main_from_exception main.cpp) 18 | target_link_libraries(main_from_exception Boost::stacktrace_from_exception Boost::${BOOST_STACKTRACE_IMPL_BACKEND}) 19 | 20 | enable_testing() 21 | add_test(main main) 22 | add_test(main_from_exception main_from_exception) 23 | -------------------------------------------------------------------------------- /test/torture.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2018. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | 8 | // This file tests for memory leaks. Some of the backtrace implementations 9 | // consume memory for internal needs and incorrect usage of those implementations 10 | // could lead to segfaults. Sanitizers do not detect such misuse, but this 11 | // test and `top` does. 12 | 13 | 14 | #include 15 | #include 16 | 17 | #include "test_impl.hpp" 18 | 19 | int main() { 20 | int result = 0; 21 | for (unsigned i = 0; i < 10000000; ++i) { 22 | result += make_some_stacktrace1()[0].source_line(); 23 | } 24 | 25 | std::cerr << "OK\nLines count " << result; 26 | } 27 | -------------------------------------------------------------------------------- /boost-stacktrace-features.jam: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016-2024, Antony Polukhin. 2 | # 3 | # Use, modification and distribution is subject to the Boost Software License, 4 | # Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at 5 | # http://www.boost.org/LICENSE_1_0.txt) 6 | # 7 | import feature ; 8 | 9 | feature.feature boost.stacktrace.noop : on off : optional propagated ; 10 | feature.feature boost.stacktrace.backtrace : on off : optional propagated ; 11 | feature.feature boost.stacktrace.addr2line : on off : optional propagated ; 12 | feature.feature boost.stacktrace.basic : on off : optional propagated ; 13 | feature.feature boost.stacktrace.windbg : on off : optional propagated ; 14 | feature.feature boost.stacktrace.windbg_cached : on off : optional propagated ; 15 | feature.feature boost.stacktrace.from_exception : on off : optional propagated ; 16 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/collect_noop.ipp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_COLLECT_NOOP_IPP 8 | #define BOOST_STACKTRACE_DETAIL_COLLECT_NOOP_IPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | namespace boost { namespace stacktrace { namespace detail { 18 | 19 | std::size_t this_thread_frames::collect(native_frame_ptr_t* /*out_frames*/, std::size_t /*max_frames_count*/, std::size_t /*skip*/) noexcept { 20 | return 0; 21 | } 22 | 23 | }}} // namespace boost::stacktrace::detail 24 | 25 | #endif // BOOST_STACKTRACE_DETAIL_COLLECT_NOOP_IPP 26 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | 15 | 16 | Boost.Stacktrace 17 | 27 | 28 | 29 |

30 | Automatic redirection failed, please go to 31 | ../../doc/html/stacktrace.html 32 |

33 |

34 | © Antony Polukhin, 2014-2024 35 |

36 | 37 | 38 | -------------------------------------------------------------------------------- /example/debug_function.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | //[getting_started_debug_function 8 | #include // ::signal 9 | #include 10 | #include // std::cerr 11 | #include // std::exit 12 | 13 | void print_signal_handler_and_exit() { 14 | typedef void(*function_t)(int); 15 | 16 | function_t old_signal_function = ::signal(SIGSEGV, SIG_DFL); 17 | boost::stacktrace::frame f(old_signal_function); 18 | std::cout << f << std::endl; 19 | std::exit(0); 20 | } 21 | //] 22 | 23 | 24 | void my_signal_handler(int /*signum*/) { 25 | std::exit(1); 26 | } 27 | 28 | int main() { 29 | ::signal(SIGSEGV, &my_signal_handler); 30 | print_signal_handler_and_exit(); 31 | } 32 | 33 | 34 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/try_dec_convert.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_TRY_DEC_CONVERT_HPP 8 | #define BOOST_STACKTRACE_DETAIL_TRY_DEC_CONVERT_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | namespace boost { namespace stacktrace { namespace detail { 18 | 19 | // We do not use boost::lexical_cast in this function to reduce module dependencies 20 | inline bool try_dec_convert(const char* s, std::size_t& res) noexcept { 21 | char* end_ptr = 0; 22 | res = std::strtoul(s, &end_ptr, 10); 23 | return *end_ptr == '\0'; 24 | } 25 | 26 | 27 | }}} // namespace boost::stacktrace::detail 28 | 29 | #endif // BOOST_STACKTRACE_DETAIL_TRY_DEC_CONVERT_HPP 30 | -------------------------------------------------------------------------------- /include/boost/stacktrace/stacktrace_fwd.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_STACKTRACE_FWD_HPP 8 | #define BOOST_STACKTRACE_STACKTRACE_FWD_HPP 9 | 10 | #include 11 | #include 12 | 13 | /// @file stacktrace_fwd.hpp This header contains only forward declarations of 14 | /// boost::stacktrace::frame, boost::stacktrace::basic_stacktrace, boost::stacktrace::stacktrace 15 | /// and does not include any other Boost headers. 16 | 17 | /// @cond 18 | namespace boost { namespace stacktrace { 19 | 20 | class frame; 21 | template > class basic_stacktrace; 22 | typedef basic_stacktrace<> stacktrace; 23 | 24 | }} // namespace boost::stacktrace 25 | /// @endcond 26 | 27 | 28 | #endif // BOOST_STACKTRACE_STACKTRACE_FWD_HPP 29 | -------------------------------------------------------------------------------- /example/user_config.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #define BOOST_USER_CONFIG 8 | 9 | #include 10 | #include // std::set_terminate, std::abort 11 | #include 12 | #include // std::cerr 13 | BOOST_NOINLINE void foo(int i); 14 | BOOST_NOINLINE void bar(int i); 15 | 16 | BOOST_NOINLINE void bar(int i) { 17 | boost::array a = {{-1, -231, -123, -23, -32}}; 18 | if (i >= 0) { 19 | foo(a[i]); 20 | } else { 21 | std::cerr << "Terminate called:\n" << boost::stacktrace::stacktrace() << '\n'; 22 | std::exit(0); 23 | } 24 | } 25 | 26 | BOOST_NOINLINE void foo(int i) { 27 | bar(--i); 28 | } 29 | 30 | int main() { 31 | foo(5); 32 | 33 | return 2; 34 | } 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /test/cmake_subdir_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2021 Peter Dimov 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt 4 | 5 | cmake_minimum_required(VERSION 3.5...3.20) 6 | 7 | project(cmake_subdir_test LANGUAGES CXX) 8 | 9 | # Put boost_stacktrace_*.dll in the same directory as main.exe 10 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}) 11 | 12 | add_subdirectory(../.. boostorg/stacktrace) 13 | 14 | # boostdep --brief stacktrace 15 | 16 | set(deps 17 | 18 | # Primary dependencies 19 | 20 | assert 21 | config 22 | container_hash 23 | core 24 | predef 25 | winapi 26 | 27 | # Secondary dependencies 28 | 29 | describe 30 | mp11 31 | static_assert 32 | throw_exception 33 | 34 | ) 35 | 36 | foreach(dep IN LISTS deps) 37 | 38 | add_subdirectory(../../../${dep} boostorg/${dep}) 39 | 40 | endforeach() 41 | 42 | add_executable(main main.cpp) 43 | target_link_libraries(main Boost::stacktrace) 44 | 45 | enable_testing() 46 | add_test(main main) 47 | -------------------------------------------------------------------------------- /test/test_from_exception_none.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2023-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #include 10 | 11 | using boost::stacktrace::stacktrace; 12 | 13 | 14 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_throw_1(const char* msg) { 15 | std::string new_msg{msg}; 16 | throw std::runtime_error(new_msg); 17 | } 18 | 19 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_no_trace_from_exception() { 20 | try { 21 | in_test_throw_1("testing basic"); 22 | } catch (const std::exception&) { 23 | auto trace = stacktrace::from_current_exception(); 24 | BOOST_TEST(!trace); 25 | } 26 | } 27 | 28 | int main() { 29 | boost::stacktrace::this_thread::set_capture_stacktraces_at_throw(false); 30 | BOOST_TEST(!boost::stacktrace::this_thread::get_capture_stacktraces_at_throw()); 31 | test_no_trace_from_exception(); 32 | 33 | return boost::report_errors(); 34 | } 35 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/collect_msvc.ipp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_COLLECT_MSVC_IPP 8 | #define BOOST_STACKTRACE_DETAIL_COLLECT_MSVC_IPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | #include 18 | 19 | namespace boost { namespace stacktrace { namespace detail { 20 | 21 | std::size_t this_thread_frames::collect(native_frame_ptr_t* out_frames, std::size_t max_frames_count, std::size_t skip) noexcept { 22 | return boost::winapi::RtlCaptureStackBackTrace( 23 | static_cast(skip), 24 | static_cast(max_frames_count), 25 | const_cast(out_frames), 26 | 0 27 | ); 28 | } 29 | 30 | 31 | }}} // namespace boost::stacktrace 32 | 33 | #endif // BOOST_STACKTRACE_DETAIL_COLLECT_MSVC_IPP 34 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/frame_noop.ipp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_FRAME_NOOP_IPP 8 | #define BOOST_STACKTRACE_DETAIL_FRAME_NOOP_IPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | namespace boost { namespace stacktrace { namespace detail { 18 | 19 | std::string to_string(const frame* /*frames*/, std::size_t /*count*/) { 20 | return std::string(); 21 | } 22 | 23 | } // namespace detail 24 | 25 | std::string frame::name() const { 26 | return std::string(); 27 | } 28 | 29 | std::string frame::source_file() const { 30 | return std::string(); 31 | } 32 | 33 | std::size_t frame::source_line() const { 34 | return 0; 35 | } 36 | 37 | std::string to_string(const frame& /*f*/) { 38 | return std::string(); 39 | } 40 | 41 | 42 | }} // namespace boost::stacktrace 43 | 44 | #endif // BOOST_STACKTRACE_DETAIL_FRAME_NOOP_IPP 45 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/safe_dump_noop.ipp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_SAFE_DUMP_NOOP_IPP 8 | #define BOOST_STACKTRACE_DETAIL_SAFE_DUMP_NOOP_IPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | namespace boost { namespace stacktrace { namespace detail { 18 | 19 | 20 | #if defined(BOOST_WINDOWS) 21 | std::size_t dump(void* /*fd*/, const native_frame_ptr_t* /*frames*/, std::size_t /*frames_count*/) noexcept { 22 | return 0; 23 | } 24 | #else 25 | std::size_t dump(int /*fd*/, const native_frame_ptr_t* /*frames*/, std::size_t /*frames_count*/) noexcept { 26 | return 0; 27 | } 28 | #endif 29 | 30 | 31 | std::size_t dump(const char* /*file*/, const native_frame_ptr_t* /*frames*/, std::size_t /*frames_count*/) noexcept { 32 | return 0; 33 | } 34 | 35 | }}} // namespace boost::stacktrace::detail 36 | 37 | #endif // BOOST_STACKTRACE_DETAIL_SAFE_DUMP_NOOP_IPP 38 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/push_options.h: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | // No include guards! Intentionally. 8 | 9 | // Link or header only 10 | #if !defined(BOOST_STACKTRACE_LINK) && defined(BOOST_STACKTRACE_DYN_LINK) 11 | # define BOOST_STACKTRACE_LINK 12 | #endif 13 | 14 | #if !defined(BOOST_STACKTRACE_LINK) && defined(BOOST_STACKTRACE_STATIC_LINK) 15 | # define BOOST_STACKTRACE_LINK 16 | #endif 17 | 18 | #if defined(BOOST_STACKTRACE_LINK) && !defined(BOOST_STACKTRACE_DYN_LINK) && !defined(BOOST_STACKTRACE_STATIC_LINK) && defined(BOOST_ALL_DYN_LINK) 19 | # define BOOST_STACKTRACE_DYN_LINK 20 | #endif 21 | 22 | #ifdef BOOST_STACKTRACE_LINK 23 | # if defined(BOOST_STACKTRACE_DYN_LINK) 24 | # ifdef BOOST_STACKTRACE_INTERNAL_BUILD_LIBS 25 | # define BOOST_STACKTRACE_FUNCTION BOOST_SYMBOL_EXPORT 26 | # else 27 | # define BOOST_STACKTRACE_FUNCTION BOOST_SYMBOL_IMPORT 28 | # endif 29 | # else 30 | # define BOOST_STACKTRACE_FUNCTION 31 | # endif 32 | #elif !defined(BOOST_STACKTRACE_DOXYGEN_INVOKED) 33 | # define BOOST_STACKTRACE_FUNCTION inline 34 | #endif 35 | 36 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/to_dec_array.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_TO_DEC_ARRAY_HPP 8 | #define BOOST_STACKTRACE_DETAIL_TO_DEC_ARRAY_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | #include // std::size_t 17 | 18 | namespace boost { namespace stacktrace { namespace detail { 19 | 20 | // We do not use boost::lexical_cast in this function to reduce module dependencies 21 | inline std::array to_dec_array(std::size_t value) noexcept { 22 | std::array ret; 23 | if (!value) { 24 | ret[0] = '0'; 25 | ret[1] = '\0'; 26 | return ret; 27 | } 28 | 29 | std::size_t digits = 0; 30 | for (std::size_t value_copy = value; value_copy; value_copy /= 10) { 31 | ++ digits; 32 | } 33 | 34 | for (std::size_t i = 1; i <= digits; ++i) { 35 | ret[digits - i] = static_cast('0' + (value % 10)); 36 | value /= 10; 37 | } 38 | 39 | ret[digits] = '\0'; 40 | 41 | return ret; 42 | } 43 | 44 | 45 | }}} // namespace boost::stacktrace::detail 46 | 47 | #endif // BOOST_STACKTRACE_DETAIL_TO_DEC_ARRAY_HPP 48 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/unwind_base_impls.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_UNWIND_BASE_IMPLS_HPP 8 | #define BOOST_STACKTRACE_DETAIL_UNWIND_BASE_IMPLS_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | namespace boost { namespace stacktrace { namespace detail { 18 | 19 | struct to_string_using_nothing { 20 | std::string res; 21 | 22 | void prepare_function_name(const void* addr) { 23 | res = boost::stacktrace::frame(addr).name(); 24 | } 25 | 26 | bool prepare_source_location(const void* /*addr*/) const noexcept { 27 | return false; 28 | } 29 | }; 30 | 31 | template class to_string_impl_base; 32 | typedef to_string_impl_base to_string_impl; 33 | 34 | inline std::string name_impl(const void* /*addr*/) { 35 | return std::string(); 36 | } 37 | 38 | } // namespace detail 39 | 40 | std::string frame::source_file() const { 41 | return std::string(); 42 | } 43 | 44 | std::size_t frame::source_line() const { 45 | return 0; 46 | } 47 | 48 | }} // namespace boost::stacktrace 49 | 50 | #endif // BOOST_STACKTRACE_DETAIL_UNWIND_BASE_IMPLS_HPP 51 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/void_ptr_cast.hpp: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Renato Tegon Forti, Antony Polukhin. 2 | // Copyright Antony Polukhin, 2015-2025. 3 | // 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt 6 | // or copy at http://www.boost.org/LICENSE_1_0.txt) 7 | 8 | #ifndef BOOST_STACKTRACE_DETAIL_VOID_PTR_CAST_HPP 9 | #define BOOST_STACKTRACE_DETAIL_VOID_PTR_CAST_HPP 10 | 11 | #include 12 | #ifdef BOOST_HAS_PRAGMA_ONCE 13 | # pragma once 14 | #endif 15 | 16 | #include 17 | 18 | #if defined(__GNUC__) && defined(__GNUC_MINOR__) && (__GNUC__ * 100 + __GNUC_MINOR__ > 301) 19 | # pragma GCC system_header 20 | #endif 21 | 22 | namespace boost { namespace stacktrace { namespace detail { 23 | 24 | // GCC warns when reinterpret_cast between function pointer and object pointer occur. 25 | // This functionsuppress the warnings and ensures that such casts are safe. 26 | template 27 | To void_ptr_cast(From* v) noexcept { 28 | static_assert( 29 | std::is_pointer::value, 30 | "`void_ptr_cast` function must be used only for casting to or from void pointers." 31 | ); 32 | 33 | static_assert( 34 | sizeof(From*) == sizeof(To), 35 | "Pointer to function and pointer to object differ in size on your platform." 36 | ); 37 | 38 | return reinterpret_cast(v); 39 | } 40 | 41 | 42 | }}} // boost::stacktrace::detail 43 | 44 | #endif // BOOST_STACKTRACE_DETAIL_VOID_PTR_CAST_HPP 45 | 46 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/to_hex_array.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_TO_HEX_ARRAY_HPP 8 | #define BOOST_STACKTRACE_DETAIL_TO_HEX_ARRAY_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | #include 17 | 18 | namespace boost { namespace stacktrace { namespace detail { 19 | 20 | BOOST_STATIC_CONSTEXPR char to_hex_array_bytes[] = "0123456789ABCDEF"; 21 | 22 | template 23 | inline std::array to_hex_array(T addr) noexcept { 24 | std::array ret = {"0x"}; 25 | ret.back() = '\0'; 26 | static_assert(!std::is_pointer::value, ""); 27 | 28 | const std::size_t s = sizeof(T); 29 | 30 | char* out = ret.data() + s * 2 + 1; 31 | 32 | for (std::size_t i = 0; i < s; ++i) { 33 | const unsigned char tmp_addr = (addr & 0xFFu); 34 | *out = to_hex_array_bytes[tmp_addr & 0xF]; 35 | -- out; 36 | *out = to_hex_array_bytes[tmp_addr >> 4]; 37 | -- out; 38 | addr >>= 8; 39 | } 40 | 41 | return ret; 42 | } 43 | 44 | inline std::array to_hex_array(const void* addr) noexcept { 45 | return to_hex_array( 46 | reinterpret_cast< std::make_unsigned::type >(addr) 47 | ); 48 | } 49 | 50 | }}} // namespace boost::stacktrace::detail 51 | 52 | #endif // BOOST_STACKTRACE_DETAIL_TO_HEX_ARRAY_HPP 53 | -------------------------------------------------------------------------------- /example/trace_addresses.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #ifdef BOOST_NO_CXX11_RANGE_BASED_FOR 10 | #include 11 | #include // std::cout 12 | 13 | namespace bs = boost::stacktrace; 14 | void dump_compact(const bs::stacktrace& st) { 15 | for (unsigned i = 0; i < st.size(); ++i) { 16 | bs::frame frame = st[i]; 17 | std::cout << frame.address() << ','; 18 | } 19 | 20 | std::cout << std::endl; 21 | } 22 | #else 23 | //[getting_started_trace_addresses 24 | #include 25 | #include // std::cout 26 | 27 | namespace bs = boost::stacktrace; 28 | void dump_compact(const bs::stacktrace& st) { 29 | for (bs::frame frame: st) { 30 | std::cout << frame.address() << ','; 31 | } 32 | 33 | std::cout << std::endl; 34 | } 35 | //] 36 | #endif 37 | 38 | BOOST_NOINLINE boost::stacktrace::stacktrace rec1(int i); 39 | BOOST_NOINLINE boost::stacktrace::stacktrace rec2(int i); 40 | 41 | BOOST_NOINLINE boost::stacktrace::stacktrace rec1(int i) { 42 | if (i < 5) { 43 | if (!i) return boost::stacktrace::stacktrace(); 44 | return rec2(--i); 45 | } 46 | 47 | return rec2(i - 2); 48 | } 49 | 50 | BOOST_NOINLINE boost::stacktrace::stacktrace rec2(int i) { 51 | if (i < 5) { 52 | if (!i) return boost::stacktrace::stacktrace(); 53 | return rec2(--i); 54 | } 55 | 56 | return rec2(i - 2); 57 | } 58 | 59 | int main() { 60 | dump_compact(rec1(8)); 61 | } 62 | 63 | 64 | -------------------------------------------------------------------------------- /example/assert_handler.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #define BOOST_ENABLE_ASSERT_HANDLER 8 | 9 | #include // std::exit 10 | #include 11 | BOOST_NOINLINE void foo(int i); 12 | BOOST_NOINLINE void bar(int i); 13 | 14 | BOOST_NOINLINE void bar(int i) { 15 | boost::array a = {{101, 100, 123, 23, 32}}; 16 | if (i >= 0) { 17 | foo(a[i]); 18 | } else { 19 | std::exit(1); 20 | } 21 | } 22 | 23 | BOOST_NOINLINE void foo(int i) { 24 | bar(--i); 25 | } 26 | 27 | //[getting_started_assert_handlers 28 | 29 | // BOOST_ENABLE_ASSERT_DEBUG_HANDLER is defined for the whole project 30 | #include // std::logic_error 31 | #include // std::cerr 32 | #include 33 | 34 | namespace boost { 35 | inline void assertion_failed_msg(char const* expr, char const* msg, char const* function, char const* /*file*/, long /*line*/) { 36 | std::cerr << "Expression '" << expr << "' is false in function '" << function << "': " << (msg ? msg : "<...>") << ".\n" 37 | << "Backtrace:\n" << boost::stacktrace::stacktrace() << '\n'; 38 | /*<-*/ std::exit(0); /*->*/ 39 | /*=std::abort();*/ 40 | } 41 | 42 | inline void assertion_failed(char const* expr, char const* function, char const* file, long line) { 43 | ::boost::assertion_failed_msg(expr, 0 /*nullptr*/, function, file, line); 44 | } 45 | } // namespace boost 46 | //] 47 | 48 | 49 | int main() { 50 | foo(5); 51 | 52 | return 2; 53 | } 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/user_config.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | 8 | //[getting_started_user_config 9 | #ifndef USER_CONFIG_HPP 10 | #define USER_CONFIG_HPP 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace boost { namespace stacktrace { 17 | 18 | template 19 | std::basic_ostream& do_stream_st(std::basic_ostream& os, const basic_stacktrace& bt); 20 | 21 | template 22 | std::basic_ostream& operator<<(std::basic_ostream& os, const stacktrace& bt) { 23 | return do_stream_st(os, bt); 24 | } 25 | 26 | }} // namespace boost::stacktrace 27 | #endif // USER_CONFIG_HPP 28 | //] 29 | 30 | #ifndef USER_CONFIG2_HPP 31 | #define USER_CONFIG2_HPP 32 | 33 | #include // std::streamsize 34 | 35 | //[getting_started_user_config_impl 36 | namespace boost { namespace stacktrace { 37 | 38 | template 39 | std::basic_ostream& do_stream_st(std::basic_ostream& os, const basic_stacktrace& bt) { 40 | const std::streamsize w = os.width(); 41 | const std::size_t frames = bt.size(); 42 | for (std::size_t i = 0; i < frames; ++i) { 43 | os.width(2); 44 | os << i; 45 | os.width(w); 46 | os << "# "; 47 | os << bt[i].name(); 48 | os << '\n'; 49 | } 50 | 51 | return os; 52 | } 53 | 54 | }} // namespace boost::stacktrace 55 | //] 56 | 57 | #endif // USER_CONFIG2_HPP 58 | 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Boost.Stacktrace](https://boost.org/libs/stacktrace) 2 | 3 | Library for storing and printing backtraces. 4 | 5 | Boost.Stacktrace is a part of the [Boost C++ Libraries](https://github.com/boostorg). 6 | 7 | 8 | ### Test results 9 | @ | Build | Tests coverage | More info 10 | ----------------|-------------- | -------------- |----------- 11 | Develop branch: | [![CI](https://github.com/boostorg/stacktrace/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/boostorg/stacktrace/actions/workflows/ci.yml) [![Build status](https://ci.appveyor.com/api/projects/status/l3aak4j8k39rx08t/branch/develop?svg=true)](https://ci.appveyor.com/project/apolukhin/stacktrace/branch/develop) | [![Coverage Status](https://coveralls.io/repos/github/boostorg/stacktrace/badge.svg?branch=develop)](https://coveralls.io/github/boostorg/stacktrace?branch=develop) | [details...](https://www.boost.org/development/tests/develop/developer/stacktrace.html) 12 | Master branch: | [![CI](https://github.com/boostorg/stacktrace/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/boostorg/stacktrace/actions/workflows/ci.yml) [![Build status](https://ci.appveyor.com/api/projects/status/l3aak4j8k39rx08t/branch/master?svg=true)](https://ci.appveyor.com/project/apolukhin/stacktrace/branch/master) | [![Coverage Status](https://coveralls.io/repos/github/boostorg/stacktrace/badge.svg?branch=master)](https://coveralls.io/github/boostorg/stacktrace?branch=master) | [details...](https://www.boost.org/development/tests/master/developer/stacktrace.html) 13 | 14 | [Latest developer documentation](https://www.boost.org/doc/libs/develop/doc/html/stacktrace.html) 15 | 16 | ### License 17 | Distributed under the [Boost Software License, Version 1.0](https://boost.org/LICENSE_1_0.txt). 18 | -------------------------------------------------------------------------------- /test/thread_safety_checking.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "test_impl.hpp" 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | using boost::stacktrace::stacktrace; 19 | 20 | 21 | void main_test_loop() { 22 | std::size_t loops = 100; 23 | int Depth = 25; 24 | 25 | boost::optional > ethalon; 26 | std::stringstream ss_ethalon; 27 | 28 | while (--loops) { 29 | std::pair res = function_from_library(Depth, function_from_main_translation_unit); 30 | if (ethalon) { 31 | BOOST_TEST(res == *ethalon); 32 | 33 | std::stringstream ss; 34 | ss << res.first; 35 | BOOST_TEST(ss.str() == ss_ethalon.str()); 36 | } else { 37 | ethalon = res; 38 | ss_ethalon << ethalon->first; 39 | } 40 | } 41 | } 42 | 43 | int main() { 44 | const auto t = std::chrono::steady_clock::now(); 45 | 46 | std::thread t1(main_test_loop); 47 | std::thread t2(main_test_loop); 48 | std::thread t3(main_test_loop); 49 | main_test_loop(); 50 | 51 | t1.join(); 52 | t2.join(); 53 | t3.join(); 54 | 55 | const auto elapsed = t - std::chrono::steady_clock::now(); 56 | std::cout << "Elapsed: " << std::chrono::duration_cast( 57 | elapsed 58 | ). count() << "ms"; 59 | return boost::report_errors(); 60 | } 61 | -------------------------------------------------------------------------------- /test/test_noop.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include "test_impl.hpp" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | #include 15 | 16 | using boost::stacktrace::stacktrace; 17 | using boost::stacktrace::frame; 18 | 19 | void test_deeply_nested_namespaces() { 20 | BOOST_TEST(return_from_nested_namespaces().size() == 0); 21 | BOOST_TEST(return_from_nested_namespaces().empty()); 22 | BOOST_TEST(!return_from_nested_namespaces()); 23 | } 24 | 25 | void test_nested() { 26 | std::pair res = function_from_library(15, function_from_main_translation_unit); 27 | 28 | BOOST_TEST(!res.first); 29 | BOOST_TEST(res.first.empty()); 30 | BOOST_TEST(res.first.size() == 0); 31 | 32 | BOOST_TEST(res.second <= res.first); 33 | BOOST_TEST(res.second >= res.first); 34 | BOOST_TEST(res.second == res.first); 35 | BOOST_TEST(res.second == res.first); 36 | BOOST_TEST(!(res.second > res.first)); 37 | } 38 | 39 | void test_empty_frame() { 40 | boost::stacktrace::frame empty_frame; 41 | BOOST_TEST(!empty_frame); 42 | BOOST_TEST(empty_frame.source_file() == ""); 43 | BOOST_TEST(empty_frame.name() == ""); 44 | BOOST_TEST(empty_frame.source_line() == 0); 45 | 46 | boost::stacktrace::frame f(0); 47 | BOOST_TEST(f.name() == ""); 48 | BOOST_TEST(f.source_file() == ""); 49 | BOOST_TEST(f.source_line() == 0); 50 | } 51 | 52 | int main() { 53 | test_deeply_nested_namespaces(); 54 | test_nested(); 55 | test_empty_frame(); 56 | 57 | return boost::report_errors(); 58 | } 59 | -------------------------------------------------------------------------------- /src/exception_headers.h: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2023-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #if defined(__x86_64__) || defined(_M_X64) || defined(__MINGW32__) 10 | #define BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING 1 11 | #else 12 | #define BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING 0 13 | #endif 14 | 15 | 16 | extern "C" { 17 | 18 | // Developer note: helper to experiment with layouts of different 19 | // exception headers https://godbolt.org/z/rrcdPbh1P 20 | 21 | // https://github.com/llvm/llvm-project/blob/b3dd14ce07f2750ae1068fe62abbf2f3bd2cade8/libcxxabi/src/cxa_exception.h 22 | struct cxa_exception_begin_llvm { 23 | const char* reserve; 24 | size_t referenceCount; 25 | }; 26 | 27 | static cxa_exception_begin_llvm* exception_begin_llvm_ptr(void* ptr) { 28 | size_t kExceptionBeginOffset = ( 29 | sizeof(void*) == 8 ? 128 : 80 30 | ); 31 | return (cxa_exception_begin_llvm*)((char*)ptr - kExceptionBeginOffset); 32 | } 33 | 34 | // https://github.com/gcc-mirror/gcc/blob/5d2a360f0a541646abb11efdbabc33c6a04de7ee/libstdc%2B%2B-v3/libsupc%2B%2B/unwind-cxx.h#L100 35 | struct cxa_exception_begin_gcc { 36 | size_t referenceCount; 37 | const char* reserve; 38 | }; 39 | 40 | static cxa_exception_begin_gcc* exception_begin_gcc_ptr(void* ptr) { 41 | size_t kExceptionBeginOffset = ( 42 | sizeof(void*) == 8 ? 128 : 96 43 | ); 44 | return (cxa_exception_begin_gcc*)((char*)ptr - kExceptionBeginOffset); 45 | } 46 | 47 | static void* get_current_exception_raw_ptr(void* exc_ptr) { 48 | // https://github.com/gcc-mirror/gcc/blob/16e2427f50c208dfe07d07f18009969502c25dc8/libstdc%2B%2B-v3/libsupc%2B%2B/eh_ptr.cc#L147 49 | return *static_cast(exc_ptr); 50 | } 51 | 52 | } // extern "C" 53 | 54 | 55 | -------------------------------------------------------------------------------- /test/test_impl.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | 8 | #include 9 | 10 | #if defined(BOOST_LEXICAL_CAST_TRY_LEXICAL_CONVERT_HPP) || defined(BOOST_LEXICAL_CAST_BAD_LEXICAL_CAST_HPP) 11 | #error "LexicalCast headers leaked into the boost/stacktrace/stacktrace.hpp" 12 | #endif 13 | 14 | #if !defined(BOOST_USE_WINDOWS_H) && defined(_WINDOWS_H) 15 | #error "windows.h header leaked into the boost/stacktrace/stacktrace.hpp" 16 | #endif 17 | 18 | #include 19 | 20 | using namespace boost::stacktrace; 21 | 22 | #ifdef BOOST_STACKTRACE_DYN_LINK 23 | # ifdef BOOST_STACKTRACE_TEST_IMPL_LIB 24 | # define BOOST_ST_API BOOST_SYMBOL_EXPORT 25 | # else 26 | # define BOOST_ST_API BOOST_SYMBOL_IMPORT 27 | # endif 28 | #else 29 | # ifdef BOOST_STACKTRACE_TEST_EXPORTS_TABLE_USAGE 30 | # define BOOST_ST_API BOOST_SYMBOL_VISIBLE 31 | # else 32 | # define BOOST_ST_API 33 | # endif 34 | #endif 35 | 36 | typedef std::pair st_pair; 37 | typedef st_pair (*foo1_t)(int i); 38 | BOOST_ST_API st_pair function_from_library(int i, foo1_t foo1); 39 | BOOST_ST_API boost::stacktrace::stacktrace return_from_nested_namespaces(); 40 | BOOST_ST_API boost::stacktrace::stacktrace make_some_stacktrace1(); 41 | BOOST_ST_API boost::stacktrace::stacktrace make_some_stacktrace2(); 42 | 43 | #ifdef BOOST_STACKTRACE_TEST_EXPORTS_TABLE_USAGE 44 | BOOST_SYMBOL_VISIBLE 45 | #endif 46 | inline st_pair function_from_main_translation_unit(int i) { 47 | if (i) { 48 | return function_from_library(i - 1, function_from_main_translation_unit); 49 | } 50 | 51 | std::pair ret; 52 | try { 53 | throw std::logic_error("test"); 54 | } catch (const std::logic_error& /*e*/) { 55 | ret.second = stacktrace(); 56 | return ret; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/safe_dump_posix.ipp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_SAFE_DUMP_POSIX_IPP 8 | #define BOOST_STACKTRACE_DETAIL_SAFE_DUMP_POSIX_IPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | #include // ::write 18 | #include // ::open 19 | #include // S_IWUSR and friends 20 | 21 | 22 | namespace boost { namespace stacktrace { namespace detail { 23 | 24 | std::size_t dump(int fd, const native_frame_ptr_t* frames, std::size_t frames_count) noexcept { 25 | // We do not retry, because this function must be typically called from signal handler so it's: 26 | // * to scary to continue in case of EINTR 27 | // * EAGAIN or EWOULDBLOCK may occur only in case of O_NONBLOCK is set for fd, 28 | // so it seems that user does not want to block 29 | if (::write(fd, frames, sizeof(native_frame_ptr_t) * frames_count) == -1) { 30 | return 0; 31 | } 32 | 33 | return frames_count; 34 | } 35 | 36 | std::size_t dump(const char* file, const native_frame_ptr_t* frames, std::size_t frames_count) noexcept { 37 | const int fd = ::open( 38 | file, 39 | O_CREAT | O_WRONLY | O_TRUNC, 40 | #if defined(S_IWUSR) && defined(S_IRUSR) // Workarounds for some Android OSes 41 | S_IWUSR | S_IRUSR 42 | #elif defined(S_IWRITE) && defined(S_IREAD) 43 | S_IWRITE | S_IREAD 44 | #else 45 | 0 46 | #endif 47 | ); 48 | if (fd == -1) { 49 | return 0; 50 | } 51 | 52 | const std::size_t size = boost::stacktrace::detail::dump(fd, frames, frames_count); 53 | ::close(fd); 54 | return size; 55 | } 56 | 57 | }}} // namespace boost::stacktrace::detail 58 | 59 | #endif // BOOST_STACKTRACE_DETAIL_SAFE_DUMP_POSIX_IPP 60 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/safe_dump_win.ipp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_SAFE_DUMP_WIN_IPP 8 | #define BOOST_STACKTRACE_DETAIL_SAFE_DUMP_WIN_IPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | #include 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace boost { namespace stacktrace { namespace detail { 25 | 26 | std::size_t dump(void* /*fd*/, const native_frame_ptr_t* /*frames*/, std::size_t /*frames_count*/) noexcept { 27 | #if 0 // This code potentially could cause deadlocks (according to the MSDN). Disabled 28 | boost::winapi::DWORD_ written; 29 | const boost::winapi::DWORD_ bytes_to_write = static_cast( 30 | sizeof(native_frame_ptr_t) * frames_count 31 | ); 32 | if (!boost::winapi::WriteFile(fd, frames, bytes_to_write, &written, 0)) { 33 | return 0; 34 | } 35 | 36 | return frames_count; 37 | #endif 38 | return 0; 39 | } 40 | 41 | std::size_t dump(const char* /*file*/, const native_frame_ptr_t* /*frames*/, std::size_t /*frames_count*/) noexcept { 42 | #if 0 // This code causing deadlocks on some platforms. Disabled 43 | void* const fd = boost::winapi::CreateFileA( 44 | file, 45 | boost::winapi::GENERIC_WRITE_, 46 | 0, 47 | 0, 48 | boost::winapi::CREATE_ALWAYS_, 49 | boost::winapi::FILE_ATTRIBUTE_NORMAL_, 50 | 0 51 | ); 52 | 53 | if (fd == boost::winapi::invalid_handle_value) { 54 | return 0; 55 | } 56 | 57 | const std::size_t size = boost::stacktrace::detail::dump(fd, frames, frames_count); 58 | boost::winapi::CloseHandle(fd); 59 | return size; 60 | #endif 61 | return 0; 62 | } 63 | 64 | }}} // namespace boost::stacktrace::detail 65 | 66 | #endif // BOOST_STACKTRACE_DETAIL_SAFE_DUMP_WIN_IPP 67 | -------------------------------------------------------------------------------- /include/boost/stacktrace/this_thread.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2023-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_THIS_THREAD_HPP 8 | #define BOOST_STACKTRACE_THIS_THREAD_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | namespace boost { namespace stacktrace { namespace this_thread { 18 | 19 | /// @brief Invoking the function with the enable parameter equal to `true` 20 | /// enables capturing of stacktraces by the current thread of execution at 21 | /// exception object construction if the `boost_stacktrace_from_exception` 22 | /// library is linked to the current binary; disables otherwise. 23 | /// 24 | /// Implements https://wg21.link/p2370r1 25 | inline void set_capture_stacktraces_at_throw(bool enable = true) noexcept { 26 | #if defined(__GNUC__) && defined(__ELF__) 27 | if (impl::ref_capture_stacktraces_at_throw) { 28 | impl::ref_capture_stacktraces_at_throw() = enable; 29 | } 30 | #elif defined(BOOST_MSVC) 31 | if (bool* p = boost_stacktrace_impl_ref_capture_stacktraces_at_throw()) { 32 | *p = enable; 33 | } 34 | #endif 35 | (void)enable; 36 | } 37 | 38 | /// @return whether the capturing of stacktraces by the current thread of 39 | /// execution is enabled and 40 | /// boost::stacktrace::basic_stacktrace::from_current_exception may return a 41 | /// non empty stacktrace. 42 | /// 43 | /// Returns true if set_capture_stacktraces_at_throw(false) was not called 44 | /// and the `boost_stacktrace_from_exception` is linked to the current binary. 45 | /// 46 | /// Implements https://wg21.link/p2370r1 47 | inline bool get_capture_stacktraces_at_throw() noexcept { 48 | #if defined(__GNUC__) && defined(__ELF__) 49 | if (impl::ref_capture_stacktraces_at_throw) { 50 | return impl::ref_capture_stacktraces_at_throw(); 51 | } 52 | #elif defined(BOOST_MSVC) 53 | if (bool* p = boost_stacktrace_impl_ref_capture_stacktraces_at_throw()) { 54 | return *p; 55 | } 56 | #endif 57 | return false; 58 | } 59 | 60 | }}} // namespace boost::stacktrace::this_thread 61 | 62 | #endif // BOOST_STACKTRACE_THIS_THREAD_HPP 63 | -------------------------------------------------------------------------------- /example/throwing_st.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 10 | 11 | //[getting_started_class_traced 12 | #include 13 | #include 14 | 15 | typedef boost::error_info traced; 16 | //] 17 | 18 | //[getting_started_class_with_trace 19 | template 20 | void throw_with_trace(const E& e) { 21 | throw boost::enable_error_info(e) 22 | << traced(boost::stacktrace::stacktrace()); 23 | } 24 | //] 25 | 26 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 27 | 28 | BOOST_NOINLINE void oops(int i); 29 | BOOST_NOINLINE void foo(int i); 30 | BOOST_NOINLINE void bar(int i); 31 | 32 | #include 33 | BOOST_NOINLINE void oops(int i) { 34 | //[getting_started_throwing_with_trace 35 | if (i >= 4) 36 | throw_with_trace(std::out_of_range("'i' must be less than 4 in oops()")); 37 | if (i <= 0) 38 | throw_with_trace(std::logic_error("'i' must be greater than zero in oops()")); 39 | //] 40 | foo(i); 41 | std::exit(1); 42 | } 43 | 44 | #include 45 | BOOST_NOINLINE void bar(int i) { 46 | boost::array a = {{0, 0, 0, 0, 0}}; 47 | if (i < 5) { 48 | if (i >= 0) { 49 | foo(a[i]); 50 | } else { 51 | oops(i); 52 | } 53 | } 54 | std::exit(2); 55 | } 56 | 57 | BOOST_NOINLINE void foo(int i) { 58 | bar(--i); 59 | } 60 | 61 | #include 62 | int main() { 63 | 64 | //[getting_started_catching_trace 65 | try { 66 | foo(5); // testing assert handler 67 | } catch (const std::exception& e) { 68 | std::cerr << e.what() << '\n'; 69 | const boost::stacktrace::stacktrace* st = boost::get_error_info(e); 70 | if (st) { 71 | std::cerr << *st << '\n'; /*<-*/ return 0; /*->*/ 72 | } /*<-*/ return 3; /*->*/ 73 | } 74 | //] 75 | 76 | return 5; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /test/test_void_ptr_cast.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2017 Antony Polukhin. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. 4 | // (See accompanying file LICENSE_1_0.txt 5 | // or copy at http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #include 10 | 11 | int foo1_func(int) { return 0; } 12 | void foo2_func(int, int, ...) {} 13 | 14 | struct test_struct { 15 | int foo1_memb(int) const { return 0; } 16 | void foo2_memb(int, int, ...) {} 17 | }; 18 | 19 | template 20 | void test(F1 foo1, F2 foo2) { 21 | using boost::stacktrace::detail::void_ptr_cast; 22 | 23 | typedef void(*void_f_ptr)(); 24 | 25 | // Function/variable to void(*)() 26 | void_f_ptr fp1 = void_ptr_cast(foo1); 27 | void_f_ptr fp2 = void_ptr_cast(foo2); 28 | BOOST_TEST(fp1); 29 | BOOST_TEST(fp2); 30 | BOOST_TEST(fp1 != fp2); 31 | 32 | // Function/variable to void* 33 | void* vp1 = void_ptr_cast(foo1); 34 | void* vp2 = void_ptr_cast(foo2); 35 | BOOST_TEST(vp1); 36 | BOOST_TEST(vp2); 37 | BOOST_TEST(vp1 != vp2); 38 | 39 | // void* to void(*)() 40 | void_f_ptr fp1_2 = void_ptr_cast(vp1); 41 | void_f_ptr fp2_2 = void_ptr_cast(vp2); 42 | BOOST_TEST(fp1_2); 43 | BOOST_TEST(fp2_2); 44 | BOOST_TEST(fp1_2 != fp2_2); 45 | BOOST_TEST(fp1 == fp1_2); 46 | BOOST_TEST(fp2 == fp2_2); 47 | 48 | // void(*)() to void* 49 | BOOST_TEST(void_ptr_cast(fp1) == vp1); 50 | BOOST_TEST(void_ptr_cast(fp2) == vp2); 51 | 52 | // void(*)() to function/variable 53 | BOOST_TEST(void_ptr_cast(fp1) == foo1); 54 | BOOST_TEST(void_ptr_cast(fp2) == foo2); 55 | 56 | // void* to function/variable 57 | BOOST_TEST(void_ptr_cast(vp1) == foo1); 58 | BOOST_TEST(void_ptr_cast(vp2) == foo2); 59 | } 60 | 61 | int main() { 62 | // Testing for functions 63 | test(foo1_func, foo2_func); 64 | 65 | typedef void(func_t)(); 66 | test( 67 | boost::stacktrace::detail::void_ptr_cast(foo1_func), 68 | boost::stacktrace::detail::void_ptr_cast(foo2_func) 69 | ); 70 | 71 | // Testing for variables (just in case...) 72 | int i = 0; 73 | double j= 1; 74 | test(&i, &j); 75 | 76 | 77 | 78 | return boost::report_errors(); 79 | } 80 | -------------------------------------------------------------------------------- /doc/Jamfile.v2: -------------------------------------------------------------------------------- 1 | # Copyright Antony Polukhin, 2016-2025. 2 | # Use, modification, and distribution are 3 | # subject to the Boost Software License, Version 1.0. (See accompanying 4 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 5 | 6 | using quickbook ; 7 | import boostbook : boostbook ; 8 | import doxygen ; 9 | 10 | doxygen autodoc 11 | : 12 | [ glob ../include/boost/stacktrace.hpp ] 13 | [ glob ../include/boost/stacktrace/*.hpp ] 14 | [ glob ../include/boost/stacktrace/detail/frame_decl.hpp ] 15 | : 16 | EXTRACT_ALL=NO 17 | HIDE_UNDOC_MEMBERS=YES 18 | EXTRACT_PRIVATE=NO 19 | ENABLE_PREPROCESSING=YES 20 | EXPAND_ONLY_PREDEF=YES 21 | MACRO_EXPANSION=YES 22 | SEARCH_INCLUDES=YES 23 | SHORT_NAMES=NO 24 | INCLUDE_PATH=../../../ 25 | "ALIASES= \\ 26 | \"asyncsafe=\\xmlonly\\endxmlonly Theoretically async signal safe \\xmlonly\\endxmlonly\" \\ 27 | " 28 | "PREDEFINED=\"stl_type_info=std::type_info\" \\ 29 | \"BOOST_EXPLICIT_OPERATOR_BOOL_NOEXCEPT()=explicit operator bool() const noexcept;\" \\ 30 | \"BOOST_CONSTEXPR_EXPLICIT_OPERATOR_BOOL()=explicit constexpr operator bool() const noexcept;\" \\ 31 | \"BOOST_STATIC_CONSTEXPR=static constexpr\" \\ 32 | \"BOOST_FORCEINLINE=inline\" \\ 33 | \"BOOST_STACKTRACE_FUNCTION=inline\" \\ 34 | \"BOOST_CONSTEXPR=constexpr\" \\ 35 | \"BOOST_STACKTRACE_DOXYGEN_INVOKED\"" 36 | "boost.doxygen.reftitle=Reference" 37 | "boost.doxygen.refid=stacktrace.reference" 38 | ; 39 | 40 | xml stacktrace : stacktrace.qbk : autodoc ; 41 | boostbook standalone 42 | : 43 | stacktrace 44 | : 45 | boost.root=http\://www.boost.org/doc/libs/1_84_0 46 | # boost.root=../../../.. 47 | pdf:boost.url.prefix=http\://www.boost.org/doc/libs/release/doc/html 48 | ; 49 | 50 | ############################################################################### 51 | alias boostdoc 52 | : stacktrace 53 | : 54 | : 55 | : ; 56 | 57 | explicit boostdoc ; 58 | alias boostrelease ; 59 | explicit boostrelease ; 60 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/addr_base_msvc.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_ADDR_BASE_MSVC_HPP 8 | #define BOOST_STACKTRACE_DETAIL_ADDR_BASE_MSVC_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #ifdef WIN32_LEAN_AND_MEAN 20 | #include 21 | #include 22 | #else 23 | // Prevent inclusion of extra Windows SDK headers which can cause conflict 24 | // with other code using Windows SDK 25 | #define WIN32_LEAN_AND_MEAN 26 | #include 27 | #include 28 | #undef WIN32_LEAN_AND_MEAN 29 | #endif 30 | 31 | namespace boost { namespace stacktrace { namespace detail { 32 | inline std::uintptr_t get_own_proc_addr_base(const void* addr) { 33 | // Try to avoid allocating memory for the modules array if possible. 34 | // The stack buffer should be large enough for most processes. 35 | HMODULE modules_stack[1024]; 36 | std::unique_ptr modules_allocated; 37 | HMODULE* modules = modules_stack; 38 | 39 | DWORD needed_bytes = 0; 40 | std::uintptr_t addr_base = 0; 41 | 42 | HANDLE process_handle = GetCurrentProcess(); 43 | auto enum_process_is_ok = EnumProcessModules(process_handle, modules, sizeof(modules), &needed_bytes); 44 | 45 | // Check if the error is because the buffer is too small. 46 | if (!enum_process_is_ok && GetLastError() == ERROR_INSUFFICIENT_BUFFER) { 47 | modules_allocated.reset(new HMODULE[needed_bytes / sizeof(HMODULE)]); 48 | modules = modules_allocated.get(); 49 | enum_process_is_ok = EnumProcessModules(process_handle, modules, needed_bytes, &needed_bytes); 50 | } 51 | 52 | if (enum_process_is_ok) { 53 | for (std::size_t i = 0; i < (needed_bytes / sizeof(HMODULE)); ++i) { 54 | MODULEINFO module_info; 55 | 56 | // Get the module name 57 | if (GetModuleInformation(process_handle, modules[i], &module_info, sizeof(module_info)) 58 | && module_info.lpBaseOfDll <= addr && addr < LPBYTE(module_info.lpBaseOfDll) + module_info.SizeOfImage) { 59 | // Module contains the address 60 | addr_base = reinterpret_cast(module_info.lpBaseOfDll); 61 | break; 62 | } 63 | } 64 | } 65 | 66 | CloseHandle(process_handle); 67 | 68 | return addr_base; 69 | } 70 | 71 | }}} // namespace boost::stacktrace::detail 72 | 73 | #endif // BOOST_STACKTRACE_DETAIL_ADDR_BASE_MSVC_HPP 74 | -------------------------------------------------------------------------------- /test/test_num_conv.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | 16 | void test_to_hex_array() { 17 | const void* ptr = 0; 18 | BOOST_TEST(std::string(boost::stacktrace::detail::to_hex_array(ptr).data()).find("0x0") != std::string::npos); 19 | 20 | ptr = reinterpret_cast(0x10); 21 | BOOST_TEST(std::string(boost::stacktrace::detail::to_hex_array(ptr).data()).find("10") != std::string::npos); 22 | 23 | ptr = reinterpret_cast(0x19); 24 | BOOST_TEST(std::string(boost::stacktrace::detail::to_hex_array(ptr).data()).find("19") != std::string::npos); 25 | 26 | ptr = reinterpret_cast(0x999999); 27 | BOOST_TEST(std::string(boost::stacktrace::detail::to_hex_array(ptr).data()).find("999999") != std::string::npos); 28 | } 29 | 30 | void test_to_dec_array() { 31 | BOOST_TEST_EQ(std::string(boost::stacktrace::detail::to_dec_array(0).data()), std::string("0")); 32 | BOOST_TEST_EQ(std::string(boost::stacktrace::detail::to_dec_array(10).data()), std::string("10")); 33 | BOOST_TEST_EQ(std::string(boost::stacktrace::detail::to_dec_array(19).data()), std::string("19")); 34 | BOOST_TEST_EQ(std::string(boost::stacktrace::detail::to_dec_array(999999).data()), std::string("999999")); 35 | } 36 | 37 | void test_try_dec_convert() { 38 | std::size_t res = 0; 39 | 40 | BOOST_TEST(boost::stacktrace::detail::try_dec_convert("0", res)); 41 | BOOST_TEST(res == 0); 42 | 43 | BOOST_TEST(boost::stacktrace::detail::try_dec_convert("+0", res)); 44 | BOOST_TEST(res == 0); 45 | 46 | BOOST_TEST(boost::stacktrace::detail::try_dec_convert("10", res)); 47 | BOOST_TEST(res == 10); 48 | 49 | BOOST_TEST(boost::stacktrace::detail::try_dec_convert("19", res)); 50 | BOOST_TEST(res == 19); 51 | 52 | BOOST_TEST(boost::stacktrace::detail::try_dec_convert("+19", res)); 53 | BOOST_TEST(res == 19); 54 | 55 | BOOST_TEST(boost::stacktrace::detail::try_dec_convert("9999", res)); 56 | BOOST_TEST(res == 9999); 57 | 58 | BOOST_TEST(!boost::stacktrace::detail::try_dec_convert("q", res)); 59 | BOOST_TEST(!boost::stacktrace::detail::try_dec_convert("0z", res)); 60 | BOOST_TEST(!boost::stacktrace::detail::try_dec_convert("0u", res)); 61 | BOOST_TEST(!boost::stacktrace::detail::try_dec_convert("+0u", res)); 62 | } 63 | 64 | 65 | int main() { 66 | test_to_hex_array(); 67 | test_to_dec_array(); 68 | test_try_dec_convert(); 69 | 70 | return boost::report_errors(); 71 | } 72 | -------------------------------------------------------------------------------- /include/boost/stacktrace/frame.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_FRAME_HPP 8 | #define BOOST_STACKTRACE_FRAME_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | #include 17 | 18 | #include // boost::stacktrace::detail::native_frame_ptr_t 19 | 20 | #include 21 | #include 22 | 23 | #if defined(BOOST_MSVC) && (defined(BOOST_STACKTRACE_INTERNAL_BUILD_LIBS) || !defined(BOOST_STACKTRACE_LINK)) 24 | extern "C" { 25 | 26 | #if defined(BOOST_STACKTRACE_DYN_LINK) 27 | BOOST_SYMBOL_EXPORT 28 | #elif defined(BOOST_STACKTRACE_LINK) 29 | #else 30 | BOOST_SYMBOL_EXPORT inline 31 | #endif 32 | void* boost_stacktrace_impl_return_nullptr() { return nullptr; } 33 | 34 | } 35 | #endif 36 | 37 | namespace boost { namespace stacktrace { 38 | 39 | /// Comparison operators that provide platform dependant ordering and have O(1) complexity; are Async-Handler-Safe. 40 | constexpr inline bool operator< (const frame& lhs, const frame& rhs) noexcept { return lhs.address() < rhs.address(); } 41 | constexpr inline bool operator> (const frame& lhs, const frame& rhs) noexcept { return rhs < lhs; } 42 | constexpr inline bool operator<=(const frame& lhs, const frame& rhs) noexcept { return !(lhs > rhs); } 43 | constexpr inline bool operator>=(const frame& lhs, const frame& rhs) noexcept { return !(lhs < rhs); } 44 | constexpr inline bool operator==(const frame& lhs, const frame& rhs) noexcept { return lhs.address() == rhs.address(); } 45 | constexpr inline bool operator!=(const frame& lhs, const frame& rhs) noexcept { return !(lhs == rhs); } 46 | 47 | /// Fast hashing support, O(1) complexity; Async-Handler-Safe. 48 | inline std::size_t hash_value(const frame& f) noexcept { 49 | return reinterpret_cast(f.address()); 50 | } 51 | 52 | /// Outputs stacktrace::frame in a human readable format to string; unsafe to use in async handlers. 53 | BOOST_STACKTRACE_FUNCTION std::string to_string(const frame& f); 54 | 55 | /// Outputs stacktrace::frame in a human readable format to output stream; unsafe to use in async handlers. 56 | template 57 | std::basic_ostream& operator<<(std::basic_ostream& os, const frame& f) { 58 | return os << boost::stacktrace::to_string(f); 59 | } 60 | 61 | }} // namespace boost::stacktrace 62 | 63 | /// @cond 64 | 65 | #include 66 | 67 | #ifndef BOOST_STACKTRACE_LINK 68 | # if defined(BOOST_STACKTRACE_USE_NOOP) 69 | # include 70 | # elif defined(BOOST_MSVC) || defined(BOOST_STACKTRACE_USE_WINDBG) || defined(BOOST_STACKTRACE_USE_WINDBG_CACHED) 71 | # include 72 | # else 73 | # include 74 | # endif 75 | #endif 76 | /// @endcond 77 | 78 | 79 | #endif // BOOST_STACKTRACE_FRAME_HPP 80 | -------------------------------------------------------------------------------- /test/test_impl.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #define BOOST_STACKTRACE_TEST_IMPL_LIB 1 8 | #include "test_impl.hpp" 9 | 10 | using namespace boost::stacktrace; 11 | 12 | BOOST_ST_API BOOST_NOINLINE std::pair function_from_library(int i, foo1_t foo1) { 13 | if (i) { 14 | return foo1(--i); 15 | } else { 16 | return foo1(i); 17 | } 18 | } 19 | 20 | 21 | namespace very_very_very_very_very_very_long_namespace { 22 | namespace very_very_very_very_very_very_long_namespace { 23 | namespace very_very_very_very_very_very_long_namespace { 24 | namespace very_very_very_very_very_very_long_namespace { 25 | namespace very_very_very_very_very_very_long_namespace { 26 | namespace very_very_very_very_very_very_long_namespace { 27 | namespace very_very_very_very_very_very_long_namespace { 28 | namespace very_very_very_very_very_very_long_namespace { 29 | namespace very_very_very_very_very_very_long_namespace { 30 | namespace very_very_very_very_very_very_long_namespace { 31 | BOOST_ST_API BOOST_NOINLINE stacktrace get_backtrace_from_nested_namespaces() { 32 | return stacktrace(); 33 | } 34 | }}}}}}}}}} 35 | 36 | BOOST_ST_API BOOST_NOINLINE stacktrace return_from_nested_namespaces() { 37 | using very_very_very_very_very_very_long_namespace::very_very_very_very_very_very_long_namespace::very_very_very_very_very_very_long_namespace 38 | ::very_very_very_very_very_very_long_namespace::very_very_very_very_very_very_long_namespace::very_very_very_very_very_very_long_namespace 39 | ::very_very_very_very_very_very_long_namespace::very_very_very_very_very_very_long_namespace::very_very_very_very_very_very_long_namespace 40 | ::very_very_very_very_very_very_long_namespace::get_backtrace_from_nested_namespaces; 41 | 42 | return get_backtrace_from_nested_namespaces(); 43 | } 44 | 45 | BOOST_ST_API BOOST_NOINLINE boost::stacktrace::stacktrace make_some_stacktrace1_impl(int d = 0) { 46 | boost::stacktrace::stacktrace result(0, 4); 47 | if (result.size() < 4) { 48 | if (d > 4) throw std::runtime_error("Stack is not growing in test OR stacktrace fails to work in `bar1` function."); 49 | return make_some_stacktrace1_impl(d + 1); 50 | } 51 | return result; 52 | } 53 | 54 | BOOST_ST_API BOOST_NOINLINE boost::stacktrace::stacktrace make_some_stacktrace2_impl(int d = 0) { 55 | boost::stacktrace::stacktrace result(0, 4); 56 | if (result.size() < 4) { 57 | if (d > 4) throw std::runtime_error("Stack is not growing in test OR stacktrace fails to work in `bar2` function."); 58 | return make_some_stacktrace2_impl(d + 1); 59 | } 60 | return result; 61 | } 62 | 63 | BOOST_ST_API BOOST_NOINLINE boost::stacktrace::stacktrace make_some_stacktrace1() { 64 | boost::stacktrace::stacktrace result = make_some_stacktrace1_impl(); 65 | return result; 66 | } 67 | 68 | BOOST_ST_API BOOST_NOINLINE boost::stacktrace::stacktrace make_some_stacktrace2() { 69 | boost::stacktrace::stacktrace result = make_some_stacktrace2_impl(); 70 | return result; 71 | } 72 | 73 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/addr_base.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_ADDR_BASE_HPP 8 | #define BOOST_STACKTRACE_DETAIL_ADDR_BASE_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | namespace boost { namespace stacktrace { namespace detail { 21 | 22 | struct mapping_entry_t { 23 | uintptr_t start = 0; 24 | uintptr_t end = 0; 25 | uintptr_t offset_from_base = 0; 26 | 27 | inline bool contains_addr(const void* addr) const { 28 | uintptr_t addr_uint = reinterpret_cast(addr); 29 | return addr_uint >= start && addr_uint < end; 30 | } 31 | }; 32 | 33 | inline uintptr_t hex_str_to_int(const std::string& str) { 34 | uintptr_t out; 35 | std::stringstream ss; 36 | ss << std::hex << str; 37 | ss >> out; 38 | if(ss.eof() && !ss.fail()) { // whole stream read, with no errors 39 | return out; 40 | } else { 41 | throw std::invalid_argument(std::string("can't convert '") + str + "' to hex"); 42 | } 43 | } 44 | 45 | // parse line from /proc//maps 46 | // format: 47 | // 7fb60d1ea000-7fb60d20c000 r--p 00000000 103:02 120327460 /usr/lib/libc.so.6 48 | // only parts 0 and 2 are interesting, these are: 49 | // 0. mapping address range 50 | // 2. mapping offset from base 51 | inline mapping_entry_t parse_proc_maps_line(const std::string& line) { 52 | std::string mapping_range_str, permissions_str, offset_from_base_str; 53 | std::istringstream line_stream(line); 54 | if(!std::getline(line_stream, mapping_range_str, ' ') || 55 | !std::getline(line_stream, permissions_str, ' ') || 56 | !std::getline(line_stream, offset_from_base_str, ' ')) { 57 | return mapping_entry_t{}; 58 | } 59 | std::string mapping_start_str, mapping_end_str; 60 | std::istringstream mapping_range_stream(mapping_range_str); 61 | if(!std::getline(mapping_range_stream, mapping_start_str, '-') || 62 | !std::getline(mapping_range_stream, mapping_end_str)) { 63 | return mapping_entry_t{}; 64 | } 65 | mapping_entry_t mapping{}; 66 | try { 67 | mapping.start = hex_str_to_int(mapping_start_str); 68 | mapping.end = hex_str_to_int(mapping_end_str); 69 | mapping.offset_from_base = hex_str_to_int(offset_from_base_str); 70 | return mapping; 71 | } catch(std::invalid_argument& e) { 72 | return mapping_entry_t{}; 73 | } 74 | } 75 | 76 | inline uintptr_t get_own_proc_addr_base(const void* addr) { 77 | std::ifstream maps_file("/proc/self/maps"); 78 | for (std::string line; std::getline(maps_file, line); ) { 79 | const mapping_entry_t mapping = parse_proc_maps_line(line); 80 | if (mapping.contains_addr(addr)) { 81 | return mapping.start - mapping.offset_from_base; 82 | } 83 | } 84 | return 0; 85 | } 86 | 87 | }}} // namespace boost::stacktrace::detail 88 | 89 | #endif // BOOST_STACKTRACE_DETAIL_ADDR_BASE_HPP 90 | -------------------------------------------------------------------------------- /test/appveyor.yml: -------------------------------------------------------------------------------- 1 | # Use, modification, and distribution are 2 | # subject to the Boost Software License, Version 1.0. (See accompanying 3 | # file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) 4 | # 5 | # Copyright Antony Polukhin, 2016-2025. 6 | 7 | # 8 | # See https://svn.boost.org/trac/boost/wiki/TravisCoverals for description of this file 9 | # and how it can be used with Boost libraries. 10 | # 11 | # File revision #6 12 | 13 | init: 14 | # boost-local/libs/ folder to put this library into. This may be useful, if you're for example running Travis 15 | # from `Boost.DLL` repo while Boost already has `dll` and with to replace `dll` with content of`Boost.DLL`. 16 | # 17 | # Otherwise just leave the default value - set BOOST_LIBS_FOLDER=%APPVEYOR_PROJECT_NAME% 18 | - set BOOST_LIBS_FOLDER=%APPVEYOR_PROJECT_NAME% 19 | 20 | ############################################################################################################### 21 | # From this point and below code is same for all the Boost libs 22 | ############################################################################################################### 23 | 24 | version: 1.88.{build}-{branch} 25 | 26 | # branches to build 27 | branches: 28 | except: 29 | - gh-pages 30 | 31 | skip_tags: true 32 | 33 | environment: 34 | matrix: 35 | - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 36 | TOOLSET: msvc-14.1 #,clang-win 37 | CXXSTD: 14,17 38 | ADDRMD: 64 39 | #- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 40 | # ADDPATH: C:\cygwin\bin; 41 | # TOOLSET: gcc 42 | # CXXSTD: 03,11,14,1z 43 | #- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 44 | # ADDPATH: C:\cygwin64\bin; 45 | # TOOLSET: gcc 46 | # CXXSTD: 03,11,14,1z 47 | # Waiting for https://github.com/boostorg/system/issues/116 48 | #- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 49 | # ADDPATH: C:\mingw\bin; 50 | # TOOLSET: gcc 51 | # CXXSTD: 03,11,14,1z 52 | #- APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2015 53 | # ADDPATH: C:\mingw-w64\x86_64-7.2.0-posix-seh-rt_v5-rev1\mingw64\bin; 54 | # TOOLSET: gcc 55 | # CXXSTD: 03,11,14,1z 56 | 57 | before_build: 58 | - set BOOST_BRANCH=develop 59 | - if "%APPVEYOR_REPO_BRANCH%" == "master" set BOOST_BRANCH=master 60 | - echo "Testing %APPVEYOR_PROJECT_NAME%" 61 | # Cloning Boost libraries (fast nondeep cloning) 62 | - set BOOST=C:/boost-local 63 | - git clone -b %BOOST_BRANCH% --depth 10 https://github.com/boostorg/boost.git %BOOST% 64 | - cd %BOOST% 65 | - git submodule update --init --depth 10 tools/build tools/boostdep 66 | 67 | - rm -rf %BOOST%/libs/%BOOST_LIBS_FOLDER% 68 | - mv -f %APPVEYOR_BUILD_FOLDER% %BOOST%/libs/%BOOST_LIBS_FOLDER% 69 | - python tools/boostdep/depinst/depinst.py --include example --git_args "--depth 10 --jobs 2" %BOOST_LIBS_FOLDER% 70 | 71 | build_script: 72 | - cmd /c bootstrap 73 | - b2.exe headers 74 | - cd %BOOST%/libs/%BOOST_LIBS_FOLDER%/test 75 | 76 | after_build: 77 | before_test: 78 | test_script: 79 | - PATH=%ADDPATH%%PATH% 80 | - if not "%CXXSTD%" == "" set CXXSTD=cxxstd=%CXXSTD% 81 | - if not "%ADDRMD%" == "" set ADDRMD=address-model=%ADDRMD% 82 | - echo "Running command ..\..\..\b2 -j3 toolset=%TOOLSET% %CXXSTD% %ADDRMD% variant=debug,release" 83 | - ..\..\..\b2.exe -j3 toolset=%TOOLSET% %CXXSTD% %ADDRMD% variant=debug,release cxxflags="-DBOOST_TRAVISCI_BUILD" 84 | 85 | after_test: 86 | on_success: 87 | on_failure: 88 | on_finish: 89 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/frame_unwind.ipp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_FRAME_UNWIND_IPP 8 | #define BOOST_STACKTRACE_DETAIL_FRAME_UNWIND_IPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | 25 | #ifdef BOOST_STACKTRACE_USE_BACKTRACE 26 | # include 27 | #elif defined(BOOST_STACKTRACE_USE_ADDR2LINE) 28 | # include 29 | #else 30 | # include 31 | #endif 32 | 33 | namespace boost { namespace stacktrace { namespace detail { 34 | 35 | template 36 | class to_string_impl_base: private Base { 37 | public: 38 | std::string operator()(boost::stacktrace::detail::native_frame_ptr_t addr) { 39 | Base::res.clear(); 40 | Base::prepare_function_name(addr); 41 | if (!Base::res.empty()) { 42 | Base::res = boost::core::demangle(Base::res.c_str()); 43 | } else { 44 | #ifdef BOOST_STACKTRACE_DISABLE_OFFSET_ADDR_BASE 45 | Base::res = to_hex_array(addr).data(); 46 | #else 47 | const auto addr_base = boost::stacktrace::detail::get_own_proc_addr_base(addr); 48 | Base::res = to_hex_array(reinterpret_cast(addr) - addr_base).data(); 49 | #endif 50 | } 51 | 52 | if (Base::prepare_source_location(addr)) { 53 | return Base::res; 54 | } 55 | 56 | boost::stacktrace::detail::location_from_symbol loc(addr); 57 | if (!loc.empty()) { 58 | Base::res += " in "; 59 | Base::res += loc.name(); 60 | } 61 | 62 | return Base::res; 63 | } 64 | }; 65 | 66 | std::string to_string(const frame* frames, std::size_t size) { 67 | std::string res; 68 | if (size == 0) { 69 | return res; 70 | } 71 | res.reserve(64 * size); 72 | 73 | to_string_impl impl; 74 | 75 | for (std::size_t i = 0; i < size; ++i) { 76 | if (i < 10) { 77 | res += ' '; 78 | } 79 | res += boost::stacktrace::detail::to_dec_array(i).data(); 80 | res += '#'; 81 | res += ' '; 82 | res += impl(frames[i].address()); 83 | res += '\n'; 84 | } 85 | 86 | return res; 87 | } 88 | 89 | 90 | } // namespace detail 91 | 92 | 93 | std::string frame::name() const { 94 | if (!addr_) { 95 | return std::string(); 96 | } 97 | 98 | #if !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) 99 | boost::stacktrace::detail::Dl_info dli; 100 | const bool dl_ok = !!boost::stacktrace::detail::dladdr(addr_, dli); 101 | if (dl_ok && dli.dli_sname) { 102 | return boost::core::demangle(dli.dli_sname); 103 | } 104 | #endif 105 | return boost::stacktrace::detail::name_impl(addr_); 106 | } 107 | 108 | std::string to_string(const frame& f) { 109 | if (!f) { 110 | return std::string(); 111 | } 112 | 113 | boost::stacktrace::detail::to_string_impl impl; 114 | return impl(f.address()); 115 | } 116 | 117 | 118 | }} // namespace boost::stacktrace 119 | 120 | #endif // BOOST_STACKTRACE_DETAIL_FRAME_UNWIND_IPP 121 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto !eol svneol=native#text/plain 2 | *.gitattributes text svneol=native#text/plain 3 | 4 | # Scriptish formats 5 | *.bat text svneol=native#text/plain 6 | *.bsh text svneol=native#text/x-beanshell 7 | *.cgi text svneol=native#text/plain 8 | *.cmd text svneol=native#text/plain 9 | *.js text svneol=native#text/javascript 10 | *.php text svneol=native#text/x-php 11 | *.pl text svneol=native#text/x-perl 12 | *.pm text svneol=native#text/x-perl 13 | *.py text svneol=native#text/x-python 14 | *.sh eol=lf svneol=LF#text/x-sh 15 | configure eol=lf svneol=LF#text/x-sh 16 | 17 | # Image formats 18 | *.bmp binary svneol=unset#image/bmp 19 | *.gif binary svneol=unset#image/gif 20 | *.ico binary svneol=unset#image/ico 21 | *.jpeg binary svneol=unset#image/jpeg 22 | *.jpg binary svneol=unset#image/jpeg 23 | *.png binary svneol=unset#image/png 24 | *.tif binary svneol=unset#image/tiff 25 | *.tiff binary svneol=unset#image/tiff 26 | *.svg text svneol=native#image/svg%2Bxml 27 | 28 | # Data formats 29 | *.pdf binary svneol=unset#application/pdf 30 | *.avi binary svneol=unset#video/avi 31 | *.doc binary svneol=unset#application/msword 32 | *.dsp text svneol=crlf#text/plain 33 | *.dsw text svneol=crlf#text/plain 34 | *.eps binary svneol=unset#application/postscript 35 | *.gz binary svneol=unset#application/gzip 36 | *.mov binary svneol=unset#video/quicktime 37 | *.mp3 binary svneol=unset#audio/mpeg 38 | *.ppt binary svneol=unset#application/vnd.ms-powerpoint 39 | *.ps binary svneol=unset#application/postscript 40 | *.psd binary svneol=unset#application/photoshop 41 | *.rdf binary svneol=unset#text/rdf 42 | *.rss text svneol=unset#text/xml 43 | *.rtf binary svneol=unset#text/rtf 44 | *.sln text svneol=native#text/plain 45 | *.swf binary svneol=unset#application/x-shockwave-flash 46 | *.tgz binary svneol=unset#application/gzip 47 | *.vcproj text svneol=native#text/xml 48 | *.vcxproj text svneol=native#text/xml 49 | *.vsprops text svneol=native#text/xml 50 | *.wav binary svneol=unset#audio/wav 51 | *.xls binary svneol=unset#application/vnd.ms-excel 52 | *.zip binary svneol=unset#application/zip 53 | 54 | # Text formats 55 | .htaccess text svneol=native#text/plain 56 | *.bbk text svneol=native#text/xml 57 | *.cmake text svneol=native#text/plain 58 | *.css text svneol=native#text/css 59 | *.dtd text svneol=native#text/xml 60 | *.htm text svneol=native#text/html 61 | *.html text svneol=native#text/html 62 | *.ini text svneol=native#text/plain 63 | *.log text svneol=native#text/plain 64 | *.mak text svneol=native#text/plain 65 | *.qbk text svneol=native#text/plain 66 | *.rst text svneol=native#text/plain 67 | *.sql text svneol=native#text/x-sql 68 | *.txt text svneol=native#text/plain 69 | *.xhtml text svneol=native#text/xhtml%2Bxml 70 | *.xml text svneol=native#text/xml 71 | *.xsd text svneol=native#text/xml 72 | *.xsl text svneol=native#text/xml 73 | *.xslt text svneol=native#text/xml 74 | *.xul text svneol=native#text/xul 75 | *.yml text svneol=native#text/plain 76 | boost-no-inspect text svneol=native#text/plain 77 | CHANGES text svneol=native#text/plain 78 | COPYING text svneol=native#text/plain 79 | INSTALL text svneol=native#text/plain 80 | Jamfile text svneol=native#text/plain 81 | Jamroot text svneol=native#text/plain 82 | Jamfile.v2 text svneol=native#text/plain 83 | Jamrules text svneol=native#text/plain 84 | Makefile* text svneol=native#text/plain 85 | README text svneol=native#text/plain 86 | TODO text svneol=native#text/plain 87 | 88 | # Code formats 89 | *.c text svneol=native#text/plain 90 | *.cpp text svneol=native#text/plain 91 | *.h text svneol=native#text/plain 92 | *.hpp text svneol=native#text/plain 93 | *.ipp text svneol=native#text/plain 94 | *.pp text svneol=native#text/plain 95 | *.tpp text svneol=native#text/plain 96 | *.jam text svneol=native#text/plain 97 | *.java text svneol=native#text/plain 98 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/collect_unwind.ipp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_COLLECT_UNWIND_IPP 8 | #define BOOST_STACKTRACE_DETAIL_COLLECT_UNWIND_IPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | // On iOS 32-bit ARM architecture _Unwind_Backtrace function doesn't exist, symbol is undefined. 18 | // Forcing libc backtrace() function usage. 19 | #include 20 | #if defined(BOOST_OS_IOS_AVAILABLE) && defined(BOOST_ARCH_ARM_AVAILABLE) && BOOST_VERSION_NUMBER_MAJOR(BOOST_ARCH_ARM) < 8 21 | #define BOOST_STACKTRACE_USE_LIBC_BACKTRACE_FUNCTION 22 | #endif 23 | 24 | #if defined(BOOST_STACKTRACE_USE_LIBC_BACKTRACE_FUNCTION) 25 | #include 26 | #include 27 | #else 28 | #include 29 | #endif 30 | #include 31 | 32 | #if !defined(_GNU_SOURCE) && !defined(BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED) && !defined(BOOST_WINDOWS) 33 | #error "Boost.Stacktrace requires `_Unwind_Backtrace` function. Define `_GNU_SOURCE` macro or `BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED` if _Unwind_Backtrace is available without `_GNU_SOURCE`." 34 | #endif 35 | 36 | namespace boost { namespace stacktrace { namespace detail { 37 | 38 | #if !defined(BOOST_STACKTRACE_USE_LIBC_BACKTRACE_FUNCTION) 39 | struct unwind_state { 40 | std::size_t frames_to_skip; 41 | native_frame_ptr_t* current; 42 | native_frame_ptr_t* end; 43 | }; 44 | 45 | inline _Unwind_Reason_Code unwind_callback(::_Unwind_Context* context, void* arg) { 46 | // Note: do not write `::_Unwind_GetIP` because it is a macro on some platforms. 47 | // Use `_Unwind_GetIP` instead! 48 | unwind_state* const state = static_cast(arg); 49 | if (state->frames_to_skip) { 50 | --state->frames_to_skip; 51 | return _Unwind_GetIP(context) ? ::_URC_NO_REASON : ::_URC_END_OF_STACK; 52 | } 53 | 54 | *state->current = reinterpret_cast( 55 | _Unwind_GetIP(context) 56 | ); 57 | 58 | ++state->current; 59 | if (!*(state->current - 1) || state->current == state->end) { 60 | return ::_URC_END_OF_STACK; 61 | } 62 | return ::_URC_NO_REASON; 63 | } 64 | #endif //!defined(BOOST_STACKTRACE_USE_LIBC_BACKTRACE_FUNCTION) 65 | 66 | std::size_t this_thread_frames::collect(native_frame_ptr_t* out_frames, std::size_t max_frames_count, std::size_t skip) noexcept { 67 | std::size_t frames_count = 0; 68 | if (!max_frames_count) { 69 | return frames_count; 70 | } 71 | skip += 1; 72 | 73 | #if defined(BOOST_STACKTRACE_USE_LIBC_BACKTRACE_FUNCTION) 74 | // According to https://opensource.apple.com/source/Libc/Libc-1272.200.26/gen/backtrace.c.auto.html 75 | // it looks like the `::backtrace` is async signal safe. 76 | frames_count = static_cast(::backtrace(const_cast(out_frames), static_cast(max_frames_count))); 77 | 78 | // NOTE: There is no way to pass "skip" count to backtrace function so we need to perform left shift operation. 79 | // If number of elements in result backtrace is >= max_frames_count then "skip" elements are wasted. 80 | if (frames_count && skip) { 81 | if (skip >= frames_count) { 82 | frames_count = 0; 83 | } else { 84 | std::copy(out_frames + skip, out_frames + frames_count, out_frames); 85 | frames_count -= skip; 86 | } 87 | } 88 | #else 89 | boost::stacktrace::detail::unwind_state state = { skip, out_frames, out_frames + max_frames_count }; 90 | ::_Unwind_Backtrace(&boost::stacktrace::detail::unwind_callback, &state); 91 | frames_count = static_cast(state.current - out_frames); 92 | #endif //defined(BOOST_STACKTRACE_USE_LIBC_BACKTRACE_FUNCTION) 93 | 94 | if (frames_count && out_frames[frames_count - 1] == 0) { 95 | -- frames_count; 96 | } 97 | 98 | return frames_count; 99 | } 100 | 101 | 102 | }}} // namespace boost::stacktrace::detail 103 | 104 | #undef BOOST_STACKTRACE_USE_LIBC_BACKTRACE_FUNCTION 105 | 106 | #endif // BOOST_STACKTRACE_DETAIL_COLLECT_UNWIND_IPP 107 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/frame_decl.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_FRAME_DECL_HPP 8 | #define BOOST_STACKTRACE_DETAIL_FRAME_DECL_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | #include 17 | 18 | #include // boost::stacktrace::detail::native_frame_ptr_t 19 | #include 20 | 21 | #include 22 | 23 | /// @file boost/stacktrace/detail/frame_decl.hpp 24 | /// Use header instead of this one! 25 | 26 | namespace boost { namespace stacktrace { 27 | 28 | /// @class boost::stacktrace::frame boost/stacktrace/detail/frame_decl.hpp 29 | /// @brief Class that stores frame/function address and can get information about it at runtime. 30 | class frame { 31 | public: 32 | typedef boost::stacktrace::detail::native_frame_ptr_t native_frame_ptr_t; 33 | 34 | private: 35 | /// @cond 36 | native_frame_ptr_t addr_; 37 | /// @endcond 38 | 39 | public: 40 | /// @brief Constructs frame that references NULL address. 41 | /// Calls to source_file() and source_line() will return empty string. 42 | /// Calls to source_line() will return 0. 43 | /// 44 | /// @b Complexity: O(1). 45 | /// 46 | /// @b Async-Handler-Safety: Safe. 47 | /// @throws Nothing. 48 | constexpr frame() noexcept 49 | : addr_(0) 50 | {} 51 | 52 | #ifdef BOOST_STACKTRACE_DOXYGEN_INVOKED 53 | /// @brief Copy constructs frame. 54 | /// 55 | /// @b Complexity: O(1). 56 | /// 57 | /// @b Async-Handler-Safety: Safe. 58 | /// @throws Nothing. 59 | constexpr frame(const frame&) = default; 60 | 61 | /// @brief Copy assigns frame. 62 | /// 63 | /// @b Complexity: O(1). 64 | /// 65 | /// @b Async-Handler-Safety: Safe. 66 | /// @throws Nothing. 67 | constexpr frame& operator=(const frame&) = default; 68 | #endif 69 | 70 | /// @brief Constructs frame that references addr and could later generate information about that address using platform specific features. 71 | /// 72 | /// @b Complexity: O(1). 73 | /// 74 | /// @b Async-Handler-Safety: Safe. 75 | /// @throws Nothing. 76 | constexpr explicit frame(native_frame_ptr_t addr) noexcept 77 | : addr_(addr) 78 | {} 79 | 80 | /// @brief Constructs frame that references function_addr and could later generate information about that function using platform specific features. 81 | /// 82 | /// @b Complexity: O(1). 83 | /// 84 | /// @b Async-Handler-Safety: Safe. 85 | /// @throws Nothing. 86 | template 87 | explicit frame(T* function_addr) noexcept 88 | : addr_(boost::stacktrace::detail::void_ptr_cast(function_addr)) 89 | {} 90 | 91 | /// @returns Name of the frame (function name in a human readable form). 92 | /// 93 | /// @b Complexity: unknown (lots of platform specific work). 94 | /// 95 | /// @b Async-Handler-Safety: Unsafe. 96 | /// @throws std::bad_alloc if not enough memory to construct resulting string. 97 | BOOST_STACKTRACE_FUNCTION std::string name() const; 98 | 99 | /// @returns Address of the frame function. 100 | /// 101 | /// @b Complexity: O(1). 102 | /// 103 | /// @b Async-Handler-Safety: Safe. 104 | /// @throws Nothing. 105 | constexpr native_frame_ptr_t address() const noexcept { 106 | return addr_; 107 | } 108 | 109 | /// @returns Path to the source file, were the function of the frame is defined. Returns empty string 110 | /// if this->source_line() == 0. 111 | /// @throws std::bad_alloc if not enough memory to construct resulting string. 112 | /// 113 | /// @b Complexity: unknown (lots of platform specific work). 114 | /// 115 | /// @b Async-Handler-Safety: Unsafe. 116 | BOOST_STACKTRACE_FUNCTION std::string source_file() const; 117 | 118 | /// @returns Code line in the source file, were the function of the frame is defined. 119 | /// @throws std::bad_alloc if not enough memory to construct string for internal needs. 120 | /// 121 | /// @b Complexity: unknown (lots of platform specific work). 122 | /// 123 | /// @b Async-Handler-Safety: Unsafe. 124 | BOOST_STACKTRACE_FUNCTION std::size_t source_line() const; 125 | 126 | /// @brief Checks that frame is not references NULL address. 127 | /// @returns `true` if `this->address() != 0` 128 | /// 129 | /// @b Complexity: O(1) 130 | /// 131 | /// @b Async-Handler-Safety: Safe. 132 | constexpr explicit operator bool () const noexcept { return !empty(); } 133 | 134 | /// @brief Checks that frame references NULL address. 135 | /// @returns `true` if `this->address() == 0` 136 | /// 137 | /// @b Complexity: O(1) 138 | /// 139 | /// @b Async-Handler-Safety: Safe. 140 | constexpr bool empty() const noexcept { return !address(); } 141 | }; 142 | 143 | 144 | namespace detail { 145 | BOOST_STACKTRACE_FUNCTION std::string to_string(const frame* frames, std::size_t size); 146 | } // namespace detail 147 | 148 | }} // namespace boost::stacktrace 149 | 150 | 151 | #include 152 | 153 | #endif // BOOST_STACKTRACE_DETAIL_FRAME_DECL_HPP 154 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2020, 2021 Peter Dimov 2 | # Distributed under the Boost Software License, Version 1.0. 3 | # https://www.boost.org/LICENSE_1_0.txt 4 | 5 | cmake_minimum_required(VERSION 3.8...4.20) 6 | 7 | project(boost_stacktrace VERSION "${BOOST_SUPERPROJECT_VERSION}" LANGUAGES CXX) 8 | 9 | function(stacktrace_add_library suffix opt libs defs) 10 | 11 | if(NOT opt) 12 | return() 13 | endif() 14 | 15 | add_library(boost_stacktrace_${suffix} 16 | src/${suffix}.cpp 17 | ) 18 | 19 | add_library(Boost::stacktrace_${suffix} ALIAS boost_stacktrace_${suffix}) 20 | 21 | target_include_directories(boost_stacktrace_${suffix} PUBLIC include) 22 | 23 | target_link_libraries(boost_stacktrace_${suffix} 24 | PUBLIC 25 | Boost::config 26 | Boost::container_hash 27 | Boost::core 28 | Boost::predef 29 | Boost::winapi 30 | PRIVATE 31 | ${libs} 32 | ) 33 | 34 | target_compile_definitions(boost_stacktrace_${suffix} 35 | PUBLIC BOOST_STACKTRACE_NO_LIB 36 | PRIVATE BOOST_STACKTRACE_SOURCE ${defs} 37 | ) 38 | 39 | if(BUILD_SHARED_LIBS) 40 | target_compile_definitions(boost_stacktrace_${suffix} PUBLIC BOOST_STACKTRACE_DYN_LINK) 41 | else() 42 | target_compile_definitions(boost_stacktrace_${suffix} PUBLIC BOOST_STACKTRACE_STATIC_LINK) 43 | endif() 44 | 45 | if(BOOST_SUPERPROJECT_VERSION AND NOT CMAKE_VERSION VERSION_LESS 3.13) 46 | boost_install(TARGETS boost_stacktrace_${suffix} VERSION ${BOOST_SUPERPROJECT_VERSION} HEADER_DIRECTORY include) 47 | endif() 48 | 49 | endfunction() 50 | 51 | include(CheckCXXSourceCompiles) 52 | 53 | function(stacktrace_check var source incs libs defs) 54 | 55 | set(CMAKE_REQUIRED_INCLUDES "${incs}") 56 | list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/build") 57 | set(CMAKE_REQUIRED_LIBRARIES "${libs}") 58 | set(CMAKE_REQUIRED_DEFINITIONS "${defs}") 59 | check_cxx_source_compiles("#include \"${source}\"" ${var}) 60 | set(${var} ${${var}} PARENT_SCOPE) 61 | 62 | endfunction() 63 | 64 | stacktrace_check(BOOST_STACKTRACE_HAS_BACKTRACE has_backtrace.cpp "" "backtrace" "") 65 | 66 | set(_default_addr2line ON) 67 | if(WIN32 AND NOT CMAKE_CXX_PLATFORM_ID MATCHES "Cygwin") 68 | set(_default_addr2line OFF) 69 | endif() 70 | 71 | stacktrace_check(BOOST_STACKTRACE_HAS_WINDBG has_windbg.cpp "" "dbgeng;ole32" "") 72 | stacktrace_check(BOOST_STACKTRACE_HAS_WINDBG_CACHED has_windbg_cached.cpp "${CMAKE_CURRENT_SOURCE_DIR}/../config/include" "dbgeng;ole32" "") 73 | 74 | set(_default_from_exception ON) 75 | if (CMAKE_CXX_PLATFORM_ID MATCHES "Cygwin") 76 | set(_default_from_exception OFF) 77 | endif() 78 | 79 | option(BOOST_STACKTRACE_ENABLE_NOOP "Boost.Stacktrace: build boost_stacktrace_noop" ON) 80 | option(BOOST_STACKTRACE_ENABLE_BACKTRACE "Boost.Stacktrace: build boost_stacktrace_backtrace" ${BOOST_STACKTRACE_HAS_BACKTRACE}) 81 | option(BOOST_STACKTRACE_ENABLE_ADDR2LINE "Boost.Stacktrace: build boost_stacktrace_addr2line" ${_default_addr2line}) 82 | option(BOOST_STACKTRACE_ENABLE_BASIC "Boost.Stacktrace: build boost_stacktrace_basic" ON) 83 | option(BOOST_STACKTRACE_ENABLE_WINDBG "Boost.Stacktrace: build boost_stacktrace_windbg" ${BOOST_STACKTRACE_HAS_WINDBG}) 84 | option(BOOST_STACKTRACE_ENABLE_WINDBG_CACHED "Boost.Stacktrace: build boost_stacktrace_windbg_cached" ${BOOST_STACKTRACE_HAS_WINDBG_CACHED}) 85 | option(BOOST_STACKTRACE_ENABLE_FROM_EXCEPTION "Boost.Stacktrace: build boost_stacktrace_from_exception" ${_default_from_exception}) 86 | 87 | unset(_default_addr2line) 88 | unset(_default_from_exception) 89 | 90 | message(STATUS "Boost.Stacktrace: " 91 | "noop ${BOOST_STACKTRACE_ENABLE_NOOP}, " 92 | "backtrace ${BOOST_STACKTRACE_ENABLE_BACKTRACE}, " 93 | "addr2line ${BOOST_STACKTRACE_ENABLE_ADDR2LINE}, " 94 | "basic ${BOOST_STACKTRACE_ENABLE_BASIC}, " 95 | "windbg ${BOOST_STACKTRACE_ENABLE_WINDBG}, " 96 | "windbg_cached ${BOOST_STACKTRACE_ENABLE_WINDBG_CACHED}, " 97 | "from_exception ${BOOST_STACKTRACE_ENABLE_FROM_EXCEPTION}" 98 | ) 99 | 100 | stacktrace_add_library(noop ${BOOST_STACKTRACE_ENABLE_NOOP} "" "") 101 | stacktrace_add_library(backtrace ${BOOST_STACKTRACE_ENABLE_BACKTRACE} "backtrace;${CMAKE_DL_LIBS}" "") 102 | stacktrace_add_library(addr2line ${BOOST_STACKTRACE_ENABLE_ADDR2LINE} "${CMAKE_DL_LIBS}" "") 103 | stacktrace_add_library(basic ${BOOST_STACKTRACE_ENABLE_BASIC} "${CMAKE_DL_LIBS}" "") 104 | stacktrace_add_library(windbg ${BOOST_STACKTRACE_ENABLE_WINDBG} "dbgeng;ole32" "_GNU_SOURCE=1") 105 | stacktrace_add_library(windbg_cached ${BOOST_STACKTRACE_ENABLE_WINDBG_CACHED} "dbgeng;ole32" "_GNU_SOURCE=1") 106 | 107 | # boost_stacktrace, default library 108 | 109 | add_library(boost_stacktrace INTERFACE) 110 | add_library(Boost::stacktrace ALIAS boost_stacktrace) 111 | 112 | target_include_directories(boost_stacktrace INTERFACE include) 113 | 114 | if(BOOST_STACKTRACE_ENABLE_WINDBG) 115 | 116 | target_link_libraries(boost_stacktrace INTERFACE Boost::stacktrace_windbg) 117 | 118 | elseif(BOOST_STACKTRACE_ENABLE_WINDBG_CACHED) 119 | 120 | target_link_libraries(boost_stacktrace INTERFACE Boost::stacktrace_windbg) 121 | 122 | elseif(BOOST_STACKTRACE_ENABLE_BACKTRACE) 123 | 124 | target_link_libraries(boost_stacktrace INTERFACE Boost::stacktrace_backtrace) 125 | 126 | elseif(BOOST_STACKTRACE_ENABLE_ADDR2LINE) 127 | 128 | target_link_libraries(boost_stacktrace INTERFACE Boost::stacktrace_addr2line) 129 | 130 | elseif(BOOST_STACKTRACE_ENABLE_BASIC) 131 | 132 | target_link_libraries(boost_stacktrace INTERFACE Boost::stacktrace_basic) 133 | 134 | elseif(BOOST_STACKTRACE_ENABLE_NOOP) 135 | 136 | target_link_libraries(boost_stacktrace INTERFACE Boost::stacktrace_noop) 137 | 138 | endif() 139 | 140 | # Boost::stacktrace_from_exception is never the default 141 | stacktrace_add_library(from_exception ${BOOST_STACKTRACE_ENABLE_FROM_EXCEPTION} "${CMAKE_DL_LIBS};boost_stacktrace" "") 142 | 143 | # 144 | 145 | if(BUILD_TESTING) 146 | add_subdirectory(test) 147 | endif() 148 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/location_from_symbol.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_LOCATION_FROM_SYMBOL_HPP 8 | #define BOOST_STACKTRACE_DETAIL_LOCATION_FROM_SYMBOL_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #if !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) 16 | # include 17 | #else 18 | # include 19 | #endif 20 | 21 | #ifdef _AIX 22 | /* AIX doesn't provide dladdr syscall. 23 | This provides a minimal implementation of dladdr which retrieves 24 | only files information. 25 | TODO: Implement the symbol name. */ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | namespace boost { namespace stacktrace { namespace detail { 34 | 35 | struct Dl_info { 36 | std::string fname_storage{}; 37 | const char *dli_fname = nullptr; 38 | const char *dli_sname = nullptr; 39 | }; 40 | 41 | int dladdr(const void* address_raw, Dl_info* info) noexcept { 42 | static constexpr std::size_t dl_buff_size = 0x1000; 43 | 44 | try { 45 | std::vector pld_info_storage; 46 | pld_info_storage.resize( 47 | (dl_buff_size + sizeof(struct ld_info) - 1) / sizeof(struct ld_info) 48 | ); 49 | 50 | if (loadquery(L_GETINFO, pld_info_storage.data(), dl_buff_size) == -1) { 51 | return 0; 52 | } 53 | 54 | const auto* pld_info = pld_info_storage.data(); 55 | const char* const address = static_cast(address_raw); 56 | while (true) { 57 | const auto* const dataorg = static_cast(pld_info->ldinfo_dataorg); 58 | const auto* const textorg = static_cast(pld_info->ldinfo_textorg); 59 | if ((address >= dataorg && address < dataorg + pld_info->ldinfo_datasize ) 60 | || (address >= textorg && address < textorg + pld_info->ldinfo_textsize )) { 61 | 62 | /* ldinfo_filename is the null-terminated path name followed 63 | by null-terminated member name. 64 | If the file is not an archive, then member name is null. */ 65 | const auto size_filename = std::strlen(pld_info->ldinfo_filename); 66 | const auto size_member = std::strlen(pld_info->ldinfo_filename + size_filename + 1); 67 | 68 | /* If member is not null, '(' and ')' must be added to create a 69 | fname looking like "filename(membername)". */ 70 | info->fname_storage.reserve(size_filename + (size_member ? size_member + 3 : 1)); 71 | info->fname_storage = pld_info->ldinfo_filename; 72 | if (size_member) { 73 | info->fname_storage += "("; 74 | info->fname_storage += pld_info->ldinfo_filename + size_filename + 1; 75 | info->fname_storage += ")"; 76 | } 77 | 78 | info->dli_fname = info->fname_storage.c_str(); 79 | return 1; 80 | } 81 | 82 | if (!pld_info->ldinfo_next) { 83 | break; 84 | } 85 | 86 | pld_info = reinterpret_cast( 87 | reinterpret_cast(pld_info) + pld_info->ldinfo_next 88 | ); 89 | }; 90 | } catch (...) { 91 | // ignore 92 | } 93 | 94 | return 0; 95 | } 96 | 97 | }}} // namespace boost::stacktrace::detail 98 | 99 | #elif !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) 100 | 101 | namespace boost { namespace stacktrace { namespace detail { 102 | 103 | using Dl_info = ::Dl_info; 104 | 105 | inline int dladdr(const void* addr, Dl_info& dli) noexcept { 106 | // `dladdr` on Solaris accepts nonconst addresses 107 | return ::dladdr(const_cast(addr), &dli); 108 | } 109 | 110 | }}} // namespace boost::stacktrace::detail 111 | 112 | #endif 113 | 114 | namespace boost { namespace stacktrace { namespace detail { 115 | 116 | #if !defined(BOOST_WINDOWS) && !defined(__CYGWIN__) 117 | class location_from_symbol { 118 | boost::stacktrace::detail::Dl_info dli_; 119 | 120 | public: 121 | explicit location_from_symbol(const void* addr) noexcept 122 | : dli_() 123 | { 124 | if (!boost::stacktrace::detail::dladdr(addr, dli_)) { 125 | dli_.dli_fname = 0; 126 | } 127 | } 128 | 129 | bool empty() const noexcept { 130 | return !dli_.dli_fname; 131 | } 132 | 133 | const char* name() const noexcept { 134 | return dli_.dli_fname; 135 | } 136 | }; 137 | 138 | class program_location { 139 | public: 140 | const char* name() const noexcept { 141 | return 0; 142 | } 143 | }; 144 | 145 | #else 146 | 147 | class location_from_symbol { 148 | BOOST_STATIC_CONSTEXPR boost::winapi::DWORD_ DEFAULT_PATH_SIZE_ = 260; 149 | char file_name_[DEFAULT_PATH_SIZE_]; 150 | 151 | public: 152 | explicit location_from_symbol(const void* addr) noexcept { 153 | file_name_[0] = '\0'; 154 | 155 | boost::winapi::MEMORY_BASIC_INFORMATION_ mbi; 156 | if (!boost::winapi::VirtualQuery(addr, &mbi, sizeof(mbi))) { 157 | return; 158 | } 159 | 160 | boost::winapi::HMODULE_ handle = reinterpret_cast(mbi.AllocationBase); 161 | if (!boost::winapi::GetModuleFileNameA(handle, file_name_, DEFAULT_PATH_SIZE_)) { 162 | file_name_[0] = '\0'; 163 | return; 164 | } 165 | } 166 | 167 | bool empty() const noexcept { 168 | return file_name_[0] == '\0'; 169 | } 170 | 171 | const char* name() const noexcept { 172 | return file_name_; 173 | } 174 | }; 175 | 176 | class program_location { 177 | BOOST_STATIC_CONSTEXPR boost::winapi::DWORD_ DEFAULT_PATH_SIZE_ = 260; 178 | char file_name_[DEFAULT_PATH_SIZE_]; 179 | 180 | public: 181 | program_location() noexcept { 182 | file_name_[0] = '\0'; 183 | 184 | const boost::winapi::HMODULE_ handle = 0; 185 | if (!boost::winapi::GetModuleFileNameA(handle, file_name_, DEFAULT_PATH_SIZE_)) { 186 | file_name_[0] = '\0'; 187 | } 188 | } 189 | 190 | const char* name() const noexcept { 191 | return file_name_[0] ? file_name_ : 0; 192 | } 193 | }; 194 | #endif 195 | 196 | }}} // namespace boost::stacktrace::detail 197 | 198 | #endif // BOOST_STACKTRACE_DETAIL_LOCATION_FROM_SYMBOL_HPP 199 | -------------------------------------------------------------------------------- /test/test_from_exception.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2023-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | namespace boost { namespace stacktrace { namespace impl { 15 | void assert_no_pending_traces() noexcept; 16 | }}} 17 | 18 | using boost::stacktrace::stacktrace; 19 | 20 | struct test_no_pending_on_finish { 21 | ~test_no_pending_on_finish() { 22 | boost::stacktrace::impl::assert_no_pending_traces(); 23 | } 24 | }; 25 | 26 | 27 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_throw_1(const char* msg) { 28 | std::string new_msg{msg}; 29 | throw std::runtime_error(new_msg); 30 | } 31 | 32 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_throw_2(const char* msg) { 33 | std::string new_msg{msg}; 34 | throw std::logic_error(new_msg); 35 | } 36 | 37 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_rethrow_1(const char* msg) { 38 | try { 39 | in_test_throw_1(msg); 40 | } catch (const std::exception&) { 41 | throw; 42 | } 43 | } 44 | 45 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void in_test_rethrow_2(const char* msg) { 46 | try { 47 | in_test_throw_2(msg); 48 | } catch (const std::exception&) { 49 | try { 50 | in_test_throw_1(msg); 51 | } catch (const std::exception&) {} 52 | 53 | throw; 54 | } 55 | } 56 | 57 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_no_exception() { 58 | auto trace = stacktrace::from_current_exception(); 59 | BOOST_TEST(!trace); 60 | } 61 | 62 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_trace_from_exception() { 63 | // const test_no_pending_on_finish guard{}; // something strange 64 | try { 65 | in_test_throw_1("testing basic"); 66 | } catch (const std::exception&) { 67 | auto trace = stacktrace::from_current_exception(); 68 | BOOST_TEST(trace); 69 | std::cout << "Tarce in test_trace_from_exception(): " << trace << '\n'; 70 | BOOST_TEST(to_string(trace).find("in_test_throw_1") != std::string::npos); 71 | } 72 | } 73 | 74 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_after_other_exception() { 75 | try { 76 | in_test_throw_1("test_other_exception_active"); 77 | } catch (const std::exception&) { 78 | try { 79 | in_test_throw_2("test_other_exception_active 2"); 80 | } catch (const std::exception&) {} 81 | 82 | auto trace = stacktrace::from_current_exception(); 83 | BOOST_TEST(trace); 84 | std::cout << "Tarce in test_after_other_exception(): " << trace; 85 | BOOST_TEST(to_string(trace).find("in_test_throw_1") != std::string::npos); 86 | BOOST_TEST(to_string(trace).find("in_test_throw_2") == std::string::npos); 87 | } 88 | } 89 | 90 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_rethrow() { 91 | try { 92 | in_test_rethrow_1("test rethrow"); 93 | } catch (const std::exception&) { 94 | auto trace = stacktrace::from_current_exception(); 95 | BOOST_TEST(trace); 96 | std::cout << "Tarce in test_rethrow(): " << trace << '\n'; 97 | BOOST_TEST(to_string(trace).find("in_test_throw_1") != std::string::npos); 98 | BOOST_TEST(to_string(trace).find("in_test_rethrow_1") != std::string::npos); 99 | } 100 | } 101 | 102 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_rethrow_after_other_exception() { 103 | try { 104 | in_test_rethrow_2("test_rethrow_after_other_exception"); 105 | } catch (const std::exception&) { 106 | auto trace = stacktrace::from_current_exception(); 107 | BOOST_TEST(trace); 108 | std::cout << "Tarce in test_rethrow_after_other_exception(): " << trace << '\n'; 109 | BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos); 110 | BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos); 111 | BOOST_TEST(to_string(trace).find("in_test_rethrow_2") != std::string::npos); 112 | } 113 | } 114 | 115 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_nested() { 116 | try { 117 | in_test_throw_1("test_other_exception_active"); 118 | } catch (const std::exception&) { 119 | try { 120 | in_test_throw_2("test_other_exception_active 2"); 121 | } catch (const std::exception&) { 122 | auto trace = stacktrace::from_current_exception(); 123 | BOOST_TEST(trace); 124 | std::cout << "Tarce in test_nested(): " << trace << '\n'; 125 | BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos); 126 | BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos); 127 | } 128 | } 129 | } 130 | 131 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_rethrow_nested() { 132 | std::exception_ptr ptr; 133 | 134 | try { 135 | in_test_throw_1("test_other_exception_active"); 136 | } catch (const std::exception&) { 137 | try { 138 | in_test_throw_2("test_other_exception_active 2"); 139 | } catch (const std::exception&) { 140 | ptr = std::current_exception(); 141 | } 142 | } 143 | 144 | try { 145 | std::rethrow_exception(ptr); 146 | } catch (...) { 147 | auto trace = stacktrace::from_current_exception(); 148 | BOOST_TEST(trace); 149 | std::cout << "Tarce in test_rethrow_nested(): " << trace << '\n'; 150 | BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos); 151 | #if defined(BOOST_MSVC) 152 | BOOST_TEST(to_string(trace).find("in_test_throw_2") == std::string::npos); 153 | #else 154 | BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos); 155 | #endif 156 | } 157 | } 158 | 159 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE void test_from_other_thread() { 160 | 161 | // MinGW error: 'thread' is not a member of 'std' 162 | #ifndef __MINGW32__ 163 | std::exception_ptr ptr; 164 | 165 | std::thread t([&ptr]{ 166 | try { 167 | in_test_throw_1("test_other_exception_active"); 168 | } catch (const std::exception&) { 169 | try { 170 | in_test_throw_2("test_other_exception_active 2"); 171 | } catch (const std::exception&) { 172 | ptr = std::current_exception(); 173 | } 174 | } 175 | }); 176 | t.join(); 177 | 178 | try { 179 | std::rethrow_exception(ptr); 180 | } catch (...) { 181 | auto trace = stacktrace::from_current_exception(); 182 | BOOST_TEST(trace); 183 | std::cout << "Tarce in test_rethrow_nested(): " << trace << '\n'; 184 | BOOST_TEST(to_string(trace).find("in_test_throw_1") == std::string::npos); 185 | #if defined(BOOST_MSVC) 186 | BOOST_TEST(to_string(trace).find("in_test_throw_2") == std::string::npos); 187 | #else 188 | BOOST_TEST(to_string(trace).find("in_test_throw_2") != std::string::npos); 189 | #endif 190 | } 191 | #endif 192 | } 193 | 194 | int main() { 195 | const test_no_pending_on_finish guard{}; 196 | 197 | BOOST_TEST(boost::stacktrace::this_thread::get_capture_stacktraces_at_throw()); 198 | 199 | test_no_exception(); 200 | test_trace_from_exception(); 201 | test_after_other_exception(); 202 | test_rethrow(); 203 | test_rethrow_after_other_exception(); 204 | test_nested(); 205 | test_rethrow_nested(); 206 | test_from_other_thread(); 207 | 208 | return boost::report_errors(); 209 | } 210 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/libbacktrace_impls.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_LIBBACKTRACE_IMPLS_HPP 8 | #define BOOST_STACKTRACE_DETAIL_LIBBACKTRACE_IMPLS_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #ifdef BOOST_STACKTRACE_BACKTRACE_INCLUDE_FILE 21 | # include BOOST_STACKTRACE_BACKTRACE_INCLUDE_FILE 22 | #else 23 | # include 24 | #endif 25 | 26 | namespace boost { namespace stacktrace { namespace detail { 27 | 28 | 29 | struct pc_data { 30 | std::string* function; 31 | std::string* filename; 32 | std::size_t line; 33 | }; 34 | 35 | inline void libbacktrace_syminfo_callback(void *data, uintptr_t /*pc*/, const char *symname, uintptr_t /*symval*/, uintptr_t /*symsize*/) { 36 | pc_data& d = *static_cast(data); 37 | if (d.function && symname) { 38 | *d.function = symname; 39 | } 40 | } 41 | 42 | // Old versions of libbacktrace have different signature for the callback 43 | inline void libbacktrace_syminfo_callback(void *data, uintptr_t pc, const char *symname, uintptr_t symval) { 44 | boost::stacktrace::detail::libbacktrace_syminfo_callback(data, pc, symname, symval, 0); 45 | } 46 | 47 | inline int libbacktrace_full_callback(void *data, uintptr_t /*pc*/, const char *filename, int lineno, const char *function) { 48 | pc_data& d = *static_cast(data); 49 | if (d.filename && filename) { 50 | *d.filename = filename; 51 | } 52 | if (d.function && function) { 53 | *d.function = function; 54 | } 55 | d.line = static_cast(lineno); 56 | return 0; 57 | } 58 | 59 | inline void libbacktrace_error_callback(void* /*data*/, const char* /*msg*/, int /*errnum*/) noexcept { 60 | // Do nothing, just return. 61 | } 62 | 63 | // Not async-signal-safe, so this method is not called from async-safe functions. 64 | // 65 | // This function is not async signal safe because: 66 | // * Dynamic initialization of a block-scope variable with static storage duration could lock a mutex 67 | // * No guarantees on `backtrace_create_state` function. 68 | // 69 | // Currently `backtrace_create_state` can not detect file name on Windows https://gcc.gnu.org/bugzilla/show_bug.cgi?id=82543 70 | // That's why we provide a `prog_location` here. 71 | BOOST_SYMBOL_VISIBLE inline ::backtrace_state* construct_state(const program_location& prog_location) noexcept { 72 | // [dcl.inline]: A static local variable in an inline function with external linkage always refers to the same object. 73 | 74 | // TODO: The most obvious solution: 75 | // 76 | //static ::backtrace_state* state = ::backtrace_create_state( 77 | // prog_location.name(), 78 | // 1, // allow safe concurrent usage of the same state 79 | // boost::stacktrace::detail::libbacktrace_error_callback, 80 | // 0 // pointer to data that will be passed to callback 81 | //); 82 | // 83 | // 84 | // Unfortunately, that solution segfaults when `construct_state()` function is in .so file 85 | // and multiple threads concurrently work with state. I failed to localize the root cause: 86 | // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87653 87 | 88 | #define BOOST_STACKTRACE_DETAIL_IS_MT 1 89 | 90 | #if !defined(BOOST_HAS_THREADS) 91 | # define BOOST_STACKTRACE_DETAIL_STORAGE static 92 | # undef BOOST_STACKTRACE_DETAIL_IS_MT 93 | # define BOOST_STACKTRACE_DETAIL_IS_MT 0 94 | #elif defined(BOOST_STACKTRACE_BACKTRACE_FORCE_STATIC) 95 | # define BOOST_STACKTRACE_DETAIL_STORAGE static 96 | #elif !defined(BOOST_NO_CXX11_THREAD_LOCAL) 97 | # define BOOST_STACKTRACE_DETAIL_STORAGE thread_local 98 | #elif defined(__GNUC__) && !defined(__clang__) 99 | # define BOOST_STACKTRACE_DETAIL_STORAGE static __thread 100 | #else 101 | # define BOOST_STACKTRACE_DETAIL_STORAGE /* just a local variable */ 102 | #endif 103 | 104 | BOOST_STACKTRACE_DETAIL_STORAGE ::backtrace_state* state = ::backtrace_create_state( 105 | prog_location.name(), 106 | BOOST_STACKTRACE_DETAIL_IS_MT, 107 | boost::stacktrace::detail::libbacktrace_error_callback, 108 | 0 109 | ); 110 | 111 | #undef BOOST_STACKTRACE_DETAIL_IS_MT 112 | #undef BOOST_STACKTRACE_DETAIL_STORAGE 113 | 114 | return state; 115 | } 116 | 117 | struct to_string_using_backtrace { 118 | std::string res; 119 | boost::stacktrace::detail::program_location prog_location; 120 | ::backtrace_state* state; 121 | std::string filename; 122 | std::size_t line; 123 | 124 | void prepare_function_name(const void* addr) { 125 | boost::stacktrace::detail::pc_data data = {&res, &filename, 0}; 126 | if (state) { 127 | ::backtrace_pcinfo( 128 | state, 129 | reinterpret_cast(addr), 130 | boost::stacktrace::detail::libbacktrace_full_callback, 131 | boost::stacktrace::detail::libbacktrace_error_callback, 132 | &data 133 | ) 134 | || 135 | ::backtrace_syminfo( 136 | state, 137 | reinterpret_cast(addr), 138 | boost::stacktrace::detail::libbacktrace_syminfo_callback, 139 | boost::stacktrace::detail::libbacktrace_error_callback, 140 | &data 141 | ); 142 | } 143 | line = data.line; 144 | } 145 | 146 | bool prepare_source_location(const void* /*addr*/) { 147 | if (filename.empty() || !line) { 148 | return false; 149 | } 150 | 151 | res += " at "; 152 | res += filename; 153 | res += ':'; 154 | res += boost::stacktrace::detail::to_dec_array(line).data(); 155 | return true; 156 | } 157 | 158 | to_string_using_backtrace() noexcept { 159 | state = boost::stacktrace::detail::construct_state(prog_location); 160 | } 161 | }; 162 | 163 | template class to_string_impl_base; 164 | typedef to_string_impl_base to_string_impl; 165 | 166 | inline std::string name_impl(const void* addr) { 167 | std::string res; 168 | 169 | boost::stacktrace::detail::program_location prog_location; 170 | ::backtrace_state* state = boost::stacktrace::detail::construct_state(prog_location); 171 | 172 | boost::stacktrace::detail::pc_data data = {&res, 0, 0}; 173 | if (state) { 174 | ::backtrace_pcinfo( 175 | state, 176 | reinterpret_cast(addr), 177 | boost::stacktrace::detail::libbacktrace_full_callback, 178 | boost::stacktrace::detail::libbacktrace_error_callback, 179 | &data 180 | ) 181 | || 182 | ::backtrace_syminfo( 183 | state, 184 | reinterpret_cast(addr), 185 | boost::stacktrace::detail::libbacktrace_syminfo_callback, 186 | boost::stacktrace::detail::libbacktrace_error_callback, 187 | &data 188 | ); 189 | } 190 | if (!res.empty()) { 191 | res = boost::core::demangle(res.c_str()); 192 | } 193 | 194 | return res; 195 | } 196 | 197 | } // namespace detail 198 | 199 | std::string frame::source_file() const { 200 | std::string res; 201 | 202 | if (!addr_) { 203 | return res; 204 | } 205 | 206 | boost::stacktrace::detail::program_location prog_location; 207 | ::backtrace_state* state = boost::stacktrace::detail::construct_state(prog_location); 208 | 209 | boost::stacktrace::detail::pc_data data = {0, &res, 0}; 210 | if (state) { 211 | ::backtrace_pcinfo( 212 | state, 213 | reinterpret_cast(addr_), 214 | boost::stacktrace::detail::libbacktrace_full_callback, 215 | boost::stacktrace::detail::libbacktrace_error_callback, 216 | &data 217 | ); 218 | } 219 | 220 | return res; 221 | } 222 | 223 | std::size_t frame::source_line() const { 224 | if (!addr_) { 225 | return 0; 226 | } 227 | 228 | boost::stacktrace::detail::program_location prog_location; 229 | ::backtrace_state* state = boost::stacktrace::detail::construct_state(prog_location); 230 | 231 | boost::stacktrace::detail::pc_data data = {0, 0, 0}; 232 | if (state) { 233 | ::backtrace_pcinfo( 234 | state, 235 | reinterpret_cast(addr_), 236 | boost::stacktrace::detail::libbacktrace_full_callback, 237 | boost::stacktrace::detail::libbacktrace_error_callback, 238 | &data 239 | ); 240 | } 241 | 242 | return data.line; 243 | } 244 | 245 | 246 | }} // namespace boost::stacktrace 247 | 248 | #endif // BOOST_STACKTRACE_DETAIL_LIBBACKTRACE_IMPLS_HPP 249 | -------------------------------------------------------------------------------- /example/terminate_handler.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | BOOST_NOINLINE void foo(int i); 9 | BOOST_NOINLINE void bar(int i); 10 | 11 | BOOST_NOINLINE void bar(int i) { 12 | boost::array a = {{-1, -231, -123, -23, -32}}; 13 | if (i >= 0) { 14 | foo(a[i]); 15 | } else { 16 | std::terminate(); 17 | } 18 | } 19 | 20 | BOOST_NOINLINE void foo(int i) { 21 | bar(--i); 22 | } 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 25 | 26 | //[getting_started_signal_handlers 27 | 28 | #include // ::signal, ::raise 29 | #include 30 | 31 | void my_signal_handler(int signum) { 32 | ::signal(signum, SIG_DFL); 33 | 34 | // Outputs nothing or trash on majority of platforms 35 | boost::stacktrace::safe_dump_to("./backtrace.dump"); 36 | 37 | ::raise(SIGABRT); 38 | } 39 | //] 40 | 41 | void setup_handlers() { 42 | //[getting_started_setup_signel_handlers 43 | ::signal(SIGSEGV, &my_signal_handler); 44 | ::signal(SIGABRT, &my_signal_handler); 45 | //] 46 | } 47 | 48 | 49 | //[getting_started_terminate_handlers 50 | #include // std::abort 51 | #include // std::set_terminate 52 | #include // std::cerr 53 | 54 | #include 55 | 56 | void my_terminate_handler() { 57 | try { 58 | std::cerr << boost::stacktrace::stacktrace(); 59 | } catch (...) {} 60 | std::abort(); 61 | } 62 | //] 63 | 64 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 65 | 66 | #include // std::cerr 67 | #include // std::ifstream 68 | #include 69 | #include 70 | 71 | #ifndef BOOST_WINDOWS 72 | inline void copy_and_run(const char* exec_name, char param, bool not_null) { 73 | std::cout << "Running with param " << param << std::endl; 74 | boost::filesystem::path command = exec_name; 75 | command = command.parent_path() / (command.stem().string() + param + command.extension().string()); 76 | boost::filesystem::copy_file(exec_name, command, boost::filesystem::copy_options::overwrite_existing); 77 | 78 | boost::filesystem::path command_args = command; 79 | command_args += ' '; 80 | command_args += param; 81 | const int ret = std::system(command_args.string().c_str()); 82 | 83 | std::cout << "End Running with param " << param << "; ret code is " << ret << std::endl; 84 | boost::system::error_code ignore; 85 | boost::filesystem::remove(command, ignore); 86 | if (not_null && !ret) { 87 | std::exit(97); 88 | } else if (!not_null && ret) { 89 | std::exit(ret); 90 | } 91 | } 92 | #endif 93 | 94 | int run_0(const char* /*argv*/[]) { 95 | //[getting_started_setup_terminate_handlers 96 | std::set_terminate(&my_terminate_handler); 97 | //] 98 | foo(5); 99 | return 1; 100 | } 101 | 102 | 103 | int run_1(const char* /*argv*/[]) { 104 | setup_handlers(); 105 | foo(5); 106 | return 11; 107 | } 108 | 109 | int run_2(const char* argv[]) { 110 | if (!boost::filesystem::exists("./backtrace.dump")) { 111 | if (std::string(argv[0]).find("noop") == std::string::npos) { 112 | return 21; 113 | } 114 | 115 | boost::stacktrace::stacktrace st = boost::stacktrace::stacktrace::from_dump(std::cin); 116 | if (st) { 117 | return 22; 118 | } 119 | return 0; 120 | } 121 | 122 | //[getting_started_on_program_restart 123 | if (boost::filesystem::exists("./backtrace.dump")) { 124 | // there is a backtrace 125 | std::ifstream ifs("./backtrace.dump"); 126 | 127 | boost::stacktrace::stacktrace st = boost::stacktrace::stacktrace::from_dump(ifs); 128 | std::cout << "Previous run crashed:\n" << st << std::endl; /*<-*/ 129 | 130 | if (!st) { 131 | return 23; 132 | } /*->*/ 133 | 134 | // cleaning up 135 | ifs.close(); 136 | boost::filesystem::remove("./backtrace.dump"); 137 | } 138 | //] 139 | 140 | return 0; 141 | } 142 | 143 | #include 144 | 145 | int test_inplace() { 146 | const bool is_noop = !boost::stacktrace::stacktrace(); 147 | 148 | { 149 | // This is very dependent on compiler and link flags. No sane way to make it work, because: 150 | // * BOOST_NOINLINE could be ignored by MSVC compiler if link-time optimization is enabled. 151 | // * BOOST_FORCEINLINE could be ignored by GCC depending on the std::vector default constructor length. 152 | const std::size_t frames_ss1 = boost::stacktrace::safe_dump_to("./backtrace2.dump"); 153 | boost::stacktrace::stacktrace ss2; 154 | std::ifstream ifs("./backtrace2.dump"); 155 | boost::stacktrace::stacktrace ss1 = boost::stacktrace::stacktrace::from_dump(ifs); 156 | ifs.close(); 157 | boost::filesystem::remove("./backtrace2.dump"); 158 | 159 | if (ss1.size() + 1 != frames_ss1 || ss2.size() != ss1.size()) { 160 | std::cerr << "51: Stacktraces differ. Dumped size == " << frames_ss1 << ".\n" << ss1 << "\n vs \n" << ss2 << '\n'; 161 | } else if (ss1.size() > 1 && ss1[1].name() != ss2[1].name()) { 162 | std::cerr << "52: Stacktraces differ:\n" << ss1 << "\n vs \n" << ss2 << '\n'; 163 | } 164 | } 165 | 166 | { 167 | // This is very dependent on compiler and link flags. No sane way to make it work, because: 168 | // * BOOST_NOINLINE could be ignored by MSVC compiler if link-time optimization is enabled. 169 | // * BOOST_FORCEINLINE could be ignored by GCC depending on the std::vector default constructor length. 170 | void* data[1024]; 171 | const std::size_t frames_ss1 = boost::stacktrace::safe_dump_to(data, sizeof(data)); 172 | boost::stacktrace::stacktrace ss2; 173 | boost::stacktrace::stacktrace ss1 = boost::stacktrace::stacktrace::from_dump(data, sizeof(data)); 174 | 175 | if (ss1.size() + 1 != frames_ss1 || ss1.size() != ss2.size()) { 176 | std::cerr << "53: Stacktraces differ. Dumped size == " << frames_ss1 << ".\n" << ss1 << "\n vs \n" << ss2 << '\n'; 177 | } else if (ss1.size() > 1 && ss1[1].name() != ss2[1].name()) { 178 | std::cerr << "54: Stacktraces differ:\n" << ss1 << "\n vs \n" << ss2 << '\n'; 179 | } 180 | } 181 | 182 | { 183 | void* data[1024]; 184 | boost::stacktrace::safe_dump_to(1024, data, sizeof(data)); 185 | if (boost::stacktrace::stacktrace::from_dump(data, sizeof(data))) { 186 | std::cerr << "Stacktrace not empty!\n"; 187 | return 55; 188 | } 189 | } 190 | 191 | { 192 | void* data[1024]; 193 | boost::stacktrace::safe_dump_to(1, data, sizeof(data)); 194 | if (!is_noop && !boost::stacktrace::stacktrace::from_dump(data, sizeof(data))) { 195 | std::cerr << "Stacktrace empty!\n"; 196 | return 56; 197 | } 198 | const std::size_t size_1_skipped = boost::stacktrace::stacktrace::from_dump(data, sizeof(data)).size(); 199 | boost::stacktrace::safe_dump_to(0, data, sizeof(data)); 200 | const std::size_t size_0_skipped = boost::stacktrace::stacktrace::from_dump(data, sizeof(data)).size(); 201 | 202 | if (!is_noop && (size_1_skipped + 1 != size_0_skipped)) { 203 | std::cerr << "failed to skip 1 frame!\n"; 204 | return 57; 205 | } 206 | } 207 | 208 | { 209 | boost::stacktrace::safe_dump_to(0, 1, "./backtrace3.dump"); 210 | std::ifstream ifs("./backtrace3.dump"); 211 | boost::stacktrace::stacktrace ss1 = boost::stacktrace::stacktrace::from_dump(ifs); 212 | ifs.close(); 213 | 214 | boost::stacktrace::safe_dump_to(1, 1, "./backtrace3.dump"); 215 | ifs.open("./backtrace3.dump"); 216 | boost::stacktrace::stacktrace ss2 = boost::stacktrace::stacktrace::from_dump(ifs); 217 | ifs.close(); 218 | 219 | boost::filesystem::remove("./backtrace3.dump"); 220 | 221 | #ifdef BOOST_WINDOWS 222 | // `ss2` could be empty on some combinations of Windows+MSVC. 223 | if (!ss2) { 224 | return 0; 225 | } 226 | #endif 227 | 228 | if (ss1.size() != ss2.size()) { 229 | std::cerr << "Stacktraces differ:\n" << ss1 << "\n vs \n" << ss2 << '\n'; 230 | return 58; 231 | } 232 | 233 | if (!is_noop && ss1.size() != 1) { 234 | std::cerr << "Stacktraces does not have size 1:\n" << ss1 << '\n'; 235 | return 59; 236 | } 237 | 238 | if (ss1 && ss1[0].address() == ss2[0].address()) { 239 | std::cerr << "Stacktraces must differ:\n" << ss1 << "\n vs \n" << ss2 << '\n'; 240 | return 60; 241 | } 242 | } 243 | 244 | return 0; 245 | } 246 | 247 | 248 | int main(int argc, const char* argv[]) { 249 | if (argc < 2) { 250 | // On Windows the debugger could be active. In that case tests hang and the CI run fails. 251 | #ifndef BOOST_WINDOWS 252 | copy_and_run(argv[0], '0', true); 253 | 254 | // We are copying files to make sure that stacktrace printing works independently from executable name 255 | copy_and_run(argv[0], '1', true); 256 | copy_and_run(argv[0], '2', false); 257 | #endif 258 | 259 | return test_inplace(); 260 | } 261 | 262 | switch (argv[1][0]) { 263 | case '0': return run_0(argv); 264 | case '1': return run_1(argv); 265 | } 266 | 267 | return 404; 268 | } 269 | 270 | 271 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/addr2line_impls.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_ADDR2LINE_IMPLS_HPP 8 | #define BOOST_STACKTRACE_DETAIL_ADDR2LINE_IMPLS_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | 28 | namespace boost { namespace stacktrace { namespace detail { 29 | 30 | 31 | #if defined(BOOST_STACKTRACE_ADDR2LINE_LOCATION) && !defined(BOOST_NO_CXX11_CONSTEXPR) 32 | 33 | constexpr bool is_abs_path(const char* path) noexcept { 34 | return *path != '\0' && ( 35 | *path == ':' || *path == '/' || is_abs_path(path + 1) 36 | ); 37 | } 38 | 39 | #endif 40 | 41 | class addr2line_pipe { 42 | ::FILE* p; 43 | ::pid_t pid; 44 | 45 | public: 46 | explicit addr2line_pipe(const char *flag, const char* exec_path, const char* addr) noexcept 47 | : p(0) 48 | , pid(0) 49 | { 50 | int pdes[2]; 51 | #ifdef BOOST_STACKTRACE_ADDR2LINE_LOCATION 52 | char prog_name[] = BOOST_STRINGIZE( BOOST_STACKTRACE_ADDR2LINE_LOCATION ); 53 | #if !defined(BOOST_NO_CXX11_CONSTEXPR) && !defined(BOOST_NO_CXX11_STATIC_ASSERT) 54 | static_assert( 55 | boost::stacktrace::detail::is_abs_path( BOOST_STRINGIZE( BOOST_STACKTRACE_ADDR2LINE_LOCATION ) ), 56 | "BOOST_STACKTRACE_ADDR2LINE_LOCATION must be an absolute path" 57 | ); 58 | #endif 59 | 60 | #else 61 | char prog_name[] = "/usr/bin/addr2line"; 62 | #endif 63 | 64 | char* argp[] = { 65 | prog_name, 66 | const_cast(flag), 67 | const_cast(exec_path), 68 | const_cast(addr), 69 | 0 70 | }; 71 | 72 | if (::pipe(pdes) < 0) { 73 | return; 74 | } 75 | 76 | pid = ::fork(); 77 | switch (pid) { 78 | case -1: 79 | // Failed... 80 | ::close(pdes[0]); 81 | ::close(pdes[1]); 82 | return; 83 | 84 | case 0: 85 | // We are the child. 86 | ::close(STDERR_FILENO); 87 | ::close(pdes[0]); 88 | if (pdes[1] != STDOUT_FILENO) { 89 | ::dup2(pdes[1], STDOUT_FILENO); 90 | } 91 | 92 | // Do not use `execlp()`, `execvp()`, and `execvpe()` here! 93 | // `exec*p*` functions are vulnerable to PATH variable evaluation attacks. 94 | ::execv(prog_name, argp); 95 | ::_exit(127); 96 | } 97 | 98 | p = ::fdopen(pdes[0], "r"); 99 | ::close(pdes[1]); 100 | } 101 | 102 | operator ::FILE*() const noexcept { 103 | return p; 104 | } 105 | 106 | ~addr2line_pipe() noexcept { 107 | if (p) { 108 | ::fclose(p); 109 | int pstat = 0; 110 | ::kill(pid, SIGKILL); 111 | ::waitpid(pid, &pstat, 0); 112 | } 113 | } 114 | }; 115 | 116 | inline std::string addr2line(const char* flag, const void* addr) { 117 | std::string res; 118 | 119 | boost::stacktrace::detail::location_from_symbol loc(addr); 120 | // For programs started through $PATH loc.name() is not absolute and 121 | // addr2line will fail. 122 | if (!loc.empty() && std::strchr(loc.name(), '/') != nullptr) { 123 | res = loc.name(); 124 | } else { 125 | res.resize(16); 126 | ssize_t rlin_size = ::readlink("/proc/self/exe", &res[0], res.size() - 1); 127 | while (rlin_size == static_cast(res.size() - 1)) { 128 | res.resize(res.size() * 4); 129 | rlin_size = ::readlink("/proc/self/exe", &res[0], res.size() - 1); 130 | } 131 | if (rlin_size == -1) { 132 | res.clear(); 133 | return res; 134 | } 135 | res.resize(static_cast(rlin_size)); 136 | } 137 | 138 | addr2line_pipe p(flag, res.c_str(), to_hex_array(addr).data()); 139 | res.clear(); 140 | 141 | if (!p) { 142 | return res; 143 | } 144 | 145 | char data[32]; 146 | while (!::feof(p)) { 147 | if (::fgets(data, sizeof(data), p)) { 148 | res += data; 149 | } else { 150 | break; 151 | } 152 | } 153 | 154 | // Trimming 155 | while (!res.empty() && (res[res.size() - 1] == '\n' || res[res.size() - 1] == '\r')) { 156 | res.erase(res.size() - 1); 157 | } 158 | 159 | return res; 160 | } 161 | 162 | inline std::string source_location(const void* addr, bool position_independent) { 163 | uintptr_t addr_base = 0; 164 | if (position_independent) { 165 | addr_base = boost::stacktrace::detail::get_own_proc_addr_base(addr); 166 | } 167 | const void* offset = reinterpret_cast(reinterpret_cast(addr) - addr_base); 168 | std::string source_line = boost::stacktrace::detail::addr2line("-Cpe", reinterpret_cast(offset)); 169 | if (source_line.empty() || source_line[0] == '?') { 170 | return ""; 171 | } 172 | 173 | return source_line; 174 | } 175 | 176 | struct to_string_using_addr2line { 177 | std::string res; 178 | void prepare_function_name(const void* addr) { 179 | res = boost::stacktrace::frame(addr).name(); 180 | } 181 | 182 | bool prepare_source_location(const void* addr) { 183 | // general idea in all addr2line uses: 184 | // in each case: 185 | // - try to resolve whole address as if it was a non-pie binary 186 | // - if that didn't work, try to resolve just an offset from binary base address 187 | // this is needed because: 188 | // - in pie binaries just passing an address to addr2line won't work (it needs an offset in this case) 189 | // - in non-pie binaries whole address is needed (offset won't work) 190 | // - there is no easy way to test if binary is position independent (that I know of) 191 | std::string source_line = boost::stacktrace::detail::source_location(addr, false); 192 | if(source_line.empty()) { 193 | source_line = boost::stacktrace::detail::source_location(addr, true); 194 | } 195 | 196 | if (!source_line.empty()) { 197 | res += " at "; 198 | res += source_line; 199 | return true; 200 | } 201 | 202 | return false; 203 | } 204 | }; 205 | 206 | template class to_string_impl_base; 207 | typedef to_string_impl_base to_string_impl; 208 | 209 | inline std::string name(const void* addr, bool position_independent) { 210 | uintptr_t addr_base = 0; 211 | if(position_independent){ 212 | addr_base = boost::stacktrace::detail::get_own_proc_addr_base(addr); 213 | } 214 | const void* offset = reinterpret_cast(reinterpret_cast(addr) - addr_base); 215 | std::string res = boost::stacktrace::detail::addr2line("-fe", offset); 216 | res = res.substr(0, res.find_last_of('\n')); 217 | res = boost::core::demangle(res.c_str()); 218 | 219 | if (res == "??") { 220 | res.clear(); 221 | } 222 | 223 | return res; 224 | } 225 | 226 | inline std::string name_impl(const void* addr) { 227 | std::string res = boost::stacktrace::detail::name(addr, false); 228 | if (res.empty()) { 229 | res = boost::stacktrace::detail::name(addr, true); 230 | } 231 | 232 | return res; 233 | } 234 | 235 | inline std::string source_file(const void* addr, bool position_independent) { 236 | std::string res; 237 | uintptr_t addr_base = 0; 238 | if(position_independent){ 239 | addr_base = boost::stacktrace::detail::get_own_proc_addr_base(addr); 240 | } 241 | const void* offset = reinterpret_cast(reinterpret_cast(addr) - addr_base); 242 | res = boost::stacktrace::detail::addr2line("-e", offset); 243 | res = res.substr(0, res.find_last_of(':')); 244 | if (res == "??") { 245 | res.clear(); 246 | } 247 | 248 | return res; 249 | } 250 | 251 | inline std::size_t source_line(const void* addr, bool position_independent) { 252 | std::size_t line_num = 0; 253 | uintptr_t addr_base = 0; 254 | if(position_independent){ 255 | addr_base = boost::stacktrace::detail::get_own_proc_addr_base(addr); 256 | } 257 | const void* offset = reinterpret_cast(reinterpret_cast(addr) - addr_base); 258 | std::string res = boost::stacktrace::detail::addr2line("-e", offset); 259 | const std::size_t last = res.find_last_of(':'); 260 | if (last == std::string::npos) { 261 | return 0; 262 | } 263 | res = res.substr(last + 1); 264 | 265 | if (!boost::stacktrace::detail::try_dec_convert(res.c_str(), line_num)) { 266 | return 0; 267 | } 268 | 269 | return line_num; 270 | } 271 | 272 | } // namespace detail 273 | 274 | 275 | std::string frame::source_file() const { 276 | std::string res = boost::stacktrace::detail::source_file(addr_, false); 277 | if (res.empty()) { 278 | res = boost::stacktrace::detail::source_file(addr_, true); 279 | } 280 | 281 | return res; 282 | } 283 | 284 | std::size_t frame::source_line() const { 285 | std::size_t line_num = boost::stacktrace::detail::source_line(addr_, false); 286 | if (line_num == 0) { 287 | line_num = boost::stacktrace::detail::source_line(addr_, true); 288 | } 289 | 290 | return line_num; 291 | } 292 | 293 | 294 | }} // namespace boost::stacktrace 295 | 296 | #endif // BOOST_STACKTRACE_DETAIL_ADDR2LINE_IMPLS_HPP 297 | -------------------------------------------------------------------------------- /include/boost/stacktrace/safe_dump_to.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_SAFE_DUMP_TO_HPP 8 | #define BOOST_STACKTRACE_SAFE_DUMP_TO_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | #if defined(BOOST_WINDOWS) 18 | #include 19 | #endif 20 | 21 | #include 22 | 23 | #ifdef BOOST_INTEL 24 | # pragma warning(push) 25 | # pragma warning(disable:2196) // warning #2196: routine is both "inline" and "noinline" 26 | #endif 27 | 28 | /// @file safe_dump_to.hpp \asyncsafe low-level 29 | /// functions for dumping call stacks. Dumps are binary serialized arrays of `void*`, 30 | /// so you could read them by using 'od -tx8 -An stacktrace_dump_failename' 31 | /// Linux command or using boost::stacktrace::stacktrace::from_dump functions. 32 | 33 | namespace boost { namespace stacktrace { 34 | 35 | /// @cond 36 | namespace detail { 37 | 38 | using native_frame_ptr_t = const void*; 39 | enum helper{ max_frames_dump = 128 }; 40 | 41 | BOOST_STACKTRACE_FUNCTION std::size_t from_dump(const char* filename, native_frame_ptr_t* out_frames); 42 | BOOST_STACKTRACE_FUNCTION std::size_t dump(const char* file, const native_frame_ptr_t* frames, std::size_t frames_count) noexcept; 43 | #if defined(BOOST_WINDOWS) 44 | BOOST_STACKTRACE_FUNCTION std::size_t dump(void* fd, const native_frame_ptr_t* frames, std::size_t frames_count) noexcept; 45 | #else 46 | // POSIX 47 | BOOST_STACKTRACE_FUNCTION std::size_t dump(int fd, const native_frame_ptr_t* frames, std::size_t frames_count) noexcept; 48 | #endif 49 | 50 | 51 | struct this_thread_frames { // struct is required to avoid warning about usage of inline+BOOST_NOINLINE 52 | BOOST_NOINLINE BOOST_STACKTRACE_FUNCTION static std::size_t collect(native_frame_ptr_t* out_frames, std::size_t max_frames_count, std::size_t skip) noexcept; 53 | 54 | BOOST_NOINLINE static std::size_t safe_dump_to_impl(void* memory, std::size_t size, std::size_t skip) noexcept { 55 | using boost::stacktrace::detail::native_frame_ptr_t; 56 | 57 | if (size < sizeof(native_frame_ptr_t)) { 58 | return 0; 59 | } 60 | 61 | native_frame_ptr_t* mem = static_cast(memory); 62 | const std::size_t frames_count = boost::stacktrace::detail::this_thread_frames::collect(mem, size / sizeof(native_frame_ptr_t) - 1, skip + 1); 63 | mem[frames_count] = 0; 64 | return frames_count + 1; 65 | } 66 | 67 | template 68 | BOOST_NOINLINE static std::size_t safe_dump_to_impl(T file, std::size_t skip, std::size_t max_depth) noexcept { 69 | using boost::stacktrace::detail::native_frame_ptr_t; 70 | 71 | native_frame_ptr_t buffer[boost::stacktrace::detail::max_frames_dump + 1]; 72 | if (max_depth > boost::stacktrace::detail::max_frames_dump) { 73 | max_depth = boost::stacktrace::detail::max_frames_dump; 74 | } 75 | 76 | const std::size_t frames_count = boost::stacktrace::detail::this_thread_frames::collect(buffer, max_depth, skip + 1); 77 | buffer[frames_count] = 0; 78 | return boost::stacktrace::detail::dump(file, buffer, frames_count + 1); 79 | } 80 | }; 81 | 82 | } // namespace detail 83 | /// @endcond 84 | 85 | /// @brief Stores current function call sequence into the memory. 86 | /// 87 | /// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. 88 | /// 89 | /// @b Async-Handler-Safety: \asyncsafe. 90 | /// 91 | /// @returns Stored call sequence depth including terminating zero frame. To get the actually consumed bytes multiply this value by the sizeof(boost::stacktrace::frame::native_frame_ptr_t) 92 | /// 93 | /// @param memory Preallocated buffer to store current function call sequence into. 94 | /// 95 | /// @param size Size of the preallocated buffer. 96 | BOOST_FORCEINLINE std::size_t safe_dump_to(void* memory, std::size_t size) noexcept { 97 | return boost::stacktrace::detail::this_thread_frames::safe_dump_to_impl(memory, size, 0); 98 | } 99 | 100 | /// @brief Stores current function call sequence into the memory. 101 | /// 102 | /// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. 103 | /// 104 | /// @b Async-Handler-Safety: \asyncsafe. 105 | /// 106 | /// @returns Stored call sequence depth including terminating zero frame. To get the actually consumed bytes multiply this value by the sizeof(boost::stacktrace::frame::native_frame_ptr_t) 107 | /// 108 | /// @param skip How many top calls to skip and do not store. 109 | /// 110 | /// @param memory Preallocated buffer to store current function call sequence into. 111 | /// 112 | /// @param size Size of the preallocated buffer. 113 | BOOST_FORCEINLINE std::size_t safe_dump_to(std::size_t skip, void* memory, std::size_t size) noexcept { 114 | return boost::stacktrace::detail::this_thread_frames::safe_dump_to_impl(memory, size, skip); 115 | } 116 | 117 | 118 | /// @brief Opens a file and rewrites its content with current function call sequence if such operations are async signal safe. 119 | /// 120 | /// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. 121 | /// 122 | /// @b Async-Handler-Safety: \asyncsafe. 123 | /// 124 | /// @returns Stored call sequence depth including terminating zero frame. 125 | /// 126 | /// @param file File to store current function call sequence. 127 | BOOST_FORCEINLINE std::size_t safe_dump_to(const char* file) noexcept { 128 | return boost::stacktrace::detail::this_thread_frames::safe_dump_to_impl(file, 0, boost::stacktrace::detail::max_frames_dump); 129 | } 130 | 131 | /// @brief Opens a file and rewrites its content with current function call sequence if such operations are async signal safe. 132 | /// 133 | /// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. 134 | /// 135 | /// @b Async-Handler-Safety: \asyncsafe. 136 | /// 137 | /// @returns Stored call sequence depth including terminating zero frame. 138 | /// 139 | /// @param skip How many top calls to skip and do not store. 140 | /// 141 | /// @param max_depth Max call sequence depth to collect. 142 | /// 143 | /// @param file File to store current function call sequence. 144 | BOOST_FORCEINLINE std::size_t safe_dump_to(std::size_t skip, std::size_t max_depth, const char* file) noexcept { 145 | return boost::stacktrace::detail::this_thread_frames::safe_dump_to_impl(file, skip, max_depth); 146 | } 147 | 148 | #ifdef BOOST_STACKTRACE_DOXYGEN_INVOKED 149 | 150 | /// @brief Writes into the provided file descriptor the current function call sequence if such operation is async signal safe. 151 | /// 152 | /// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. 153 | /// 154 | /// @b Async-Handler-Safety: \asyncsafe. 155 | /// 156 | /// @returns Stored call sequence depth including terminating zero frame. 157 | /// 158 | /// @param file File to store current function call sequence. 159 | BOOST_FORCEINLINE std::size_t safe_dump_to(platform_specific_descriptor fd) noexcept; 160 | 161 | /// @brief Writes into the provided file descriptor the current function call sequence if such operation is async signal safe. 162 | /// 163 | /// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. 164 | /// 165 | /// @b Async-Handler-Safety: \asyncsafe. 166 | /// 167 | /// @returns Stored call sequence depth including terminating zero frame. 168 | /// 169 | /// @param skip How many top calls to skip and do not store. 170 | /// 171 | /// @param max_depth Max call sequence depth to collect. 172 | /// 173 | /// @param file File to store current function call sequence. 174 | BOOST_FORCEINLINE std::size_t safe_dump_to(std::size_t skip, std::size_t max_depth, platform_specific_descriptor fd) noexcept; 175 | 176 | #elif defined(BOOST_WINDOWS) 177 | 178 | BOOST_FORCEINLINE std::size_t safe_dump_to(void* fd) noexcept { 179 | return boost::stacktrace::detail::this_thread_frames::safe_dump_to_impl(fd, 0, boost::stacktrace::detail::max_frames_dump); 180 | } 181 | 182 | BOOST_FORCEINLINE std::size_t safe_dump_to(std::size_t skip, std::size_t max_depth, void* fd) noexcept { 183 | return boost::stacktrace::detail::this_thread_frames::safe_dump_to_impl(fd, skip, max_depth); 184 | } 185 | 186 | #else 187 | 188 | // POSIX 189 | BOOST_FORCEINLINE std::size_t safe_dump_to(int fd) noexcept { 190 | return boost::stacktrace::detail::this_thread_frames::safe_dump_to_impl(fd, 0, boost::stacktrace::detail::max_frames_dump); 191 | } 192 | 193 | BOOST_FORCEINLINE std::size_t safe_dump_to(std::size_t skip, std::size_t max_depth, int fd) noexcept { 194 | return boost::stacktrace::detail::this_thread_frames::safe_dump_to_impl(fd, skip, max_depth); 195 | } 196 | 197 | #endif 198 | 199 | 200 | }} // namespace boost::stacktrace 201 | 202 | #ifdef BOOST_INTEL 203 | # pragma warning(pop) 204 | #endif 205 | 206 | #include 207 | 208 | #if !defined(BOOST_STACKTRACE_LINK) || defined(BOOST_STACKTRACE_INTERNAL_BUILD_LIBS) 209 | # if defined(BOOST_STACKTRACE_USE_NOOP) 210 | # include 211 | # include 212 | # else 213 | # if defined(BOOST_WINDOWS) 214 | # include 215 | # else 216 | # include 217 | # endif 218 | # if defined(BOOST_WINDOWS) && !defined(BOOST_WINAPI_IS_MINGW) // MinGW does not provide RtlCaptureStackBackTrace. MinGW-w64 does. 219 | # include 220 | # else 221 | # include 222 | # endif 223 | # endif 224 | #endif 225 | 226 | #endif // BOOST_STACKTRACE_SAFE_DUMP_TO_HPP 227 | -------------------------------------------------------------------------------- /test/test.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | #include 19 | 20 | #include "test_impl.hpp" 21 | 22 | using boost::stacktrace::stacktrace; 23 | using boost::stacktrace::frame; 24 | 25 | 26 | #if (defined(BOOST_GCC) && defined(BOOST_WINDOWS) && !defined(BOOST_STACKTRACE_USE_BACKTRACE) && !defined(BOOST_STACKTRACE_USE_ADDR2LINE)) \ 27 | || defined(BOOST_STACKTRACE_TEST_NO_DEBUG_AT_ALL) 28 | 29 | # define BOOST_STACKTRACE_TEST_SHOULD_OUTPUT_READABLE_NAMES 0 30 | #else 31 | # define BOOST_STACKTRACE_TEST_SHOULD_OUTPUT_READABLE_NAMES 1 32 | #endif 33 | 34 | void test_deeply_nested_namespaces() { 35 | std::stringstream ss; 36 | ss << return_from_nested_namespaces(); 37 | std::cout << ss.str() << '\n'; 38 | #if BOOST_STACKTRACE_TEST_SHOULD_OUTPUT_READABLE_NAMES 39 | BOOST_TEST(ss.str().find("main") != std::string::npos); 40 | 41 | BOOST_TEST(ss.str().find("get_backtrace_from_nested_namespaces") != std::string::npos 42 | || ss.str().find("1# return_from_nested_namespaces") != std::string::npos); // GCC with -O1 has strange inlining, so this line is true while the prev one is false. 43 | 44 | BOOST_TEST(ss.str().find("return_from_nested_namespaces") != std::string::npos); 45 | #endif 46 | 47 | stacktrace ns1 = return_from_nested_namespaces(); 48 | BOOST_TEST(ns1 != return_from_nested_namespaces()); // Different addresses in test_deeply_nested_namespaces() function 49 | } 50 | 51 | std::size_t count_unprintable_chars(const std::string& s) { 52 | std::size_t result = 0; 53 | for (std::size_t i = 0; i < s.size(); ++i) { 54 | result += (std::isprint(s[i]) ? 0 : 1); 55 | } 56 | 57 | return result; 58 | } 59 | 60 | void test_frames_string_data_validity() { 61 | stacktrace trace = return_from_nested_namespaces(); 62 | for (std::size_t i = 0; i < trace.size(); ++i) { 63 | BOOST_TEST_EQ(count_unprintable_chars(trace[i].source_file()), 0); 64 | BOOST_TEST_EQ(count_unprintable_chars(trace[i].name()), 0); 65 | } 66 | 67 | BOOST_TEST(to_string(trace).find('\0') == std::string::npos); 68 | } 69 | 70 | // Template parameter Depth is to produce different functions on each Depth. This simplifies debugging when one of the tests catches error 71 | template 72 | void test_nested(bool print = true) { 73 | std::pair res = function_from_library(Depth, function_from_main_translation_unit); 74 | 75 | std::stringstream ss1, ss2; 76 | 77 | ss1 << res.first; 78 | ss2 << res.second; 79 | if (print) { 80 | std::cout << "'" << ss1.str() << "'\n\n" << ss2.str() << std::endl; 81 | } 82 | BOOST_TEST(!ss1.str().empty()); 83 | BOOST_TEST(!ss2.str().empty()); 84 | 85 | BOOST_TEST(ss1.str().find(" 0# ") != std::string::npos); 86 | BOOST_TEST(ss2.str().find(" 0# ") != std::string::npos); 87 | 88 | BOOST_TEST(ss1.str().find(" 1# ") != std::string::npos); 89 | BOOST_TEST(ss2.str().find(" 1# ") != std::string::npos); 90 | 91 | BOOST_TEST(ss1.str().find(" in ") != std::string::npos); 92 | BOOST_TEST(ss2.str().find(" in ") != std::string::npos); 93 | 94 | #if BOOST_STACKTRACE_TEST_SHOULD_OUTPUT_READABLE_NAMES 95 | BOOST_TEST(ss1.str().find("main") != std::string::npos); 96 | BOOST_TEST(ss2.str().find("main") != std::string::npos); 97 | 98 | BOOST_TEST(ss1.str().find("function_from_library") != std::string::npos); 99 | BOOST_TEST(ss2.str().find("function_from_library") != std::string::npos); 100 | 101 | BOOST_TEST(ss1.str().find("function_from_main_translation_unit") != std::string::npos); 102 | BOOST_TEST(ss2.str().find("function_from_main_translation_unit") != std::string::npos); 103 | #endif 104 | } 105 | 106 | template 107 | void test_comparisons_base(Bt nst, Bt st) { 108 | Bt cst(st); 109 | st = st; 110 | cst = cst; 111 | BOOST_TEST(nst); 112 | BOOST_TEST(st); 113 | #if !defined(BOOST_MSVC) && !defined(BOOST_STACKTRACE_USE_WINDBG) 114 | // This is very dependent on compiler and link flags. No sane way to make it work, because 115 | // BOOST_NOINLINE could be ignored by MSVC compiler if link-time optimization is enabled. 116 | BOOST_TEST(nst[0] != st[0]); 117 | #endif 118 | 119 | BOOST_TEST(nst != st); 120 | BOOST_TEST(st != nst); 121 | BOOST_TEST(st == st); 122 | BOOST_TEST(nst == nst); 123 | 124 | BOOST_TEST(nst != cst); 125 | BOOST_TEST(cst != nst); 126 | BOOST_TEST(cst == st); 127 | BOOST_TEST(cst == cst); 128 | 129 | BOOST_TEST(nst < st || nst > st); 130 | BOOST_TEST(st < nst || nst < st); 131 | BOOST_TEST(st <= st); 132 | BOOST_TEST(nst <= nst); 133 | BOOST_TEST(st >= st); 134 | BOOST_TEST(nst >= nst); 135 | 136 | BOOST_TEST(nst < cst || cst < nst); 137 | BOOST_TEST(nst > cst || cst > nst); 138 | 139 | 140 | BOOST_TEST(hash_value(nst) == hash_value(nst)); 141 | BOOST_TEST(hash_value(cst) == hash_value(st)); 142 | 143 | BOOST_TEST(hash_value(nst) != hash_value(cst)); 144 | BOOST_TEST(hash_value(st) != hash_value(nst)); 145 | } 146 | 147 | void test_comparisons() { 148 | stacktrace nst = return_from_nested_namespaces(); 149 | stacktrace st; 150 | test_comparisons_base(nst, st); 151 | } 152 | 153 | void test_iterators() { 154 | stacktrace st; 155 | 156 | BOOST_TEST(st.begin() == st.begin()); 157 | BOOST_TEST(st.cbegin() == st.cbegin()); 158 | BOOST_TEST(st.crbegin() == st.crbegin()); 159 | BOOST_TEST(st.rbegin() == st.rbegin()); 160 | 161 | BOOST_TEST(st.begin() + 1 == st.begin() + 1); 162 | BOOST_TEST(st.cbegin() + 1 == st.cbegin() + 1); 163 | BOOST_TEST(st.crbegin() + 1 == st.crbegin() + 1); 164 | BOOST_TEST(st.rbegin() + 1 == st.rbegin() + 1); 165 | 166 | BOOST_TEST(st.end() == st.end()); 167 | BOOST_TEST(st.cend() == st.cend()); 168 | BOOST_TEST(st.crend() == st.crend()); 169 | BOOST_TEST(st.rend() == st.rend()); 170 | 171 | BOOST_TEST(st.end() > st.begin()); 172 | BOOST_TEST(st.end() > st.cbegin()); 173 | BOOST_TEST(st.cend() > st.cbegin()); 174 | BOOST_TEST(st.cend() > st.begin()); 175 | 176 | BOOST_TEST(st.size() == static_cast(st.end() - st.begin())); 177 | BOOST_TEST(st.size() == static_cast(st.end() - st.cbegin())); 178 | BOOST_TEST(st.size() == static_cast(st.cend() - st.cbegin())); 179 | BOOST_TEST(st.size() == static_cast(st.cend() - st.begin())); 180 | 181 | BOOST_TEST(st.size() == static_cast(std::distance(st.rbegin(), st.rend()))); 182 | BOOST_TEST(st.size() == static_cast(std::distance(st.crbegin(), st.rend()))); 183 | BOOST_TEST(st.size() == static_cast(std::distance(st.crbegin(), st.crend()))); 184 | BOOST_TEST(st.size() == static_cast(std::distance(st.rbegin(), st.crend()))); 185 | 186 | 187 | boost::stacktrace::stacktrace::iterator it = st.begin(); 188 | ++ it; 189 | BOOST_TEST(it == st.begin() + 1); 190 | } 191 | 192 | void test_frame() { 193 | stacktrace nst = return_from_nested_namespaces(); 194 | stacktrace st = make_some_stacktrace1(); 195 | 196 | const std::size_t min_size = (nst.size() < st.size() ? nst.size() : st.size()); 197 | BOOST_TEST(min_size > 2); 198 | 199 | for (std::size_t i = 0; i < min_size; ++i) { 200 | BOOST_TEST(st[i] == st[i]); 201 | BOOST_TEST(st[i].source_file() == st[i].source_file()); 202 | BOOST_TEST(st[i].source_line() == st[i].source_line()); 203 | BOOST_TEST(st[i] <= st[i]); 204 | BOOST_TEST(st[i] >= st[i]); 205 | 206 | frame fv = nst[i]; 207 | BOOST_TEST(fv); 208 | if (i > 1 && i < min_size - 3) { // Begin ...and end of the trace may match, skipping 209 | BOOST_TEST(st[i] != fv); 210 | 211 | #if !(defined(BOOST_STACKTRACE_TEST_NO_DEBUG_AT_ALL) && defined(BOOST_MSVC)) 212 | // MSVC can not get function name withhout debug symbols even if it is exported 213 | BOOST_TEST(st[i].name() != fv.name()); 214 | BOOST_TEST(st[i] != fv); 215 | BOOST_TEST(st[i] < fv || st[i] > fv); 216 | BOOST_TEST(hash_value(st[i]) != hash_value(fv)); 217 | #endif 218 | 219 | if (st[i].source_line()) { 220 | BOOST_TEST(st[i].source_file() != fv.source_file() || st[i].source_line() != fv.source_line()); 221 | } 222 | BOOST_TEST(st[i]); 223 | } 224 | 225 | fv = st[i]; 226 | BOOST_TEST(hash_value(st[i]) == hash_value(fv)); 227 | } 228 | 229 | boost::stacktrace::frame empty_frame; 230 | BOOST_TEST(!empty_frame); 231 | BOOST_TEST_EQ(empty_frame.source_file(), ""); 232 | BOOST_TEST_EQ(empty_frame.name(), ""); 233 | BOOST_TEST_EQ(empty_frame.source_line(), 0); 234 | } 235 | 236 | // Template parameter bool BySkip is to produce different functions on each BySkip. This simplifies debugging when one of the tests catches error 237 | template 238 | void test_empty_basic_stacktrace() { 239 | typedef boost::stacktrace::stacktrace st_t; 240 | st_t st = BySkip ? st_t(100500, 1024) : st_t(0, 0); 241 | 242 | BOOST_TEST(!st); 243 | BOOST_TEST(st.empty()); 244 | BOOST_TEST(st.size() == 0); 245 | BOOST_TEST(st.begin() == st.end()); 246 | BOOST_TEST(st.cbegin() == st.end()); 247 | BOOST_TEST(st.cbegin() == st.cend()); 248 | BOOST_TEST(st.begin() == st.cend()); 249 | 250 | BOOST_TEST(st.rbegin() == st.rend()); 251 | BOOST_TEST(st.crbegin() == st.rend()); 252 | BOOST_TEST(st.crbegin() == st.crend()); 253 | BOOST_TEST(st.rbegin() == st.crend()); 254 | 255 | BOOST_TEST(hash_value(st) == hash_value(st_t(0, 0))); 256 | BOOST_TEST(st == st_t(0, 0)); 257 | BOOST_TEST(!(st < st_t(0, 0))); 258 | BOOST_TEST(!(st > st_t(0, 0))); 259 | } 260 | 261 | void test_stacktrace_limits() 262 | { 263 | BOOST_TEST_EQ(boost::stacktrace::stacktrace(0, 1).size(), 1); 264 | BOOST_TEST_EQ(boost::stacktrace::stacktrace(1, 1).size(), 1); 265 | } 266 | 267 | std::size_t get_file_size(const char* file_name) { 268 | std::ifstream file(file_name, std::ios::binary | std::ios::ate); 269 | const auto file_size = file.tellg(); 270 | BOOST_TEST(file_size > 0); 271 | return static_cast(file_size); 272 | } 273 | 274 | uintptr_t get_address_from_frame(const std::string& frame) { 275 | std::size_t address = 0; 276 | std::string hex_address; 277 | std::size_t pos = frame.find("0x"); 278 | 279 | if (pos != std::string::npos) { 280 | // Extract the hex address substring 281 | hex_address = frame.substr(pos + 2); // Skip "0x" 282 | 283 | // Convert hex string to std::size_t 284 | std::stringstream ss; 285 | ss << std::hex << hex_address; 286 | ss >> address; 287 | } 288 | 289 | return address; 290 | } 291 | 292 | void test_relative_virtual_address(const char* file_path) 293 | { 294 | const auto frame = to_string(boost::stacktrace::stacktrace(0, 1).as_vector().front()); 295 | 296 | // Skip the test if the frame does not contain an address 297 | if (frame.find("0x") == std::string::npos) { 298 | return; 299 | } 300 | 301 | const auto file_size = get_file_size(file_path); 302 | BOOST_TEST(file_size > 0); 303 | 304 | const auto address = get_address_from_frame(frame); 305 | BOOST_TEST(address > 0); 306 | 307 | // Verify that the address is within the binary 308 | BOOST_TEST(address <= file_size); 309 | } 310 | 311 | int main(const int, const char* argv[]) { 312 | test_deeply_nested_namespaces(); 313 | test_frames_string_data_validity(); 314 | test_nested<15>(); 315 | test_comparisons(); 316 | test_iterators(); 317 | test_frame(); 318 | test_empty_basic_stacktrace(); 319 | test_empty_basic_stacktrace(); 320 | 321 | BOOST_TEST(&make_some_stacktrace1 != &make_some_stacktrace2); 322 | boost::stacktrace::stacktrace b1 = make_some_stacktrace1(); 323 | BOOST_TEST(b1.size() == 4); 324 | boost::stacktrace::stacktrace b2 = make_some_stacktrace2(); 325 | BOOST_TEST(b2.size() == 4); 326 | test_comparisons_base(make_some_stacktrace1(), make_some_stacktrace2()); 327 | 328 | test_nested<260>(false); 329 | 330 | test_stacktrace_limits(); 331 | test_relative_virtual_address(argv[0]); 332 | 333 | return boost::report_errors(); 334 | } 335 | -------------------------------------------------------------------------------- /src/from_exception.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2023-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #if defined(__MINGW32__) || defined(_MSC_VER) 8 | 9 | #include 10 | #include 11 | 12 | extern "C" void** __cdecl __current_exception(); // exported from vcruntime.dll 13 | #define _pCurrentException static_cast(*__current_exception()) 14 | 15 | namespace { 16 | 17 | constexpr std::size_t kStacktraceDumpSize = 4096; 18 | 19 | struct thrown_info { 20 | ULONG_PTR object; 21 | char* dump; 22 | }; 23 | 24 | struct exception_data { 25 | bool capture_stacktraces_at_throw = true; 26 | unsigned count = 0; 27 | thrown_info* info = nullptr; 28 | 29 | ~exception_data() noexcept { 30 | HANDLE hHeap = GetProcessHeap(); 31 | for (unsigned i = 0; i < count; ++i) { 32 | HeapFree(hHeap, 0, info[i].dump); 33 | } 34 | HeapFree(hHeap, 0, info); 35 | } 36 | }; 37 | 38 | thread_local exception_data data; 39 | 40 | inline bool PER_IS_MSVC_EH(PEXCEPTION_RECORD p) noexcept { 41 | const DWORD EH_EXCEPTION_NUMBER = 0xE06D7363; 42 | const ULONG_PTR EH_MAGIC_NUMBER1 = 0x19930520; 43 | 44 | return p->ExceptionCode == EH_EXCEPTION_NUMBER && 45 | (p->NumberParameters == 3 || p->NumberParameters == 4) && 46 | p->ExceptionInformation[0] == EH_MAGIC_NUMBER1; 47 | } 48 | 49 | inline ULONG_PTR PER_PEXCEPTOBJ(PEXCEPTION_RECORD p) noexcept { 50 | return p->ExceptionInformation[1]; 51 | } 52 | 53 | unsigned current_cxx_exception_index() noexcept { 54 | if (PEXCEPTION_RECORD current_cxx_exception = _pCurrentException) { 55 | for (unsigned i = data.count; i > 0;) { 56 | --i; 57 | if (data.info[i].object == PER_PEXCEPTOBJ(current_cxx_exception)) { 58 | return i; 59 | } 60 | } 61 | } 62 | return data.count; 63 | } 64 | 65 | bool is_processing_rethrow(PEXCEPTION_RECORD p) noexcept { 66 | // Processing flow: 67 | // 0. throw; 68 | // 1. _CxxThrowException(pExceptionObject = nullptr) 69 | // 2. VEH & SEH (may throw new c++ exceptions!) 70 | // 3. __RethrowException(_pCurrentException) 71 | // 4. VEH 72 | if (PER_PEXCEPTOBJ(p) == 0) return true; 73 | PEXCEPTION_RECORD current_cxx_exception = _pCurrentException; 74 | if (current_cxx_exception == nullptr) return false; 75 | return PER_PEXCEPTOBJ(p) == PER_PEXCEPTOBJ(current_cxx_exception); 76 | } 77 | 78 | LONG NTAPI veh(PEXCEPTION_POINTERS p) { 79 | if (data.capture_stacktraces_at_throw && 80 | PER_IS_MSVC_EH(p->ExceptionRecord) && 81 | !is_processing_rethrow(p->ExceptionRecord)) { 82 | HANDLE hHeap = GetProcessHeap(); 83 | unsigned index = current_cxx_exception_index(); 84 | unsigned new_count = 1 + (index < data.count ? index + 1 : 0); 85 | 86 | for (unsigned i = new_count; i < data.count; ++i) { 87 | HeapFree(hHeap, 0, data.info[i].dump); 88 | data.info[i].dump = nullptr; 89 | } 90 | 91 | void* new_info; 92 | if (data.info) { 93 | new_info = HeapReAlloc(hHeap, HEAP_ZERO_MEMORY, data.info, sizeof(thrown_info) * new_count); 94 | } else { 95 | new_info = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(thrown_info) * new_count); 96 | } 97 | if (new_info) { 98 | data.count = new_count; 99 | data.info = static_cast(new_info); 100 | data.info[data.count - 1].object = PER_PEXCEPTOBJ(p->ExceptionRecord); 101 | char*& dump_ptr = data.info[data.count - 1].dump; 102 | if (dump_ptr == nullptr) { 103 | dump_ptr = static_cast(HeapAlloc(hHeap, 0, kStacktraceDumpSize)); 104 | } 105 | if (dump_ptr != nullptr) { 106 | const std::size_t kSkip = 4; 107 | boost::stacktrace::safe_dump_to(kSkip, dump_ptr, kStacktraceDumpSize); 108 | } 109 | } else if (new_count <= data.count) { 110 | data.count = new_count - 1; 111 | HeapFree(hHeap, 0, data.info[data.count - 1].dump); 112 | data.info[data.count - 1].dump = nullptr; 113 | } 114 | } 115 | return EXCEPTION_CONTINUE_SEARCH; 116 | } 117 | 118 | struct veh_installer { 119 | PVOID h; 120 | veh_installer() noexcept : h(AddVectoredExceptionHandler(1, veh)) {} 121 | ~veh_installer() noexcept { RemoveVectoredExceptionHandler(h); } 122 | } installer; 123 | 124 | } 125 | 126 | extern "C" { 127 | 128 | BOOST_SYMBOL_EXPORT const char* boost_stacktrace_impl_current_exception_stacktrace() { 129 | unsigned index = current_cxx_exception_index(); 130 | return index < data.count ? data.info[index].dump : nullptr; 131 | } 132 | 133 | BOOST_SYMBOL_EXPORT bool* boost_stacktrace_impl_ref_capture_stacktraces_at_throw() { 134 | return &data.capture_stacktraces_at_throw; 135 | } 136 | 137 | } 138 | 139 | namespace boost { namespace stacktrace { namespace impl { 140 | 141 | BOOST_SYMBOL_EXPORT void assert_no_pending_traces() noexcept { 142 | } 143 | 144 | }}} // namespace boost::stacktrace::impl 145 | 146 | #else 147 | 148 | #include "exception_headers.h" 149 | 150 | // At the moment the file is used only on POSIX. _Unwind_Backtrace may be 151 | // available on some platforms only if _GNU_SOURCE is defined. 152 | #ifndef _GNU_SOURCE 153 | # define _GNU_SOURCE 154 | #endif 155 | 156 | #include 157 | #include 158 | 159 | #include 160 | #include 161 | #include 162 | 163 | 164 | #if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING 165 | #include 166 | #include 167 | #include 168 | #include 169 | 170 | #include 171 | #endif 172 | 173 | namespace { 174 | 175 | constexpr std::size_t kStacktraceDumpSize = 4096; 176 | 177 | struct decrement_on_destroy { 178 | std::size_t& to_decrement; 179 | 180 | ~decrement_on_destroy() { --to_decrement; } 181 | }; 182 | 183 | #if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING 184 | // Inspired by the coursework by Andrei Nekrashevich in the `libsfe` 185 | /*constinit*/ std::mutex g_mapping_mutex; 186 | std::unordered_map g_exception_to_dump_mapping; 187 | #endif 188 | 189 | } // namespace 190 | 191 | namespace boost { namespace stacktrace { namespace impl { 192 | 193 | BOOST_SYMBOL_EXPORT bool& ref_capture_stacktraces_at_throw() noexcept { 194 | /*constinit*/ thread_local bool g_capture_stacktraces_at_throw{true}; 195 | return g_capture_stacktraces_at_throw; 196 | } 197 | 198 | }}} // namespace boost::stacktrace::impl 199 | 200 | namespace __cxxabiv1 { 201 | 202 | #if defined(__GNUC__) && defined(__ELF__) 203 | 204 | // libc++-runtime specific function 205 | extern "C" BOOST_NOINLINE BOOST_SYMBOL_VISIBLE __attribute__((weak)) 206 | void __cxa_increment_exception_refcount(void *primary_exception) throw(); 207 | 208 | static bool is_libcpp_runtime() noexcept { 209 | return __cxa_increment_exception_refcount; 210 | } 211 | 212 | #else 213 | 214 | static bool is_libcpp_runtime() noexcept { return false; } 215 | 216 | #endif 217 | 218 | static const char*& reference_to_empty_padding(void* ptr) noexcept { 219 | if (is_libcpp_runtime()) { 220 | // libc++-runtime 221 | BOOST_ASSERT_MSG( 222 | sizeof(void*) != 4, 223 | "32bit platforms are unsupported with libc++ runtime padding reusage. " 224 | "Please report this issue to the library maintainters." 225 | ); 226 | return exception_begin_llvm_ptr(ptr)->reserve; 227 | } 228 | 229 | return exception_begin_gcc_ptr(ptr)->reserve; 230 | } 231 | 232 | extern "C" BOOST_SYMBOL_EXPORT 233 | void* __cxa_allocate_exception(size_t thrown_size) throw() { 234 | static const auto orig_allocate_exception = []() { 235 | void* const ptr = ::dlsym(RTLD_NEXT, "__cxa_allocate_exception"); 236 | BOOST_ASSERT_MSG(ptr, "Failed to find '__cxa_allocate_exception'"); 237 | return reinterpret_cast(ptr); 238 | }(); 239 | 240 | if (!boost::stacktrace::impl::ref_capture_stacktraces_at_throw()) { 241 | return orig_allocate_exception(thrown_size); 242 | } 243 | 244 | #ifndef NDEBUG 245 | static thread_local std::size_t in_allocate_exception = 0; 246 | BOOST_ASSERT_MSG(in_allocate_exception < 10, "Suspicious recursion"); 247 | ++in_allocate_exception; 248 | const decrement_on_destroy guard{in_allocate_exception}; 249 | #endif 250 | 251 | static constexpr std::size_t kAlign = alignof(std::max_align_t); 252 | thrown_size = (thrown_size + kAlign - 1) & (~(kAlign - 1)); 253 | 254 | void* const ptr = orig_allocate_exception(thrown_size + kStacktraceDumpSize); 255 | char* const dump_ptr = static_cast(ptr) + thrown_size; 256 | 257 | constexpr size_t kSkip = 1; 258 | boost::stacktrace::safe_dump_to(kSkip, dump_ptr, kStacktraceDumpSize); 259 | 260 | #if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING 261 | if (is_libcpp_runtime()) { 262 | const std::lock_guard guard{g_mapping_mutex}; 263 | g_exception_to_dump_mapping[ptr] = dump_ptr; 264 | } else 265 | #endif 266 | { 267 | BOOST_ASSERT_MSG( 268 | reference_to_empty_padding(ptr) == nullptr, 269 | "Not zeroed out, unsupported implementation" 270 | ); 271 | reference_to_empty_padding(ptr) = dump_ptr; 272 | } 273 | 274 | return ptr; 275 | } 276 | 277 | #if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING 278 | 279 | // __cxa_free_exception is not called in libc++ as the 280 | // __cxa_decrement_exception_refcount has an inlined call to 281 | // __cxa_free_exception. Overriding libc++ specific function 282 | extern "C" BOOST_SYMBOL_EXPORT 283 | void __cxa_decrement_exception_refcount(void *thrown_object) throw() { 284 | BOOST_ASSERT(is_libcpp_runtime()); 285 | 286 | #if !defined(BOOST_STACKTRACE_LIBCXX_RUNTIME_MAY_CAUSE_MEMORY_LEAK) && defined(BOOST_HAS_THREADS) 287 | static const char* leaks_are_fine = std::getenv("BOOST_STACKTRACE_LIBCXX_RUNTIME_MAY_CAUSE_MEMORY_LEAK"); 288 | if (!leaks_are_fine || leaks_are_fine[0] != '1') { 289 | const char* const warning = 290 | "\n\n" 291 | "=======================================================================================\n" 292 | "\n" 293 | "On this platform, memory leaks may occur if capturing stacktrace from exceptions is\n" 294 | "enabled and exceptions are thrown concurrently by libc++ runtime (libc++abi).\n" 295 | "\n" 296 | "A proper workaround is to use libstdc++ runtime (libgcc_s) instead.\n" 297 | "\n" 298 | "Alternatively, if you are willing to accept potential MEMORY LEAKS, set the environment\n" 299 | "variable `BOOST_STACKTRACE_LIBCXX_RUNTIME_MAY_CAUSE_MEMORY_LEAK=1` to proceed. You can\n" 300 | "also define the `BOOST_STACKTRACE_LIBCXX_RUNTIME_MAY_CAUSE_MEMORY_LEAK` macro when\n" 301 | "building the `boost_stacktrace_from_exception` library to disable this warning and to\n" 302 | "get the MEMORY LEAKS silently on libc++ runtime.\n" 303 | "\n" 304 | "=======================================================================================\n" 305 | ; 306 | write(STDERR_FILENO, warning, std::strlen(warning)); 307 | std::abort(); 308 | } 309 | #endif 310 | 311 | if (!thrown_object) { 312 | return; 313 | } 314 | 315 | static const auto orig_decrement_refcount = []() { 316 | void* const ptr = ::dlsym(RTLD_NEXT, "__cxa_decrement_exception_refcount"); 317 | BOOST_ASSERT_MSG(ptr, "Failed to find '__cxa_decrement_exception_refcount'"); 318 | return reinterpret_cast(ptr); 319 | }(); 320 | 321 | const auto* exception_header = exception_begin_llvm_ptr(thrown_object); 322 | 323 | // The following line has a race and could give false positives and false 324 | // negatives. In first case we remove the trace earlier, in the second case 325 | // we get a memory leak. 326 | if (exception_header->referenceCount == 1) { 327 | const std::lock_guard guard{g_mapping_mutex}; 328 | g_exception_to_dump_mapping.erase(thrown_object); 329 | } 330 | 331 | orig_decrement_refcount(thrown_object); 332 | } 333 | 334 | #endif 335 | 336 | } // namespace __cxxabiv1 337 | 338 | namespace boost { namespace stacktrace { namespace impl { 339 | 340 | BOOST_SYMBOL_EXPORT const char* current_exception_stacktrace() noexcept { 341 | if (!ref_capture_stacktraces_at_throw()) { 342 | return nullptr; 343 | } 344 | 345 | auto exc_ptr = std::current_exception(); 346 | void* const exc_raw_ptr = get_current_exception_raw_ptr(&exc_ptr); 347 | if (!exc_raw_ptr) { 348 | return nullptr; 349 | } 350 | 351 | #if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING 352 | if (__cxxabiv1::is_libcpp_runtime()) { 353 | const std::lock_guard guard{g_mapping_mutex}; 354 | const auto it = g_exception_to_dump_mapping.find(exc_raw_ptr); 355 | if (it != g_exception_to_dump_mapping.end()) { 356 | return it->second; 357 | } else { 358 | return nullptr; 359 | } 360 | } else 361 | #endif 362 | { 363 | return __cxxabiv1::reference_to_empty_padding(exc_raw_ptr); 364 | } 365 | } 366 | 367 | BOOST_SYMBOL_EXPORT void assert_no_pending_traces() noexcept { 368 | #if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING 369 | if (__cxxabiv1::is_libcpp_runtime()) { 370 | const std::lock_guard guard{g_mapping_mutex}; 371 | BOOST_ASSERT(g_exception_to_dump_mapping.empty()); 372 | } 373 | #endif 374 | } 375 | 376 | }}} // namespace boost::stacktrace::impl 377 | 378 | #endif 379 | -------------------------------------------------------------------------------- /include/boost/stacktrace/detail/frame_msvc.ipp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_DETAIL_FRAME_MSVC_IPP 8 | #define BOOST_STACKTRACE_DETAIL_FRAME_MSVC_IPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #ifndef BOOST_STACKTRACE_DISABLE_OFFSET_ADDR_BASE 23 | #include 24 | #endif 25 | 26 | #ifdef WIN32_LEAN_AND_MEAN 27 | #include 28 | #else 29 | // Prevent inclusion of extra Windows SDK headers which can cause conflict 30 | // with other code using Windows SDK 31 | #define WIN32_LEAN_AND_MEAN 32 | #include 33 | #undef WIN32_LEAN_AND_MEAN 34 | #endif 35 | 36 | #include "dbgeng.h" 37 | 38 | #include 39 | 40 | #if defined(__clang__) || defined(BOOST_MSVC) 41 | # pragma comment(lib, "ole32.lib") 42 | # pragma comment(lib, "Dbgeng.lib") 43 | #endif 44 | 45 | 46 | #ifdef __CRT_UUID_DECL // for __MINGW32__ 47 | #if !defined(__MINGW32__) || \ 48 | (!defined(__clang__) && __GNUC__ < 12) || \ 49 | (defined(__clang__) && __clang_major__ < 16) 50 | __CRT_UUID_DECL(IDebugClient,0x27fe5639,0x8407,0x4f47,0x83,0x64,0xee,0x11,0x8f,0xb0,0x8a,0xc8) 51 | __CRT_UUID_DECL(IDebugControl,0x5182e668,0x105e,0x416e,0xad,0x92,0x24,0xef,0x80,0x04,0x24,0xba) 52 | __CRT_UUID_DECL(IDebugSymbols,0x8c31e98c,0x983a,0x48a5,0x90,0x16,0x6f,0xe5,0xd6,0x67,0xa9,0x50) 53 | #endif 54 | #elif defined(DEFINE_GUID) && !defined(BOOST_MSVC) 55 | DEFINE_GUID(IID_IDebugClient,0x27fe5639,0x8407,0x4f47,0x83,0x64,0xee,0x11,0x8f,0xb0,0x8a,0xc8); 56 | DEFINE_GUID(IID_IDebugControl,0x5182e668,0x105e,0x416e,0xad,0x92,0x24,0xef,0x80,0x04,0x24,0xba); 57 | DEFINE_GUID(IID_IDebugSymbols,0x8c31e98c,0x983a,0x48a5,0x90,0x16,0x6f,0xe5,0xd6,0x67,0xa9,0x50); 58 | #endif 59 | 60 | 61 | 62 | // Testing. Remove later 63 | //# define __uuidof(x) ::IID_ ## x 64 | 65 | namespace boost { namespace stacktrace { namespace detail { 66 | 67 | template 68 | class com_holder: boost::noncopyable { 69 | T* holder_; 70 | 71 | public: 72 | com_holder() noexcept 73 | : holder_(0) 74 | {} 75 | 76 | T* operator->() const noexcept { 77 | return holder_; 78 | } 79 | 80 | void** to_void_ptr_ptr() noexcept { 81 | return reinterpret_cast(&holder_); 82 | } 83 | 84 | bool is_inited() const noexcept { 85 | return !!holder_; 86 | } 87 | 88 | ~com_holder() noexcept { 89 | if (holder_) { 90 | holder_->Release(); 91 | } 92 | } 93 | }; 94 | 95 | 96 | inline std::string mingw_demangling_workaround(const std::string& s) { 97 | #ifdef BOOST_GCC 98 | if (s.empty()) { 99 | return s; 100 | } 101 | 102 | if (s[0] != '_') { 103 | return boost::core::demangle(('_' + s).c_str()); 104 | } 105 | 106 | return boost::core::demangle(s.c_str()); 107 | #else 108 | return s; 109 | #endif 110 | } 111 | 112 | inline void trim_right_zeroes(std::string& s) { 113 | // MSVC-9 does not have back() and pop_back() functions in std::string 114 | while (!s.empty()) { 115 | const std::size_t last = static_cast(s.size() - 1); 116 | if (s[last] != '\0') { 117 | break; 118 | } 119 | s.resize(last); 120 | } 121 | } 122 | 123 | class debugging_symbols: boost::noncopyable { 124 | static void try_init_com(com_holder< ::IDebugSymbols>& idebug) noexcept { 125 | com_holder< ::IDebugClient> iclient; 126 | if (S_OK != ::DebugCreate(__uuidof(IDebugClient), iclient.to_void_ptr_ptr())) { 127 | return; 128 | } 129 | 130 | com_holder< ::IDebugControl> icontrol; 131 | const bool res0 = (S_OK == iclient->QueryInterface( 132 | __uuidof(IDebugControl), 133 | icontrol.to_void_ptr_ptr() 134 | )); 135 | if (!res0) { 136 | return; 137 | } 138 | 139 | const bool res1 = (S_OK == iclient->AttachProcess( 140 | 0, 141 | ::GetCurrentProcessId(), 142 | DEBUG_ATTACH_NONINVASIVE | DEBUG_ATTACH_NONINVASIVE_NO_SUSPEND 143 | )); 144 | if (!res1) { 145 | return; 146 | } 147 | 148 | if (S_OK != icontrol->WaitForEvent(DEBUG_WAIT_DEFAULT, INFINITE)) { 149 | return; 150 | } 151 | 152 | // No checking: QueryInterface sets the output parameter to NULL in case of error. 153 | iclient->QueryInterface(__uuidof(IDebugSymbols), idebug.to_void_ptr_ptr()); 154 | } 155 | 156 | #ifndef BOOST_STACKTRACE_USE_WINDBG_CACHED 157 | static std::mutex& get_mutex_inst() noexcept { 158 | static std::mutex m; 159 | return m; 160 | } 161 | 162 | static com_holder< ::IDebugSymbols>& get_static_debug_inst() noexcept { 163 | // [class.mfct]: A static local variable or local type in a member function always refers to the same entity, whether 164 | // or not the member function is inline. 165 | static com_holder< ::IDebugSymbols> idebug; 166 | 167 | if (!idebug.is_inited()) { 168 | try_init_com(idebug); 169 | } 170 | 171 | return idebug; 172 | } 173 | 174 | std::lock_guard guard_; 175 | com_holder< ::IDebugSymbols>& idebug_; 176 | public: 177 | debugging_symbols() noexcept 178 | : guard_( get_mutex_inst() ) 179 | , idebug_( get_static_debug_inst() ) 180 | {} 181 | #else 182 | 183 | #ifdef BOOST_NO_CXX11_THREAD_LOCAL 184 | # error Your compiler does not support C++11 thread_local storage. It`s impossible to build with BOOST_STACKTRACE_USE_WINDBG_CACHED. 185 | #endif 186 | 187 | static com_holder< ::IDebugSymbols>& get_thread_local_debug_inst() noexcept { 188 | // [class.mfct]: A static local variable or local type in a member function always refers to the same entity, whether 189 | // or not the member function is inline. 190 | static thread_local com_holder< ::IDebugSymbols> idebug; 191 | 192 | if (!idebug.is_inited()) { 193 | try_init_com(idebug); 194 | } 195 | 196 | return idebug; 197 | } 198 | 199 | com_holder< ::IDebugSymbols>& idebug_; 200 | public: 201 | debugging_symbols() noexcept 202 | : idebug_( get_thread_local_debug_inst() ) 203 | {} 204 | 205 | #endif // #ifndef BOOST_STACKTRACE_USE_WINDBG_CACHED 206 | 207 | bool is_inited() const noexcept { 208 | return idebug_.is_inited(); 209 | } 210 | 211 | std::string get_name_impl(const void* addr, std::string* module_name = 0) const { 212 | std::string result; 213 | if (!is_inited()) { 214 | return result; 215 | } 216 | const ULONG64 offset = reinterpret_cast(addr); 217 | 218 | char name[256]; 219 | name[0] = '\0'; 220 | ULONG size = 0; 221 | bool res = (S_OK == idebug_->GetNameByOffset( 222 | offset, 223 | name, 224 | sizeof(name), 225 | &size, 226 | 0 227 | )); 228 | 229 | if (!res && size != 0) { 230 | result.resize(size); 231 | res = (S_OK == idebug_->GetNameByOffset( 232 | offset, 233 | &result[0], 234 | static_cast(result.size()), 235 | &size, 236 | 0 237 | )); 238 | 239 | // According to https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/dbgeng/nf-dbgeng-idebugsymbols-getnamebyoffset 240 | // "This size includes the space for the '\0' terminating character." 241 | result.resize(size - 1); 242 | } else if (res) { 243 | result.assign(name, size - 1); 244 | } 245 | 246 | if (!res) { 247 | result.clear(); 248 | return result; 249 | } 250 | 251 | const std::size_t delimiter = result.find_first_of('!'); 252 | if (module_name) { 253 | *module_name = result.substr(0, delimiter); 254 | if (!module_name->empty()) { 255 | ULONG64 base = 0; 256 | res = (S_OK == idebug_->GetModuleByOffset( 257 | offset, 258 | 0, 259 | nullptr, 260 | &base 261 | )); 262 | 263 | if (res) { 264 | name[0] = '\0'; 265 | size = 0; 266 | res = (S_OK == idebug_->GetModuleNames( 267 | DEBUG_ANY_ID, 268 | base, 269 | name, 270 | sizeof(name), 271 | &size, 272 | nullptr, 273 | 0, 274 | nullptr, 275 | nullptr, 276 | 0, 277 | nullptr 278 | )); 279 | } 280 | 281 | if (!res && size != 0) 282 | { 283 | std::string module_path(size, char()); 284 | res = (S_OK == idebug_->GetModuleNames( 285 | DEBUG_ANY_ID, 286 | base, 287 | &module_path[0], 288 | static_cast(module_path.size()), 289 | &size, 290 | nullptr, 291 | 0, 292 | nullptr, 293 | nullptr, 294 | 0, 295 | nullptr 296 | )); 297 | if (res && size > 1) { 298 | module_name->assign(module_path, size - 1); 299 | } 300 | } 301 | else if (res && size > 1) { 302 | module_name->assign(name, size - 1); 303 | } 304 | } 305 | } 306 | 307 | if (delimiter == std::string::npos) { 308 | // If 'delimiter' is equal to 'std::string::npos' then we have only module name. 309 | result.clear(); 310 | return result; 311 | } 312 | 313 | result = mingw_demangling_workaround( 314 | result.substr(delimiter + 1) 315 | ); 316 | 317 | return result; 318 | } 319 | 320 | std::size_t get_line_impl(const void* addr) const noexcept { 321 | ULONG result = 0; 322 | if (!is_inited()) { 323 | return result; 324 | } 325 | 326 | const bool is_ok = (S_OK == idebug_->GetLineByOffset( 327 | reinterpret_cast(addr), 328 | &result, 329 | 0, 330 | 0, 331 | 0, 332 | 0 333 | )); 334 | 335 | return (is_ok ? result : 0); 336 | } 337 | 338 | std::pair get_source_file_line_impl(const void* addr) const { 339 | std::pair result; 340 | if (!is_inited()) { 341 | return result; 342 | } 343 | const ULONG64 offset = reinterpret_cast(addr); 344 | 345 | char name[256]; 346 | name[0] = 0; 347 | ULONG size = 0; 348 | ULONG line_num = 0; 349 | bool res = (S_OK == idebug_->GetLineByOffset( 350 | offset, 351 | &line_num, 352 | name, 353 | sizeof(name), 354 | &size, 355 | 0 356 | )); 357 | 358 | if (res) { 359 | result.first = name; 360 | result.second = line_num; 361 | return result; 362 | } 363 | 364 | if (!res && size == 0) { 365 | return result; 366 | } 367 | 368 | result.first.resize(size); 369 | res = (S_OK == idebug_->GetLineByOffset( 370 | offset, 371 | &line_num, 372 | &result.first[0], 373 | static_cast(result.first.size()), 374 | &size, 375 | 0 376 | )); 377 | trim_right_zeroes(result.first); 378 | result.second = line_num; 379 | 380 | if (!res) { 381 | result.first.clear(); 382 | result.second = 0; 383 | } 384 | 385 | return result; 386 | } 387 | 388 | void to_string_impl(const void* addr, std::string& res) const { 389 | if (!is_inited()) { 390 | return; 391 | } 392 | 393 | std::string module_name; 394 | std::string name = this->get_name_impl(addr, &module_name); 395 | if (!name.empty()) { 396 | res += name; 397 | } else { 398 | #ifdef BOOST_STACKTRACE_DISABLE_OFFSET_ADDR_BASE 399 | res += to_hex_array(addr).data(); 400 | #else 401 | // Get own base address 402 | const uintptr_t base_addr = get_own_proc_addr_base(addr); 403 | res += to_hex_array(reinterpret_cast(addr) - base_addr).data(); 404 | #endif 405 | } 406 | 407 | std::pair source_line = this->get_source_file_line_impl(addr); 408 | if (!source_line.first.empty() && source_line.second) { 409 | res += " at "; 410 | res += source_line.first; 411 | res += ':'; 412 | res += boost::stacktrace::detail::to_dec_array(source_line.second).data(); 413 | } else if (!module_name.empty()) { 414 | res += " in "; 415 | res += module_name; 416 | } 417 | } 418 | }; 419 | 420 | std::string to_string(const frame* frames, std::size_t size) { 421 | boost::stacktrace::detail::debugging_symbols idebug; 422 | if (!idebug.is_inited()) { 423 | return std::string(); 424 | } 425 | 426 | std::string res; 427 | res.reserve(64 * size); 428 | for (std::size_t i = 0; i < size; ++i) { 429 | if (i < 10) { 430 | res += ' '; 431 | } 432 | res += boost::stacktrace::detail::to_dec_array(i).data(); 433 | res += '#'; 434 | res += ' '; 435 | idebug.to_string_impl(frames[i].address(), res); 436 | res += '\n'; 437 | } 438 | 439 | return res; 440 | } 441 | 442 | } // namespace detail 443 | 444 | std::string frame::name() const { 445 | boost::stacktrace::detail::debugging_symbols idebug; 446 | return idebug.get_name_impl(addr_); 447 | } 448 | 449 | 450 | std::string frame::source_file() const { 451 | boost::stacktrace::detail::debugging_symbols idebug; 452 | return idebug.get_source_file_line_impl(addr_).first; 453 | } 454 | 455 | std::size_t frame::source_line() const { 456 | boost::stacktrace::detail::debugging_symbols idebug; 457 | return idebug.get_line_impl(addr_); 458 | } 459 | 460 | std::string to_string(const frame& f) { 461 | std::string res; 462 | 463 | boost::stacktrace::detail::debugging_symbols idebug; 464 | idebug.to_string_impl(f.address(), res); 465 | return res; 466 | } 467 | 468 | }} // namespace boost::stacktrace 469 | 470 | #endif // BOOST_STACKTRACE_DETAIL_FRAME_MSVC_IPP 471 | -------------------------------------------------------------------------------- /include/boost/stacktrace/stacktrace.hpp: -------------------------------------------------------------------------------- 1 | // Copyright Antony Polukhin, 2016-2025. 2 | // 3 | // Distributed under the Boost Software License, Version 1.0. (See 4 | // accompanying file LICENSE_1_0.txt or copy at 5 | // http://www.boost.org/LICENSE_1_0.txt) 6 | 7 | #ifndef BOOST_STACKTRACE_STACKTRACE_HPP 8 | #define BOOST_STACKTRACE_STACKTRACE_HPP 9 | 10 | #include 11 | #ifdef BOOST_HAS_PRAGMA_ONCE 12 | # pragma once 13 | #endif 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #ifndef BOOST_NO_CXX11_HDR_TYPE_TRAITS 23 | # include 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #ifdef BOOST_INTEL 32 | # pragma warning(push) 33 | # pragma warning(disable:2196) // warning #2196: routine is both "inline" and "noinline" 34 | #endif 35 | 36 | #if defined(BOOST_MSVC) 37 | 38 | extern "C" { 39 | 40 | const char* boost_stacktrace_impl_current_exception_stacktrace(); 41 | bool* boost_stacktrace_impl_ref_capture_stacktraces_at_throw(); 42 | 43 | } 44 | 45 | #ifdef _M_IX86 46 | # pragma comment(linker, "/ALTERNATENAME:_boost_stacktrace_impl_current_exception_stacktrace=_boost_stacktrace_impl_return_nullptr") 47 | # pragma comment(linker, "/ALTERNATENAME:_boost_stacktrace_impl_ref_capture_stacktraces_at_throw=_boost_stacktrace_impl_return_nullptr") 48 | #else 49 | # pragma comment(linker, "/ALTERNATENAME:boost_stacktrace_impl_current_exception_stacktrace=boost_stacktrace_impl_return_nullptr") 50 | # pragma comment(linker, "/ALTERNATENAME:boost_stacktrace_impl_ref_capture_stacktraces_at_throw=boost_stacktrace_impl_return_nullptr") 51 | #endif 52 | 53 | #endif 54 | 55 | namespace boost { namespace stacktrace { 56 | 57 | namespace impl { 58 | 59 | #if defined(__GNUC__) && defined(__ELF__) 60 | 61 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE __attribute__((weak)) 62 | const char* current_exception_stacktrace() noexcept; 63 | 64 | BOOST_NOINLINE BOOST_SYMBOL_VISIBLE __attribute__((weak)) 65 | bool& ref_capture_stacktraces_at_throw() noexcept; 66 | 67 | #endif 68 | 69 | } // namespace impl 70 | 71 | /// Class that on construction copies minimal information about call stack into its internals and provides access to that information. 72 | /// @tparam Allocator Allocator to use during stack capture. 73 | template 74 | class basic_stacktrace { 75 | std::vector impl_; 76 | typedef boost::stacktrace::detail::native_frame_ptr_t native_frame_ptr_t; 77 | 78 | /// @cond 79 | void fill(native_frame_ptr_t* begin, std::size_t size) { 80 | if (!size) { 81 | return; 82 | } 83 | 84 | impl_.reserve(static_cast(size)); 85 | for (std::size_t i = 0; i < size; ++i) { 86 | if (!begin[i]) { 87 | return; 88 | } 89 | impl_.push_back( 90 | frame(begin[i]) 91 | ); 92 | } 93 | } 94 | 95 | static std::size_t frames_count_from_buffer_size(std::size_t buffer_size) noexcept { 96 | const std::size_t ret = (buffer_size > sizeof(native_frame_ptr_t) ? buffer_size / sizeof(native_frame_ptr_t) : 0); 97 | return (ret > 1024 ? 1024 : ret); // Dealing with suspiciously big sizes 98 | } 99 | 100 | BOOST_NOINLINE void init(std::size_t frames_to_skip, std::size_t max_depth) { 101 | constexpr std::size_t buffer_size = 128; 102 | if (!max_depth) { 103 | return; 104 | } 105 | 106 | BOOST_TRY { 107 | { // Fast path without additional allocations 108 | native_frame_ptr_t buffer[buffer_size]; 109 | const std::size_t frames_count = boost::stacktrace::detail::this_thread_frames::collect(buffer, buffer_size < max_depth ? buffer_size : max_depth, frames_to_skip + 1); 110 | if (buffer_size > frames_count || frames_count == max_depth) { 111 | fill(buffer, frames_count); 112 | return; 113 | } 114 | } 115 | 116 | // Failed to fit in `buffer_size`. Allocating memory: 117 | #ifdef BOOST_NO_CXX11_ALLOCATOR 118 | typedef typename Allocator::template rebind::other allocator_void_t; 119 | #else 120 | typedef typename std::allocator_traits::template rebind_alloc allocator_void_t; 121 | #endif 122 | std::vector buf(buffer_size * 2, 0, impl_.get_allocator()); 123 | do { 124 | const std::size_t frames_count = boost::stacktrace::detail::this_thread_frames::collect(&buf[0], buf.size() < max_depth ? buf.size() : max_depth, frames_to_skip + 1); 125 | if (buf.size() > frames_count || frames_count == max_depth) { 126 | fill(&buf[0], frames_count); 127 | return; 128 | } 129 | 130 | buf.resize(buf.size() * 2); 131 | } while (buf.size() < buf.max_size()); // close to `true`, but suppresses `C4127: conditional expression is constant`. 132 | } BOOST_CATCH (...) { 133 | // ignore exception 134 | } 135 | BOOST_CATCH_END 136 | } 137 | /// @endcond 138 | 139 | public: 140 | typedef typename std::vector::value_type value_type; 141 | typedef typename std::vector::allocator_type allocator_type; 142 | typedef typename std::vector::const_pointer pointer; 143 | typedef typename std::vector::const_pointer const_pointer; 144 | typedef typename std::vector::const_reference reference; 145 | typedef typename std::vector::const_reference const_reference; 146 | typedef typename std::vector::size_type size_type; 147 | typedef typename std::vector::difference_type difference_type; 148 | typedef typename std::vector::const_iterator iterator; 149 | typedef typename std::vector::const_iterator const_iterator; 150 | typedef typename std::vector::const_reverse_iterator reverse_iterator; 151 | typedef typename std::vector::const_reverse_iterator const_reverse_iterator; 152 | 153 | /// @brief Stores the current function call sequence inside *this without any decoding or any other heavy platform specific operations. 154 | /// 155 | /// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. 156 | /// 157 | /// @b Async-Handler-Safety: \asyncsafe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe. 158 | BOOST_FORCEINLINE basic_stacktrace() noexcept 159 | : impl_() 160 | { 161 | init(0 , static_cast(-1)); 162 | } 163 | 164 | /// @brief Stores the current function call sequence inside *this without any decoding or any other heavy platform specific operations. 165 | /// 166 | /// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. 167 | /// 168 | /// @b Async-Handler-Safety: \asyncsafe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe. 169 | /// 170 | /// @param a Allocator that would be passed to underlying storage. 171 | BOOST_FORCEINLINE explicit basic_stacktrace(const allocator_type& a) noexcept 172 | : impl_(a) 173 | { 174 | init(0 , static_cast(-1)); 175 | } 176 | 177 | /// @brief Stores [skip, skip + max_depth) of the current function call sequence inside *this without any decoding or any other heavy platform specific operations. 178 | /// 179 | /// @b Complexity: O(N) where N is call sequence length, O(1) if BOOST_STACKTRACE_USE_NOOP is defined. 180 | /// 181 | /// @b Async-Handler-Safety: \asyncsafe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe. 182 | /// 183 | /// @param skip How many top calls to skip and do not store in *this. 184 | /// 185 | /// @param max_depth Max call sequence depth to collect. 186 | /// 187 | /// @param a Allocator that would be passed to underlying storage. 188 | /// 189 | /// @throws Nothing. Note that default construction of allocator may throw, however it is 190 | /// performed outside the constructor and exception in `allocator_type()` would not result in calling `std::terminate`. 191 | BOOST_FORCEINLINE basic_stacktrace(std::size_t skip, std::size_t max_depth, const allocator_type& a = allocator_type()) noexcept 192 | : impl_(a) 193 | { 194 | init(skip , max_depth); 195 | } 196 | 197 | /// @b Complexity: O(st.size()) 198 | /// 199 | /// @b Async-Handler-Safety: \asyncsafe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe. 200 | basic_stacktrace(const basic_stacktrace& st) 201 | : impl_(st.impl_) 202 | {} 203 | 204 | /// @b Complexity: O(st.size()) 205 | /// 206 | /// @b Async-Handler-Safety: \asyncsafe if Allocator construction, copying, Allocator::allocate and Allocator::deallocate are async signal safe. 207 | basic_stacktrace& operator=(const basic_stacktrace& st) { 208 | impl_ = st.impl_; 209 | return *this; 210 | } 211 | 212 | #ifdef BOOST_STACKTRACE_DOXYGEN_INVOKED 213 | /// @b Complexity: O(1) 214 | /// 215 | /// @b Async-Handler-Safety: \asyncsafe if Allocator::deallocate is async signal safe. 216 | ~basic_stacktrace() noexcept = default; 217 | #endif 218 | 219 | #if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) 220 | /// @b Complexity: O(1) 221 | /// 222 | /// @b Async-Handler-Safety: \asyncsafe if Allocator construction and copying are async signal safe. 223 | basic_stacktrace(basic_stacktrace&& st) noexcept 224 | : impl_(std::move(st.impl_)) 225 | {} 226 | 227 | /// @b Complexity: O(st.size()) 228 | /// 229 | /// @b Async-Handler-Safety: \asyncsafe if Allocator construction and copying are async signal safe. 230 | basic_stacktrace& operator=(basic_stacktrace&& st) 231 | #ifndef BOOST_NO_CXX11_HDR_TYPE_TRAITS 232 | noexcept(( std::is_nothrow_move_assignable< std::vector >::value )) 233 | #else 234 | noexcept 235 | #endif 236 | { 237 | impl_ = std::move(st.impl_); 238 | return *this; 239 | } 240 | #endif 241 | 242 | /// @returns Number of function names stored inside the class. 243 | /// 244 | /// @b Complexity: O(1) 245 | /// 246 | /// @b Async-Handler-Safety: \asyncsafe. 247 | size_type size() const noexcept { 248 | return impl_.size(); 249 | } 250 | 251 | /// @param frame_no Zero based index of frame to return. 0 252 | /// is the function index where stacktrace was constructed and 253 | /// index close to this->size() contains function `main()`. 254 | /// @returns frame that references the actual frame info, stored inside *this. 255 | /// 256 | /// @b Complexity: O(1). 257 | /// 258 | /// @b Async-Handler-Safety: \asyncsafe. 259 | const_reference operator[](std::size_t frame_no) const noexcept { 260 | return impl_[frame_no]; 261 | } 262 | 263 | /// @b Complexity: O(1) 264 | /// 265 | /// @b Async-Handler-Safety: \asyncsafe. 266 | const_iterator begin() const noexcept { return impl_.begin(); } 267 | /// @b Complexity: O(1) 268 | /// 269 | /// @b Async-Handler-Safety: \asyncsafe. 270 | const_iterator cbegin() const noexcept { return impl_.begin(); } 271 | /// @b Complexity: O(1) 272 | /// 273 | /// @b Async-Handler-Safety: \asyncsafe. 274 | const_iterator end() const noexcept { return impl_.end(); } 275 | /// @b Complexity: O(1) 276 | /// 277 | /// @b Async-Handler-Safety: \asyncsafe. 278 | const_iterator cend() const noexcept { return impl_.end(); } 279 | 280 | /// @b Complexity: O(1) 281 | /// 282 | /// @b Async-Handler-Safety: \asyncsafe. 283 | const_reverse_iterator rbegin() const noexcept { return impl_.rbegin(); } 284 | /// @b Complexity: O(1) 285 | /// 286 | /// @b Async-Handler-Safety: \asyncsafe. 287 | const_reverse_iterator crbegin() const noexcept { return impl_.rbegin(); } 288 | /// @b Complexity: O(1) 289 | /// 290 | /// @b Async-Handler-Safety: \asyncsafe. 291 | const_reverse_iterator rend() const noexcept { return impl_.rend(); } 292 | /// @b Complexity: O(1) 293 | /// 294 | /// @b Async-Handler-Safety: \asyncsafe. 295 | const_reverse_iterator crend() const noexcept { return impl_.rend(); } 296 | 297 | 298 | /// @brief Allows to check that stack trace capturing was successful. 299 | /// @returns `true` if `this->size() != 0` 300 | /// 301 | /// @b Complexity: O(1) 302 | /// 303 | /// @b Async-Handler-Safety: \asyncsafe. 304 | constexpr explicit operator bool () const noexcept { return !empty(); } 305 | 306 | /// @brief Allows to check that stack trace failed. 307 | /// @returns `true` if `this->size() == 0` 308 | /// 309 | /// @b Complexity: O(1) 310 | /// 311 | /// @b Async-Handler-Safety: \asyncsafe. 312 | bool empty() const noexcept { return !size(); } 313 | 314 | const std::vector& as_vector() const noexcept { 315 | return impl_; 316 | } 317 | 318 | /// Constructs stacktrace from basic_istreamable that references the dumped stacktrace. Terminating zero frame is discarded. 319 | /// 320 | /// @b Complexity: O(N) 321 | template 322 | static basic_stacktrace from_dump(std::basic_istream& in, const allocator_type& a = allocator_type()) { 323 | typedef typename std::basic_istream::pos_type pos_type; 324 | basic_stacktrace ret(0, 0, a); 325 | 326 | // reserving space 327 | const pos_type pos = in.tellg(); 328 | in.seekg(0, in.end); 329 | const std::size_t frames_count = frames_count_from_buffer_size(static_cast(in.tellg())); 330 | in.seekg(pos); 331 | 332 | if (!frames_count) { 333 | return ret; 334 | } 335 | 336 | native_frame_ptr_t ptr = 0; 337 | ret.impl_.reserve(frames_count); 338 | while (in.read(reinterpret_cast(&ptr), sizeof(ptr))) { 339 | if (!ptr) { 340 | break; 341 | } 342 | 343 | ret.impl_.push_back(frame(ptr)); 344 | } 345 | 346 | return ret; 347 | } 348 | 349 | /// Constructs stacktrace from raw memory dump. Terminating zero frame is discarded. 350 | /// 351 | /// @param begin Beginning of the memory where the stacktrace was saved using the boost::stacktrace::safe_dump_to 352 | /// 353 | /// @param buffer_size_in_bytes Size of the memory. Usually the same value that was passed to the boost::stacktrace::safe_dump_to 354 | /// 355 | /// @b Complexity: O(size) in worst case 356 | static basic_stacktrace from_dump(const void* begin, std::size_t buffer_size_in_bytes, const allocator_type& a = allocator_type()) { 357 | basic_stacktrace ret(0, 0, a); 358 | const native_frame_ptr_t* first = static_cast(begin); 359 | const std::size_t frames_count = frames_count_from_buffer_size(buffer_size_in_bytes); 360 | if (!frames_count) { 361 | return ret; 362 | } 363 | 364 | const native_frame_ptr_t* const last = first + frames_count; 365 | ret.impl_.reserve(frames_count); 366 | for (; first != last; ++first) { 367 | if (!*first) { 368 | break; 369 | } 370 | 371 | ret.impl_.push_back(frame(*first)); 372 | } 373 | 374 | return ret; 375 | } 376 | 377 | /// Returns a basic_stacktrace object containing a stacktrace captured at 378 | /// the point where the currently handled exception was thrown by its 379 | /// initial throw-expression (i.e. not a rethrow), or an empty 380 | /// basic_stacktrace object if: 381 | /// 382 | /// - the `boost_stacktrace_from_exception` library is not linked to the 383 | /// current binary, or 384 | /// - the initialization of stacktrace failed, or 385 | /// - stacktrace captures are not enabled for the throwing thread, or 386 | /// - no exception is being handled, or 387 | /// - due to implementation-defined reasons. 388 | /// 389 | /// `alloc` is passed to the constructor of the stacktrace object. 390 | /// Rethrowing an exception using a throw-expression with no operand does 391 | /// not alter the captured stacktrace. 392 | /// 393 | /// Implements https://wg21.link/p2370r1 394 | static basic_stacktrace from_current_exception(const allocator_type& alloc = allocator_type()) noexcept { 395 | // Matches the constant from implementation 396 | constexpr std::size_t kStacktraceDumpSize = 4096; 397 | 398 | const char* trace = nullptr; 399 | #if defined(__GNUC__) && defined(__ELF__) 400 | if (impl::current_exception_stacktrace) { 401 | trace = impl::current_exception_stacktrace(); 402 | } 403 | #elif defined(BOOST_MSVC) 404 | trace = boost_stacktrace_impl_current_exception_stacktrace(); 405 | #endif 406 | 407 | if (trace) { 408 | try { 409 | return basic_stacktrace::from_dump(trace, kStacktraceDumpSize, alloc); 410 | } catch (const std::exception&) { 411 | // ignore 412 | } 413 | } 414 | return basic_stacktrace{0, 0, alloc}; 415 | } 416 | }; 417 | 418 | /// @brief Compares stacktraces for less, order is platform dependent. 419 | /// 420 | /// @b Complexity: Amortized O(1); worst case O(size()) 421 | /// 422 | /// @b Async-Handler-Safety: \asyncsafe. 423 | template 424 | bool operator< (const basic_stacktrace& lhs, const basic_stacktrace& rhs) noexcept { 425 | return lhs.size() < rhs.size() || (lhs.size() == rhs.size() && lhs.as_vector() < rhs.as_vector()); 426 | } 427 | 428 | /// @brief Compares stacktraces for equality. 429 | /// 430 | /// @b Complexity: Amortized O(1); worst case O(size()) 431 | /// 432 | /// @b Async-Handler-Safety: \asyncsafe. 433 | template 434 | bool operator==(const basic_stacktrace& lhs, const basic_stacktrace& rhs) noexcept { 435 | return lhs.as_vector() == rhs.as_vector(); 436 | } 437 | 438 | 439 | /// Comparison operators that provide platform dependant ordering and have amortized O(1) complexity; O(size()) worst case complexity; are Async-Handler-Safe. 440 | template 441 | bool operator> (const basic_stacktrace& lhs, const basic_stacktrace& rhs) noexcept { 442 | return rhs < lhs; 443 | } 444 | 445 | template 446 | bool operator<=(const basic_stacktrace& lhs, const basic_stacktrace& rhs) noexcept { 447 | return !(lhs > rhs); 448 | } 449 | 450 | template 451 | bool operator>=(const basic_stacktrace& lhs, const basic_stacktrace& rhs) noexcept { 452 | return !(lhs < rhs); 453 | } 454 | 455 | template 456 | bool operator!=(const basic_stacktrace& lhs, const basic_stacktrace& rhs) noexcept { 457 | return !(lhs == rhs); 458 | } 459 | 460 | /// Fast hashing support, O(st.size()) complexity; Async-Handler-Safe. 461 | template 462 | std::size_t hash_value(const basic_stacktrace& st) noexcept { 463 | return boost::hash_range(st.as_vector().begin(), st.as_vector().end()); 464 | } 465 | 466 | /// Returns std::string with the stacktrace in a human readable format; unsafe to use in async handlers. 467 | template 468 | std::string to_string(const basic_stacktrace& bt) { 469 | if (!bt) { 470 | return std::string(); 471 | } 472 | 473 | return boost::stacktrace::detail::to_string(&bt.as_vector()[0], bt.size()); 474 | } 475 | 476 | /// Outputs stacktrace in a human readable format to the output stream `os`; unsafe to use in async handlers. 477 | template 478 | std::basic_ostream& operator<<(std::basic_ostream& os, const basic_stacktrace& bt) { 479 | return os << boost::stacktrace::to_string(bt); 480 | } 481 | 482 | /// This is the typedef to use unless you'd like to provide a specific allocator to boost::stacktrace::basic_stacktrace. 483 | typedef basic_stacktrace<> stacktrace; 484 | 485 | }} // namespace boost::stacktrace 486 | 487 | #ifdef BOOST_INTEL 488 | # pragma warning(pop) 489 | #endif 490 | 491 | #endif // BOOST_STACKTRACE_STACKTRACE_HPP 492 | --------------------------------------------------------------------------------