Add StackWalk64 backend (#48)
This commit is contained in:
parent
edf55395d7
commit
6de61e7755
@ -34,6 +34,7 @@ option(CPPTRACE_GET_SYMBOLS_WITH_NOTHING "" OFF)
|
||||
option(CPPTRACE_UNWIND_WITH_UNWIND "" OFF)
|
||||
option(CPPTRACE_UNWIND_WITH_EXECINFO "" OFF)
|
||||
option(CPPTRACE_UNWIND_WITH_WINAPI "" OFF)
|
||||
option(CPPTRACE_UNWIND_WITH_DBGHELP "" OFF)
|
||||
option(CPPTRACE_UNWIND_WITH_NOTHING "" OFF)
|
||||
|
||||
option(CPPTRACE_DEMANGLE_WITH_CXXABI "" OFF)
|
||||
@ -123,6 +124,7 @@ if(
|
||||
CPPTRACE_UNWIND_WITH_UNWIND OR
|
||||
CPPTRACE_UNWIND_WITH_EXECINFO OR
|
||||
CPPTRACE_UNWIND_WITH_WINAPI OR
|
||||
CPPTRACE_UNWIND_WITH_DBGHELP OR
|
||||
CPPTRACE_UNWIND_WITH_NOTHING
|
||||
)
|
||||
)
|
||||
@ -150,7 +152,7 @@ if(
|
||||
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
|
||||
endif()
|
||||
elseif(WIN32)
|
||||
set(CPPTRACE_UNWIND_WITH_WINAPI On)
|
||||
set(CPPTRACE_UNWIND_WITH_DBGHELP On)
|
||||
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
|
||||
endif()
|
||||
else()
|
||||
@ -228,6 +230,7 @@ set(
|
||||
src/unwind/unwind_with_nothing.cpp
|
||||
src/unwind/unwind_with_unwind.cpp
|
||||
src/unwind/unwind_with_winapi.cpp
|
||||
src/unwind/unwind_with_dbghelp.cpp
|
||||
)
|
||||
|
||||
if(CPPTRACE_STATIC)
|
||||
@ -352,6 +355,11 @@ if(CPPTRACE_UNWIND_WITH_WINAPI)
|
||||
target_compile_definitions(cpptrace PUBLIC CPPTRACE_UNWIND_WITH_WINAPI)
|
||||
endif()
|
||||
|
||||
if(CPPTRACE_UNWIND_WITH_DBGHELP)
|
||||
target_compile_definitions(cpptrace PUBLIC CPPTRACE_UNWIND_WITH_DBGHELP)
|
||||
target_link_libraries(cpptrace PRIVATE dbghelp)
|
||||
endif()
|
||||
|
||||
if(CPPTRACE_UNWIND_WITH_NOTHING)
|
||||
target_compile_definitions(cpptrace PUBLIC CPPTRACE_UNWIND_WITH_NOTHING)
|
||||
endif()
|
||||
|
||||
@ -468,7 +468,7 @@ To static link the library set `-DCPPTRACE_STATIC=On`.
|
||||
Cpptrace supports a number of back-ends and middle-ends to produce stack traces. Stack traces are produced in roughly
|
||||
three steps: Unwinding, symbol resolution, and demangling. Cpptrace by default on linux / macos will generate traces
|
||||
with `_Unwind_Backtrace`, libdwarf, and `__cxa_demangle`. On windows traces are generated by default with
|
||||
`CaptureStackBackTrace` and dbghelp.h (no demangling is needed with dbghelp). Under mingw libdwarf and dbghelp.h are
|
||||
`StackWalk64` and dbghelp.h (no demangling is needed with dbghelp). Under mingw libdwarf and dbghelp.h are
|
||||
used, along with `__cxa_demangle`. Support for these is the main focus of cpptrace and they should work well. If you
|
||||
want to use a different back-end such as addr2line, however, you can configure the library to do so.
|
||||
|
||||
@ -479,10 +479,12 @@ want to use a different back-end such as addr2line, however, you can configure t
|
||||
| libgcc unwind | `CPPTRACE_UNWIND_WITH_UNWIND` | linux, macos, mingw | Frames are captured with libgcc's `_Unwind_Backtrace`, which currently produces the most accurate stack traces on gcc/clang/mingw. Libgcc is often linked by default, and llvm has something equivalent. |
|
||||
| execinfo.h | `CPPTRACE_UNWIND_WITH_EXECINFO` | linux, macos | Frames are captured with `execinfo.h`'s `backtrace`, part of libc on linux/unix systems. |
|
||||
| winapi | `CPPTRACE_UNWIND_WITH_WINAPI` | windows, mingw | Frames are captured with `CaptureStackBackTrace`. |
|
||||
| dbghelp | `CPPTRACE_UNWIND_WITH_DBGHELP` | windows, mingw | Frames are captured with `StackWalk64`. |
|
||||
| N/A | `CPPTRACE_UNWIND_WITH_NOTHING` | all | Unwinding is not done, stack traces will be empty. |
|
||||
|
||||
These back-ends require a fixed buffer has to be created to read addresses into while unwinding. By default the buffer
|
||||
can hold addresses for 100 frames (beyond the `skip` frames). This is configurable with `CPPTRACE_HARD_MAX_FRAMES`.
|
||||
Some back-ends (execinfo and `CaptureStackBackTrace`) require a fixed buffer has to be created to read addresses into
|
||||
while unwinding. By default the buffer can hold addresses for 100 frames (beyond the `skip` frames). This is
|
||||
configurable with `CPPTRACE_HARD_MAX_FRAMES`.
|
||||
|
||||
**Symbol resolution**
|
||||
|
||||
@ -530,6 +532,7 @@ Back-ends:
|
||||
- `CPPTRACE_UNWIND_WITH_UNWIND=On/Off`
|
||||
- `CPPTRACE_UNWIND_WITH_EXECINFO=On/Off`
|
||||
- `CPPTRACE_UNWIND_WITH_WINAPI=On/Off`
|
||||
- `CPPTRACE_UNWIND_WITH_DBGHELP=On/Off`
|
||||
- `CPPTRACE_UNWIND_WITH_NOTHING=On/Off`
|
||||
- `CPPTRACE_DEMANGLE_WITH_CXXABI=On/Off`
|
||||
- `CPPTRACE_DEMANGLE_WITH_WINAPI=On/Off`
|
||||
|
||||
@ -232,6 +232,7 @@ def main():
|
||||
"std": ["11", "20"],
|
||||
"unwind": [
|
||||
"CPPTRACE_UNWIND_WITH_WINAPI",
|
||||
"CPPTRACE_UNWIND_WITH_DBGHELP",
|
||||
"CPPTRACE_UNWIND_WITH_UNWIND",
|
||||
"CPPTRACE_UNWIND_WITH_NOTHING",
|
||||
],
|
||||
|
||||
@ -395,6 +395,7 @@ def main():
|
||||
"std": ["11", "20"],
|
||||
"unwind": [
|
||||
"CPPTRACE_UNWIND_WITH_WINAPI",
|
||||
"CPPTRACE_UNWIND_WITH_DBGHELP",
|
||||
#"CPPTRACE_UNWIND_WITH_UNWIND", # Broken on github actions for some reason
|
||||
#"CPPTRACE_UNWIND_WITH_NOTHING",
|
||||
],
|
||||
|
||||
45
src/platform/dbghelp_syminit_manager.hpp
Normal file
45
src/platform/dbghelp_syminit_manager.hpp
Normal file
@ -0,0 +1,45 @@
|
||||
#ifndef DBGHELP_SYMINIT_MANAGER_HPP
|
||||
#define DBGHELP_SYMINIT_MANAGER_HPP
|
||||
|
||||
#include "common.hpp"
|
||||
#include "utils.hpp"
|
||||
|
||||
#include <mutex>
|
||||
#include <unordered_set>
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
struct dbghelp_syminit_manager {
|
||||
std::unordered_set<HANDLE> set;
|
||||
std::mutex mutex;
|
||||
|
||||
~dbghelp_syminit_manager() {
|
||||
for(auto handle : set) {
|
||||
if(!SymCleanup(handle)) {
|
||||
ASSERT(false, stringf("Cpptrace SymCleanup failed with code %llu\n", to_ull(GetLastError())));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init(HANDLE proc) {
|
||||
if(set.count(proc) == 0) {
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
if(!SymInitialize(proc, NULL, TRUE)) {
|
||||
throw std::logic_error(stringf("SymInitialize failed %llu", to_ull(GetLastError())));
|
||||
}
|
||||
set.insert(proc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
inline dbghelp_syminit_manager& get_syminit_manager() {
|
||||
static dbghelp_syminit_manager syminit_manager;
|
||||
return syminit_manager;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -3,6 +3,7 @@
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "symbols.hpp"
|
||||
#include "../platform/program_name.hpp"
|
||||
#include "../platform/dbghelp_syminit_manager.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
@ -397,10 +398,7 @@ namespace dbghelp {
|
||||
// TODO: When does this need to be called? Can it be moved to the symbolizer?
|
||||
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
|
||||
HANDLE proc = GetCurrentProcess();
|
||||
if(!SymInitialize(proc, NULL, TRUE)) {
|
||||
//TODO?
|
||||
throw std::logic_error("SymInitialize failed");
|
||||
}
|
||||
get_syminit_manager().init(proc);
|
||||
for(const auto frame : frames) {
|
||||
try {
|
||||
trace.push_back(resolve_frame(proc, frame));
|
||||
@ -411,10 +409,6 @@ namespace dbghelp {
|
||||
trace.push_back(null_frame);
|
||||
}
|
||||
}
|
||||
if(!SymCleanup(proc)) {
|
||||
//throw std::logic_error("SymCleanup failed");
|
||||
}
|
||||
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
|
||||
127
src/unwind/unwind_with_dbghelp.cpp
Normal file
127
src/unwind/unwind_with_dbghelp.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
|
||||
|
||||
#include <cpptrace/cpptrace.hpp>
|
||||
#include "unwind.hpp"
|
||||
#include "../platform/common.hpp"
|
||||
#include "../platform/utils.hpp"
|
||||
#include "../platform/dbghelp_syminit_manager.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
|
||||
#include <windows.h>
|
||||
#include <dbghelp.h>
|
||||
|
||||
// Fucking windows headers
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
CPPTRACE_FORCE_NO_INLINE
|
||||
std::vector<uintptr_t> capture_frames(size_t skip, size_t max_depth) {
|
||||
skip++;
|
||||
// https://jpassing.com/2008/03/12/walking-the-stack-of-the-current-thread/
|
||||
|
||||
// Get current thread context
|
||||
// GetThreadContext cannot be used on the current thread.
|
||||
// RtlCaptureContext doesn't work on i386
|
||||
CONTEXT context;
|
||||
#ifdef _M_IX86
|
||||
ZeroMemory(&context, sizeof(CONTEXT));
|
||||
context.ContextFlags = CONTEXT_CONTROL;
|
||||
__asm {
|
||||
label:
|
||||
mov [context.Ebp], ebp;
|
||||
mov [context.Esp], esp;
|
||||
mov eax, [label];
|
||||
mov [context.Eip], eax;
|
||||
}
|
||||
#else
|
||||
RtlCaptureContext(&context);
|
||||
#endif
|
||||
// Setup current frame
|
||||
STACKFRAME64 frame;
|
||||
ZeroMemory(&frame, sizeof(STACKFRAME64));
|
||||
DWORD machine_type;
|
||||
#ifdef _M_IX86
|
||||
machine_type = IMAGE_FILE_MACHINE_I386;
|
||||
frame.AddrPC.Offset = context.Eip;
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Offset = context.Ebp;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Offset = context.Esp;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
#elif _M_X64
|
||||
machine_type = IMAGE_FILE_MACHINE_AMD64;
|
||||
frame.AddrPC.Offset = context.Rip;
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Offset = context.Rsp;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Offset = context.Rsp;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
#elif _M_IA64
|
||||
machine_type = IMAGE_FILE_MACHINE_IA64;
|
||||
frame.AddrPC.Offset = context.StIIP;
|
||||
frame.AddrPC.Mode = AddrModeFlat;
|
||||
frame.AddrFrame.Offset = context.IntSp;
|
||||
frame.AddrFrame.Mode = AddrModeFlat;
|
||||
frame.AddrBStore.Offset= context.RsBSP;
|
||||
frame.AddrBStore.Mode = AddrModeFlat;
|
||||
frame.AddrStack.Offset = context.IntSp;
|
||||
frame.AddrStack.Mode = AddrModeFlat;
|
||||
#else
|
||||
#error "Cpptrace: StackWalk64 not supported for this platform yet"
|
||||
#endif
|
||||
|
||||
std::vector<uintptr_t> trace;
|
||||
|
||||
// Dbghelp is is single-threaded, so acquire a lock.
|
||||
static std::mutex mutex;
|
||||
std::lock_guard<std::mutex> lock(mutex);
|
||||
// For some reason SymInitialize must be called before StackWalk64
|
||||
// Note that the code assumes that
|
||||
// SymInitialize( GetCurrentProcess(), NULL, TRUE ) has
|
||||
// already been called.
|
||||
//
|
||||
HANDLE proc = GetCurrentProcess();
|
||||
HANDLE thread = GetCurrentThread();
|
||||
get_syminit_manager().init(proc);
|
||||
while(trace.size() < max_depth) {
|
||||
if(
|
||||
!StackWalk64(
|
||||
machine_type,
|
||||
proc,
|
||||
thread,
|
||||
&frame,
|
||||
machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context,
|
||||
NULL,
|
||||
SymFunctionTableAccess64,
|
||||
SymGetModuleBase64,
|
||||
NULL
|
||||
)
|
||||
) {
|
||||
// Either failed or finished walking
|
||||
break;
|
||||
}
|
||||
if(frame.AddrPC.Offset != 0) {
|
||||
// Valid frame
|
||||
if(skip) {
|
||||
skip--;
|
||||
} else {
|
||||
trace.push_back(frame.AddrPC.Offset);
|
||||
}
|
||||
} else {
|
||||
// base
|
||||
break;
|
||||
}
|
||||
}
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Loading…
Reference in New Issue
Block a user