Add StackWalk64 backend (#48)

This commit is contained in:
Jeremy Rifkin 2023-09-24 17:30:56 -04:00 committed by GitHub
parent edf55395d7
commit 6de61e7755
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 191 additions and 12 deletions

View File

@ -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()

View File

@ -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`

View File

@ -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",
],

View File

@ -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",
],

View 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

View File

@ -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;
}
}

View 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