diff --git a/CMakeLists.txt b/CMakeLists.txt index ba565a8..5564795 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,6 +214,7 @@ target_sources( src/binary/safe_dl.cpp src/cpptrace.cpp src/ctrace.cpp + src/from_current.cpp src/demangle/demangle_with_cxxabi.cpp src/demangle/demangle_with_nothing.cpp src/demangle/demangle_with_winapi.cpp diff --git a/include/cpptrace/from_current.hpp b/include/cpptrace/from_current.hpp index dff0720..6388628 100644 --- a/include/cpptrace/from_current.hpp +++ b/include/cpptrace/from_current.hpp @@ -3,8 +3,6 @@ #include -#include - namespace cpptrace { const raw_trace& raw_trace_from_current_exception(); const stacktrace& from_current_exception(); @@ -20,18 +18,20 @@ namespace cpptrace { void do_prepare_unwind_interceptor(); - __attribute__((constructor)) inline void prepare_unwind_interceptor() { - // __attribute__((constructor)) inline functions can be called for every source file they're #included in - // there is still only one copy of the inline function in the final executable, though - // LTO can make the redundant constructs fire only once - // do_prepare_unwind_interceptor prevents against multiple preparations however it makes sense to guard - // against it here too as a fast path, not that this should matter for performance - static bool did_prepare = false; - if(!did_prepare) { - do_prepare_unwind_interceptor(); - did_prepare = true; - } - } + #ifndef CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON + __attribute__((constructor)) inline void prepare_unwind_interceptor() { + // __attribute__((constructor)) inline functions can be called for every source file they're #included in + // there is still only one copy of the inline function in the final executable, though + // LTO can make the redundant constructs fire only once + // do_prepare_unwind_interceptor prevents against multiple preparations however it makes sense to guard + // against it here too as a fast path, not that this should matter for performance + static bool did_prepare = false; + if(!did_prepare) { + do_prepare_unwind_interceptor(); + did_prepare = true; + } + } + #endif #endif } } diff --git a/src/cpptrace.cpp b/src/cpptrace.cpp index 1823cb0..0941f0e 100644 --- a/src/cpptrace.cpp +++ b/src/cpptrace.cpp @@ -1,5 +1,4 @@ #include -#include #include #include @@ -12,13 +11,6 @@ #include #include -#ifndef _MSC_VER - #include - #include - #include - #include -#endif - #include "symbols/symbols.hpp" #include "unwind/unwind.hpp" #include "demangle/demangle.hpp" @@ -703,126 +695,4 @@ namespace cpptrace { throw nested_exception(std::current_exception(), detail::get_raw_trace_and_absorb(skip + 1)); } } - - namespace detail { - thread_local lazy_trace_holder current; - - #ifndef _MSC_VER - CPPTRACE_FORCE_NO_INLINE - bool intercept_unwind(const std::type_info*, const std::type_info*, void**, unsigned) { - current = lazy_trace_holder(cpptrace::generate_raw_trace(1)); - return false; - } - - unwind_interceptor::~unwind_interceptor() = default; - - #if defined(__GLIBCXX__) || defined(__GLIBCPP__) - constexpr size_t vtable_size = 11; - #elif defined(_LIBCPP_VERSION) - constexpr size_t vtable_size = 10; - #else - #warning "Cpptrace from_current: Unrecognized C++ standard library, from_current() won't be supported" - constexpr size_t vtable_size = 0; - #endif - - std::array new_vtable; - - void mprotect_page(void* page, int page_size, int protections) { - if(mprotect(page, page_size, protections) != 0) { - throw std::runtime_error(microfmt::format("mprotect call failed: {}", strerror(errno))); - } - } - - void clobber_type_info(const std::type_info& info) { - if(vtable_size == 0) { // set to zero if we don't know what standard library we're working with - return; - } - void* type_info_pointer = const_cast(reinterpret_cast(&info)); - void* type_info_vtable_pointer = *reinterpret_cast(type_info_pointer); - // the type info vtable pointer points to two pointers inside the vtable, adjust it back - type_info_vtable_pointer = reinterpret_cast(reinterpret_cast(type_info_vtable_pointer) - 2); - - // for libstdc++ the class type info vtable looks like - // 0x7ffff7f89d18 <_ZTVN10__cxxabiv117__class_type_infoE>: 0x0000000000000000 0x00007ffff7f89d00 - // [offset ][typeinfo pointer ] - // 0x7ffff7f89d28 <_ZTVN10__cxxabiv117__class_type_infoE+16>: 0x00007ffff7dd65a0 0x00007ffff7dd65c0 - // [base destructor ][deleting dtor ] - // 0x7ffff7f89d38 <_ZTVN10__cxxabiv117__class_type_infoE+32>: 0x00007ffff7dd8f10 0x00007ffff7dd8f10 - // [__is_pointer_p ][__is_function_p ] - // 0x7ffff7f89d48 <_ZTVN10__cxxabiv117__class_type_infoE+48>: 0x00007ffff7dd6640 0x00007ffff7dd6500 - // [__do_catch ][__do_upcast ] - // 0x7ffff7f89d58 <_ZTVN10__cxxabiv117__class_type_infoE+64>: 0x00007ffff7dd65e0 0x00007ffff7dd66d0 - // [__do_upcast ][__do_dyncast ] - // 0x7ffff7f89d68 <_ZTVN10__cxxabiv117__class_type_infoE+80>: 0x00007ffff7dd6580 0x00007ffff7f8abe8 - // [__do_find_public_src][other ] - // In libc++ the layout is - // [offset ][typeinfo pointer ] - // [base destructor ][deleting dtor ] - // [noop1 ][noop2 ] - // [can_catch ][search_above_dst ] - // [search_below_dst ][has_unambiguous_public_base] - // Relevant documentation/implementation: - // https://itanium-cxx-abi.github.io/cxx-abi/abi.html - // libstdc++ - // https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/typeinfo - // https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/class_type_info.cc - // libc++ - // https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxx/include/typeinfo - // https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxxabi/src/private_typeinfo.h - - // make our own copy of the vtable - memcpy(new_vtable.data(), type_info_vtable_pointer, vtable_size * sizeof(void*)); - // ninja in the custom __do_catch interceptor - new_vtable[6] = reinterpret_cast(intercept_unwind); - // make the vtable pointer for unwind_interceptor's type_info point to the new vtable - auto page_size = getpagesize(); - if(page_size <= 0 && (page_size & (page_size - 1)) != 0) { - throw std::runtime_error( - microfmt::format("getpagesize() is not a power of 2 greater than zero (was {})", page_size) - ); - } - auto type_info_addr = reinterpret_cast(type_info_pointer); - auto page_addr = type_info_addr & ~(page_size - 1); - // make sure the memory we're going to set is within the page - if(type_info_addr - page_addr + sizeof(void*) > static_cast(page_size)) { - throw std::runtime_error("pointer crosses page boundaries"); - } - // TODO: Check perms of page - mprotect_page(reinterpret_cast(page_addr), page_size, PROT_READ | PROT_WRITE); - *reinterpret_cast(type_info_pointer) = new_vtable.data() + 2; - mprotect_page(reinterpret_cast(page_addr), page_size, PROT_READ); - } - - void do_prepare_unwind_interceptor() { - static bool did_prepare = false; - if(!did_prepare) { - try { - clobber_type_info(typeid(cpptrace::detail::unwind_interceptor)); - } catch(std::exception& e) { - std::fprintf( - stderr, - "Cpptrace: Exception occurred while preparing from_current support: %s", - e.what() - ); - } catch(...) { - std::fprintf(stderr, "Cpptrace: Unknown exception occurred while preparing from_current support"); - } - did_prepare = true; - } - } - #else - CPPTRACE_FORCE_NO_INLINE int exception_filter() { - current = lazy_trace_holder(cpptrace::generate_raw_trace(1)); - return EXCEPTION_CONTINUE_SEARCH; - } - #endif - } - - const raw_trace& raw_trace_from_current_exception() { - return detail::current.get_raw_trace(); - } - - const stacktrace& from_current_exception() { - return detail::current.get_resolved_trace(); - } } diff --git a/src/from_current.cpp b/src/from_current.cpp new file mode 100644 index 0000000..a90ddc7 --- /dev/null +++ b/src/from_current.cpp @@ -0,0 +1,165 @@ +#include +#define CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON +#include + +#include +#include + +#include "utils/common.hpp" +#include "utils/microfmt.hpp" +#include "utils/utils.hpp" + +#ifndef _MSC_VER + #include + #include + #ifndef _WIN32 + #include + #include + #else + #include + #endif +#endif + +namespace cpptrace { + namespace detail { + thread_local lazy_trace_holder current_exception_trace; + + #ifndef _MSC_VER + CPPTRACE_FORCE_NO_INLINE + bool intercept_unwind(const std::type_info*, const std::type_info*, void**, unsigned) { + current_exception_trace = lazy_trace_holder(cpptrace::generate_raw_trace(1)); + return false; + } + + unwind_interceptor::~unwind_interceptor() = default; + + #if defined(__GLIBCXX__) || defined(__GLIBCPP__) + constexpr size_t vtable_size = 11; + #elif defined(_LIBCPP_VERSION) + constexpr size_t vtable_size = 10; + #else + #warning "Cpptrace from_current: Unrecognized C++ standard library, from_current() won't be supported" + constexpr size_t vtable_size = 0; + #endif + + std::array new_vtable; + + #if IS_WINDOWS + int get_page_size() { + SYSTEM_INFO info; + GetSystemInfo(&info); + return info.dwPageSize; + } + constexpr auto memory_read = PAGE_READONLY; + constexpr auto memory_readwrite = PAGE_READWRITE; + void mprotect_page(void* page, int page_size, int protections) { + DWORD old_protections; + if(!VirtualProtect(page, page_size, protections, &old_protections)) { + throw std::runtime_error(microfmt::format("VirtualProtect call failed: {}", std::system_error(GetLastError(), std::system_category()).what())); + } + } + #else + int get_page_size() { + return getpagesize(); + } + constexpr auto memory_read = PROT_READ; + constexpr auto memory_readwrite = PROT_READ | PROT_WRITE; + void mprotect_page(void* page, int page_size, int protections) { + if(mprotect(page, page_size, protections) != 0) { + throw std::runtime_error(microfmt::format("mprotect call failed: {}", strerror(errno))); + } + } + #endif + + void clobber_type_info(const std::type_info& info) { + if(vtable_size == 0) { // set to zero if we don't know what standard library we're working with + return; + } + void* type_info_pointer = const_cast(reinterpret_cast(&info)); + void* type_info_vtable_pointer = *reinterpret_cast(type_info_pointer); + // the type info vtable pointer points to two pointers inside the vtable, adjust it back + type_info_vtable_pointer = reinterpret_cast(reinterpret_cast(type_info_vtable_pointer) - 2); + + // for libstdc++ the class type info vtable looks like + // 0x7ffff7f89d18 <_ZTVN10__cxxabiv117__class_type_infoE>: 0x0000000000000000 0x00007ffff7f89d00 + // [offset ][typeinfo pointer ] + // 0x7ffff7f89d28 <_ZTVN10__cxxabiv117__class_type_infoE+16>: 0x00007ffff7dd65a0 0x00007ffff7dd65c0 + // [base destructor ][deleting dtor ] + // 0x7ffff7f89d38 <_ZTVN10__cxxabiv117__class_type_infoE+32>: 0x00007ffff7dd8f10 0x00007ffff7dd8f10 + // [__is_pointer_p ][__is_function_p ] + // 0x7ffff7f89d48 <_ZTVN10__cxxabiv117__class_type_infoE+48>: 0x00007ffff7dd6640 0x00007ffff7dd6500 + // [__do_catch ][__do_upcast ] + // 0x7ffff7f89d58 <_ZTVN10__cxxabiv117__class_type_infoE+64>: 0x00007ffff7dd65e0 0x00007ffff7dd66d0 + // [__do_upcast ][__do_dyncast ] + // 0x7ffff7f89d68 <_ZTVN10__cxxabiv117__class_type_infoE+80>: 0x00007ffff7dd6580 0x00007ffff7f8abe8 + // [__do_find_public_src][other ] + // In libc++ the layout is + // [offset ][typeinfo pointer ] + // [base destructor ][deleting dtor ] + // [noop1 ][noop2 ] + // [can_catch ][search_above_dst ] + // [search_below_dst ][has_unambiguous_public_base] + // Relevant documentation/implementation: + // https://itanium-cxx-abi.github.io/cxx-abi/abi.html + // libstdc++ + // https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/typeinfo + // https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/class_type_info.cc + // libc++ + // https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxx/include/typeinfo + // https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxxabi/src/private_typeinfo.h + + // make our own copy of the vtable + memcpy(new_vtable.data(), type_info_vtable_pointer, vtable_size * sizeof(void*)); + // ninja in the custom __do_catch interceptor + new_vtable[6] = reinterpret_cast(intercept_unwind); + // make the vtable pointer for unwind_interceptor's type_info point to the new vtable + auto page_size = get_page_size(); + if(page_size <= 0 && (page_size & (page_size - 1)) != 0) { + throw std::runtime_error( + microfmt::format("getpagesize() is not a power of 2 greater than zero (was {})", page_size) + ); + } + auto type_info_addr = reinterpret_cast(type_info_pointer); + auto page_addr = type_info_addr & ~(page_size - 1); + // make sure the memory we're going to set is within the page + if(type_info_addr - page_addr + sizeof(void*) > static_cast(page_size)) { + throw std::runtime_error("pointer crosses page boundaries"); + } + // TODO: Check perms of page + mprotect_page(reinterpret_cast(page_addr), page_size, memory_readwrite); + *reinterpret_cast(type_info_pointer) = new_vtable.data() + 2; + mprotect_page(reinterpret_cast(page_addr), page_size, memory_read); + } + + void do_prepare_unwind_interceptor() { + static bool did_prepare = false; + if(!did_prepare) { + try { + clobber_type_info(typeid(cpptrace::detail::unwind_interceptor)); + } catch(std::exception& e) { + std::fprintf( + stderr, + "Cpptrace: Exception occurred while preparing from_current support: %s", + e.what() + ); + } catch(...) { + std::fprintf(stderr, "Cpptrace: Unknown exception occurred while preparing from_current support"); + } + did_prepare = true; + } + } + #else + CPPTRACE_FORCE_NO_INLINE int exception_filter() { + current = lazy_trace_holder(cpptrace::generate_raw_trace(1)); + return EXCEPTION_CONTINUE_SEARCH; + } + #endif + } + const raw_trace& raw_trace_from_current_exception() { + return detail::current_exception_trace.get_raw_trace(); + } + + const stacktrace& from_current_exception() { + return detail::current_exception_trace.get_resolved_trace(); + } +}