mirror of
https://github.com/boostorg/stacktrace.git
synced 2026-01-19 04:42:12 +00:00
379 lines
11 KiB
C++
379 lines
11 KiB
C++
// Copyright Antony Polukhin, 2023-2026.
|
|
//
|
|
// Distributed under the Boost Software License, Version 1.0. (See
|
|
// accompanying file LICENSE_1_0.txt or copy at
|
|
// http://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
#if defined(__MINGW32__) || defined(_MSC_VER)
|
|
|
|
#include <boost/stacktrace/safe_dump_to.hpp>
|
|
#include <windows.h>
|
|
|
|
extern "C" void** __cdecl __current_exception(); // exported from vcruntime.dll
|
|
#define _pCurrentException static_cast<PEXCEPTION_RECORD>(*__current_exception())
|
|
|
|
namespace {
|
|
|
|
constexpr std::size_t kStacktraceDumpSize = 4096;
|
|
|
|
struct thrown_info {
|
|
ULONG_PTR object;
|
|
char* dump;
|
|
};
|
|
|
|
struct exception_data {
|
|
bool capture_stacktraces_at_throw = true;
|
|
unsigned count = 0;
|
|
thrown_info* info = nullptr;
|
|
|
|
~exception_data() noexcept {
|
|
HANDLE hHeap = GetProcessHeap();
|
|
for (unsigned i = 0; i < count; ++i) {
|
|
HeapFree(hHeap, 0, info[i].dump);
|
|
}
|
|
HeapFree(hHeap, 0, info);
|
|
}
|
|
};
|
|
|
|
thread_local exception_data data;
|
|
|
|
inline bool PER_IS_MSVC_EH(PEXCEPTION_RECORD p) noexcept {
|
|
const DWORD EH_EXCEPTION_NUMBER = 0xE06D7363;
|
|
const ULONG_PTR EH_MAGIC_NUMBER1 = 0x19930520;
|
|
|
|
return p->ExceptionCode == EH_EXCEPTION_NUMBER &&
|
|
(p->NumberParameters == 3 || p->NumberParameters == 4) &&
|
|
p->ExceptionInformation[0] == EH_MAGIC_NUMBER1;
|
|
}
|
|
|
|
inline ULONG_PTR PER_PEXCEPTOBJ(PEXCEPTION_RECORD p) noexcept {
|
|
return p->ExceptionInformation[1];
|
|
}
|
|
|
|
unsigned current_cxx_exception_index() noexcept {
|
|
if (PEXCEPTION_RECORD current_cxx_exception = _pCurrentException) {
|
|
for (unsigned i = data.count; i > 0;) {
|
|
--i;
|
|
if (data.info[i].object == PER_PEXCEPTOBJ(current_cxx_exception)) {
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
return data.count;
|
|
}
|
|
|
|
bool is_processing_rethrow(PEXCEPTION_RECORD p) noexcept {
|
|
// Processing flow:
|
|
// 0. throw;
|
|
// 1. _CxxThrowException(pExceptionObject = nullptr)
|
|
// 2. VEH & SEH (may throw new c++ exceptions!)
|
|
// 3. __RethrowException(_pCurrentException)
|
|
// 4. VEH
|
|
if (PER_PEXCEPTOBJ(p) == 0) return true;
|
|
PEXCEPTION_RECORD current_cxx_exception = _pCurrentException;
|
|
if (current_cxx_exception == nullptr) return false;
|
|
return PER_PEXCEPTOBJ(p) == PER_PEXCEPTOBJ(current_cxx_exception);
|
|
}
|
|
|
|
LONG NTAPI veh(PEXCEPTION_POINTERS p) {
|
|
if (data.capture_stacktraces_at_throw &&
|
|
PER_IS_MSVC_EH(p->ExceptionRecord) &&
|
|
!is_processing_rethrow(p->ExceptionRecord)) {
|
|
HANDLE hHeap = GetProcessHeap();
|
|
unsigned index = current_cxx_exception_index();
|
|
unsigned new_count = 1 + (index < data.count ? index + 1 : 0);
|
|
|
|
for (unsigned i = new_count; i < data.count; ++i) {
|
|
HeapFree(hHeap, 0, data.info[i].dump);
|
|
data.info[i].dump = nullptr;
|
|
}
|
|
|
|
void* new_info;
|
|
if (data.info) {
|
|
new_info = HeapReAlloc(hHeap, HEAP_ZERO_MEMORY, data.info, sizeof(thrown_info) * new_count);
|
|
} else {
|
|
new_info = HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(thrown_info) * new_count);
|
|
}
|
|
if (new_info) {
|
|
data.count = new_count;
|
|
data.info = static_cast<thrown_info*>(new_info);
|
|
data.info[data.count - 1].object = PER_PEXCEPTOBJ(p->ExceptionRecord);
|
|
char*& dump_ptr = data.info[data.count - 1].dump;
|
|
if (dump_ptr == nullptr) {
|
|
dump_ptr = static_cast<char*>(HeapAlloc(hHeap, 0, kStacktraceDumpSize));
|
|
}
|
|
if (dump_ptr != nullptr) {
|
|
const std::size_t kSkip = 4;
|
|
boost::stacktrace::safe_dump_to(kSkip, dump_ptr, kStacktraceDumpSize);
|
|
}
|
|
} else if (new_count <= data.count) {
|
|
data.count = new_count - 1;
|
|
HeapFree(hHeap, 0, data.info[data.count - 1].dump);
|
|
data.info[data.count - 1].dump = nullptr;
|
|
}
|
|
}
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
struct veh_installer {
|
|
PVOID h;
|
|
veh_installer() noexcept : h(AddVectoredExceptionHandler(1, veh)) {}
|
|
~veh_installer() noexcept { RemoveVectoredExceptionHandler(h); }
|
|
} installer;
|
|
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
BOOST_SYMBOL_EXPORT const char* boost_stacktrace_impl_current_exception_stacktrace() {
|
|
unsigned index = current_cxx_exception_index();
|
|
return index < data.count ? data.info[index].dump : nullptr;
|
|
}
|
|
|
|
BOOST_SYMBOL_EXPORT bool* boost_stacktrace_impl_ref_capture_stacktraces_at_throw() {
|
|
return &data.capture_stacktraces_at_throw;
|
|
}
|
|
|
|
}
|
|
|
|
namespace boost { namespace stacktrace { namespace impl {
|
|
|
|
BOOST_SYMBOL_EXPORT void assert_no_pending_traces() noexcept {
|
|
}
|
|
|
|
}}} // namespace boost::stacktrace::impl
|
|
|
|
#else
|
|
|
|
#include "exception_headers.h"
|
|
|
|
// At the moment the file is used only on POSIX. _Unwind_Backtrace may be
|
|
// available on some platforms only if _GNU_SOURCE is defined.
|
|
#ifndef _GNU_SOURCE
|
|
# define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include <boost/assert.hpp>
|
|
#include <boost/stacktrace/safe_dump_to.hpp>
|
|
|
|
#include <cstddef>
|
|
#include <exception>
|
|
#include <dlfcn.h>
|
|
|
|
|
|
#if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
#include <unordered_map>
|
|
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
namespace {
|
|
|
|
constexpr std::size_t kStacktraceDumpSize = 4096;
|
|
|
|
struct decrement_on_destroy {
|
|
std::size_t& to_decrement;
|
|
|
|
~decrement_on_destroy() { --to_decrement; }
|
|
};
|
|
|
|
#if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING
|
|
// Inspired by the coursework by Andrei Nekrashevich in the `libsfe`
|
|
/*constinit*/ std::mutex g_mapping_mutex;
|
|
std::unordered_map<void*, const char*> g_exception_to_dump_mapping;
|
|
#endif
|
|
|
|
} // namespace
|
|
|
|
namespace boost { namespace stacktrace { namespace impl {
|
|
|
|
BOOST_SYMBOL_EXPORT bool& ref_capture_stacktraces_at_throw() noexcept {
|
|
/*constinit*/ thread_local bool g_capture_stacktraces_at_throw{true};
|
|
return g_capture_stacktraces_at_throw;
|
|
}
|
|
|
|
}}} // namespace boost::stacktrace::impl
|
|
|
|
namespace __cxxabiv1 {
|
|
|
|
#if defined(__GNUC__) && defined(__ELF__)
|
|
|
|
// libc++-runtime specific function
|
|
extern "C" BOOST_NOINLINE BOOST_SYMBOL_VISIBLE __attribute__((weak))
|
|
void __cxa_increment_exception_refcount(void *primary_exception) throw();
|
|
|
|
static bool is_libcpp_runtime() noexcept {
|
|
return __cxa_increment_exception_refcount;
|
|
}
|
|
|
|
#else
|
|
|
|
static bool is_libcpp_runtime() noexcept { return false; }
|
|
|
|
#endif
|
|
|
|
static const char*& reference_to_empty_padding(void* ptr) noexcept {
|
|
if (is_libcpp_runtime()) {
|
|
// libc++-runtime
|
|
BOOST_ASSERT_MSG(
|
|
sizeof(void*) != 4,
|
|
"32bit platforms are unsupported with libc++ runtime padding reusage. "
|
|
"Please report this issue to the library maintainters."
|
|
);
|
|
return exception_begin_llvm_ptr(ptr)->reserve;
|
|
}
|
|
|
|
return exception_begin_gcc_ptr(ptr)->reserve;
|
|
}
|
|
|
|
extern "C" BOOST_SYMBOL_EXPORT
|
|
void* __cxa_allocate_exception(size_t thrown_size) throw() {
|
|
static const auto orig_allocate_exception = []() {
|
|
void* const ptr = ::dlsym(RTLD_NEXT, "__cxa_allocate_exception");
|
|
BOOST_ASSERT_MSG(ptr, "Failed to find '__cxa_allocate_exception'");
|
|
return reinterpret_cast<void*(*)(size_t)>(ptr);
|
|
}();
|
|
|
|
if (!boost::stacktrace::impl::ref_capture_stacktraces_at_throw()) {
|
|
return orig_allocate_exception(thrown_size);
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
static thread_local std::size_t in_allocate_exception = 0;
|
|
BOOST_ASSERT_MSG(in_allocate_exception < 10, "Suspicious recursion");
|
|
++in_allocate_exception;
|
|
const decrement_on_destroy guard{in_allocate_exception};
|
|
#endif
|
|
|
|
static constexpr std::size_t kAlign = alignof(std::max_align_t);
|
|
thrown_size = (thrown_size + kAlign - 1) & (~(kAlign - 1));
|
|
|
|
void* const ptr = orig_allocate_exception(thrown_size + kStacktraceDumpSize);
|
|
char* const dump_ptr = static_cast<char*>(ptr) + thrown_size;
|
|
|
|
constexpr size_t kSkip = 1;
|
|
boost::stacktrace::safe_dump_to(kSkip, dump_ptr, kStacktraceDumpSize);
|
|
|
|
#if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING
|
|
if (is_libcpp_runtime()) {
|
|
const std::lock_guard<std::mutex> guard{g_mapping_mutex};
|
|
g_exception_to_dump_mapping[ptr] = dump_ptr;
|
|
} else
|
|
#endif
|
|
{
|
|
BOOST_ASSERT_MSG(
|
|
reference_to_empty_padding(ptr) == nullptr,
|
|
"Not zeroed out, unsupported implementation"
|
|
);
|
|
reference_to_empty_padding(ptr) = dump_ptr;
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
#if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING
|
|
|
|
// __cxa_free_exception is not called in libc++ as the
|
|
// __cxa_decrement_exception_refcount has an inlined call to
|
|
// __cxa_free_exception. Overriding libc++ specific function
|
|
extern "C" BOOST_SYMBOL_EXPORT
|
|
void __cxa_decrement_exception_refcount(void *thrown_object) throw() {
|
|
BOOST_ASSERT(is_libcpp_runtime());
|
|
|
|
#if !defined(BOOST_STACKTRACE_LIBCXX_RUNTIME_MAY_CAUSE_MEMORY_LEAK) && defined(BOOST_HAS_THREADS)
|
|
static const char* leaks_are_fine = std::getenv("BOOST_STACKTRACE_LIBCXX_RUNTIME_MAY_CAUSE_MEMORY_LEAK");
|
|
if (!leaks_are_fine || leaks_are_fine[0] != '1') {
|
|
const char* const warning =
|
|
"\n\n"
|
|
"=======================================================================================\n"
|
|
"\n"
|
|
"On this platform, memory leaks may occur if capturing stacktrace from exceptions is\n"
|
|
"enabled and exceptions are thrown concurrently by libc++ runtime (libc++abi).\n"
|
|
"\n"
|
|
"A proper workaround is to use libstdc++ runtime (libgcc_s) instead.\n"
|
|
"\n"
|
|
"Alternatively, if you are willing to accept potential MEMORY LEAKS, set the environment\n"
|
|
"variable `BOOST_STACKTRACE_LIBCXX_RUNTIME_MAY_CAUSE_MEMORY_LEAK=1` to proceed. You can\n"
|
|
"also define the `BOOST_STACKTRACE_LIBCXX_RUNTIME_MAY_CAUSE_MEMORY_LEAK` macro when\n"
|
|
"building the `boost_stacktrace_from_exception` library to disable this warning and to\n"
|
|
"get the MEMORY LEAKS silently on libc++ runtime.\n"
|
|
"\n"
|
|
"=======================================================================================\n"
|
|
;
|
|
write(STDERR_FILENO, warning, std::strlen(warning));
|
|
std::abort();
|
|
}
|
|
#endif
|
|
|
|
if (!thrown_object) {
|
|
return;
|
|
}
|
|
|
|
static const auto orig_decrement_refcount = []() {
|
|
void* const ptr = ::dlsym(RTLD_NEXT, "__cxa_decrement_exception_refcount");
|
|
BOOST_ASSERT_MSG(ptr, "Failed to find '__cxa_decrement_exception_refcount'");
|
|
return reinterpret_cast<void(*)(void*)>(ptr);
|
|
}();
|
|
|
|
const auto* exception_header = exception_begin_llvm_ptr(thrown_object);
|
|
|
|
// The following line has a race and could give false positives and false
|
|
// negatives. In first case we remove the trace earlier, in the second case
|
|
// we get a memory leak.
|
|
if (exception_header->referenceCount == 1) {
|
|
const std::lock_guard<std::mutex> guard{g_mapping_mutex};
|
|
g_exception_to_dump_mapping.erase(thrown_object);
|
|
}
|
|
|
|
orig_decrement_refcount(thrown_object);
|
|
}
|
|
|
|
#endif
|
|
|
|
} // namespace __cxxabiv1
|
|
|
|
namespace boost { namespace stacktrace { namespace impl {
|
|
|
|
BOOST_SYMBOL_EXPORT const char* current_exception_stacktrace() noexcept {
|
|
if (!ref_capture_stacktraces_at_throw()) {
|
|
return nullptr;
|
|
}
|
|
|
|
auto exc_ptr = std::current_exception();
|
|
void* const exc_raw_ptr = get_current_exception_raw_ptr(&exc_ptr);
|
|
if (!exc_raw_ptr) {
|
|
return nullptr;
|
|
}
|
|
|
|
#if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING
|
|
if (__cxxabiv1::is_libcpp_runtime()) {
|
|
const std::lock_guard<std::mutex> guard{g_mapping_mutex};
|
|
const auto it = g_exception_to_dump_mapping.find(exc_raw_ptr);
|
|
if (it != g_exception_to_dump_mapping.end()) {
|
|
return it->second;
|
|
} else {
|
|
return nullptr;
|
|
}
|
|
} else
|
|
#endif
|
|
{
|
|
return __cxxabiv1::reference_to_empty_padding(exc_raw_ptr);
|
|
}
|
|
}
|
|
|
|
BOOST_SYMBOL_EXPORT void assert_no_pending_traces() noexcept {
|
|
#if !BOOST_STACKTRACE_ALWAYS_STORE_IN_PADDING
|
|
if (__cxxabiv1::is_libcpp_runtime()) {
|
|
const std::lock_guard<std::mutex> guard{g_mapping_mutex};
|
|
BOOST_ASSERT(g_exception_to_dump_mapping.empty());
|
|
}
|
|
#endif
|
|
}
|
|
|
|
}}} // namespace boost::stacktrace::impl
|
|
|
|
#endif
|