From 0a0699c212e912a1c1c2e999da5218db236266a5 Mon Sep 17 00:00:00 2001 From: janwas Date: Tue, 21 Jun 2005 16:39:40 +0000 Subject: [PATCH] - split into symbol engine and everything else - thread-safe init and stack trace output (more robust) - better symbol display (no longer suppress all STL; UDT line splitting - add debug_wprintf and debug_disable_leak_reporting - exception handler now called from __try and vectored chain (if supported by OS); will even work while being debugged and isn't stomped on by some other UnhandledExceptionFilter users This was SVN commit r2419. --- source/lib/sysdep/win/wdbg.cpp | 2004 ++-------------------------- source/lib/sysdep/win/wdbg_sym.cpp | 1668 +++++++++++++++++++++++ 2 files changed, 1774 insertions(+), 1898 deletions(-) create mode 100755 source/lib/sysdep/win/wdbg_sym.cpp diff --git a/source/lib/sysdep/win/wdbg.cpp b/source/lib/sysdep/win/wdbg.cpp index 02beb64ac1..f122806989 100755 --- a/source/lib/sysdep/win/wdbg.cpp +++ b/source/lib/sysdep/win/wdbg.cpp @@ -1,4 +1,4 @@ -// stack trace, improved assert and exception handler for Win32 +// Windows debug backend // Copyright (c) 2002-2005 Jan Wassenberg // // This program is free software; you can redistribute it and/or @@ -21,46 +21,25 @@ #include #include "lib.h" - #include "win_internal.h" -#define _NO_CVCONST_H // request SymTagEnum be defined -#include "dbghelp.h" -#include // VARIANT #include "posix.h" +#include "sysdep/cpu.h" +#include "wdbg.h" +#include "byte_order.h" // FOURCC // optional: enables translation of the "unhandled exception" dialog. #ifdef I18N #include "ps/i18n.h" #endif -#include "sysdep/cpu.h" -#include "wdbg.h" -#include "error_dialog.h" -#include "byte_order.h" // FOURCC -#ifdef _MSC_VER -#pragma comment(lib, "dbghelp.lib") -#pragma comment(lib, "oleaut32.lib") // VariantChangeType -#endif - - -// automatic module init (before main) and shutdown (before termination) -#pragma data_seg(".LIB$WCC") +#pragma data_seg(WIN_CALLBACK_PRE_MAIN(b)) WIN_REGISTER_FUNC(wdbg_init); -#pragma data_seg(".LIB$WTB") -WIN_REGISTER_FUNC(wdbg_shutdown); #pragma data_seg() -// debug_warn usually uses assert2, but we don't want to call that from -// inside an assert2 (from inside another assert2 (from inside another assert2 -// (... etc))), so just use the normal assert -#undef debug_warn -#define debug_warn(str) assert(0 && (str)) - -// protects dbghelp (which isn't thread-safe) and -// parameter passing to the breakpoint helper thread. +// protects the breakpoint helper thread. static void lock() { win_lock(WDBG_CS); @@ -71,23 +50,12 @@ static void unlock() win_unlock(WDBG_CS); } - -enum WdbgError +// used in a desperate attempt to avoid deadlock in wdbg_exception_handler. +static bool is_locked() { - // the value is stored in an external module and therefore cannot be - // displayed. - WDBG_UNRETRIEVABLE_STATIC = -100000, + return win_is_locked(WDBG_CS) == 1; +} - // the value is stored in a register and therefore cannot be displayed - // (see CV_HREG_e). - WDBG_UNRETRIEVABLE_REG = -100001, - - // an essential call to SymGetTypeInfo or SymFromIndex failed. - WDBG_TYPE_INFO_UNAVAILABLE = -100002, - - // exception raised while processing the symbol. - WDBG_INTERNAL_ERROR = -100003, -}; // return localized version of , if i18n functionality is available. @@ -153,18 +121,17 @@ void debug_printf(const char* fmt, ...) vsnprintf(buf, MAX_CNT-1, fmt, ap); va_end(ap); - OutputDebugString(buf); + OutputDebugStringA(buf); } - void debug_wprintf(const wchar_t* fmt, ...) { wchar_t buf[MAX_CNT]; - buf[MAX_CNT-1] = L'\0'; + buf[MAX_CNT-1] = '\0'; va_list ap; va_start(ap, fmt); - vsnwprintf(buf, MAX_CNT-1, fmt, ap); + vswprintf(buf, MAX_CNT-1, fmt, ap); va_end(ap); OutputDebugStringW(buf); @@ -182,6 +149,14 @@ void debug_check_heap() } } +void debug_disable_leak_reporting() +{ +#ifdef HAVE_DEBUGALLOC + uint flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); + _CrtSetDbgFlag(flags & ~_CRTDBG_LEAK_CHECK_DF); +#endif +} + ////////////////////////////////////////////////////////////////////////////// @@ -435,1732 +410,12 @@ int debug_remove_all_breaks() } -////////////////////////////////////////////////////////////////////////////// -// -// dbghelp -// -////////////////////////////////////////////////////////////////////////////// - -// passed to all dbghelp symbol query functions. we're not interested in -// resolving symbols in other processes; the purpose here is only to -// generate a stack trace. if that changes, we need to init a local copy -// of these in dump_sym_cb and pass them to all subsequent dump_*. -static HANDLE hProcess; -static ULONG64 mod_base; - -// for StackWalk64; taken from PE header by wdbg_init -static WORD machine; - -static const STACKFRAME64* current_stackframe64; - -static int sym_init() -{ - hProcess = GetCurrentProcess(); - - SymSetOptions(SYMOPT_DEFERRED_LOADS/*/*|SYMOPT_DEBUG*/); - // loads symbols for all active modules. - BOOL ok = SymInitialize(hProcess, 0, TRUE); - if(!ok) - display_msg("wdbg_init", "SymInitialize failed"); - - mod_base = SymGetModuleBase64(hProcess, (u64)&wdbg_init); - IMAGE_NT_HEADERS* header = ImageNtHeader((void*)mod_base); - machine = header->FileHeader.Machine; - - return 0; -} - - -static int sym_shutdown() -{ - SymCleanup(hProcess); - return 0; -} - - -struct SYMBOL_INFO_PACKAGE2 : public SYMBOL_INFO_PACKAGE -{ - SYMBOL_INFO_PACKAGE2() - { - si.SizeOfStruct = sizeof(si); - si.MaxNameLen = MAX_SYM_NAME; - } -}; - -// ~500µs -int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int* line) -{ - const DWORD64 addr = (DWORD64)ptr_of_interest; - int successes = 0; - - lock(); - - // get symbol name - if(sym_name) - { - sym_name[0] = '\0'; - SYMBOL_INFO_PACKAGE2 sp; - SYMBOL_INFO* sym = &sp.si; - if(SymFromAddr(hProcess, addr, 0, sym)) - { - snprintf(sym_name, DBG_SYMBOL_LEN, "%s", sym->Name); - successes++; - } - } - - // get source file + line number - if(file || line) - { - IMAGEHLP_LINE64 line_info = { sizeof(IMAGEHLP_LINE64) }; - DWORD displacement; // unused but required by SymGetLineFromAddr64! - if(SymGetLineFromAddr64(hProcess, addr, &displacement, &line_info)) - successes++; - // note: were left zeroed if SymGetLineFromAddr64 failed - if(file) - snprintf(file, DBG_FILE_LEN, "%s", line_info.FileName); - if(line) - *line = line_info.LineNumber; - } - - unlock(); - return (successes == 0)? -1 : 0; -} - - -////////////////////////////////////////////////////////////////////////////// -// -// stack walk via dbghelp -// -////////////////////////////////////////////////////////////////////////////// - -// rationale: to function properly, StackWalk64 requires a CONTEXT on -// non-x86 systems (documented) or when in release mode (observed). -// exception handlers can call walk_stack with their context record; -// otherwise (e.g. dump_stack from assert2), we need to query it. -// there are 2 platform-independent ways to do so: -// - intentionally raise an SEH exception, then proceed as above; -// - GetThreadContext while suspended (*). -// the latter is more complicated and slower, so we go with the former -// despite it outputting "first chance exception" on each call. -// -// on IA-32, we use ia32_get_win_context instead of the above because -// it is 100% accurate (noticeable in StackWalk64 results) and simplest. -// -// * it used to be common practice not to query the current thread's context, -// but WinXP SP2 and above require it be suspended. - -// copy from CONTEXT to STACKFRAME64 -#if defined(_M_AMD64) -# define PC_ Rip -# define FP_ Rbp -# define SP_ Rsp -#elif defined(_M_IX86) -# define PC_ Eip -# define FP_ Ebp -# define SP_ Esp -#endif - -#ifdef _M_IX86 - -// optimized for size. -static __declspec(naked) void __cdecl get_current_context(void* pcontext) -{ -__asm -{ - pushad - pushfd - mov edi, [esp+4+32+4] ;// pcontext - - ;// ContextFlags - mov eax, 0x10007 ;// segs, int, control - stosd - - ;// DRx and FloatSave - ;// rationale: we can't access the debug registers from Ring3, and - ;// the FPU save area is irrelevant, so zero them. - xor eax, eax - push 6+8+20 - pop ecx -rep stosd - - ;// CONTEXT_SEGMENTS - mov ax, gs - stosd - mov ax, fs - stosd - mov ax, es - stosd - mov ax, ds - stosd - - ;// CONTEXT_INTEGER - mov eax, [esp+4+32-32] ;// edi - stosd - xchg eax, esi - stosd - xchg eax, ebx - stosd - xchg eax, edx - stosd - mov eax, [esp+4+32-8] ;// ecx - stosd - mov eax, [esp+4+32-4] ;// eax - stosd - - ;// CONTEXT_CONTROL - xchg eax, ebp - stosd - mov eax, [esp+4+32] ;// eip - sub eax, 5 ;// back up to call site from ret addr - stosd - xor eax, eax - mov ax, cs - stosd - pop eax ;// eflags - stosd - lea eax, [esp+32+4+4] ;// esp - stosd - xor eax, eax - mov ax, ss - stosd - - ;// ExtendedRegisters - push 512/4 - pop ecx -rep stosd - - popad - ret -} -} - -#else // #ifdef _M_IX86 - -static void get_current_context(CONTEXT* pcontext) -{ - __try - { - RaiseException(0xF00L, 0, 0, 0); - } - __except(*pcontext = (GetExceptionInformation())->ContextRecord, EXCEPTION_CONTINUE_EXECUTION) - { - assert(0); // never reached - } -} - -#endif - - - - -// called for each stack frame found by walk_stack, passing information -// about the frame and . -// return <= 0 to stop immediately and have walk_stack return that; -// otherwise, > 0 to continue. -// -// rationale: we can't just pass function's address to the callback - -// dump_frame_cb needs the frame pointer for reg-relative variables. -typedef int (*StackFrameCallback)(const STACKFRAME64*, void*); - -// iterate over a call stack, calling back for each frame encountered. -// if != 0, we start there; otherwise, at the current context. -// return -1 if callback never succeeded (returned 0). lock must be held. -static int walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip = 0, const CONTEXT* pcontext = 0) -{ - const HANDLE hThread = GetCurrentThread(); - - // get CONTEXT (see above) - CONTEXT context; - // .. caller knows the context (most likely from an exception); - // since StackWalk64 may modify it, copy to a local variable. - if(pcontext) - context = *pcontext; - // .. need to determine context ourselves. - else - { - get_current_context(&context); - skip++; // skip walk_stack's frame - } - pcontext = &context; - - STACKFRAME64 sf; - memset(&sf, 0, sizeof(sf)); - sf.AddrPC.Offset = pcontext->PC_; - sf.AddrPC.Mode = AddrModeFlat; - sf.AddrFrame.Offset = pcontext->FP_; - sf.AddrFrame.Mode = AddrModeFlat; - sf.AddrStack.Offset = pcontext->SP_; - sf.AddrStack.Mode = AddrModeFlat; - - // for each stack frame found: - for(;;) - { - BOOL ok = StackWalk64(machine, hProcess, hThread, &sf, (void*)pcontext, - 0, SymFunctionTableAccess64, SymGetModuleBase64, 0); - - // callback never indicated success and no (more) frames found: abort. - // note: also test FP because StackWalk64 sometimes erroneously - // reports success. unfortunately it doesn't SetLastError either, - // so we can't indicate the cause of failure. *sigh* - if(!ok || !sf.AddrFrame.Offset) - return -911; // distinctive error value - - if(skip) - { - skip--; - continue; - } - - int ret = cb(&sf, user_arg); - // callback reports it's done; stop calling it and return that value. - // (can be 0 for success, or a negative error code) - if(ret <= 0) - return ret; - } -} - - -// -// get address of Nth function above us on the call stack (uses walk_stack) -// - -// called by walk_stack for each stack frame -static int nth_caller_cb(const STACKFRAME64* sf, void* user_arg) -{ - void** pfunc = (void**)user_arg; - - // return its address - *pfunc = (void*)sf->AddrPC.Offset; - return 0; -} - - -// n starts at 1 -void* debug_get_nth_caller(uint n) -{ - void* func; // set by callback - const uint skip = n-1 + 3; - // make 0-based; skip walk_stack, debug_get_nth_caller and its caller. - if(walk_stack(nth_caller_cb, &func, skip) == 0) - return func; - return 0; -} - - -////////////////////////////////////////////////////////////////////////////// -// -// helper routines for symbol value dump -// -////////////////////////////////////////////////////////////////////////////// - -// overflow is impossible in practice. keep in sync with DumpState. -static const uint MAX_INDIRECTION = 256; -static const uint MAX_LEVEL = 256; - -struct DumpState -{ - // keep in sync with MAX_* above - uint level : 8; - uint indirection : 8; - - DumpState() - { - level = 0; - indirection = 0; - } -}; - -static const size_t DUMP_BUF_SIZE = 64*KiB; -static wchar_t dump_buf[DUMP_BUF_SIZE]; -static wchar_t* dump_buf_pos; - -static void out(const wchar_t* fmt, ...) -{ - // Don't overflow the buffer (and abort if we're about to) - if (dump_buf_pos-dump_buf+1000 > DUMP_BUF_SIZE) - { - debug_warn("out: buffer about to overflow"); - return; - }; - - va_list args; - va_start(args, fmt); - dump_buf_pos += vswprintf(dump_buf_pos, 1000, fmt, args); - va_end(args); -} - - -static void out_erase(size_t num_chars) -{ - dump_buf_pos -= num_chars; - assert2(dump_buf_pos >= dump_buf); // check for underrun - *dump_buf_pos = '\0'; - // make sure it's 0-terminated in case there is no further output. -} - - -static void out_reset() -{ - dump_buf_pos = dump_buf; -} - - -#define INDENT STMT(for(uint i = 0; i <= state.level+1; i++) out(L" ");) - - -// does it look like an ASCII string is located at ? -// set to 2 to search for WCS-2 strings (of western characters!). -// called by dump_sequence for its string special-case. -// -// algorithm: scan the "string" and count # text chars vs. garbage. -static bool is_string(const u8* p, size_t stride) -{ - // note: access violations are caught by dump_sym; output is "?". - int score = 0; - for(;;) - { - // current character is: - const int c = *p & 0xff; // prevent sign extension - p += stride; - // .. text - if(isalnum(c)) - score += 5; - // .. end of string - else if(!c) - break; - // .. garbage - else if(!isprint(c)) - score -= 4; - - // got enough information either way => done. - // (we don't want to unnecessarily scan huge binary arrays) - if(abs(score) >= 10) - break; - } - - return (score > 0); -} - - -static bool is_bogus_pointer(const void* p) -{ -#ifdef _M_IX86 - if(p < (void*)0x10000) - return true; - if(p >= (void*)(uintptr_t)0x80000000) - return true; -#endif - - return IsBadReadPtr(p, 1) != 0; -} - - -// provide c_str() access for any specialization of std::basic_string -// (since dump_string doesn't know type at compile-time). -// also performs a basic sanity check to see if the object is initialized. -struct AnyString : public std::string -{ - const void* safe_c_str(size_t el_size) const - { - // bogus - if(_Myres < _Mysize) - return 0; - return (_Myres < 16/el_size)? _Bx._Buf : _Bx._Ptr; - } -}; - -static int dump_string(WCHAR* type_name, const u8* p, size_t size, DumpState state) -{ - size_t el_size; - const WCHAR* pretty_name = type_name; - const void* string_data = 0; - - // Pyrogenesis CStr - if(!wcsncmp(type_name, L"CStr", 4)) - { - assert(size == 32/*sizeof(CStr)*/); - - // determine type - if(type_name[4] == '8') - el_size = sizeof(char); - else if(type_name[4] == 'W') - el_size = sizeof(wchar_t); - // .. unknown, shouldn't handle it - else - return 1; - - p += 4; // skip vptr (mixed in by ISerializable) - string_data = ((AnyString*)p)->safe_c_str(el_size); - } - // std::basic_string and its specializations - else if(!wcsncmp(type_name, L"std::basic_string", 17)) - { - assert(size == sizeof(std::string) || size == 16); - // dbghelp bug: std::wstring size is given as 16 - - // determine type - if(!wcsncmp(type_name+18, L"char", 4)) - { - el_size = sizeof(char); - pretty_name = L"std::string"; - } - else if(!wcsncmp(type_name+18, L"unsigned short", 14)) - { - el_size = sizeof(wchar_t); - pretty_name = L"std::wstring"; - } - // .. unknown, shouldn't handle it - else - return 1; - - string_data = ((AnyString*)p)->safe_c_str(el_size); - } - // type_name isn't a known string object; we can't handle it. - else - return 1; - - // type_name is known but its contents are bogus; so indicate. - if(is_bogus_pointer(string_data) || !is_string((const u8*)string_data, el_size)) - out(L"(uninitialized/invalid %s)", pretty_name); - // valid; display it. - else - { - const wchar_t* fmt = (el_size == sizeof(wchar_t))? L"\"%s\"" : L"\"%hs\""; - out(fmt, string_data); - } - - // it was a string object (valid or not) -> we handled it. - return 0; -} - - -static bool should_suppress_udt(WCHAR* type_name) -{ - // STL - if(!wcsncmp(type_name, L"std::", 5)) - return true; - - // specialized HANDLEs are defined as pointers to structs by - // DECLARE_HANDLE. we only want the numerical value (pointer address), - // so prevent these structs from being displayed. - // note: no need to check for indirection; these are only found in - // HANDLEs (which are pointers). - // removed obsolete defs: HEVENT, HFILE, HUMPD - if(type_name[0] != 'H') - goto not_handle; -#define SUPPRESS_HANDLE(name) if(!wcscmp(type_name, L#name L"__")) return true; - SUPPRESS_HANDLE(HACCEL); - SUPPRESS_HANDLE(HBITMAP); - SUPPRESS_HANDLE(HBRUSH); - SUPPRESS_HANDLE(HCOLORSPACE); - SUPPRESS_HANDLE(HCURSOR); - SUPPRESS_HANDLE(HDC); - SUPPRESS_HANDLE(HENHMETAFILE); - SUPPRESS_HANDLE(HFONT); - SUPPRESS_HANDLE(HGDIOBJ); - SUPPRESS_HANDLE(HGLOBAL); - SUPPRESS_HANDLE(HGLRC); - SUPPRESS_HANDLE(HHOOK); - SUPPRESS_HANDLE(HICON); - SUPPRESS_HANDLE(HIMAGELIST); - SUPPRESS_HANDLE(HIMC); - SUPPRESS_HANDLE(HINSTANCE); - SUPPRESS_HANDLE(HKEY); - SUPPRESS_HANDLE(HKL); - SUPPRESS_HANDLE(HKLOCAL); - SUPPRESS_HANDLE(HMENU); - SUPPRESS_HANDLE(HMETAFILE); - SUPPRESS_HANDLE(HMODULE); - SUPPRESS_HANDLE(HMONITOR); - SUPPRESS_HANDLE(HPALETTE); - SUPPRESS_HANDLE(HPEN); - SUPPRESS_HANDLE(HRGN); - SUPPRESS_HANDLE(HRSRC); - SUPPRESS_HANDLE(HSTR); - SUPPRESS_HANDLE(HTASK); - SUPPRESS_HANDLE(HWINEVENTHOOK); - SUPPRESS_HANDLE(HWINSTA); - SUPPRESS_HANDLE(HWND); -not_handle: - - return false; -} - - -static int dump_special_udt(WCHAR* type_name, const u8* p, size_t size, DumpState state) -{ - int ret; - - ret = dump_string(type_name, p, size, state); - if(ret <= 0) - return ret; - - if(should_suppress_udt(type_name)) - { - // the data symbol is pointer-to-UDT. since we won't display its - // contents, leave only the pointer's value. - if(state.indirection) - out_erase(4); // " -> " - - return 0; - } - - return 1; // not handled -} - - - -// forward decl; called by dump_sequence and some of dump_sym_*. -static int dump_sym(DWORD idx, const u8* p, DumpState state); - - -static int dump_sequence(const u8* p, uint num_elements, DWORD el_idx, size_t el_size, DumpState state) -{ - // special case for character arrays: display as string - if(el_size == sizeof(char) || el_size == sizeof(wchar_t)) - if(is_string(p, el_size)) - { - // make sure it's 0-terminated - wchar_t buf[512]; - if(el_size == sizeof(wchar_t)) - wcscpy_s(buf, ARRAY_SIZE(buf), (const wchar_t*)p); - else - { - size_t i; - for(i = 0; i < ARRAY_SIZE(buf)-1; i++) - { - buf[i] = (wchar_t)p[i]; - if(buf[i] == '\0') - break; - } - buf[i] = '\0'; - } - - out(L"\"%s\"", buf); - return 0; - } - - // regular array: - const uint num_elements_to_show = MIN(20, num_elements); - const bool fits_on_one_line = - (el_size == sizeof(char) && num_elements <= 16) || - (el_size <= sizeof(int ) && num_elements <= 8); - - state.level++; - out(fits_on_one_line? L"{ " : L"\r\n"); - - int err = 0; - for(uint i = 0; i < num_elements_to_show; i++) - { - if(!fits_on_one_line) - INDENT; - - int ret = dump_sym(el_idx, p + i*el_size, state); - if(err == 0) // remember first error - err = ret; - - // add separator unless this is the last element - // (can't just erase below due to additional "...") - if(i != num_elements_to_show-1) - out(fits_on_one_line? L", " : L"\r\n"); - } - // we truncated some - if(num_elements != num_elements_to_show) - out(L" ..."); - - if(fits_on_one_line) - out(L" }"); - return err; -} - -// from cvconst.h -// -// rationale: we don't provide a get_register routine, since only the -// value of FP is known to dump_frame_cb (via STACKFRAME64). -// displaying variables stored in registers is out of the question; -// all we can do is display FP-relative variables. -enum CV_HREG_e -{ - CV_REG_EAX = 17, - CV_REG_ECX = 18, - CV_REG_EDX = 19, - CV_REG_EBX = 20, - CV_REG_ESP = 21, - CV_REG_EBP = 22, - CV_REG_ESI = 23, - CV_REG_EDI = 24 -}; - - -static const wchar_t* string_for_register(CV_HREG_e reg) -{ - switch(reg) - { - case CV_REG_EAX: - return L"eax"; - case CV_REG_ECX: - return L"ecx"; - case CV_REG_EDX: - return L"edx"; - case CV_REG_EBX: - return L"ebx"; - case CV_REG_ESP: - return L"esp"; - case CV_REG_EBP: - return L"ebp"; - case CV_REG_ESI: - return L"esi"; - case CV_REG_EDI: - return L"edi"; - default: - { - static wchar_t buf[19]; - swprintf(buf, ARRAY_SIZE(buf), L"0x%x", reg); - return buf; - } - } -} - - -static int determine_symbol_address(DWORD idx, DWORD type_idx, const u8** pp) -{ - const STACKFRAME64* sf = current_stackframe64; - - ULONG64 size_ = 0; - if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_LENGTH, &size_)) - return WDBG_TYPE_INFO_UNAVAILABLE; - const size_t size = (size_t)size_; - - DWORD data_kind; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_DATAKIND, &data_kind)) - return WDBG_TYPE_INFO_UNAVAILABLE; - switch(data_kind) - { - // SymFromIndex will fail - case DataIsMember: - { - DWORD ofs = 0; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_OFFSET, &ofs)) - return WDBG_TYPE_INFO_UNAVAILABLE; - //assert(ofs < size); - *pp += ofs; - return 0; - } - - // note: sometimes erroneously reported, but there's nothing we can do - // because TI_GET_ADDRESS returns mod_base, TI_GET_ADDRESSOFFSET 0, - // and TI_GET_OFFSET fails (it's only for members). - case DataIsStaticMember: - return WDBG_UNRETRIEVABLE_STATIC; - - } - - SYMBOL_INFO_PACKAGE2 sp; - SYMBOL_INFO* sym = &sp.si; - if(!SymFromIndex(hProcess, mod_base, idx, sym)) - return WDBG_TYPE_INFO_UNAVAILABLE; - -DWORD addrofs = 0; -ULONG64 addr2 = 0; -DWORD ofs2 = 0; -SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_ADDRESSOFFSET, &addrofs); -SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_ADDRESS, &addr2); -SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_OFFSET, &ofs2); - - - - // get address - ULONG64 addr = sym->Address; - // .. relative to a register - if(sym->Flags & SYMFLAG_REGREL) - { - if(sym->Register == CV_REG_EBP) - addr += sf->AddrFrame.Offset; - else - goto in_register; - } - // .. relative to FP (appears to be obsolete) - else if(sym->Flags & SYMFLAG_FRAMEREL) - addr += sf->AddrFrame.Offset; - // .. in register (this happens when optimization is enabled, - // but we can't do anything; see SymbolInfoRegister) - else if(sym->Flags & SYMFLAG_REGISTER) - goto in_register; - - *pp = (const u8*)addr; - -#ifdef NDEBUG - if(!(sym->Flags & SYMFLAG_PARAMETER)) - *pp += 4; -#endif - -debug_printf("DET_SYM_ADDR %s at %p flags=%X dk=%d sym->addr=%I64X addrofs=%X addr2=%I64X ofs2=%X\n", sym->Name, *pp, sym->Flags, data_kind, sym->Address, addrofs, addr2, ofs2); - - return 0; - -in_register: - *pp = (const u8*)(uintptr_t)sym->Register; - return WDBG_UNRETRIEVABLE_REG; - - - /* - - switch(data_kind) - { - // plain variables: p is already correct - case DataIsLocal: - case DataIsParam: - - case DataIsGlobal: - case DataIsStaticLocal: - case DataIsFileStatic: - - case DataIsObjectPtr: - break; - - // UDT member: get offset - case DataIsMember: - *pp += ofs; - break; - - - default: - debug_warn("dump_sym_data: invalid data kind"); - return -1; - } - - // success - return 0; - -*/ -} - - -// note: we can't derive from TI_FINDCHILDREN_PARAMS because its members -// aren't guaranteed to precede ours (although they do in practice). -struct TI_FINDCHILDREN_PARAMS2 -{ - TI_FINDCHILDREN_PARAMS2(DWORD num_children) - { - p.Start = 0; - p.Count = MIN(num_children, MAX_CHILDREN); - } - - static const size_t MAX_CHILDREN = 400; - TI_FINDCHILDREN_PARAMS p; - DWORD additional_children[MAX_CHILDREN-1]; -}; - - -////////////////////////////////////////////////////////////////////////////// -// -// dump routines for each dbghelp symbol type -// -////////////////////////////////////////////////////////////////////////////// - -// these functions return -1 if they're not able to produce any reasonable -// output; dump_data_sym will display value as "?" -// called by dump_sym; lock is held. - - -static int dump_sym_array(DWORD idx, const u8* p, DumpState state) -{ - ULONG64 size_ = 0; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_LENGTH, &size_)) - return WDBG_TYPE_INFO_UNAVAILABLE; - const size_t size = (size_t)size_; - - // get element count and size - DWORD el_idx = 0; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_TYPEID, &el_idx)) - return WDBG_TYPE_INFO_UNAVAILABLE; - // .. workaround: TI_GET_COUNT returns total struct size for - // arrays-of-struct. therefore, calculate as size / el_size. - ULONG64 el_size_; - if(!SymGetTypeInfo(hProcess, mod_base, el_idx, TI_GET_LENGTH, &el_size_)) - return WDBG_TYPE_INFO_UNAVAILABLE; - const size_t el_size = (size_t)el_size_; - assert(el_size != 0); - const uint num_elements = (uint)(size / el_size); - assert2(num_elements != 0); - - // display element count - out_erase(3); // " = " - out(L"[%d] = ", num_elements); - - return dump_sequence(p, num_elements, el_idx, el_size, state); -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_base_type(DWORD idx, const u8* p, DumpState state) -{ - DWORD base_type; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_BASETYPE, &base_type)) - return WDBG_TYPE_INFO_UNAVAILABLE; - ULONG64 size_ = 0; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_LENGTH, &size_)) - return WDBG_TYPE_INFO_UNAVAILABLE; - const size_t size = (size_t)size_; - - u64 data = movzx_64le(p, size); - // if value is 0xCC..CC (uninitialized mem), we display as hex. - // the output would otherwise be garbage; this makes it obvious. - // note: be very careful to correctly handle size=0 (e.g. void*). - for(size_t i = 0; i < size; i++) - { - if(p[i] != 0xCC) - break; - if(i == size-1) - goto uninitialized; - } - - // single out() call. note: we pass a single u64 for all sizes, - // which will only work on little-endian systems. - const wchar_t* fmt; - - switch(base_type) - { - // boolean - case btBool: - assert(size == sizeof(bool)); - fmt = L"%hs"; - data = (u64)(data? "true " : "false"); - break; - - // floating-point - case btFloat: - if(size == sizeof(float)) - fmt = L"%g"; - else if(size == sizeof(double)) - fmt = L"%lg"; - else - debug_warn("dump_sym_base_type: invalid float size"); - break; - - // signed integers (displayed as decimal) - case btInt: - case btLong: - if(size == 1 || size == 2 || size == 4 || size == 8) - fmt = L"%I64d"; - else - debug_warn("dump_sym_base_type: invalid int size"); - break; - - // unsigned integers (displayed as hex) - // note: 0x00000000 can get annoying (0 would be nicer), - // but it indicates the variable size and makes for consistently - // formatted structs/arrays. (0x1234 0 0x5678 is ugly) - case btUInt: - case btULong: -uninitialized: - if(size == 1) - { - // _TUCHAR - if(state.indirection) - { - state.indirection = 0; - return dump_sequence(p, 8, idx, size, state); - } - fmt = L"0x%02X"; - } - else if(size == 2) - fmt = L"0x%04X"; - else if(size == 4) - fmt = L"0x%08X"; - else if(size == 8) - fmt = L"0x%016I64X"; - else - debug_warn("dump_sym_base_type: invalid uint size"); - break; - - // character - case btChar: - case btWChar: - assert(size == sizeof(char) || size == sizeof(wchar_t)); - // char*, wchar_t* - if(state.indirection) - { - state.indirection = 0; - return dump_sequence(p, 8, idx, size, state); - } - // either integer or character; - // if printable, the character will be appended below. - fmt = L"%d"; - break; - - // note: void* is sometimes indicated as (pointer, btNoType). - case btVoid: - case btNoType: - // void* - cannot display what it's pointing to (type unknown). - if(state.indirection) - { - out_erase(4); // " -> " - fmt = L""; - } - else - debug_warn("dump_sym_base_type: non-pointer btVoid or btNoType"); - break; - - default: - debug_warn("dump_sym_base_type: unknown type"); - //-fallthrough - - // unsupported complex types - case btBCD: - case btCurrency: - case btDate: - case btVariant: - case btComplex: - case btBit: - case btBSTR: - case btHresult: - return -1; - } - - out(fmt, data); - - // if the current value is a printable character, display in that form. - // this isn't only done in btChar because sometimes ints store characters. - if(data < 0x100) - { - int c = (int)data; - if(isprint(c)) - out(L" ('%hc')", c); - } - - return 0; -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_base_class(DWORD idx, const u8* p, DumpState state) -{ - // unsupported: virtual base classes would require reading the VTbl, - // which is difficult given lack of documentation and not worth it. - return 0; -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_data(DWORD idx, const u8* p, DumpState state) -{ - // SymFromIndex will fail if dataKind happens to be DataIsMember, so - // we use SymGetTypeInfo (slower and less convenient, but no choice). - DWORD type_idx; - WCHAR* name; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_TYPEID, &type_idx)) - return WDBG_TYPE_INFO_UNAVAILABLE; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_SYMNAME, &name)) - return WDBG_TYPE_INFO_UNAVAILABLE; - - - out(L"%s = ", name); - LocalFree(name); - - int err; - __try - { - err = determine_symbol_address(idx, type_idx, &p); - if(err == 0) - err = dump_sym(type_idx, p, state); - } - __except(EXCEPTION_EXECUTE_HANDLER) - { - err = WDBG_INTERNAL_ERROR; - } - - if(err < 0) - switch(err) - { - case WDBG_UNRETRIEVABLE_STATIC: - out(L"(unavailable - located in another module)"); - break; - case WDBG_UNRETRIEVABLE_REG: - out(L"(unavailable - stored in register %s)", string_for_register((CV_HREG_e)(uintptr_t)p)); - break; - case WDBG_TYPE_INFO_UNAVAILABLE: - out(L"(unavailable - type info request failed; GLE=%d)", GetLastError()); - break; - case WDBG_INTERNAL_ERROR: - out(L"(internal error)\r\n"); - break; - // .. failed to produce any reasonable output for whatever reason. - default: - out(L"(?)"); - break; - } - - return 0; - // by aborting *for this symbol* and displaying value as "?", - // any errors are considered handled. we don't want one faulty - // member to prevent the entire remaining UDT from being displayed. - // anything really serious (unknown ATM) should be special-cased. -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_enum(DWORD idx, const u8* p, DumpState state) -{ - ULONG64 size_ = 0; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_LENGTH, &size_)) - return WDBG_TYPE_INFO_UNAVAILABLE; - const size_t size = (size_t)size_; - - const i64 current_value = movsx_64le(p, size); - - // get children - DWORD num_children; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_CHILDRENCOUNT, &num_children)) - return WDBG_TYPE_INFO_UNAVAILABLE; - TI_FINDCHILDREN_PARAMS2 fcp(num_children); - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_FINDCHILDREN, &fcp)) - return WDBG_TYPE_INFO_UNAVAILABLE; - - for(uint i = 0; i < fcp.p.Count; i++) - { - DWORD child_data_idx = fcp.p.ChildId[i]; - - // get enum value. don't make any assumptions about the - // variant's type (i.e. size) - no restriction is documented. - // also don't do this manually - it's tedious and we might not - // cover everything. the OLE DLL is already pulled in anyway. - VARIANT v; - if(!SymGetTypeInfo(hProcess, mod_base, child_data_idx, TI_GET_VALUE, &v)) - return WDBG_TYPE_INFO_UNAVAILABLE; - if(VariantChangeType(&v, &v, 0, VT_I8) != S_OK) - continue; - - if(current_value == v.llVal) - { - WCHAR* name; - if(!SymGetTypeInfo(hProcess, mod_base, child_data_idx, TI_GET_SYMNAME, &name)) - return WDBG_TYPE_INFO_UNAVAILABLE; - - out(L"%s", name); - LocalFree(name); - return 0; - } - } - - // we weren't able to retrieve a matching enum value, but can still - // produce reasonable output (the numeric value). - // note: could goto here after a SGTI fails, but we fail instead - // to make sure those errors are noticed. - out(L"%I64d", current_value); - return 1; -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_function(DWORD idx, const u8* p, DumpState state) -{ - return 0; -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_function_type(DWORD idx, const u8* p, DumpState state) -{ - // this symbol gives class parent, return type, and parameter count. - // unfortunately the one thing we care about, its name, - // isn't exposed via TI_GET_SYMNAME, so we resolve it ourselves. - - unlock(); // prevent recursive lock - - char name[DBG_SYMBOL_LEN]; - int err = debug_resolve_symbol((void*)p, name, 0, 0); - - lock(); - - out(L"0x%p", p); - if(err == 0) - out(L" (%hs)", name); - return 0; -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_pointer(DWORD idx, const u8* p, DumpState state) -{ - ULONG64 size_ = 0; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_LENGTH, &size_)) - return WDBG_TYPE_INFO_UNAVAILABLE; - const size_t size = (size_t)size_; - - // read+output pointer's value. - p = (const u8*)movzx_64le(p, size); - out(L"0x%p", p); - - // bail if it's obvious the pointer is bogus - // (=> can't display what it's pointing to) - if(is_bogus_pointer(p)) - return 0; - - // display what the pointer is pointing to. if the pointer is invalid - // (despite "bogus" check above), dump_sym recovers via SEH and - // returns -1; dump_sym_data will print "?" - out(L" -> "); // we out_erase this if it's a void* pointer - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_TYPEID, &idx)) - return WDBG_TYPE_INFO_UNAVAILABLE; - state.indirection++; - return dump_sym(idx, p, state); -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_typedef(DWORD idx, const u8* p, DumpState state) -{ - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_TYPEID, &idx)) - return WDBG_TYPE_INFO_UNAVAILABLE; - return dump_sym(idx, p, state); -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_udt(DWORD idx, const u8* p, DumpState state) -{ - ULONG64 size_ = 0; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_LENGTH, &size_)) - return WDBG_TYPE_INFO_UNAVAILABLE; - const size_t size = (size_t)size_; - - // handle special cases (e.g. HANDLE, std::string). - WCHAR* type_name; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_SYMNAME, &type_name)) - return WDBG_TYPE_INFO_UNAVAILABLE; - int ret = dump_special_udt(type_name, p, size, state); - LocalFree(type_name); - if(ret <= 0) // it "handled" this (with or without failure) - return ret; - - // get array of child symbols (members/functions/base classes). - DWORD num_children; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_CHILDRENCOUNT, &num_children)) - return WDBG_TYPE_INFO_UNAVAILABLE; - TI_FINDCHILDREN_PARAMS2 fcp(num_children); - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_FINDCHILDREN, &fcp)) - return WDBG_TYPE_INFO_UNAVAILABLE; - - const size_t avg_size = size / MAX(fcp.p.Count, 1); // prevent / 0 - // if num_children ends up large (e.g. due to member functions), - // avg_size is 0. fits_on_one_line will then be false anyway. - const bool fits_on_one_line = size <= sizeof(int) || // empty - (fcp.p.Count <= 2 && avg_size <= sizeof(int)); // few and small - - state.level++; - out(fits_on_one_line? L"{ " : L"\r\n"); - - int err = 0; - for(uint i = 0; i < fcp.p.Count; i++) - { - const DWORD child_idx = fcp.p.ChildId[i]; - - DWORD type_tag = 0; - // for reasons unknown this fails sometimes - if(!SymGetTypeInfo(hProcess, mod_base, child_idx, TI_GET_SYMTAG, &type_tag)) - continue; - if(type_tag != SymTagData) - continue; - - if(!fits_on_one_line) - INDENT; - - int ret = dump_sym(child_idx, p, state); - if(err == 0) - err = ret; - - out(fits_on_one_line? L", " : L"\r\n"); - } - - if(fits_on_one_line) - { - // note: can't avoid writing this by checking if i == fcp->Count-1: - // each child might be the last valid data member. - out_erase(2); // ", " - out(L" }"); - } - - return err; -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_vtable(DWORD idx, const u8* p, DumpState state) -{ - // unsupported (vtable internals are undocumented; too much work). - return 0; -} - - -////////////////////////////////////////////////////////////////////////////// - - -static int dump_sym_unknown(DWORD idx, const u8* p, DumpState state) -{ - // redundant (already done in dump_sym), but this is rare. - DWORD type_tag; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_SYMTAG, &type_tag)) - return WDBG_TYPE_INFO_UNAVAILABLE; - - debug_printf("Unknown tag: %d\n", type_tag); - out(L"(unknown symbol type)"); - return 0; -} - - -////////////////////////////////////////////////////////////////////////////// - - -// write name and value of the symbol to the output buffer. -// delegates to dump_sym_* depending on the symbol's tag. -static int dump_sym(DWORD idx, const u8* p, DumpState state) -{ - DWORD type_tag; - if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_SYMTAG, &type_tag)) - return WDBG_TYPE_INFO_UNAVAILABLE; - switch(type_tag) - { - case SymTagArrayType: - return dump_sym_array (idx, p, state); - case SymTagBaseType: - return dump_sym_base_type (idx, p, state); - case SymTagBaseClass: - return dump_sym_base_class (idx, p, state); - case SymTagData: - return dump_sym_data (idx, p, state); - case SymTagEnum: - return dump_sym_enum (idx, p, state); - case SymTagFunction: - return dump_sym_function (idx, p, state); - case SymTagFunctionType: - return dump_sym_function_type (idx, p, state); - case SymTagPointerType: - return dump_sym_pointer (idx, p, state); - case SymTagTypedef: - return dump_sym_typedef (idx, p, state); - case SymTagUDT: - return dump_sym_udt (idx, p, state); - case SymTagVTable: - return dump_sym_vtable (idx, p, state); - default: - return dump_sym_unknown (idx, p, state); - } -} - - -////////////////////////////////////////////////////////////////////////////// -// -// stack trace -// -////////////////////////////////////////////////////////////////////////////// - - -// xxx get actual address of what the symbol represents (may be relative -// to frame pointer); demarcate local/param sections; output name+value via -// dump_sym_data. -// -// called from dump_frame_cb for each local symbol; lock is held. -static BOOL CALLBACK dump_sym_cb(SYMBOL_INFO* sym, ULONG size, void* ctx) -{ - mod_base = sym->ModBase; - DumpState state; - - INDENT; - dump_sym(sym->Index, (const u8*)sym->Address, state); - out(L"\r\n"); - - return TRUE; // continue -} - - -////////////////////////////////////////////////////////////////////////////// - - -struct IMAGEHLP_STACK_FRAME2 : public IMAGEHLP_STACK_FRAME -{ - IMAGEHLP_STACK_FRAME2(const STACKFRAME64* sf) - { - // apparently only PC, FP and SP are necessary, but - // we go whole-hog to be safe. - memset(this, 0, sizeof(IMAGEHLP_STACK_FRAME2)); - InstructionOffset = sf->AddrPC.Offset; - ReturnOffset = sf->AddrReturn.Offset; - FrameOffset = sf->AddrFrame.Offset; - StackOffset = sf->AddrStack.Offset; - BackingStoreOffset = sf->AddrBStore.Offset; - FuncTableEntry = (ULONG64)sf->FuncTableEntry; - Virtual = sf->Virtual; - // (note: array of different types, can't copy directly) - for(int i = 0; i < 4; i++) - Params[i] = sf->Params[i]; - } -}; - -// called by walk_stack for each stack frame -static int dump_frame_cb(const STACKFRAME64* sf, void* user_arg) -{ - // note: keep frame formatting in sync with get_exception_locus. - - UNUSED(user_arg); - - current_stackframe64 = sf; - - void* func = (void*)sf->AddrPC.Offset; - // don't trace back into kernel32: we need a defined stop point, - // or walk_stack will end up returning -1; stopping here also - // reduces the risk of confusing the stack dump code below. - wchar_t module_path[MAX_PATH]; - wchar_t* module_filename = get_module_filename(func, module_path); - if(!wcscmp(module_filename, L"kernel32.dll")) - return 0; // done - - char func_name[DBG_SYMBOL_LEN]; char path[DBG_FILE_LEN]; int line; - if(debug_resolve_symbol(func, func_name, path, &line) == 0) - { - const char* slash = strrchr(path, DIR_SEP); - const char* file = slash? slash+1 : path; - out(L"%hs %hs (%lu)\r\n", func_name, file, line); - } - else - out(L"%p\r\n", func); - -//debug_printf("FRAME %s: stored regs: fp=0x%x sp=0x%x\n", func_name, sf->AddrFrame.Offset, sf->AddrFrame.Offset); - - // only enumerate symbols for this stack frame - // (i.e. its locals and parameters) - // problem: debug info is scope-aware, so we won't see any variables - // declared in sub-blocks. we'd have to pass an address in that block, - // which isn't worth the trouble. since - IMAGEHLP_STACK_FRAME2 imghlp_frame(sf); - SymSetContext(hProcess, &imghlp_frame, 0); // last param is ignored - - SymEnumSymbols(hProcess, 0, 0, dump_sym_cb, 0); - // 2nd and 3rd params indicate scope set by SymSetContext - // should be used. - - out(L"\r\n"); - return 1; // keep calling -} - - -// most recent stack frames will be skipped -// (we don't want to show e.g. GetThreadContext / this call) -static const wchar_t* dump_stack(uint skip, const CONTEXT* pcontext = 0) -{ - // need to skip one frame. if !pcontext, it's dump_stack; - // otherwise, RaiseException (must be skipped because dump_frame_cb - // will stop if it sees a kernel32 function). - skip++; - - int err = walk_stack(dump_frame_cb, 0, skip, pcontext); - if(err != 0) - out(L"(error while building stack trace: %d)", err); - - return dump_buf; -} - - -////////////////////////////////////////////////////////////////////////////// -// -// "program error" dialog (triggered by assert and exception) -// -////////////////////////////////////////////////////////////////////////////// - -// -// support for resizing the dialog / its controls -// (have to do this manually - grr) -// - -static POINTS dlg_client_origin; -static POINTS dlg_prev_client_size; - -const int ANCHOR_LEFT = 0x01; -const int ANCHOR_RIGHT = 0x02; -const int ANCHOR_TOP = 0x04; -const int ANCHOR_BOTTOM = 0x08; -const int ANCHOR_ALL = 0x0f; - -static void dlg_resize_control(HWND hDlg, int dlg_item, int dx,int dy, int anchors) -{ - HWND hControl = GetDlgItem(hDlg, dlg_item); - RECT r; - GetWindowRect(hControl, &r); - - int w = r.right - r.left, h = r.bottom - r.top; - int x = r.left - dlg_client_origin.x, y = r.top - dlg_client_origin.y; - - if(anchors & ANCHOR_RIGHT) - { - // right only - if(!(anchors & ANCHOR_LEFT)) - x += dx; - // horizontal (stretch width) - else - w += dx; - } - - if(anchors & ANCHOR_BOTTOM) - { - // bottom only - if(!(anchors & ANCHOR_TOP)) - y += dy; - // vertical (stretch height) - else - h += dy; - } - - SetWindowPos(hControl, 0, x,y, w,h, SWP_NOZORDER); -} - - -static void dlg_resize(HWND hDlg, WPARAM wParam, LPARAM lParam) -{ - // 'minimize' was clicked. we need to ignore this, otherwise - // dx/dy would reduce some control positions to less than 0. - // since Windows clips them, we wouldn't later be able to - // reconstruct the previous values when 'restoring'. - if(wParam == SIZE_MINIMIZED) - return; - - // first call for this dialog instance. WM_MOVE hasn't been sent yet, - // so dlg_client_origin are invalid => must not call resize_control(). - // we need to set dlg_prev_client_size for the next call before exiting. - bool first_call = (dlg_prev_client_size.y == 0); - - POINTS dlg_client_size = MAKEPOINTS(lParam); - int dx = dlg_client_size.x - dlg_prev_client_size.x; - int dy = dlg_client_size.y - dlg_prev_client_size.y; - dlg_prev_client_size = dlg_client_size; - - if(first_call) - return; - - dlg_resize_control(hDlg, IDC_CONTINUE, dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM); - dlg_resize_control(hDlg, IDC_SUPPRESS, dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM); - dlg_resize_control(hDlg, IDC_BREAK , dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM); - dlg_resize_control(hDlg, IDC_EXIT , dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM); - dlg_resize_control(hDlg, IDC_COPY , dx,dy, ANCHOR_RIGHT|ANCHOR_BOTTOM); - dlg_resize_control(hDlg, IDC_EDIT1 , dx,dy, ANCHOR_ALL); -} - - -enum DialogType -{ - ASSERT, - EXCEPTION -}; - -struct DialogParams -{ - DialogType type; - const wchar_t* body_text; -}; - - -static int CALLBACK error_dialog_proc(HWND hDlg, unsigned int msg, WPARAM wParam, LPARAM lParam) -{ - switch(msg) - { - case WM_INITDIALOG: - { - const DialogParams* params = (const DialogParams*)lParam; - - // need to reset for new instance of dialog - dlg_client_origin.x = dlg_client_origin.y = 0; - dlg_prev_client_size.x = dlg_prev_client_size.y = 0; - - if(params->type != ASSERT) - { - HWND h = GetDlgItem(hDlg, IDC_SUPPRESS); - EnableWindow(h, FALSE); - } - - SetDlgItemTextW(hDlg, IDC_EDIT1, params->body_text); - return TRUE; // set default keyboard focus - } - - case WM_SYSCOMMAND: - // close dialog if [X] is clicked (doesn't happen automatically) - // note: lower 4 bits are reserved - if((wParam & 0xFFF0) == SC_CLOSE) - { - EndDialog(hDlg, 0); - return 0; // processed - } - break; - - // return 0 if processed, otherwise break - case WM_COMMAND: - switch(wParam) - { - case IDC_COPY: - { - const size_t max_chars = 100000; - wchar_t* buf = (wchar_t*)malloc(max_chars*sizeof(wchar_t)); - if(buf) - { - GetDlgItemTextW(hDlg, IDC_EDIT1, buf, max_chars); - clipboard_set(buf); - } - return 0; - } - - case IDC_CONTINUE: - EndDialog(hDlg, ER_CONTINUE); - return 0; - case IDC_SUPPRESS: - EndDialog(hDlg, ER_SUPPRESS); - return 0; - case IDC_BREAK: - EndDialog(hDlg, ER_BREAK); - return 0; - case IDC_EXIT: - exit(0); - return 0; - - default: - break; - } - break; - - case WM_MOVE: - dlg_client_origin = MAKEPOINTS(lParam); - break; - - case WM_GETMINMAXINFO: - { - // we must make sure resize_control will never set negative coords - - // Windows would clip them, and its real position would be lost. - // restrict to a reasonable and good looking minimum size [pixels]. - MINMAXINFO* mmi = (MINMAXINFO*)lParam; - mmi->ptMinTrackSize.x = 407; - mmi->ptMinTrackSize.y = 159; // determined experimentally - return 0; - } - - case WM_SIZE: - dlg_resize(hDlg, wParam, lParam); - break; - - default: - break; - } - - // we didn't process the message; caller will perform default action. - return FALSE; -} - - -// show error dialog with stack trace (must be stored in dump_buf[]) -// exits directly if 'exit' is clicked. -static ErrorReaction error_dialog(DialogType type, const wchar_t* body_text) -{ - const DialogParams params = { type, body_text }; - - const HINSTANCE hInstance = GetModuleHandle(0); - LPCSTR lpTemplateName = MAKEINTRESOURCE(IDD_DIALOG1); - const HWND hWndParent = GetDesktopWindow(); - // we don't know if the enclosing app has a hwnd, so use the desktop. - - INT_PTR ret = DialogBoxParam(hInstance, lpTemplateName, hWndParent, error_dialog_proc, (LPARAM)¶ms); - // failed; warn user and make sure we return an ErrorReaction. - if(ret == 0 || ret == -1) - { - translate_and_display_msg(L"Error", L"Unable to display detailed error dialog."); - return ER_CONTINUE; - } - return (ErrorReaction)ret; -} - - -// notify the user that an assertion failed; displays a stack trace with -// local variables. -ErrorReaction debug_assert_failed(const char* file, int line, const char* expr) -{ - // display in output window; double-click will navigate to error location. - char* slash = strrchr(file, DIR_SEP); - const char* filename = slash? slash+1 : file; - debug_printf("%s(%d): assertion failed: \"%s\"\n", filename, line, expr); - - lock(); - - out_reset(); - out(L"Assertion failed in %hs, line %d: \"%hs\"\r\n", file, line, expr); - out(L"\r\nCall stack:\r\n\r\n"); - dump_stack(+1); // skip the current frame (debug_assert_failed) - wchar_t* dump_buf_copy = wcsdup(dump_buf); - - unlock(); - -#if defined(SCED) && !(defined(NDEBUG)||defined(TESTING)) - // ScEd keeps running while the dialog is showing, and tends to crash before - // there's a chance to read the assert message. So, just break immediately. - debug_break(); -#endif - - ErrorReaction er = error_dialog(ASSERT, dump_buf_copy); - free(dump_buf_copy); - return er; -} - - ////////////////////////////////////////////////////////////////////////////// // // exception handler // ////////////////////////////////////////////////////////////////////////////// -// write out a "minidump" containing register and stack state; this enables -// examining the crash in a debugger. called by unhandled_exception_filter. -// heavily modified from http://www.codeproject.com/debug/XCrashReportPt3.asp -// lock must be held. -static void write_minidump(EXCEPTION_POINTERS* exception_pointers) -{ - HANDLE hFile = CreateFile("crashlog.dmp", GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0); - if(hFile == INVALID_HANDLE_VALUE) - goto fail; - - MINIDUMP_EXCEPTION_INFORMATION mei; - mei.ThreadId = GetCurrentThreadId(); - mei.ExceptionPointers = exception_pointers; - mei.ClientPointers = FALSE; - // exception_pointers is not in our address space. - - // note: we don't store other crashlog info within the dump file - // (UserStreamParam), since we will need to generate a plain text file on - // non-Windows platforms. users will just have to send us both files. - - HANDLE hProcess = GetCurrentProcess(); DWORD pid = GetCurrentProcessId(); - if(!MiniDumpWriteDump(hProcess, pid, hFile, MiniDumpNormal, &mei, 0, 0)) - { -fail: - translate_and_display_msg(L"Error", L"Unable to generate minidump."); - } - - CloseHandle(hFile); -} - /* CSmartHandle hImpersonationToken = NULL; @@ -2627,15 +882,15 @@ static const wchar_t* get_exception_description(const EXCEPTION_POINTERS* ep) static const wchar_t* get_exception_locus(const EXCEPTION_POINTERS* ep) { // HACK: provides no useful information - ExceptionAddress always - // points to kernel32!RaiseException. we use dump_stack to determine the + // points to kernel32!RaiseException. we use debug_dump_stack to determine the // real location. - out_reset(); - const wchar_t* stack_trace = dump_stack(+0, ep->ContextRecord); + wchar_t buf[8000]; + const wchar_t* stack_trace = debug_dump_stack(buf, ARRAY_SIZE(buf), +0, ep->ContextRecord); const size_t max_chars = 256; static wchar_t locus[max_chars]; - wcsncpy_s(locus, max_chars, dump_buf, max_chars-1); + wcsncpy_s(locus, max_chars, stack_trace, max_chars-1); wchar_t* end = wcschr(locus, '\r'); if(end) *end = '\0'; @@ -2643,97 +898,40 @@ static const wchar_t* get_exception_locus(const EXCEPTION_POINTERS* ep) } -// called when an SEH exception was not caught by the app; +// called* when an SEH exception was not caught by the app; // provides detailed debugging information and exits. -// this overrides the normal OS "program error" dialog; see rationale below. -static LONG WINAPI unhandled_exception_filter(EXCEPTION_POINTERS* ep) -{ - // note: we risk infinite recursion if someone raises an SEH exception - // from within this function. therefore, abort immediately if we've - // already been called; the first error is the most important, anyway. - static uintptr_t already_crashed = 0; - if(!CAS(&already_crashed, 0, 1)) - return EXCEPTION_EXECUTE_HANDLER; - - lock(); - - // extract details from ExceptionRecord. - const wchar_t* description = get_exception_description(ep); - const wchar_t* locus = get_exception_locus (ep); - - // display in output window; double-click will navigate to error location. - { - wchar_t func_name[DBG_SYMBOL_LEN]; wchar_t file[DBG_FILE_LEN]; int line; wchar_t fmt[50]; - swprintf(fmt, ARRAY_SIZE(fmt), L"%%%ds %%%ds (%%d)", DBG_SYMBOL_LEN, DBG_FILE_LEN); - if(swscanf(locus, fmt, func_name, file, &line) == 3) - debug_wprintf(L"%s(%d): unhandled exception: \"%s\"\n", file, line, description); - } - - // get call stack. - out_reset(); - out(L"Unhandled Exception: %s at %s\r\n", description, locus); - out(L"\r\nCall stack:\r\n\r\n"); - const wchar_t* stack_trace = dump_stack(+0, ep->ContextRecord); - - // write out crash log and minidump. - write_minidump(ep); - debug_write_crashlog(description, locus, stack_trace); - -#ifdef NDEBUG - // dumbed-down end-user version: show message box. - // (call stack is stored in the crashlog) - static const wchar_t fmt[] = - L"Much to our regret we must report the program has encountered an error and cannot continue.\r\n" - L"\n" - L"Please let us know at http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n" - L"\n" - L"Details: %s at %s."; - wchar_t text[1000]; - swprintf(text, ARRAY_SIZE(text), translate(fmt), description, locus); - wdisplay_msg(translate(L"Problem"), text); -#else - // developer version: show stack trace immediately. - switch(error_dialog(EXCEPTION, dump_buf)) - { - case ER_EXIT: - ExitProcess(ep->ExceptionRecord->ExceptionCode); - case ER_BREAK: - debug_break(); - } -#endif - - unlock(); - - // disable memory-leak reporting to avoid a flood of warnings - // (lots of stuff will leak since we exit abnormally). -#ifdef HAVE_DEBUGALLOC - uint flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); - _CrtSetDbgFlag(flags & ~_CRTDBG_LEAK_CHECK_DF); -#endif - - // invoke the default exception handler - it calls ExitProcess for - // most exception types. - return EXCEPTION_EXECUTE_HANDLER; -} - - -// called from wdbg_init. +// (via win.cpp!entry's __except or as a vectored handler; see below) +// +// note: keep memory allocs and lock usage to an absolute minimum, because we may +// deadlock the process // // rationale: // we want to replace the OS "program error" dialog box because // it is not all too helpful in debugging. to that end, there are -// 4 ways to make sure unhandled SEH exceptions are caught: +// 5 ways to make sure unhandled SEH exceptions are caught: // - via WaitForDebugEvent; the app is run from a separate debugger process. // this complicates analysis, since the exception is in another // address space. also, we are basically implementing a full-featured // debugger - overkill. -// - wrapping all threads in __try (necessary since the handler chain -// is in TLS) is very difficult to guarantee; it would also pollute main(). -// - vectored exception handlers work across threads, but -// are only available on WinXP (unacceptable). -// - setting the per-process unhandled exception filter does the job, -// with the following caveat: it is never called when a debugger is active. -// workaround: call from a regular SEH __except, e.g. wrapped around main(). +// - by wrapping all threads in __try (necessary since the handler chain +// is in TLS). this is very difficult to guarantee. +// - with a vectored exception handler. this works across threads, but +// is never called when the process is being debugged +// (messing with the PEB flag doesn't help; root cause is the +// Win32 KiUserExceptionDispatcher implementation). +// worse, it's only available on WinXP (unacceptable). +// - by setting the per-process unhandled exception filter. as above, +// this works across threads and isn't called while a debugger is active; +// it is at least portable across Win32. unfortunately, some Win32 DLLs +// appear to register their own handlers, so this isn't reliable. +// - by hooking the exception dispatcher. this isn't future-proof. +// +// so, SNAFU. we compromise and register a regular __except filter at +// program entry point and add a vectored exception handler +// (if supported by the OS) to cover other threads. +// we steer clear of the process-wide unhandled exception filter, +// because it is not understood in what cases it can be overwritten; +// this precludes reliable use. // // since C++ exceptions are implemented via SEH, we can also catch those here; // it's nicer than a global try{} and avoids duplicating this code. @@ -2747,62 +945,72 @@ static LONG WINAPI unhandled_exception_filter(EXCEPTION_POINTERS* ep) // - a new fat exception class would have to be created to hold the // SEH exception information (e.g. CONTEXT for a stack trace), and // - this information would not be available for C++ exceptions. -static void set_exception_handler() + +extern void wdbg_write_minidump(EXCEPTION_POINTERS* ep); + +LONG WINAPI wdbg_exception_filter(EXCEPTION_POINTERS* ep) { - void* prev_filter = SetUnhandledExceptionFilter(unhandled_exception_filter); - if(prev_filter) - assert2("conflict with SetUnhandledExceptionFilter. must implement chaining to previous handler"); + // note: we risk infinite recursion if someone raises an SEH exception + // from within this function. therefore, abort immediately if we've + // already been called; the first error is the most important, anyway. + static uintptr_t already_crashed = 0; + if(!CAS(&already_crashed, 0, 1)) + return EXCEPTION_CONTINUE_SEARCH; -struct Small -{ - int i1; - int i2; -}; + // someone is already holding the dbghelp lock - this is bad. + // we'll report this problem first and then try to display the + // exception info regardless (maybe dbghelp won't blow up). + if(is_locked()) + DISPLAY_ERROR(L"Exception raised while critical section is held - may deadlock.."); -struct Large -{ - double d1; - double d2; - double d3; - double d4; -}; + // extract details from ExceptionRecord. + const wchar_t* description = get_exception_description(ep); + const wchar_t* locus = get_exception_locus (ep); -Large large_array_of_large_structs[8] = { { 0.0,0.0,0.0,0.0 } }; -Large small_array_of_large_structs[2] = { { 0.0,0.0,0.0,0.0 } }; -Small large_array_of_small_structs[8] = { { 1,2 } }; -Small small_array_of_small_structs[2] = { { 1,2 } }; + wchar_t func_name[DBG_SYMBOL_LEN]; char file[DBG_FILE_LEN] = {0}; int line = 0; wchar_t fmt[50]; + swprintf(fmt, ARRAY_SIZE(fmt), L"%%%ds %%%dhs (%%d)", DBG_SYMBOL_LEN, DBG_FILE_LEN); + if(swscanf(locus, fmt, func_name, file, &line) != 3) + debug_warn("error extracting file/line from exception locus"); - int ar1[] = { 1,2,3,4,5 }; - char ar2[] = { 't','e','s','t', 0 }; + wchar_t buf[500]; + const wchar_t* msg_fmt = + L"Much to our regret we must report the program has encountered an error and cannot continue.\r\n" + L"\r\n" + L"Please let us know at http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n" + L"\r\n" + L"Details: unhandled exception (%s at %s)\r\n"; + swprintf(buf, ARRAY_SIZE(buf), msg_fmt, description, locus); + int flags = 0; + if(ep->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE) + flags = DE_NO_CONTINUE; + ErrorReaction er = display_error(buf, flags, 0, ep->ContextRecord, file, line); + assert(er > 0); - // tests - //__try - { - //assert2(0 && "test assert2"); // not exception (works when run from debugger) - //__asm xor edx,edx __asm div edx // named SEH - //RaiseException(0x87654321, 0, 0, 0); // unknown SEH - //throw std::bad_exception("what() is ok"); // C++ - } - //__except(unhandled_exception_filter(GetExceptionInformation())) - { - } + wdbg_write_minidump(ep); + + // invoke the Win32 default handler - it calls ExitProcess for + // most exception types. + return EXCEPTION_CONTINUE_SEARCH; } - - -static int wdbg_init() +static int wdbg_init(void) { - RETURN_ERR(sym_init()); +assert2(0); + + // add vectored exception handler (if supported by the OS). + // see rationale above. +#if _WIN32_WINNT >= 0x0500 // this is how winbase.h tests for it + const HMODULE hKernel32Dll = LoadLibrary("kernel32.dll"); + PVOID (WINAPI *pAddVectoredExceptionHandler)(IN ULONG FirstHandler, IN PVECTORED_EXCEPTION_HANDLER VectoredHandler); + *(void**)&AddVectoredExceptionHandler = GetProcAddress(hKernel32Dll, "AddVectoredExceptionHandler"); + FreeLibrary(hKernel32Dll); + // make sure the reference is released so BoundsChecker + // doesn't complain. it won't actually be unloaded anyway - + // there is at least one other reference. + if(pAddVectoredExceptionHandler) + pAddVectoredExceptionHandler(TRUE, wdbg_exception_filter); +#endif - // rationale: see definition. note: unhandled_exception_filter uses the - // dbghelp symbol engine, so it must be initialized first. - set_exception_handler(); return 0; } - - -static int wdbg_shutdown(void) -{ - return sym_shutdown(); -} diff --git a/source/lib/sysdep/win/wdbg_sym.cpp b/source/lib/sysdep/win/wdbg_sym.cpp new file mode 100755 index 0000000000..7f184c4656 --- /dev/null +++ b/source/lib/sysdep/win/wdbg_sym.cpp @@ -0,0 +1,1668 @@ +// stack trace, improved assert and exception handler for Win32 +// Copyright (c) 2002-2005 Jan Wassenberg +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License as +// published by the Free Software Foundation; either version 2 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// Contact info: +// Jan.Wassenberg@stud.uni-karlsruhe.de +// http://www.stud.uni-karlsruhe.de/~urkt/ + +#include "precompiled.h" + +#include +#include + +#include "lib.h" + +#include "win_internal.h" +#define _NO_CVCONST_H // request SymTagEnum be defined +#include "dbghelp.h" +#include // VARIANT +#include "posix.h" +#include "sysdep/cpu.h" +#include "wdbg.h" + +// optional: enables translation of the "unhandled exception" dialog. +#ifdef I18N +#include "ps/i18n.h" +#endif + +#ifdef _MSC_VER +#pragma comment(lib, "dbghelp.lib") +#pragma comment(lib, "oleaut32.lib") // VariantChangeType +#endif + + +// automatic module shutdown (before process termination) +#pragma data_seg(WIN_CALLBACK_POST_ATEXIT(b)) +WIN_REGISTER_FUNC(sym_shutdown); +#pragma data_seg() + + +// debug_warn usually uses assert2, but we don't want to call that from +// inside an assert2 (from inside another assert2 (from inside another assert2 +// (... etc))), so just use the normal assert +#undef debug_warn +#define debug_warn(str) assert(0 && (str)) + + +// protects dbghelp (which isn't thread-safe) and +// parameter passing to the breakpoint helper thread. +static void lock() +{ + win_lock(WDBG_CS); +} + +static void unlock() +{ + win_unlock(WDBG_CS); +} + + + +enum WdbgError +{ + // the value is stored in an external module and therefore cannot be + // displayed. + WDBG_UNRETRIEVABLE_STATIC = -100000, + + // the value is stored in a register and therefore cannot be displayed + // (see CV_HREG_e). + WDBG_UNRETRIEVABLE_REG = -100001, + + // an essential call to SymGetTypeInfo or SymFromIndex failed. + WDBG_TYPE_INFO_UNAVAILABLE = -100002, + + // exception raised while processing the symbol. + WDBG_INTERNAL_ERROR = -100003, +}; + + +//---------------------------------------------------------------------------- +// dbghelp +//---------------------------------------------------------------------------- + +// passed to all dbghelp symbol query functions. we're not interested in +// resolving symbols in other processes; the purpose here is only to +// generate a stack trace. if that changes, we need to init a local copy +// of these in dump_sym_cb and pass them to all subsequent dump_*. +static HANDLE hProcess; +static ULONG64 mod_base; + +// for StackWalk64; taken from PE header by wdbg_init. +static WORD machine; + +static const STACKFRAME64* current_stackframe64; + +// call on-demand (allows handling exceptions raised before win.cpp +// init functions are called); no effect if already initialized. +static int sym_init() +{ + // bail if already initialized (there's nothing to do). + static uintptr_t already_initialized = 0; + if(!CAS(&already_initialized, 0, 1)) + return 0; + + hProcess = GetCurrentProcess(); + + SymSetOptions(SYMOPT_DEFERRED_LOADS/*/*|SYMOPT_DEBUG*/); + // loads symbols for all active modules. + BOOL ok = SymInitialize(hProcess, 0, TRUE); + assert2(ok); + + mod_base = SymGetModuleBase64(hProcess, (u64)&sym_init); + IMAGE_NT_HEADERS* header = ImageNtHeader((void*)mod_base); + machine = header->FileHeader.Machine; + + return 0; +} + + +// called from shutdown hook. +static int sym_shutdown() +{ + SymCleanup(hProcess); + return 0; +} + + +struct SYMBOL_INFO_PACKAGE2 : public SYMBOL_INFO_PACKAGE +{ + SYMBOL_INFO_PACKAGE2() + { + si.SizeOfStruct = sizeof(si); + si.MaxNameLen = MAX_SYM_NAME; + } +}; + +// ~500µs +int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int* line) +{ + sym_init(); + + const DWORD64 addr = (DWORD64)ptr_of_interest; + int successes = 0; + + lock(); + + // get symbol name + if(sym_name) + { + sym_name[0] = '\0'; + SYMBOL_INFO_PACKAGE2 sp; + SYMBOL_INFO* sym = &sp.si; + if(SymFromAddr(hProcess, addr, 0, sym)) + { + snprintf(sym_name, DBG_SYMBOL_LEN, "%s", sym->Name); + successes++; + } + } + + // get source file + line number + if(file || line) + { + IMAGEHLP_LINE64 line_info = { sizeof(IMAGEHLP_LINE64) }; + DWORD displacement; // unused but required by SymGetLineFromAddr64! + if(SymGetLineFromAddr64(hProcess, addr, &displacement, &line_info)) + successes++; + // note: were left zeroed if SymGetLineFromAddr64 failed + if(file) + snprintf(file, DBG_FILE_LEN, "%s", line_info.FileName); + if(line) + *line = line_info.LineNumber; + } + + unlock(); + return (successes == 0)? -1 : 0; +} + + +//---------------------------------------------------------------------------- +// stack walk via dbghelp +//---------------------------------------------------------------------------- + +// rationale: to function properly, StackWalk64 requires a CONTEXT on +// non-x86 systems (documented) or when in release mode (observed). +// exception handlers can call walk_stack with their context record; +// otherwise (e.g. dump_stack from assert2), we need to query it. +// there are 2 platform-independent ways to do so: +// - intentionally raise an SEH exception, then proceed as above; +// - GetThreadContext while suspended (*). +// the latter is more complicated and slower, so we go with the former +// despite it outputting "first chance exception" on each call. +// +// on IA-32, we use ia32_get_win_context instead of the above because +// it is 100% accurate (noticeable in StackWalk64 results) and simplest. +// +// * it used to be common practice not to query the current thread's context, +// but WinXP SP2 and above require it be suspended. + +// copy from CONTEXT to STACKFRAME64 +#if defined(_M_AMD64) +# define PC_ Rip +# define FP_ Rbp +# define SP_ Rsp +#elif defined(_M_IX86) +# define PC_ Eip +# define FP_ Ebp +# define SP_ Esp +#endif + +#ifdef _M_IX86 + +// optimized for size. +static __declspec(naked) void __cdecl get_current_context(void* pcontext) +{ +__asm +{ + pushad + pushfd + mov edi, [esp+4+32+4] ;// pcontext + + ;// ContextFlags + mov eax, 0x10007 ;// segs, int, control + stosd + + ;// DRx and FloatSave + ;// rationale: we can't access the debug registers from Ring3, and + ;// the FPU save area is irrelevant, so zero them. + xor eax, eax + push 6+8+20 + pop ecx +rep stosd + + ;// CONTEXT_SEGMENTS + mov ax, gs + stosd + mov ax, fs + stosd + mov ax, es + stosd + mov ax, ds + stosd + + ;// CONTEXT_INTEGER + mov eax, [esp+4+32-32] ;// edi + stosd + xchg eax, esi + stosd + xchg eax, ebx + stosd + xchg eax, edx + stosd + mov eax, [esp+4+32-8] ;// ecx + stosd + mov eax, [esp+4+32-4] ;// eax + stosd + + ;// CONTEXT_CONTROL + xchg eax, ebp + stosd + mov eax, [esp+4+32] ;// eip + sub eax, 5 ;// back up to call site from ret addr + stosd + xor eax, eax + mov ax, cs + stosd + pop eax ;// eflags + stosd + lea eax, [esp+32+4+4] ;// esp + stosd + xor eax, eax + mov ax, ss + stosd + + ;// ExtendedRegisters + push 512/4 + pop ecx +rep stosd + + popad + ret +} +} + +#else // #ifdef _M_IX86 + +static void get_current_context(CONTEXT* pcontext) +{ + __try + { + RaiseException(0xF001, 0, 0, 0); + } + __except(*pcontext = (GetExceptionInformation())->ContextRecord, EXCEPTION_CONTINUE_EXECUTION) + { + } +} + +#endif + + + + +// called for each stack frame found by walk_stack, passing information +// about the frame and . +// return <= 0 to stop immediately and have walk_stack return that; +// otherwise, > 0 to continue. +// +// rationale: we can't just pass function's address to the callback - +// dump_frame_cb needs the frame pointer for reg-relative variables. +typedef int (*StackFrameCallback)(const STACKFRAME64*, void*); + +// iterate over a call stack, calling back for each frame encountered. +// if != 0, we start there; otherwise, at the current context. +// return -1 if callback never succeeded (returned 0). lock must be held. +static int walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip = 0, const CONTEXT* pcontext = 0) +{ + sym_init(); + + const HANDLE hThread = GetCurrentThread(); + + // get CONTEXT (see above) + CONTEXT context; + // .. caller knows the context (most likely from an exception); + // since StackWalk64 may modify it, copy to a local variable. + if(pcontext) + context = *pcontext; + // .. need to determine context ourselves. + else + { + get_current_context(&context); + skip++; // skip walk_stack's frame + } + pcontext = &context; + + STACKFRAME64 sf; + memset(&sf, 0, sizeof(sf)); + sf.AddrPC.Offset = pcontext->PC_; + sf.AddrPC.Mode = AddrModeFlat; + sf.AddrFrame.Offset = pcontext->FP_; + sf.AddrFrame.Mode = AddrModeFlat; + sf.AddrStack.Offset = pcontext->SP_; + sf.AddrStack.Mode = AddrModeFlat; + + // for each stack frame found: + for(;;) + { + BOOL ok = StackWalk64(machine, hProcess, hThread, &sf, (void*)pcontext, + 0, SymFunctionTableAccess64, SymGetModuleBase64, 0); + + // callback never indicated success and no (more) frames found: abort. + // note: also test FP because StackWalk64 sometimes erroneously + // reports success. unfortunately it doesn't SetLastError either, + // so we can't indicate the cause of failure. *sigh* + if(!ok || !sf.AddrFrame.Offset) + return -911; // distinctive error value + + if(skip) + { + skip--; + continue; + } + + int ret = cb(&sf, user_arg); + // callback reports it's done; stop calling it and return that value. + // (can be 0 for success, or a negative error code) + if(ret <= 0) + return ret; + } +} + + +// +// get address of Nth function above us on the call stack (uses walk_stack) +// + +// called by walk_stack for each stack frame +static int nth_caller_cb(const STACKFRAME64* sf, void* user_arg) +{ + void** pfunc = (void**)user_arg; + + // return its address + *pfunc = (void*)sf->AddrPC.Offset; + return 0; +} + + +// n starts at 1 +void* debug_get_nth_caller(uint n) +{ + void* func; // set by callback + const uint skip = n-1 + 3; + // make 0-based; skip walk_stack, debug_get_nth_caller and its caller. + if(walk_stack(nth_caller_cb, &func, skip) == 0) + return func; + return 0; +} + + +////////////////////////////////////////////////////////////////////////////// +// +// helper routines for symbol value dump +// +////////////////////////////////////////////////////////////////////////////// + +// overflow is impossible in practice. keep in sync with DumpState. +static const uint MAX_INDIRECTION = 256; +static const uint MAX_LEVEL = 256; + +struct DumpState +{ + // keep in sync with MAX_* above + uint level : 8; + uint indirection : 8; + + DumpState() + { + level = 0; + indirection = 0; + } +}; + + +static ssize_t out_chars_left; +static wchar_t* out_pos; + +static void out_init(wchar_t* buf, size_t max_chars) +{ + out_pos = buf; + out_chars_left = (ssize_t)max_chars; +} + + +static void out(const wchar_t* fmt, ...) +{ + const size_t MAX_OUT_CHARS = 1000; + + // we assume each out() call will produce less than MAX_OUT_CHARS. + // if there's not that much left, overflow would be possible, so we bail. + // false alarms are possible but we never actually overflow. + if(out_chars_left < MAX_OUT_CHARS) + { + debug_warn("out: aborting (possibly not enough room in buffer)"); + return; + }; + + va_list args; + va_start(args, fmt); + int len = vswprintf(out_pos, MAX_OUT_CHARS, fmt, args); + va_end(args); + + // limit exceeded; shouldn't happen, but we're careful. + if(len < 0) + { + debug_warn("out: arbitrary limit of 1000 exceeded; why?"); + len = MAX_OUT_CHARS; + } + + out_pos += len; + out_chars_left -= len; +} + + +static void out_erase(size_t num_chars) +{ + out_chars_left += (ssize_t)num_chars; + out_pos -= num_chars; + *out_pos = '\0'; + // make sure it's 0-terminated in case there is no further output. +} + + +#define INDENT STMT(for(uint i = 0; i <= state.level+1; i++) out(L" ");) + + +// does it look like an ASCII string is located at ? +// set to 2 to search for WCS-2 strings (of western characters!). +// called by dump_sequence for its string special-case. +// +// algorithm: scan the "string" and count # text chars vs. garbage. +static bool is_string(const u8* p, size_t stride) +{ + // note: access violations are caught by dump_sym; output is "?". + int score = 0; + for(;;) + { + // current character is: + const int c = *p & 0xff; // prevent sign extension + p += stride; + // .. text + if(isalnum(c)) + score += 5; + // .. end of string + else if(!c) + break; + // .. garbage + else if(!isprint(c)) + score -= 4; + + // got enough information either way => done. + // (we don't want to unnecessarily scan huge binary arrays) + if(abs(score) >= 10) + break; + } + + return (score > 0); +} + + +static bool is_bogus_pointer(const void* p) +{ +#ifdef _M_IX86 + if(p < (void*)0x10000) + return true; + if(p >= (void*)(uintptr_t)0x80000000) + return true; +#endif + + return IsBadReadPtr(p, 1) != 0; +} + + +// provide c_str() access for any specialization of std::basic_string +// (since dump_string doesn't know type at compile-time). +// also performs a basic sanity check to see if the object is initialized. +struct AnyString : public std::string +{ + const void* safe_c_str(size_t el_size) const + { + // bogus + if(_Myres < _Mysize) + return 0; + return (_Myres < 16/el_size)? _Bx._Buf : _Bx._Ptr; + } +}; + +static int dump_string(WCHAR* type_name, const u8* p, size_t size, DumpState state) +{ + size_t el_size; + const WCHAR* pretty_name = type_name; + const void* string_data = 0; + + // Pyrogenesis CStr + if(!wcsncmp(type_name, L"CStr", 4)) + { + assert(size == 32/*sizeof(CStr)*/); + + // determine type + if(type_name[4] == '8') + el_size = sizeof(char); + else if(type_name[4] == 'W') + el_size = sizeof(wchar_t); + // .. unknown, shouldn't handle it + else + return 1; + + p += 4; // skip vptr (mixed in by ISerializable) + string_data = ((AnyString*)p)->safe_c_str(el_size); + } + // std::basic_string and its specializations + else if(!wcsncmp(type_name, L"std::basic_string", 17)) + { + assert(size == sizeof(std::string) || size == 16); + // dbghelp bug: std::wstring size is given as 16 + + // determine type + if(!wcsncmp(type_name+18, L"char", 4)) + { + el_size = sizeof(char); + pretty_name = L"std::string"; + } + else if(!wcsncmp(type_name+18, L"unsigned short", 14)) + { + el_size = sizeof(wchar_t); + pretty_name = L"std::wstring"; + } + // .. unknown, shouldn't handle it + else + return 1; + + string_data = ((AnyString*)p)->safe_c_str(el_size); + } + // type_name isn't a known string object; we can't handle it. + else + return 1; + + // type_name is known but its contents are bogus; so indicate. + if(is_bogus_pointer(string_data) || !is_string((const u8*)string_data, el_size)) + out(L"(uninitialized/invalid %s)", pretty_name); + // valid; display it. + else + { + const wchar_t* fmt = (el_size == sizeof(wchar_t))? L"\"%s\"" : L"\"%hs\""; + out(fmt, string_data); + } + + // it was a string object (valid or not) -> we handled it. + return 0; +} + + +static bool should_suppress_udt(WCHAR* type_name) +{ + // STL +#define SUPPRESS_STL(name) if(!wcscmp(type_name, L"std::" L#name)) return true; + SUPPRESS_STL(allocator); + SUPPRESS_STL(char_traits); +#undef SUPPRESS_STL + + // specialized HANDLEs are defined as pointers to structs by + // DECLARE_HANDLE. we only want the numerical value (pointer address), + // so prevent these structs from being displayed. + // note: no need to check for indirection; these are only found in + // HANDLEs (which are pointers). + // removed obsolete defs: HEVENT, HFILE, HUMPD + if(type_name[0] != 'H') + goto not_handle; +#define SUPPRESS_HANDLE(name) if(!wcscmp(type_name, L#name L"__")) return true; + SUPPRESS_HANDLE(HACCEL); + SUPPRESS_HANDLE(HBITMAP); + SUPPRESS_HANDLE(HBRUSH); + SUPPRESS_HANDLE(HCOLORSPACE); + SUPPRESS_HANDLE(HCURSOR); + SUPPRESS_HANDLE(HDC); + SUPPRESS_HANDLE(HENHMETAFILE); + SUPPRESS_HANDLE(HFONT); + SUPPRESS_HANDLE(HGDIOBJ); + SUPPRESS_HANDLE(HGLOBAL); + SUPPRESS_HANDLE(HGLRC); + SUPPRESS_HANDLE(HHOOK); + SUPPRESS_HANDLE(HICON); + SUPPRESS_HANDLE(HIMAGELIST); + SUPPRESS_HANDLE(HIMC); + SUPPRESS_HANDLE(HINSTANCE); + SUPPRESS_HANDLE(HKEY); + SUPPRESS_HANDLE(HKL); + SUPPRESS_HANDLE(HKLOCAL); + SUPPRESS_HANDLE(HMENU); + SUPPRESS_HANDLE(HMETAFILE); + SUPPRESS_HANDLE(HMODULE); + SUPPRESS_HANDLE(HMONITOR); + SUPPRESS_HANDLE(HPALETTE); + SUPPRESS_HANDLE(HPEN); + SUPPRESS_HANDLE(HRGN); + SUPPRESS_HANDLE(HRSRC); + SUPPRESS_HANDLE(HSTR); + SUPPRESS_HANDLE(HTASK); + SUPPRESS_HANDLE(HWINEVENTHOOK); + SUPPRESS_HANDLE(HWINSTA); + SUPPRESS_HANDLE(HWND); +not_handle: + + return false; +} + + +static int dump_special_udt(WCHAR* type_name, const u8* p, size_t size, DumpState state) +{ + int ret; + + ret = dump_string(type_name, p, size, state); + if(ret <= 0) + return ret; + + if(should_suppress_udt(type_name)) + { + // the data symbol is pointer-to-UDT. since we won't display its + // contents, leave only the pointer's value. + if(state.indirection) + out_erase(4); // " -> " + + // indicate something was deliberately left out + // (otherwise, lack of output may be taken for an error) + out(L" (..)"); + + return 0; + } + + return 1; // not handled +} + + + +// forward decl; called by dump_sequence and some of dump_sym_*. +static int dump_sym(DWORD idx, const u8* p, DumpState state); + + +static int dump_sequence(const u8* p, uint num_elements, DWORD el_idx, size_t el_size, DumpState state) +{ + // special case for character arrays: display as string + if(el_size == sizeof(char) || el_size == sizeof(wchar_t)) + if(is_string(p, el_size)) + { + // make sure it's 0-terminated + wchar_t buf[512]; + if(el_size == sizeof(wchar_t)) + wcscpy_s(buf, ARRAY_SIZE(buf), (const wchar_t*)p); + else + { + size_t i; + for(i = 0; i < ARRAY_SIZE(buf)-1; i++) + { + buf[i] = (wchar_t)p[i]; + if(buf[i] == '\0') + break; + } + buf[i] = '\0'; + } + + out(L"\"%s\"", buf); + return 0; + } + + // regular array: + const uint num_elements_to_show = MIN(20, num_elements); + const bool fits_on_one_line = + (el_size == sizeof(char) && num_elements <= 16) || + (el_size <= sizeof(int ) && num_elements <= 8); + + state.level++; + out(fits_on_one_line? L"{ " : L"\r\n"); + + int err = 0; + for(uint i = 0; i < num_elements_to_show; i++) + { + if(!fits_on_one_line) + INDENT; + + int ret = dump_sym(el_idx, p + i*el_size, state); + if(err == 0) // remember first error + err = ret; + + // add separator unless this is the last element + // (can't just erase below due to additional "...") + if(i != num_elements_to_show-1) + out(fits_on_one_line? L", " : L"\r\n"); + } + // we truncated some + if(num_elements != num_elements_to_show) + out(L" ..."); + + if(fits_on_one_line) + out(L" }"); + return err; +} + +// from cvconst.h +// +// rationale: we don't provide a get_register routine, since only the +// value of FP is known to dump_frame_cb (via STACKFRAME64). +// displaying variables stored in registers is out of the question; +// all we can do is display FP-relative variables. +enum CV_HREG_e +{ + CV_REG_EAX = 17, + CV_REG_ECX = 18, + CV_REG_EDX = 19, + CV_REG_EBX = 20, + CV_REG_ESP = 21, + CV_REG_EBP = 22, + CV_REG_ESI = 23, + CV_REG_EDI = 24 +}; + + +static const wchar_t* string_for_register(CV_HREG_e reg) +{ + switch(reg) + { + case CV_REG_EAX: + return L"eax"; + case CV_REG_ECX: + return L"ecx"; + case CV_REG_EDX: + return L"edx"; + case CV_REG_EBX: + return L"ebx"; + case CV_REG_ESP: + return L"esp"; + case CV_REG_EBP: + return L"ebp"; + case CV_REG_ESI: + return L"esi"; + case CV_REG_EDI: + return L"edi"; + default: + { + static wchar_t buf[19]; + swprintf(buf, ARRAY_SIZE(buf), L"0x%x", reg); + return buf; + } + } +} + + +static int determine_symbol_address(DWORD idx, DWORD type_idx, const u8** pp) +{ + const STACKFRAME64* sf = current_stackframe64; + + ULONG64 size_ = 0; + if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_LENGTH, &size_)) + return WDBG_TYPE_INFO_UNAVAILABLE; + const size_t size = (size_t)size_; + + DWORD data_kind; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_DATAKIND, &data_kind)) + return WDBG_TYPE_INFO_UNAVAILABLE; + switch(data_kind) + { + // SymFromIndex will fail + case DataIsMember: + { + DWORD ofs = 0; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_OFFSET, &ofs)) + return WDBG_TYPE_INFO_UNAVAILABLE; + //assert(ofs < size); + *pp += ofs; + return 0; + } + + // note: sometimes erroneously reported, but there's nothing we can do + // because TI_GET_ADDRESS returns mod_base, TI_GET_ADDRESSOFFSET 0, + // and TI_GET_OFFSET fails (it's only for members). + case DataIsStaticMember: + return WDBG_UNRETRIEVABLE_STATIC; + + } + + SYMBOL_INFO_PACKAGE2 sp; + SYMBOL_INFO* sym = &sp.si; + if(!SymFromIndex(hProcess, mod_base, idx, sym)) + return WDBG_TYPE_INFO_UNAVAILABLE; + +DWORD addrofs = 0; +ULONG64 addr2 = 0; +DWORD ofs2 = 0; +SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_ADDRESSOFFSET, &addrofs); +SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_ADDRESS, &addr2); +SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_OFFSET, &ofs2); + + + + // get address + ULONG64 addr = sym->Address; + // .. relative to a register + if(sym->Flags & SYMFLAG_REGREL) + { + if(sym->Register == CV_REG_EBP) + addr += sf->AddrFrame.Offset; + else + goto in_register; + } + // .. relative to FP (appears to be obsolete) + else if(sym->Flags & SYMFLAG_FRAMEREL) + addr += sf->AddrFrame.Offset; + // .. in register (this happens when optimization is enabled, + // but we can't do anything; see SymbolInfoRegister) + else if(sym->Flags & SYMFLAG_REGISTER) + goto in_register; + + *pp = (const u8*)addr; + +#ifdef NDEBUG + if(!(sym->Flags & SYMFLAG_PARAMETER)) + *pp += 4; +#endif + +debug_printf("DET_SYM_ADDR %s at %p flags=%X dk=%d sym->addr=%I64X addrofs=%X addr2=%I64X ofs2=%X\n", sym->Name, *pp, sym->Flags, data_kind, sym->Address, addrofs, addr2, ofs2); + + return 0; + +in_register: + *pp = (const u8*)(uintptr_t)sym->Register; + return WDBG_UNRETRIEVABLE_REG; + + + /* + + switch(data_kind) + { + // plain variables: p is already correct + case DataIsLocal: + case DataIsParam: + + case DataIsGlobal: + case DataIsStaticLocal: + case DataIsFileStatic: + + case DataIsObjectPtr: + break; + + // UDT member: get offset + case DataIsMember: + *pp += ofs; + break; + + + default: + debug_warn("dump_sym_data: invalid data kind"); + return -1; + } + + // success + return 0; + +*/ +} + + +// note: we can't derive from TI_FINDCHILDREN_PARAMS because its members +// aren't guaranteed to precede ours (although they do in practice). +struct TI_FINDCHILDREN_PARAMS2 +{ + TI_FINDCHILDREN_PARAMS2(DWORD num_children) + { + p.Start = 0; + p.Count = MIN(num_children, MAX_CHILDREN); + } + + static const size_t MAX_CHILDREN = 400; + TI_FINDCHILDREN_PARAMS p; + DWORD additional_children[MAX_CHILDREN-1]; +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// dump routines for each dbghelp symbol type +// +////////////////////////////////////////////////////////////////////////////// + +// these functions return -1 if they're not able to produce any reasonable +// output; dump_data_sym will display value as "?" +// called by dump_sym; lock is held. + + +static int dump_sym_array(DWORD idx, const u8* p, DumpState state) +{ + ULONG64 size_ = 0; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_LENGTH, &size_)) + return WDBG_TYPE_INFO_UNAVAILABLE; + const size_t size = (size_t)size_; + + // get element count and size + DWORD el_idx = 0; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_TYPEID, &el_idx)) + return WDBG_TYPE_INFO_UNAVAILABLE; + // .. workaround: TI_GET_COUNT returns total struct size for + // arrays-of-struct. therefore, calculate as size / el_size. + ULONG64 el_size_; + if(!SymGetTypeInfo(hProcess, mod_base, el_idx, TI_GET_LENGTH, &el_size_)) + return WDBG_TYPE_INFO_UNAVAILABLE; + const size_t el_size = (size_t)el_size_; + assert(el_size != 0); + const uint num_elements = (uint)(size / el_size); + assert2(num_elements != 0); + + // display element count + out_erase(3); // " = " + out(L"[%d] = ", num_elements); + + return dump_sequence(p, num_elements, el_idx, el_size, state); +} + + +////////////////////////////////////////////////////////////////////////////// + + +static int dump_sym_base_type(DWORD idx, const u8* p, DumpState state) +{ + DWORD base_type; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_BASETYPE, &base_type)) + return WDBG_TYPE_INFO_UNAVAILABLE; + ULONG64 size_ = 0; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_LENGTH, &size_)) + return WDBG_TYPE_INFO_UNAVAILABLE; + const size_t size = (size_t)size_; + + u64 data = movzx_64le(p, size); + // if value is 0xCC..CC (uninitialized mem), we display as hex. + // the output would otherwise be garbage; this makes it obvious. + // note: be very careful to correctly handle size=0 (e.g. void*). + for(size_t i = 0; i < size; i++) + { + if(p[i] != 0xCC) + break; + if(i == size-1) + goto uninitialized; + } + + // single out() call. note: we pass a single u64 for all sizes, + // which will only work on little-endian systems. + const wchar_t* fmt; + + switch(base_type) + { + // boolean + case btBool: + assert(size == sizeof(bool)); + fmt = L"%hs"; + data = (u64)(data? "true " : "false"); + break; + + // floating-point + case btFloat: + if(size == sizeof(float)) + fmt = L"%g"; + else if(size == sizeof(double)) + fmt = L"%lg"; + else + debug_warn("dump_sym_base_type: invalid float size"); + break; + + // signed integers (displayed as decimal) + case btInt: + case btLong: + // need to re-load and sign-extend, because we output 64 bits. + data = movsx_64le(p, size); + if(size == 1 || size == 2 || size == 4 || size == 8) + fmt = L"%I64d"; + else + debug_warn("dump_sym_base_type: invalid int size"); + break; + + // unsigned integers (displayed as hex) + // note: 0x00000000 can get annoying (0 would be nicer), + // but it indicates the variable size and makes for consistently + // formatted structs/arrays. (0x1234 0 0x5678 is ugly) + case btUInt: + case btULong: +uninitialized: + if(size == 1) + { + // _TUCHAR + if(state.indirection) + { + state.indirection = 0; + return dump_sequence(p, 8, idx, size, state); + } + fmt = L"0x%02X"; + } + else if(size == 2) + fmt = L"0x%04X"; + else if(size == 4) + fmt = L"0x%08X"; + else if(size == 8) + fmt = L"0x%016I64X"; + else + debug_warn("dump_sym_base_type: invalid uint size"); + break; + + // character + case btChar: + case btWChar: + assert(size == sizeof(char) || size == sizeof(wchar_t)); + // char*, wchar_t* + if(state.indirection) + { + state.indirection = 0; + return dump_sequence(p, 8, idx, size, state); + } + // either integer or character; + // if printable, the character will be appended below. + fmt = L"%d"; + break; + + // note: void* is sometimes indicated as (pointer, btNoType). + case btVoid: + case btNoType: + // void* - cannot display what it's pointing to (type unknown). + if(state.indirection) + { + out_erase(4); // " -> " + fmt = L""; + } + else + debug_warn("dump_sym_base_type: non-pointer btVoid or btNoType"); + break; + + default: + debug_warn("dump_sym_base_type: unknown type"); + //-fallthrough + + // unsupported complex types + case btBCD: + case btCurrency: + case btDate: + case btVariant: + case btComplex: + case btBit: + case btBSTR: + case btHresult: + return -1; + } + + out(fmt, data); + + // if the current value is a printable character, display in that form. + // this isn't only done in btChar because sometimes ints store characters. + if(data < 0x100) + { + int c = (int)data; + if(isprint(c)) + out(L" ('%hc')", c); + } + + return 0; +} + + +////////////////////////////////////////////////////////////////////////////// + + +static int dump_sym_base_class(DWORD idx, const u8* p, DumpState state) +{ + // unsupported: virtual base classes would require reading the VTbl, + // which is difficult given lack of documentation and not worth it. + return 0; +} + + +////////////////////////////////////////////////////////////////////////////// + + +static int dump_sym_data(DWORD idx, const u8* p, DumpState state) +{ + // SymFromIndex will fail if dataKind happens to be DataIsMember, so + // we use SymGetTypeInfo (slower and less convenient, but no choice). + DWORD type_idx; + WCHAR* name; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_TYPEID, &type_idx)) + return WDBG_TYPE_INFO_UNAVAILABLE; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_SYMNAME, &name)) + return WDBG_TYPE_INFO_UNAVAILABLE; + + + out(L"%s = ", name); + LocalFree(name); + + int err; + __try + { + err = determine_symbol_address(idx, type_idx, &p); + if(err == 0) + err = dump_sym(type_idx, p, state); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + err = WDBG_INTERNAL_ERROR; + } + + if(err < 0) + switch(err) + { + case WDBG_UNRETRIEVABLE_STATIC: + out(L"(unavailable - located in another module)"); + break; + case WDBG_UNRETRIEVABLE_REG: + out(L"(unavailable - stored in register %s)", string_for_register((CV_HREG_e)(uintptr_t)p)); + break; + case WDBG_TYPE_INFO_UNAVAILABLE: + out(L"(unavailable - type info request failed; GLE=%d)", GetLastError()); + break; + case WDBG_INTERNAL_ERROR: + out(L"(internal error)\r\n"); + break; + // .. failed to produce any reasonable output for whatever reason. + default: + out(L"(?)"); + break; + } + + return 0; + // by aborting *for this symbol* and displaying value as "?", + // any errors are considered handled. we don't want one faulty + // member to prevent the entire remaining UDT from being displayed. + // anything really serious (unknown ATM) should be special-cased. +} + + +////////////////////////////////////////////////////////////////////////////// + + +static int dump_sym_enum(DWORD idx, const u8* p, DumpState state) +{ + ULONG64 size_ = 0; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_LENGTH, &size_)) + return WDBG_TYPE_INFO_UNAVAILABLE; + const size_t size = (size_t)size_; + + const i64 current_value = movsx_64le(p, size); + + // get children + DWORD num_children; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_CHILDRENCOUNT, &num_children)) + return WDBG_TYPE_INFO_UNAVAILABLE; + TI_FINDCHILDREN_PARAMS2 fcp(num_children); + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_FINDCHILDREN, &fcp)) + return WDBG_TYPE_INFO_UNAVAILABLE; + + for(uint i = 0; i < fcp.p.Count; i++) + { + DWORD child_data_idx = fcp.p.ChildId[i]; + + // get enum value. don't make any assumptions about the + // variant's type (i.e. size) - no restriction is documented. + // also don't do this manually - it's tedious and we might not + // cover everything. the OLE DLL is already pulled in anyway. + VARIANT v; + if(!SymGetTypeInfo(hProcess, mod_base, child_data_idx, TI_GET_VALUE, &v)) + return WDBG_TYPE_INFO_UNAVAILABLE; + if(VariantChangeType(&v, &v, 0, VT_I8) != S_OK) + continue; + + if(current_value == v.llVal) + { + WCHAR* name; + if(!SymGetTypeInfo(hProcess, mod_base, child_data_idx, TI_GET_SYMNAME, &name)) + return WDBG_TYPE_INFO_UNAVAILABLE; + + out(L"%s", name); + LocalFree(name); + return 0; + } + } + + // we weren't able to retrieve a matching enum value, but can still + // produce reasonable output (the numeric value). + // note: could goto here after a SGTI fails, but we fail instead + // to make sure those errors are noticed. + out(L"%I64d", current_value); + return 1; +} + + +////////////////////////////////////////////////////////////////////////////// + + +static int dump_sym_function(DWORD idx, const u8* p, DumpState state) +{ + return 0; +} + + +////////////////////////////////////////////////////////////////////////////// + + +static int dump_sym_function_type(DWORD idx, const u8* p, DumpState state) +{ + // this symbol gives class parent, return type, and parameter count. + // unfortunately the one thing we care about, its name, + // isn't exposed via TI_GET_SYMNAME, so we resolve it ourselves. + + unlock(); // prevent recursive lock + + char name[DBG_SYMBOL_LEN]; + int err = debug_resolve_symbol((void*)p, name, 0, 0); + + lock(); + + out(L"0x%p", p); + if(err == 0) + out(L" (%hs)", name); + return 0; +} + + +////////////////////////////////////////////////////////////////////////////// + + +static int dump_sym_pointer(DWORD idx, const u8* p, DumpState state) +{ + ULONG64 size_ = 0; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_LENGTH, &size_)) + return WDBG_TYPE_INFO_UNAVAILABLE; + const size_t size = (size_t)size_; + + // read+output pointer's value. + p = (const u8*)movzx_64le(p, size); + out(L"0x%p", p); + + // bail if it's obvious the pointer is bogus + // (=> can't display what it's pointing to) + if(is_bogus_pointer(p)) + return 0; + + // display what the pointer is pointing to. if the pointer is invalid + // (despite "bogus" check above), dump_sym recovers via SEH and + // returns -1; dump_sym_data will print "?" + out(L" -> "); // we out_erase this if it's a void* pointer + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_TYPEID, &idx)) + return WDBG_TYPE_INFO_UNAVAILABLE; + state.indirection++; + return dump_sym(idx, p, state); +} + + +////////////////////////////////////////////////////////////////////////////// + + +static int dump_sym_typedef(DWORD idx, const u8* p, DumpState state) +{ + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_TYPEID, &idx)) + return WDBG_TYPE_INFO_UNAVAILABLE; + return dump_sym(idx, p, state); +} + + +////////////////////////////////////////////////////////////////////////////// + + +// (by now) non-trivial heuristic to determine if a UDT should be +// displayed on one line or several. split out of dump_sym_udt. +static bool udt_fits_on_one_line(uint child_count, size_t total_size) +{ + // prevent division by 0. + if(child_count == 0) + child_count = 1; + + // even if avg_size ends up small, we don't want to return true for + // UDTs with lots of static symbols. + if(child_count > 6) + return false; + + // small UDT with a few (small) members: fits on one line. + // note: don't worry about avg_size == 0 - if we have lots of members, + // that's weeded out above. + const size_t avg_size = total_size / child_count; + if(child_count <= 2 && avg_size <= sizeof(int)) + return true; + + return false; +} + + +static int dump_sym_udt(DWORD idx, const u8* p, DumpState state) +{ + ULONG64 size_ = 0; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_LENGTH, &size_)) + return WDBG_TYPE_INFO_UNAVAILABLE; + const size_t size = (size_t)size_; + + // handle special cases (e.g. HANDLE, std::string). + WCHAR* type_name; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_SYMNAME, &type_name)) + return WDBG_TYPE_INFO_UNAVAILABLE; + int ret = dump_special_udt(type_name, p, size, state); + LocalFree(type_name); + if(ret <= 0) // it "handled" this (with or without failure) + return ret; + + // get array of child symbols (members/functions/base classes). + DWORD num_children; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_CHILDRENCOUNT, &num_children)) + return WDBG_TYPE_INFO_UNAVAILABLE; + TI_FINDCHILDREN_PARAMS2 fcp(num_children); + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_FINDCHILDREN, &fcp)) + return WDBG_TYPE_INFO_UNAVAILABLE; + + const bool fits_on_one_line = udt_fits_on_one_line(fcp.p.Count, size); + + state.level++; + out(fits_on_one_line? L"{ " : L"\r\n"); + + bool had_data_member = false; + int err = 0; + for(uint i = 0; i < fcp.p.Count; i++) + { + const DWORD child_idx = fcp.p.ChildId[i]; + + // make sure this is a data symbol + // (avoids outputting newline for invisible base class symbols) + DWORD type_tag = 0; + // for reasons unknown this fails sometimes + if(!SymGetTypeInfo(hProcess, mod_base, child_idx, TI_GET_SYMTAG, &type_tag)) + continue; + if(type_tag != SymTagData) + continue; + had_data_member = true; + + if(!fits_on_one_line) + INDENT; + + int ret = dump_sym(child_idx, p, state); + if(err == 0) + err = ret; + + out(fits_on_one_line? L", " : L"\r\n"); + } + + state.level--; + if(!had_data_member) + { + INDENT; + out(L"(no data members)"); + } + + if(fits_on_one_line) + { + // note: can't avoid writing this by checking if i == fcp->Count-1: + // each child might be the last valid data member. + out_erase(2); // ", " + out(L" }"); + } + + return err; +} + + +////////////////////////////////////////////////////////////////////////////// + + +static int dump_sym_vtable(DWORD idx, const u8* p, DumpState state) +{ + // unsupported (vtable internals are undocumented; too much work). + return 0; +} + + +////////////////////////////////////////////////////////////////////////////// + + +static int dump_sym_unknown(DWORD idx, const u8* p, DumpState state) +{ + // redundant (already done in dump_sym), but this is rare. + DWORD type_tag; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_SYMTAG, &type_tag)) + return WDBG_TYPE_INFO_UNAVAILABLE; + + debug_printf("Unknown tag: %d\n", type_tag); + out(L"(unknown symbol type)"); + return 0; +} + + +////////////////////////////////////////////////////////////////////////////// + + +// write name and value of the symbol to the output buffer. +// delegates to dump_sym_* depending on the symbol's tag. +static int dump_sym(DWORD idx, const u8* p, DumpState state) +{ + DWORD type_tag; + if(!SymGetTypeInfo(hProcess, mod_base, idx, TI_GET_SYMTAG, &type_tag)) + return WDBG_TYPE_INFO_UNAVAILABLE; + switch(type_tag) + { + case SymTagArrayType: + return dump_sym_array (idx, p, state); + case SymTagBaseType: + return dump_sym_base_type (idx, p, state); + case SymTagBaseClass: + return dump_sym_base_class (idx, p, state); + case SymTagData: + return dump_sym_data (idx, p, state); + case SymTagEnum: + return dump_sym_enum (idx, p, state); + case SymTagFunction: + return dump_sym_function (idx, p, state); + case SymTagFunctionType: + return dump_sym_function_type (idx, p, state); + case SymTagPointerType: + return dump_sym_pointer (idx, p, state); + case SymTagTypedef: + return dump_sym_typedef (idx, p, state); + case SymTagUDT: + return dump_sym_udt (idx, p, state); + case SymTagVTable: + return dump_sym_vtable (idx, p, state); + default: + return dump_sym_unknown (idx, p, state); + } +} + + +////////////////////////////////////////////////////////////////////////////// +// +// stack trace +// +////////////////////////////////////////////////////////////////////////////// + + +// xxx get actual address of what the symbol represents (may be relative +// to frame pointer); demarcate local/param sections; output name+value via +// dump_sym_data. +// +// called from dump_frame_cb for each local symbol; lock is held. +static BOOL CALLBACK dump_sym_cb(SYMBOL_INFO* sym, ULONG size, void* ctx) +{ + mod_base = sym->ModBase; + DumpState state; + + INDENT; + dump_sym(sym->Index, (const u8*)sym->Address, state); + out(L"\r\n"); + + return TRUE; // continue +} + + +////////////////////////////////////////////////////////////////////////////// + + +struct IMAGEHLP_STACK_FRAME2 : public IMAGEHLP_STACK_FRAME +{ + IMAGEHLP_STACK_FRAME2(const STACKFRAME64* sf) + { + // apparently only PC, FP and SP are necessary, but + // we go whole-hog to be safe. + memset(this, 0, sizeof(IMAGEHLP_STACK_FRAME2)); + InstructionOffset = sf->AddrPC.Offset; + ReturnOffset = sf->AddrReturn.Offset; + FrameOffset = sf->AddrFrame.Offset; + StackOffset = sf->AddrStack.Offset; + BackingStoreOffset = sf->AddrBStore.Offset; + FuncTableEntry = (ULONG64)sf->FuncTableEntry; + Virtual = sf->Virtual; + // (note: array of different types, can't copy directly) + for(int i = 0; i < 4; i++) + Params[i] = sf->Params[i]; + } +}; + +// called by walk_stack for each stack frame +static int dump_frame_cb(const STACKFRAME64* sf, void* user_arg) +{ + // note: keep frame formatting in sync with get_exception_locus. + + UNUSED(user_arg); + + current_stackframe64 = sf; + + void* func = (void*)sf->AddrPC.Offset; + // don't trace back into kernel32: we need a defined stop point, + // or walk_stack will end up returning -1; stopping here also + // reduces the risk of confusing the stack dump code below. + wchar_t module_path[MAX_PATH]; + wchar_t* module_filename = get_module_filename(func, module_path); + if(!wcscmp(module_filename, L"kernel32.dll")) + return 0; // done + + char func_name[DBG_SYMBOL_LEN]; char path[DBG_FILE_LEN]; int line; + if(debug_resolve_symbol(func, func_name, path, &line) == 0) + { + const char* slash = strrchr(path, DIR_SEP); + const char* file = slash? slash+1 : path; + out(L"%hs %hs (%lu)\r\n", func_name, file, line); + } + else + out(L"%p\r\n", func); + +//debug_printf("FRAME %s: stored regs: fp=0x%x sp=0x%x\n", func_name, sf->AddrFrame.Offset, sf->AddrFrame.Offset); + + // only enumerate symbols for this stack frame + // (i.e. its locals and parameters) + // problem: debug info is scope-aware, so we won't see any variables + // declared in sub-blocks. we'd have to pass an address in that block, + // which isn't worth the trouble. since + IMAGEHLP_STACK_FRAME2 imghlp_frame(sf); + SymSetContext(hProcess, &imghlp_frame, 0); // last param is ignored + + SymEnumSymbols(hProcess, 0, 0, dump_sym_cb, 0); + // 2nd and 3rd params indicate scope set by SymSetContext + // should be used. + + out(L"\r\n"); + return 1; // keep calling +} + + +// most recent stack frames will be skipped +// (we don't want to show e.g. GetThreadContext / this call) +const wchar_t* debug_dump_stack(wchar_t* buf, size_t max_chars, uint skip, void* pcontext) +{ + // need to skip one frame. if !pcontext, this function's; + // otherwise, RaiseException (must be skipped because dump_frame_cb + // will stop if it sees a kernel32 function). + skip++; + + lock(); + out_init(buf, max_chars); + + int err = walk_stack(dump_frame_cb, 0, skip, (const CONTEXT*)pcontext); + if(err != 0) + out(L"(error while building stack trace: %d)", err); + + unlock(); + + return buf; +} + + + + + +// write out a "minidump" containing register and stack state; this enables +// examining the crash in a debugger. called by wdbg_exception_filter. +// heavily modified from http://www.codeproject.com/debug/XCrashReportPt3.asp +// lock must be held. +void wdbg_write_minidump(EXCEPTION_POINTERS* exception_pointers) +{ + lock(); + + HANDLE hFile = CreateFile("crashlog.dmp", GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0); + if(hFile == INVALID_HANDLE_VALUE) + goto fail; + + MINIDUMP_EXCEPTION_INFORMATION mei; + mei.ThreadId = GetCurrentThreadId(); + mei.ExceptionPointers = exception_pointers; + mei.ClientPointers = FALSE; + // exception_pointers is not in our address space. + + // note: we don't store other crashlog info within the dump file + // (UserStreamParam), since we will need to generate a plain text file on + // non-Windows platforms. users will just have to send us both files. + + HANDLE hProcess = GetCurrentProcess(); DWORD pid = GetCurrentProcessId(); + if(!MiniDumpWriteDump(hProcess, pid, hFile, MiniDumpNormal, &mei, 0, 0)) + { +fail: + DISPLAY_ERROR(L"Unable to generate minidump."); + } + + CloseHandle(hFile); + unlock(); +} + + + +/* +tests + +struct Small +{ +int i1; +int i2; +}; + +struct Large +{ +double d1; +double d2; +double d3; +double d4; +}; + +Large large_array_of_large_structs[8] = { { 0.0,0.0,0.0,0.0 } }; +Large small_array_of_large_structs[2] = { { 0.0,0.0,0.0,0.0 } }; +Small large_array_of_small_structs[8] = { { 1,2 } }; +Small small_array_of_small_structs[2] = { { 1,2 } }; + +int ar1[] = { 1,2,3,4,5 }; +char ar2[] = { 't','e','s','t', 0 }; + +//assert2(0 && "test assert2"); // not exception (works when run from debugger) +//__asm xor edx,edx __asm div edx // named SEH +//RaiseException(0x87654321, 0, 0, 0); // unknown SEH +//throw std::bad_exception("what() is ok"); // C++ +} +*/ \ No newline at end of file