Files
SpeedBreakerProject--speedb…/runtime/platform/stack.h
T
2026-10-24 19:31:48 +00:00

88 lines
4.7 KiB
C++

// SpeedBreaker runtime. GPL-3.0-or-later (see COPYING).
//
// Stacks and the names in them, on Windows (Windows only; report/crash.cpp
// and report/watchdog.cpp use it for crash and hang reports). macOS and
// Linux take theirs from backtrace(), dladdr and the executable's .symtab.
//
// - Names come from DbgHelp and SpeedBreaker.pdb, the PDB next to the .exe
// (found by its name in the .exe's folder only: never the build machine's
// path the .exe records, a symbol server or _NT_SYMBOL_PATH, so nothing
// is fetched from anywhere). The runtime's functions have full debug
// information; the recompiled code has only public symbols (__imp__sub_...
// and the sub_... wrappers), which name it just as well. Without the PDB,
// only the system DLLs' exports have names. DbgHelp loads it on a thread of
// its own at startup (about 0.1 s and 25-35 MB on the ThinkPad, for a 49 MB
// PDB), so a crash finds it ready.
// - Stacks are walked with StackWalk64 from a thread's registers, using the
// unwind tables every x64 function has (RtlLookupFunctionEntry: any module,
// loaded with DbgHelp or not).
// - Another thread's stack (a hang report): Capture suspends it, copies its
// registers and the top of its stack into a buffer allocated beforehand,
// and resumes it, calling nothing that allocates or takes a lock while it
// is suspended (it may hold the heap's lock); the walk then reads the
// copy, so the thread is stopped for a moment only.
// - DbgHelp is single-threaded: every call here takes one lock. The crash
// handler keeps it once it has it (EnterCrash) and gives up on DbgHelp if
// it can't have it soon, or if the crash happened inside DbgHelp; a fault
// inside DbgHelp during a crash is caught, and the frame keeps a plainer
// name.
#pragma once
#ifdef _WIN32
#include <platform/os.h>
#include <cstddef>
#include <cstdint>
namespace platform::stack
{
// Loads the symbols (blocking: call it on a thread of its own). Each of
// `warm` (code addresses in the program, may be null) is looked up once,
// so the PDB's tables the crash handler would need are read now.
void LoadSymbols(const void* const* warm, int warmCount);
// LoadSymbols has finished (whether or not it found the PDB).
bool SymbolsReady();
// What LoadSymbols found, for reports: "SpeedBreaker.pdb", or why there
// are no names ("no SpeedBreaker.pdb next to SpeedBreaker.exe"; DbgHelp
// didn't start, failed in an earlier walk, or wasn't to be had in a crash).
const char* SymbolsState();
// A thread's registers and the top of its stack, as Capture found them.
struct Snapshot
{
CONTEXT context;
uint8_t* buffer = nullptr; // allocated by the caller
size_t capacity = 0;
uintptr_t low = 0; // the stack address of buffer[0] (its rsp)
size_t size = 0; // bytes copied
};
// Suspends `thread` (a handle with THREAD_SUSPEND_RESUME and
// THREAD_GET_CONTEXT), takes its registers and its stack from rsp up to
// `stackHigh` (at most `capacity` bytes), and resumes it. False if it
// couldn't be suspended or read (it has ended).
bool Capture(HANDLE thread, uintptr_t stackHigh, Snapshot& snapshot);
// The stack from `context`: frames[0] is the context's rip, then the
// return addresses, innermost first. With `snapshot`, stack memory comes
// from its copy where it has it. 0 if DbgHelp isn't usable (not loaded
// yet, busy past the crash handler's wait, or broken by the crash).
int Walk(const CONTEXT& context, void** frames, int max, const Snapshot* snapshot = nullptr);
// The same walk without DbgHelp, for when Walk can't (a hang report's
// threads): RtlVirtualUnwind and the unwind tables over the snapshot's
// copy of the stack, never the live stack. Ends where the copy does.
int Unwind(const Snapshot& snapshot, void** frames, int max);
// The function `pc` is in: its name (undecorated, cut to fit `size`), its
// start, and its size (0 for a public symbol or an export: only its start
// is known). False if there is no name, or DbgHelp isn't usable. Outside a
// crash, a module DbgHelp hasn't seen yet (a driver's DLL) is loaded first.
bool Name(const void* pc, char* name, size_t size, uintptr_t& start, uint32_t& length);
// The crash handler, before it walks or names anything: DbgHelp for this
// thread from now on, waiting up to `waitMs` for another thread to finish
// with it. False (and DbgHelp unused from then on) if it isn't to be had,
// the crash happened inside it on this thread, or it was unusable already;
// SymbolsState then says which.
bool EnterCrash(unsigned waitMs);
}
#endif