8#include <Zydis/Zydis.h>
57#define MAX_STACK_FRAMES 64
58#define SYMBOL_NAME_CAPACITY 256
59#define SYMBOL_BUFFER_SIZE (sizeof(IMAGEHLP_SYMBOL) + SYMBOL_NAME_CAPACITY)
60#define DISASM_BYTES_BEFORE 16u
61#define DISASM_BYTES_AFTER 32u
62#define DISASM_MAX_BYTES (DISASM_BYTES_BEFORE + DISASM_BYTES_AFTER)
63#define DISASM_MAX_INSTRUCTIONS 6
64#define DISASM_MAX_LINES (DISASM_BYTES_BEFORE + DISASM_MAX_INSTRUCTIONS)
65#define DISASM_TEXT_CAPACITY 160
68 if (protect & (PAGE_GUARD | PAGE_NOACCESS)) {
77 case PAGE_EXECUTE_READ:
78 case PAGE_EXECUTE_READWRITE:
79 case PAGE_EXECUTE_WRITECOPY:
89 "\n<disassembly unavailable: %s>",
90 reason ? reason :
"unknown"
106 return sdscatprintf(message,
"\n=> 0x%08lX %s", address, text);
109 return sdscatprintf(message,
"\n0x%08lX %s", address, text);
114 ZydisFormatter *formatter,
115 const uint8_t *bytes,
118 ZydisDecodedInstruction *instruction,
122 ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT] = {0};
123 if (!ZYAN_SUCCESS(ZydisDecoderDecodeFull(
decoder, bytes,
size, instruction, operands))
124 || instruction->length == 0) {
128 return ZYAN_SUCCESS(ZydisFormatterFormatInstruction(
132 instruction->operand_count_visible,
143 ZydisFormatter *formatter,
144 const uint8_t *bytes,
151 ZydisDecodedInstruction instruction = {0};
158 failed_eip + (
DWORD)offset,
163 return emitted > 0 ? message
169 failed_eip + (
DWORD)offset,
173 offset += instruction.length;
181 const uint8_t *bytes,
186 if (!bytes ||
size == 0 || failed_eip < runtime_base) {
190 const size_t failed_offset = (size_t)(failed_eip - runtime_base);
191 if (failed_offset >=
size) {
197 ZydisDecoderInit(&
decoder, ZYDIS_MACHINE_MODE_LEGACY_32, ZYDIS_STACK_WIDTH_32)
202 ZydisFormatter formatter = {0};
203 if (!ZYAN_SUCCESS(ZydisFormatterInit(&formatter, ZYDIS_FORMATTER_STYLE_INTEL))) {
208 size_t line_count = 0;
209 size_t failed_index = SIZE_MAX;
213 ZydisDecodedInstruction instruction = {0};
219 runtime_base + (
DWORD)offset,
221 lines[line_count].text,
222 sizeof(lines[line_count].text)
227 lines[line_count].
address = runtime_base + (
DWORD)offset;
228 if (lines[line_count].address == failed_eip) {
229 failed_index = line_count;
233 offset += instruction.length;
236 if (failed_index == SIZE_MAX) {
241 bytes + failed_offset,
242 size - failed_offset,
247 size_t first = failed_index > 2 ? failed_index - 2 : 0;
249 if (last > line_count) {
253 const size_t available = last - first;
254 if (available < DISASM_MAX_INSTRUCTIONS && first > 0) {
256 first = first > backfill ? first - backfill : 0;
260 for (
size_t i = first; i < last; i++) {
274 return sdscat(message,
"\n<context unavailable>");
279 "\nContextFlags=0x%08lX"
280 "\nEAX=0x%08lX EBX=0x%08lX ECX=0x%08lX EDX=0x%08lX"
281 "\nESI=0x%08lX EDI=0x%08lX EBP=0x%08lX ESP=0x%08lX"
282 "\nEIP=0x%08lX EFLAGS=0x%08lX"
283 "\nSEGCS=0x%08lX SEGSS=0x%08lX SEGDS=0x%08lX SEGES=0x%08lX "
284 "SEGFS=0x%08lX SEGGS=0x%08lX"
285 "\nDR0=0x%08lX DR1=0x%08lX DR2=0x%08lX DR3=0x%08lX "
286 "DR6=0x%08lX DR7=0x%08lX"
289 "\nControlWord=0x%08lX StatusWord=0x%08lX TagWord=0x%08lX"
290 "\nErrorOffset=0x%08lX ErrorSelector=0x%08lX"
291 "\nDataOffset=0x%08lX DataSelector=0x%08lX Cr0NpxState=0x%08lX"
316 ctx->FloatSave.ControlWord,
317 ctx->FloatSave.StatusWord,
318 ctx->FloatSave.TagWord,
319 ctx->FloatSave.ErrorOffset,
320 ctx->FloatSave.ErrorSelector,
321 ctx->FloatSave.DataOffset,
322 ctx->FloatSave.DataSelector,
323 ctx->FloatSave.Cr0NpxState
332 MEMORY_BASIC_INFORMATION mbi = {0};
333 if (VirtualQueryEx(process, (LPCVOID)(uintptr_t)failed_eip, &mbi,
sizeof(mbi))
342 const uintptr_t region_base = (uintptr_t)mbi.BaseAddress;
343 const uintptr_t region_end = region_base + mbi.RegionSize;
346 : (uintptr_t)failed_eip;
347 if (start < region_base) {
352 if (end > region_end) {
360 size_t size = (size_t)(end - start);
366 SIZE_T bytes_read = 0;
367 if (!ReadProcessMemory(process, (LPCVOID)start, bytes,
size, &bytes_read)
368 || bytes_read == 0) {
382 message = sdscat(message,
"\n");
393 return MiniDumpWithDataSegs | MiniDumpWithIndirectlyReferencedMemory;
396 return MiniDumpNormal;
403 "Exception 0x%08lX\n\nDump written to:\n%s",
411 "Exception 0x%08lX\n\nFailed to write crash dump.",
418 message = sdsempty();
422 message = sdscat(message, stack_trace);
432 EXCEPTION_POINTERS *exception_info
437 sds filename = sdscatprintf(
439 "%sdttr_crash_%04d%02d%02d_%02d%02d%02d.dmp",
449 HANDLE file = CreateFileA(
455 FILE_ATTRIBUTE_NORMAL,
458 if (file == INVALID_HANDLE_VALUE) {
464 MINIDUMP_EXCEPTION_INFORMATION mei = {
466 .ExceptionPointers = exception_info,
467 .ClientPointers =
FALSE,
492 OutputDebugStringA(message);
493 OutputDebugStringA(
"\n");
498 message = sdscat(message,
"\n\nStack trace:");
499 if (!process || !thread || !context) {
500 return sdscat(message,
"\n <unavailable>");
503 CONTEXT
ctx = *context;
505 if (!SymInitialize(process,
NULL, TRUE)) {
507 return sdscat(message,
"\n <symbols unavailable>");
512 STACKFRAME frame = {0};
513 frame.AddrPC.Offset =
ctx.Eip;
514 frame.AddrPC.Mode = AddrModeFlat;
515 frame.AddrFrame.Offset =
ctx.Ebp;
516 frame.AddrFrame.Mode = AddrModeFlat;
517 frame.AddrStack.Offset =
ctx.Esp;
518 frame.AddrStack.Mode = AddrModeFlat;
523 IMAGE_FILE_MACHINE_I386,
529 SymFunctionTableAccess,
533 || frame.AddrPC.Offset == 0) {
537 const DWORD addr = (
DWORD)frame.AddrPC.Offset;
539 IMAGEHLP_MODULE module_info = {.SizeOfStruct =
sizeof(IMAGEHLP_MODULE)};
540 const char *mod_name =
"???";
541 if (SymGetModuleInfo(process, addr, &module_info)) {
542 mod_name = module_info.ModuleName;
548 IMAGEHLP_SYMBOL *sym = (IMAGEHLP_SYMBOL *)sym_buf;
549 sym->SizeOfStruct =
sizeof(IMAGEHLP_SYMBOL);
552 DWORD displacement = 0;
553 if (!SymGetSymFromAddr(process, addr, &displacement, sym)) {
554 message = sdscatprintf(message,
"\n %s!0x%08lX", mod_name, addr);
558 message = sdscatprintf(
569 if (frame_count == 0) {
570 message = sdscat(message,
"\n <unavailable>");
577 const DWORD code = exception_info->ExceptionRecord->ExceptionCode;
578 const HANDLE process = GetCurrentProcess();
579 const DWORD pid = GetCurrentProcessId();
580 const DWORD tid = GetCurrentThreadId();
587 exception_info->ContextRecord
591 sdsfree(stack_trace);
598 SDL_MESSAGEBOX_ERROR,
605 sdsfree(report_message);
608 return EXCEPTION_CONTINUE_SEARCH;
612 sds dump_dir_path = sdsnew(base_dir);
615 sdsfree(dump_dir_path);
619 if (!CreateDirectoryA(dump_dir_path,
NULL)
620 && GetLastError() != ERROR_ALREADY_EXISTS) {
621 DTTR_LOG_ERROR(
"Failed to create crash dump directory %s", dump_dir_path);
622 sdsfree(dump_dir_path);
628 sdsfree(dump_dir_path);
632 sdsfree(dump_dir_path);
DTTR_Graphics_COM_Direct3DDevice7 void *status DTTR_Graphics_COM_Direct3DDevice7 DWORD DWORD void DWORD DWORD f DTTR_Graphics_COM_Direct3DDevice7 DWORD void DWORD st
DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 void void void void DWORD f BOOL
DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 DWORD block DTTR_Graphics_COM_Direct3DDevice7 void void void void DWORD f FALSE
DTTR_Graphics_COM_DirectDrawSurface7 DWORD flags void NULL
static sds append_failed_instruction_decode(sds message, ZydisDecoder *decoder, ZydisFormatter *formatter, const uint8_t *bytes, size_t size, DWORD failed_eip)
void DTTR_CrashDump_Init(const char *const dump_dir)
#define DISASM_TEXT_CAPACITY
void DTTR_CrashDump_LogAndTraceReport(const char *message)
static sds append_disassembly_from_bytes(sds message, const uint8_t *bytes, size_t size, DWORD runtime_base, DWORD failed_eip)
static sds append_disassembly_window(sds message, HANDLE process, DWORD failed_eip)
void DTTR_CrashDump_SetSymbolProvider(DTTR_CrashDump_SymbolProvider provider, void *context)
Registers a synchronous symbol provider used by crash stack formatting.
static bool format_instruction_at(ZydisDecoder *decoder, ZydisFormatter *formatter, const uint8_t *bytes, size_t size, DWORD address, ZydisDecodedInstruction *instruction, char *text, size_t text_size)
static BOOL init_dbghelp_lock(PINIT_ONCE, PVOID, PVOID *)
static sds append_disassembly_unavailable(sds message, const char *reason)
static sds append_registers(sds message, const CONTEXT *ctx)
sds DTTR_CrashDump_Write(HANDLE process, DWORD pid, DWORD tid, EXCEPTION_POINTERS *exception_info)
static bool set_dump_dir(const char *base_dir)
static sds build_crash_message(DWORD code, const char *filename)
static CRITICAL_SECTION dbghelp_lock
static void enter_dbghelp_lock()
void DTTR_CrashDump_ClearSymbolProvider()
sds DTTR_CrashDump_FormatStackTrace(HANDLE process, HANDLE thread, const CONTEXT *context)
Formats a stack trace from a thread context. Caller frees the returned sds.
static LONG unhandled_exception_filter(EXCEPTION_POINTERS *const exception_info)
#define SYMBOL_NAME_CAPACITY
static sds append_crash_diagnostics(sds message, HANDLE process, const CONTEXT *context)
sds DTTR_CrashDump_AppendReportMessage(sds message, const char *stack_trace)
static char crash_dump_dir[MAX_PATH]
#define DISASM_MAX_INSTRUCTIONS
static void invoke_symbol_provider(HANDLE process)
static bool memory_protection_is_readable(DWORD protect)
static DTTR_CrashDump_SymbolProvider crash_symbol_provider
#define SYMBOL_BUFFER_SIZE
#define DISASM_BYTES_AFTER
static sds append_disassembly_line(sds message, DWORD address, const char *text, bool failed)
static INIT_ONCE dbghelp_lock_once
static MINIDUMP_TYPE minidump_type()
static void leave_dbghelp_lock()
#define DISASM_BYTES_BEFORE
static void * crash_symbol_provider_context
bool(* DTTR_CrashDump_SymbolProvider)(HANDLE process, void *context)
Adds process-local symbols after DbgHelp initialization and before stack walking.
#define DTTR_REPORT_SUFFIX
bool DTTR_ImGui_ErrorShow(const char *title, const char *message)
#define DTTR_LOG_DEBUG(...)
#define DTTR_LOG_INFO(...)
#define DTTR_LOG_ERROR(...)
bool DTTR_Path_CopySds(char *out, size_t out_size, sds value)
#define DTTR_PATH_NATIVE_SEPARATOR
bool DTTR_Path_AppendSeparator(sds *path, char separator)
bool DTTR_Path_AppendSegment(sds *path, const char *segment, char separator)
bool DTTR_SDL_ShowSimpleMessageBox(SDL_MessageBoxFlags flags, const char *title, const char *message, SDL_Window *window)
static ZydisDecoder decoder
char text[DISASM_TEXT_CAPACITY]