diff --git a/src/from_current.cpp b/src/from_current.cpp index ad68ea8..04fc5a6 100644 --- a/src/from_current.cpp +++ b/src/from_current.cpp @@ -42,43 +42,68 @@ namespace cpptrace { 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_readonly = 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())); + throw std::runtime_error( + microfmt::format( + "VirtualProtect call failed: {}", + std::system_error(GetLastError(), std::system_category()).what() + ) + ); } } + void* allocate_page(int page_size) { + auto page = VirtualAlloc(nullptr, page_size, MEM_COMMIT | MEM_RESERVE, memory_readwrite); + if(!page) { + throw std::runtime_error( + microfmt::format( + "VirtualAlloc call failed: {}", + std::system_error(GetLastError(), std::system_category()).what() + ) + ); + } + return page; + } #else int get_page_size() { return getpagesize(); } - constexpr auto memory_read = PROT_READ; + constexpr auto memory_readonly = 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))); } } + void* allocate_page(int page_size) { + auto page = mmap(nullptr, page_size, memory_readwrite, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if(page == MAP_FAILED) { + throw std::runtime_error(microfmt::format("mmap call failed: {}", strerror(errno))); + } + return page; + } #endif - void clobber_type_info(const std::type_info& info) { + // allocated below, cleaned up by OS after exit + void* new_vtable_page = nullptr; + + void perform_typeinfo_surgery(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); + void* type_info_pointer = const_cast(static_cast(&info)); + void* type_info_vtable_pointer = *static_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); + type_info_vtable_pointer = static_cast(static_cast(type_info_vtable_pointer) - 2); // for libstdc++ the class type info vtable looks like // 0x7ffff7f89d18 <_ZTVN10__cxxabiv117__class_type_infoE>: 0x0000000000000000 0x00007ffff7f89d00 @@ -108,17 +133,25 @@ namespace cpptrace { // 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 + // probably 4096 but out of an abundance of caution 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) ); } + + // allocate a page for the new vtable so it can be made read-only later + new_vtable_page = allocate_page(page_size); + // make our own copy of the vtable + memcpy(new_vtable_page, type_info_vtable_pointer, vtable_size * sizeof(void*)); + // ninja in the custom __do_catch interceptor + auto new_vtable = static_cast(new_vtable_page); + new_vtable[6] = reinterpret_cast(intercept_unwind); + // make the page read-only + mprotect_page(new_vtable_page, page_size, memory_readonly); + + // make the vtable pointer for unwind_interceptor's type_info point to the new vtable 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 @@ -127,23 +160,23 @@ namespace cpptrace { } // 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); + *static_cast(type_info_pointer) = static_cast(new_vtable + 2); + mprotect_page(reinterpret_cast(page_addr), page_size, memory_readonly); } void do_prepare_unwind_interceptor() { static bool did_prepare = false; if(!did_prepare) { try { - clobber_type_info(typeid(cpptrace::detail::unwind_interceptor)); + perform_typeinfo_surgery(typeid(cpptrace::detail::unwind_interceptor)); } catch(std::exception& e) { std::fprintf( stderr, - "Cpptrace: Exception occurred while preparing from_current support: %s", + "Cpptrace: Exception occurred while preparing from_current support: %s\n", e.what() ); } catch(...) { - std::fprintf(stderr, "Cpptrace: Unknown exception occurred while preparing from_current support"); + std::fprintf(stderr, "Cpptrace: Unknown exception occurred while preparing from_current support\n"); } did_prepare = true; } @@ -155,6 +188,7 @@ namespace cpptrace { } #endif } + const raw_trace& raw_trace_from_current_exception() { return detail::current_exception_trace.get_raw_trace(); }