Add cpptrace::from_current (#155)
This commit is contained in:
parent
d5eed55dcd
commit
26e009c688
@ -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
|
||||
|
||||
109
README.md
109
README.md
@ -28,7 +28,10 @@ Cpptrace also has a C API, docs [here](docs/c-api.md).
|
||||
- [Raw Traces](#raw-traces)
|
||||
- [Utilities](#utilities)
|
||||
- [Configuration](#configuration)
|
||||
- [Traced Exceptions](#traced-exceptions)
|
||||
- [Traces From All Exceptions](#traces-from-all-exceptions)
|
||||
- [How it works](#how-it-works)
|
||||
- [Performance](#performance)
|
||||
- [Traced Exception Objects](#traced-exception-objects)
|
||||
- [Wrapping std::exceptions](#wrapping-stdexceptions)
|
||||
- [Exception handling with cpptrace](#exception-handling-with-cpptrace)
|
||||
- [Signal-Safe Tracing](#signal-safe-tracing)
|
||||
@ -80,7 +83,30 @@ const auto raw_trace = cpptrace::generate_raw_trace();
|
||||
raw_trace.resolve().print();
|
||||
```
|
||||
|
||||
Cpptrace also provides exception types that store stack traces:
|
||||
Cpptrace provides a way to produce stack traces on arbitrary exceptions. More information on this system
|
||||
[below](#traces-from-all-exceptions).
|
||||
```cpp
|
||||
#include <cpptrace/from_current.hpp>
|
||||
void foo() {
|
||||
throw std::runtime_error("foo failed");
|
||||
}
|
||||
int main() {
|
||||
CPPTRACE_TRY {
|
||||
foo();
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
std::cerr<<"Exception: "<<e.what()<<std::endl;
|
||||
cpptrace::from_current_exception().print();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||

|
||||
|
||||
There are a few extraneous frames at the top of the stack corresponding to internals of exception handling in the
|
||||
standard library. These are a small price to pay for stack traces on all exceptions.
|
||||
|
||||
Cpptrace also provides a handful of traced exception objects that store stack traces when thrown. This is useful when
|
||||
the exceptions might not be caught by `CPPTRACE_CATCH`:
|
||||
```cpp
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
|
||||
@ -89,7 +115,7 @@ void trace() {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
Additional notable features:
|
||||
|
||||
@ -342,11 +368,70 @@ namespace cpptrace {
|
||||
}
|
||||
```
|
||||
|
||||
### Traced Exceptions
|
||||
### Traces From All Exceptions
|
||||
|
||||
Cpptrace provides an interface for a traced exceptions, `cpptrace::exception`, as well as a set of exception classes
|
||||
that that generate stack traces when thrown. These exceptions generate relatively lightweight raw traces and resolve
|
||||
symbols and line numbers lazily if and when requested.
|
||||
Cpptrace provides `CPPTRACE_TRY` and `CPPTRACE_CATCH` macros that allow a stack trace to be collected from the current
|
||||
thrown exception object, with no overhead in the non-throwing path:
|
||||
|
||||
```cpp
|
||||
CPPTRACE_TRY {
|
||||
foo();
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
std::cout<<"Exception: "<<e.what()<<std::endl;
|
||||
std::cout<<cpptrace::from_current_exception().to_string(true)<<std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
Any declarator `catch` accepts works with `CPPTRACE_CATCH`, including `...`.
|
||||
|
||||

|
||||
|
||||
There are a few extraneous frames at the top of the stack corresponding to internals of exception handling in the
|
||||
standard library. These are a small price to pay for stack traces on all exceptions.
|
||||
|
||||
API functions:
|
||||
- `cpptrace::raw_trace_from_current_exception`: Returns `const raw_trace&` from the current exception.
|
||||
- `cpptrace::from_current_exception`: Returns a resolved `const stacktrace&` from the current exception. Invalidates
|
||||
references to traces returned by `cpptrace::raw_trace_from_current_exception`.
|
||||
|
||||
#### How it works
|
||||
C++ does not provide any language support for collecting stack traces when exceptions are thrown, however, exception
|
||||
handling under both the Itanium ABI and by SEH (used to implement C++ exceptions on windows) involves unwinding the
|
||||
stack twice, the first unwind searches for an appropriate `catch` handler, the second actually unwinds the stack and
|
||||
calls destructors. Since the stack remains intact during the search phase it's possible to collect a stack trace with
|
||||
zero overhead when the `catch` is considered for matching the exception.
|
||||
|
||||
N.b.: Cpptrace uses the same mechanism proposed for use in [P2490R3][P2490R3].
|
||||
|
||||
#### Performance
|
||||
|
||||
`CPPTRACE_CATCH` internally generates lightweight raw traces when considered in the search phase. These are quite fast
|
||||
to generate and are only resolved when `cpptrace::from_current_exception` is called.
|
||||
|
||||
Currently `CPPTRACE_CATCH` always generates a raw trace when considered as a candidate. That means that if there is a
|
||||
nesting of handlers, either directly in code or as a result of the current call stack, the current stack may be traced
|
||||
mutliple times until the appropriate handler is found.
|
||||
|
||||
This should not matter for the vast majority applications given that performance very rarely is critical in throwing
|
||||
paths, how exception handling is usually used, and the shallowness of most call stacks. However, it's an important
|
||||
consideration to be aware of.
|
||||
|
||||
To put the scale of this performance consideration into perspective: In my benchmarking I have found generation of raw
|
||||
traces to take on the order of `75ns` per frame. Thus, even if there were 100 non-matching handlers before a matching
|
||||
handler in a 100-deep call stack the total time would stil be on the order of less than one millisecond.
|
||||
|
||||
It's possible to avoid this by adding some bookkeeping to the `CPPTRACE_TRY` block. With the tradeoff between
|
||||
zero-overhead try-catch in the happy path and a little extra overhead in the unhappy throwing path I decided to keep
|
||||
try-catch zero-overhead. Should this be a concern to anyone, I'm happy to facilitate both solutions.
|
||||
|
||||
### Traced Exception Objects
|
||||
|
||||
Cpptrace provides a handful of traced exception classes which automatically collect stack traces when thrown. These
|
||||
are useful when throwing exceptions that may not be caught by `CPPTRACE_CATCH`.
|
||||
|
||||
The base traced exception class is `cpptrace::exception` and cpptrace provides a handful of helper classes for working
|
||||
with traced exceptions. These exceptions generate relatively lightweight raw traces and resolve symbols and line numbers
|
||||
lazily if and when requested.
|
||||
|
||||
These are provided both as a useful utility and as a reference implementation for traced exceptions.
|
||||
|
||||
@ -429,6 +514,10 @@ namespace cpptrace {
|
||||
|
||||
## Wrapping std::exceptions
|
||||
|
||||
> [!NOTE]
|
||||
> This section is largely obsolete now that cpptrace provides a better mechanism for collecting
|
||||
> [traces from exceptions](#traces-from-exceptions)
|
||||
|
||||
Cpptrace exceptions can provide great information for user-controlled exceptions. For non-cpptrace::exceptions that may
|
||||
originate outside of code you control, e.g. the standard library, cpptrace provides some wrapper utilities that can
|
||||
rethrow these exceptions nested in traced cpptrace exceptions. The trace won't be perfect, the trace will start where
|
||||
@ -446,6 +535,10 @@ std::cout<<CPPTRACE_WRAP(foo.at(12))<<std::endl;
|
||||
|
||||
## Exception handling with cpptrace
|
||||
|
||||
> [!NOTE]
|
||||
> This section pertains to cpptrace traced exception objects and not the mechanism for collecting
|
||||
> [traces from arbitrary exceptions](#traces-from-exceptions)
|
||||
|
||||
Working with cpptrace exceptions in your code:
|
||||
```cpp
|
||||
try {
|
||||
@ -1000,3 +1093,5 @@ This library is under the MIT license.
|
||||
|
||||
Cpptrace uses libdwarf on linux, macos, and mingw/cygwin unless configured to use something else. If this library is
|
||||
statically linked with libdwarf then the library's binary will itself be LGPL.
|
||||
|
||||
[P2490R3]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2490r3.html
|
||||
|
||||
@ -298,6 +298,7 @@ namespace cpptrace {
|
||||
lazy_trace_holder& operator=(lazy_trace_holder&& other) noexcept;
|
||||
~lazy_trace_holder();
|
||||
// access
|
||||
const raw_trace& get_raw_trace() const;
|
||||
stacktrace& get_resolved_trace();
|
||||
const stacktrace& get_resolved_trace() const;
|
||||
private:
|
||||
|
||||
61
include/cpptrace/from_current.hpp
Normal file
61
include/cpptrace/from_current.hpp
Normal file
@ -0,0 +1,61 @@
|
||||
#ifndef CPPTRACE_FROM_CURRENT_HPP
|
||||
#define CPPTRACE_FROM_CURRENT_HPP
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
|
||||
namespace cpptrace {
|
||||
CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception();
|
||||
CPPTRACE_EXPORT const stacktrace& from_current_exception();
|
||||
|
||||
namespace detail {
|
||||
#ifdef _MSC_VER
|
||||
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE int exception_filter();
|
||||
#else
|
||||
class CPPTRACE_EXPORT unwind_interceptor {
|
||||
public:
|
||||
virtual ~unwind_interceptor();
|
||||
};
|
||||
|
||||
CPPTRACE_EXPORT void do_prepare_unwind_interceptor();
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
// this awful double-IILE is due to C2713 "You can't use structured exception handling (__try/__except) and C++
|
||||
// exception handling (try/catch) in the same function."
|
||||
#define CPPTRACE_TRY \
|
||||
try { \
|
||||
[&]() { \
|
||||
__try { \
|
||||
[&]() {
|
||||
#define CPPTRACE_CATCH(param) \
|
||||
}(); \
|
||||
} __except(::cpptrace::detail::exception_filter()) {} \
|
||||
}(); \
|
||||
} catch(param)
|
||||
#else
|
||||
#define CPPTRACE_TRY \
|
||||
try { \
|
||||
try {
|
||||
#define CPPTRACE_CATCH(param) \
|
||||
} catch(::cpptrace::detail::unwind_interceptor&) {} \
|
||||
} catch(param)
|
||||
#endif
|
||||
|
||||
#endif
|
||||
BIN
res/from_current.png
Normal file
BIN
res/from_current.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 35 KiB |
@ -592,6 +592,14 @@ namespace cpptrace {
|
||||
clear();
|
||||
}
|
||||
// access
|
||||
const raw_trace& lazy_trace_holder::get_raw_trace() const {
|
||||
if(resolved) {
|
||||
throw std::logic_error(
|
||||
"cpptrace::detail::lazy_trace_holder::get_resolved_trace called on resolved holder"
|
||||
);
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
stacktrace& lazy_trace_holder::get_resolved_trace() {
|
||||
if(!resolved) {
|
||||
raw_trace old_trace = std::move(trace);
|
||||
@ -605,7 +613,7 @@ namespace cpptrace {
|
||||
// TODO: Append to message somehow?
|
||||
std::fprintf(
|
||||
stderr,
|
||||
"Exception occurred while resolving trace in cpptrace::exception object:\n%s\n",
|
||||
"Exception occurred while resolving trace in cpptrace::detail::lazy_trace_holder:\n%s\n",
|
||||
e.what()
|
||||
);
|
||||
}
|
||||
@ -616,7 +624,7 @@ namespace cpptrace {
|
||||
const stacktrace& lazy_trace_holder::get_resolved_trace() const {
|
||||
if(!resolved) {
|
||||
throw std::logic_error(
|
||||
"cpptrace::detaillazy_trace_holder::get_resolved_trace called on unresolved const object"
|
||||
"cpptrace::detail::lazy_trace_holder::get_resolved_trace called on unresolved const holder"
|
||||
);
|
||||
}
|
||||
return resolved_trace;
|
||||
|
||||
293
src/from_current.cpp
Normal file
293
src/from_current.cpp
Normal file
@ -0,0 +1,293 @@
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#define CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON
|
||||
#include <cpptrace/from_current.hpp>
|
||||
|
||||
#include <system_error>
|
||||
#include <typeinfo>
|
||||
|
||||
#include "utils/common.hpp"
|
||||
#include "utils/microfmt.hpp"
|
||||
#include "utils/utils.hpp"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#include <array>
|
||||
#include <string.h>
|
||||
#if IS_WINDOWS
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <sys/mman.h>
|
||||
#include <unistd.h>
|
||||
#if IS_APPLE
|
||||
#include <mach/mach.h>
|
||||
#include <mach/mach_vm.h>
|
||||
#else
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#endif
|
||||
#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
|
||||
|
||||
#if IS_WINDOWS
|
||||
int get_page_size() {
|
||||
SYSTEM_INFO info;
|
||||
GetSystemInfo(&info);
|
||||
return info.dwPageSize;
|
||||
}
|
||||
constexpr auto memory_readonly = PAGE_READONLY;
|
||||
constexpr auto memory_readwrite = PAGE_READWRITE;
|
||||
int mprotect_page_and_return_old_protections(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()
|
||||
)
|
||||
);
|
||||
}
|
||||
return old_protections;
|
||||
}
|
||||
void mprotect_page(void* page, int page_size, int protections) {
|
||||
mprotect_page_and_return_old_protections(page, page_size, protections);
|
||||
}
|
||||
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_readonly = PROT_READ;
|
||||
constexpr auto memory_readwrite = PROT_READ | PROT_WRITE;
|
||||
#if IS_APPLE
|
||||
int get_page_protections(void* page) {
|
||||
// https://stackoverflow.com/a/12627784/15675011
|
||||
mach_vm_size_t vmsize;
|
||||
mach_vm_address_t address = (mach_vm_address_t)page;
|
||||
vm_region_basic_info_data_t info;
|
||||
mach_msg_type_number_t info_count =
|
||||
sizeof(size_t) == 8 ? VM_REGION_BASIC_INFO_COUNT_64 : VM_REGION_BASIC_INFO_COUNT;
|
||||
memory_object_name_t object;
|
||||
kern_return_t status = mach_vm_region(
|
||||
mach_task_self(),
|
||||
&address,
|
||||
&vmsize,
|
||||
VM_REGION_BASIC_INFO,
|
||||
(vm_region_info_t)&info,
|
||||
&info_count,
|
||||
&object
|
||||
);
|
||||
if(status == KERN_INVALID_ADDRESS) {
|
||||
throw std::runtime_error("vm_region failed with KERN_INVALID_ADDRESS");
|
||||
}
|
||||
int perms = 0;
|
||||
if(info.protection & VM_PROT_READ) {
|
||||
perms |= PROT_READ;
|
||||
}
|
||||
if(info.protection & VM_PROT_WRITE) {
|
||||
perms |= PROT_WRITE;
|
||||
}
|
||||
if(info.protection & VM_PROT_EXECUTE) {
|
||||
perms |= PROT_EXEC;
|
||||
}
|
||||
return perms;
|
||||
}
|
||||
#else
|
||||
int get_page_protections(void* page) {
|
||||
auto page_addr = reinterpret_cast<uintptr_t>(page);
|
||||
std::ifstream stream("/proc/self/maps");
|
||||
stream>>std::hex;
|
||||
while(!stream.eof()) {
|
||||
uintptr_t start;
|
||||
uintptr_t stop;
|
||||
stream>>start;
|
||||
stream.ignore(1);
|
||||
stream>>stop;
|
||||
if(stream.eof()) {
|
||||
break;
|
||||
}
|
||||
if(stream.fail()) {
|
||||
throw std::runtime_error("Failure reading /proc/self/maps");
|
||||
}
|
||||
if(page_addr >= start && page_addr < stop) {
|
||||
stream.ignore(1);
|
||||
char r, w, x;
|
||||
stream>>r>>w>>x;
|
||||
if(stream.fail() || stream.eof()) {
|
||||
throw std::runtime_error("Failure reading /proc/self/maps");
|
||||
}
|
||||
int perms = 0;
|
||||
if(r == 'r') {
|
||||
perms |= PROT_READ;
|
||||
}
|
||||
if(w == 'w') {
|
||||
perms |= PROT_WRITE;
|
||||
}
|
||||
if(x == 'x') {
|
||||
perms |= PROT_EXEC;
|
||||
}
|
||||
// std::cerr<<"--parsed: "<<std::hex<<start<<" "<<stop<<" "<<r<<w<<x<<std::endl;
|
||||
return perms;
|
||||
}
|
||||
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
|
||||
}
|
||||
throw std::runtime_error("Failed to find mapping with page in /proc/self/maps");
|
||||
}
|
||||
#endif
|
||||
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)));
|
||||
}
|
||||
}
|
||||
int mprotect_page_and_return_old_protections(void* page, int page_size, int protections) {
|
||||
auto old_protections = get_page_protections(page);
|
||||
mprotect_page(page, page_size, protections);
|
||||
return old_protections;
|
||||
}
|
||||
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
|
||||
|
||||
// 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<void*>(static_cast<const void*>(&info));
|
||||
void* type_info_vtable_pointer = *static_cast<void**>(type_info_pointer);
|
||||
// the type info vtable pointer points to two pointers inside the vtable, adjust it back
|
||||
type_info_vtable_pointer = static_cast<void*>(static_cast<void**>(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
|
||||
|
||||
// shouldn't be anything other than 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<void**>(new_vtable_page);
|
||||
new_vtable[6] = reinterpret_cast<void*>(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<uintptr_t>(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<unsigned>(page_size)) {
|
||||
throw std::runtime_error("pointer crosses page boundaries");
|
||||
}
|
||||
auto old_protections = mprotect_page_and_return_old_protections(
|
||||
reinterpret_cast<void*>(page_addr),
|
||||
page_size,
|
||||
memory_readwrite
|
||||
);
|
||||
*static_cast<void**>(type_info_pointer) = static_cast<void*>(new_vtable + 2);
|
||||
mprotect_page(reinterpret_cast<void*>(page_addr), page_size, old_protections);
|
||||
}
|
||||
|
||||
void do_prepare_unwind_interceptor() {
|
||||
static bool did_prepare = false;
|
||||
if(!did_prepare) {
|
||||
try {
|
||||
perform_typeinfo_surgery(typeid(cpptrace::detail::unwind_interceptor));
|
||||
} catch(std::exception& e) {
|
||||
std::fprintf(
|
||||
stderr,
|
||||
"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\n");
|
||||
}
|
||||
did_prepare = true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
CPPTRACE_FORCE_NO_INLINE int exception_filter() {
|
||||
current_exception_trace = 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();
|
||||
}
|
||||
}
|
||||
@ -83,6 +83,7 @@ if(NOT CPPTRACE_SKIP_UNIT)
|
||||
unit/raw_trace.cpp
|
||||
unit/object_trace.cpp
|
||||
unit/stacktrace.cpp
|
||||
unit/from_current.cpp
|
||||
)
|
||||
target_compile_features(unittest PRIVATE cxx_std_20)
|
||||
target_link_libraries(unittest PRIVATE ${target_name} GTest::gtest_main GTest::gmock_main)
|
||||
|
||||
143
test/unit/from_current.cpp
Normal file
143
test/unit/from_current.cpp
Normal file
@ -0,0 +1,143 @@
|
||||
#include <algorithm>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include <string>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <gtest/gtest-matchers.h>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include <cpptrace/from_current.hpp>
|
||||
|
||||
using namespace std::literals;
|
||||
|
||||
|
||||
// NOTE: returning something and then return stacktrace_from_current_3(line_numbers) * 2; later helps prevent the call from
|
||||
// being optimized to a jmp
|
||||
CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_3(std::vector<int>& line_numbers) {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
throw std::runtime_error("foobar");
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_2(std::vector<int>& line_numbers) {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
return stacktrace_from_current_3(line_numbers) * 2;
|
||||
}
|
||||
|
||||
CPPTRACE_FORCE_NO_INLINE int stacktrace_from_current_1(std::vector<int>& line_numbers) {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
return stacktrace_from_current_2(line_numbers) * 2;
|
||||
}
|
||||
|
||||
TEST(FromCurrent, Basic) {
|
||||
std::vector<int> line_numbers;
|
||||
CPPTRACE_TRY {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
stacktrace_from_current_1(line_numbers);
|
||||
} CPPTRACE_CATCH(const std::runtime_error& e) {
|
||||
EXPECT_EQ(e.what(), "foobar"sv);
|
||||
const auto& trace = cpptrace::from_current_exception();
|
||||
ASSERT_GE(trace.frames.size(), 4);
|
||||
auto it = std::find_if(
|
||||
trace.frames.begin(),
|
||||
trace.frames.end(),
|
||||
[](const cpptrace::stacktrace_frame& frame) {
|
||||
return frame.filename.find("from_current.cpp") != std::string::npos
|
||||
&& frame.symbol.find("lambda") == std::string::npos; // due to msvc
|
||||
}
|
||||
);
|
||||
ASSERT_NE(it, trace.frames.end());
|
||||
size_t i = static_cast<size_t>(it - trace.frames.begin());
|
||||
int j = 0;
|
||||
ASSERT_LT(i, trace.frames.size());
|
||||
ASSERT_LT(j, line_numbers.size());
|
||||
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
|
||||
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_3"));
|
||||
i++;
|
||||
j++;
|
||||
ASSERT_LT(i, trace.frames.size());
|
||||
ASSERT_LT(j, line_numbers.size());
|
||||
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
|
||||
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_2"));
|
||||
i++;
|
||||
j++;
|
||||
ASSERT_LT(i, trace.frames.size());
|
||||
ASSERT_LT(j, line_numbers.size());
|
||||
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
|
||||
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("stacktrace_from_current_1"));
|
||||
i++;
|
||||
j++;
|
||||
ASSERT_LT(i, trace.frames.size());
|
||||
ASSERT_LT(j, line_numbers.size());
|
||||
EXPECT_THAT(trace.frames[i].filename, testing::EndsWith("from_current.cpp"));
|
||||
EXPECT_EQ(trace.frames[i].line.value(), line_numbers[j]);
|
||||
EXPECT_THAT(trace.frames[i].symbol, testing::HasSubstr("FromCurrent_Basic_Test::TestBody"));
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FromCurrent, CorrectHandler) {
|
||||
std::vector<int> line_numbers;
|
||||
CPPTRACE_TRY {
|
||||
CPPTRACE_TRY {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
stacktrace_from_current_1(line_numbers);
|
||||
} CPPTRACE_CATCH(const std::logic_error&) {
|
||||
FAIL();
|
||||
}
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
EXPECT_EQ(e.what(), "foobar"sv);
|
||||
const auto& trace = cpptrace::from_current_exception();
|
||||
auto it = std::find_if(
|
||||
trace.frames.begin(),
|
||||
trace.frames.end(),
|
||||
[](const cpptrace::stacktrace_frame& frame) {
|
||||
return frame.filename.find("from_current.cpp") != std::string::npos
|
||||
&& frame.symbol.find("lambda") == std::string::npos;
|
||||
}
|
||||
);
|
||||
EXPECT_NE(it, trace.frames.end());
|
||||
it = std::find_if(
|
||||
trace.frames.begin(),
|
||||
trace.frames.end(),
|
||||
[](const cpptrace::stacktrace_frame& frame) {
|
||||
return frame.symbol.find("FromCurrent_CorrectHandler_Test::TestBody") != std::string::npos;
|
||||
}
|
||||
);
|
||||
EXPECT_NE(it, trace.frames.end());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(FromCurrent, RawTrace) {
|
||||
std::vector<int> line_numbers;
|
||||
CPPTRACE_TRY {
|
||||
line_numbers.insert(line_numbers.begin(), __LINE__ + 1);
|
||||
stacktrace_from_current_1(line_numbers);
|
||||
} CPPTRACE_CATCH(const std::exception& e) {
|
||||
EXPECT_EQ(e.what(), "foobar"sv);
|
||||
const auto& raw_trace = cpptrace::raw_trace_from_current_exception();
|
||||
auto trace = raw_trace.resolve();
|
||||
auto it = std::find_if(
|
||||
trace.frames.begin(),
|
||||
trace.frames.end(),
|
||||
[](const cpptrace::stacktrace_frame& frame) {
|
||||
return frame.filename.find("from_current.cpp") != std::string::npos
|
||||
&& frame.symbol.find("lambda") == std::string::npos;
|
||||
}
|
||||
);
|
||||
EXPECT_NE(it, trace.frames.end());
|
||||
it = std::find_if(
|
||||
trace.frames.begin(),
|
||||
trace.frames.end(),
|
||||
[](const cpptrace::stacktrace_frame& frame) {
|
||||
return frame.symbol.find("FromCurrent_RawTrace_Test::TestBody") != std::string::npos;
|
||||
}
|
||||
);
|
||||
EXPECT_NE(it, trace.frames.end());
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user