// stack walk, improved assert and exception handler // Copyright (c) 2002 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 "wdbg.h" #include "assert_dlg.h" #include "lib/timer.h" #ifdef _MSC_VER #pragma comment(lib, "dbghelp.lib") #endif // automatic module init (before main) and shutdown (before termination) #pragma data_seg(".LIB$WCC") WIN_REGISTER_FUNC(wdbg_init); #pragma data_seg(".LIB$WTB") WIN_REGISTER_FUNC(wdbg_shutdown); #pragma data_seg() //#define LOCALISED_TEXT #ifdef LOCALISED_TEXT #include "ps/i18n.h" #endif // LOCALISED_TEXT // dbghelp isn't thread-safe. lock is taken by walk_stack, // debug_resolve_symbol, and while dumping a stack frame (dump_frame_cb). static void lock() { win_lock(DBGHELP_CS); } static void unlock() { win_unlock(DBGHELP_CS); } static void DumpMiniDump(HANDLE hFile, PEXCEPTION_POINTERS excpInfo); static void set_exception_handler(); static HINSTANCE hInstance; static HANDLE hProcess; static uintptr_t mod_base; static WORD machine; // needed by StackWalk static int wdbg_init() { // main.cpp will have an exception handler, but this covers low-level init // (before main is called) set_exception_handler(); hProcess = GetCurrentProcess(); hInstance = GetModuleHandle(0); SymSetOptions(SYMOPT_DEFERRED_LOADS); SymInitialize(hProcess, 0, TRUE); u64 base = SymGetModuleBase64(hProcess, (u64)&wdbg_init); IMAGE_NT_HEADERS* header = ImageNtHeader((void*)base); machine = header->FileHeader.Machine; return 0; } static int wdbg_shutdown(void) { SymCleanup(hProcess); return 0; } ////////////////////////////////////////////////////////////////////////////// // need to shoehorn printf-style variable params into // the OutputDebugString call. // - don't want to split into multiple calls - would add newlines to output. // - fixing Win32 _vsnprintf to return # characters that would be written, // as required by C99, looks difficult and unnecessary. if any other code // needs that, implement GNU vasprintf. // - fixed size buffers aren't nice, but much simpler than vasprintf-style // allocate+expand_until_it_fits. these calls are for quick debug output, // not loads of data, anyway. static const int MAX_CNT = 512; // max output size of 1 call of (w)debug_out (including \0) void debug_out(const char* fmt, ...) { char buf[MAX_CNT]; buf[MAX_CNT-1] = '\0'; va_list ap; va_start(ap, fmt); vsnprintf(buf, MAX_CNT-1, fmt, ap); va_end(ap); OutputDebugString(buf); } void wdebug_out(const wchar_t* fmt, ...) { wchar_t buf[MAX_CNT]; buf[MAX_CNT-1] = L'\0'; va_list ap; va_start(ap, fmt); vsnwprintf(buf, MAX_CNT-1, fmt, ap); va_end(ap); OutputDebugStringW(buf); } ////////////////////////////////////////////////////////////////////////////// // // dbghelp support routines for walking the stack // ////////////////////////////////////////////////////////////////////////////// // iterate over a call stack. // if != 0, we start there; otherwise, at the current // stack frame. call for each stack frame found, also passing the // user-specified . if it returns <= 0, we stop immediately and // pass on that value; otherwise, the eventual return value is -1 // ("callback never succeeded"). // // note: can't just pass function's address to the callback - // dump_frame_cb needs the frame pointer for reg-relative variables. static int walk_stack(int (*cb)(STACKFRAME64*, void*), void* ctx, CONTEXT* thread_context = 0) { HANDLE hThread = GetCurrentThread(); // we need to set STACKFRAME64.AddrPC and AddrFrame for the initial // StackWalk call in our loop. there is no portable way to do this, // since CONTEXT is platform-specific. if the caller passed in a thread // context (e.g. if calling from an exception handler), we use that; // otherwise, determine the current PC / frame pointer ourselves. // GetThreadContext is documented not to work if the current thread // is running, but that seems to be current practice. Regardless, we // avoid using it, since simple asm code is safer. STACKFRAME64 frame; memset(&frame, 0, sizeof(frame)); #if defined(_M_AMD64) DWORD64 rip_, rbp_; if(thread_context) { rip_ = thread_context->Rip; rbp_ = thread_context->Rbp; } else { __asm { cur_rip: mov rax, offset cur_rip mov [rip_], rax mov [rbp_], rbp } } frame.AddrPC.Offset = rip_; frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Offset = rbp_; frame.AddrFrame.Mode = AddrModeFlat; #elif defined(_M_IX86) DWORD eip_, ebp_; if(thread_context) { eip_ = thread_context->Eip; ebp_ = thread_context->Ebp; } else { __asm { cur_eip: mov eax, offset cur_eip mov [eip_], eax mov [ebp_], ebp } } frame.AddrPC.Offset = eip_; frame.AddrPC.Mode = AddrModeFlat; frame.AddrFrame.Offset = ebp_; frame.AddrFrame.Mode = AddrModeFlat; #else #error "TODO: set STACKFRAME64.AddrPC, AddrFrame for this platform" #endif // for each stack frame found: for(;;) { lock(); BOOL ok = StackWalk64(machine, hProcess, hThread, &frame, thread_context, 0, SymFunctionTableAccess64, SymGetModuleBase64, 0); unlock(); // no more frames found, and callback never succeeded; abort. if(!ok) return -1; void* func = (void*)frame.AddrPC.Offset; int ret = cb(&frame, ctx); // 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; } } // ~500µs int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int* line) { int ret = -1; lock(); { const DWORD64 addr = (DWORD64)ptr_of_interest; // get symbol name SYMBOL_INFO_PACKAGE sp; SYMBOL_INFO* sym = &sp.si; sym->SizeOfStruct = sizeof(sp.si); sym->MaxNameLen = MAX_SYM_NAME; if(!SymFromAddr(hProcess, addr, 0, sym)) goto fail; sprintf(sym_name, "%s", sym->Name); // get source file + line number IMAGEHLP_LINE64 line_info = { sizeof(IMAGEHLP_LINE64) }; DWORD displacement; // unused but required by SymGetLineFromAddr64! if(!SymGetLineFromAddr64(hProcess, addr, &displacement, &line_info)) goto fail; sprintf(file, "%s", line_info.FileName); *line = line_info.LineNumber; ret = 0; } fail: unlock(); return ret; } ////////////////////////////////////////////////////////////////////////////// // // get address of Nth function above us on the call stack (uses walk_stack) // ////////////////////////////////////////////////////////////////////////////// struct NthCallerParams { int left_to_skip; void* func; }; // called by walk_stack for each stack frame static int nth_caller_cb(STACKFRAME64* frame, void* ctx) { NthCallerParams* p = (NthCallerParams*)ctx; // not the one we want yet if(p->left_to_skip > 0) { p->left_to_skip--; return 1; // keep calling } // return its address p->func = (void*)frame->AddrPC.Offset; return 0; } // n starts at 1 void* debug_get_nth_caller(uint n) { NthCallerParams params = { (int)n-1 + 3, 0 }; // skip walk_stack, debug_get_nth_caller and its caller if(walk_stack(nth_caller_cb, ¶ms) == 0) return params.func; return 0; } ////////////////////////////////////////////////////////////////////////////// // // helper routines for symbol value dump // ////////////////////////////////////////////////////////////////////////////// static const size_t DUMP_BUF_SIZE = 64000; static wchar_t buf[DUMP_BUF_SIZE]; // buffer for stack trace static wchar_t* pos; // current pos in buf static void out(const wchar_t* fmt, ...) { // Don't overflow the buffer (and abort if we're about to) if (pos-buf+1000 > DUMP_BUF_SIZE) { debug_warn(""); return; }; va_list args; va_start(args, fmt); pos += vswprintf(pos, 1000, fmt, args); va_end(args); } // is an ASCII string apparently located at ? // set to 2 to search for WCS-2 strings (of western characters!). // called by dump_ptr and dump_array for their string special-cases. // // algorithm: scan the "string" and count # text chars vs. garbage. static bool is_string_ptr(u64 addr, size_t stride = 1) { // completely bogus on IA32; save ourselves the segfault (slow). #ifdef _WIN32 if(addr < 0x10000 || addr > 0xc0000000) return false; #endif const char* str = (const char*)addr; __try { int score = 0; for(;;) { // current character is: const int c = *str & 0xff; // prevent sign extension // .. text if(isalnum(c)) score += 2; // .. end of string else if(!c) break; // .. garbage else if(!isprint(c)) score -= 5; // too much garbage found, probably not a string. // abort fairly early so we don't segfault unnecessarily (slow). if(score <= -10) break; str += stride; } return (score > 0); } __except(1) { return false; } } // zero-extend (truncated to 8) bytes of little-endian data to u64, // starting at address

(need not be aligned). static u64 movzx_64le(const u8* p, size_t size) { if(size > 8) size = 8; u64 data = 0; for(u64 i = 0; i < MIN(size,8); i++) data |= ((u64)p[i]) << (i*8); return data; } // sign-extend (truncated to 8) bytes of little-endian data to i64, // starting at address

(need not be aligned). static i64 movsx_64le(const u8* p, size_t size) { if(size > 8) size = 8; u64 data = movzx_64le(p, size); // no point in sign-extending if >= 8 bytes were requested if(size < 8) { u64 sign_bit = 1; sign_bit <<= (size*8)-1; // be sure that we don't shift more than variable's bit width // number would be negative in the smaller type, // so sign-extend, i.e. set all more significant bits. if(data & sign_bit) { const u64 size_mask = (sign_bit+sign_bit)-1; data |= ~size_mask; } } return (i64)data; } ////////////////////////////////////////////////////////////////////////////// // // output values of specific types of local variables // ////////////////////////////////////////////////////////////////////////////// // forward decl; called by dump_udt. static int dump_data_sym(DWORD data_idx, const u8* p, uint level); // forward decl; called by dump_array. static int dump_type_sym(DWORD type_idx, const u8* p, uint level); // these functions return -1 if they're not able to produce any reasonable // output; dump_type_sym will display value as "?" // is a SymTagPointerType; output its value. // called by dump_type_sym; lock is held. static int dump_ptr(const u8* p, size_t size) { const u64 data = movzx_64le(p, size); const wchar_t* fmt; // char* if(is_string_ptr(data, sizeof(char))) fmt = L"\"%hs\""; // WCHAR* else if(is_string_ptr(data, sizeof(WCHAR))) fmt = L"\"%s\""; // generic 32-bit pointer else if(size == 4) fmt = L"0x%08X"; // generic 64-bit pointer else fmt = L"0x%I64016X"; out(fmt, data); return 0; } ////////////////////////////////////////////////////////////////////////////// // is a SymTagBaseType; output its value. // called by dump_type_sym; lock is held. static int dump_base_type(DWORD type_idx, const u8* p, size_t size, uint level) { UNUSED(level); DWORD base_type; if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_BASETYPE, &base_type)) return -1; u64 data = movzx_64le(p, size); // single out() call // (note: passing pointers in u64 assumes little-endian) const wchar_t* fmt; switch(base_type) { case btBool: assert(size == sizeof(bool)); fmt = L"%hs"; data = (u64)(data? "true " : "false"); break; case btFloat: if(size == sizeof(float)) fmt = L"%f"; else if(size == sizeof(double)) fmt = L"%lf"; else assert(0); break; // signed integers case btInt: case btLong: data = movsx_64le(p, size); if(size == 2 || size == 4) fmt = L"%d"; else if(size == 8) fmt = L"%I64d"; else assert(0); break; // unsigned integers (displayed as hex) case btUInt: case btULong: if(size == 2) fmt = L"0x%04X"; else if(size == 4) fmt = L"0x%08X"; else if(size == 8) fmt = L"0x%016I64X"; else assert(0); break; // either 8-bit integer or character (character value appended below) case btChar: assert(size == sizeof(char)); fmt = L"%I64d"; break; case btWChar: assert(size == sizeof(wchar_t)); fmt = L"%c"; break; // shouldn't happen case btNoType: case btVoid: default: //-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); // cannot be shoehorned into single out(). // note: no need for type checks; if the value is right, // we just display what character that corresponds to. if(data < 0x100) { int c = (int)data; if(isprint(c)) out(L" ('%hc')", c); } return 0; } ////////////////////////////////////////////////////////////////////////////// // is a SymTagEnum; output its value. // called by dump_type_sym; lock is held. static int dump_enum(DWORD type_idx, const u8* p, size_t size, uint level) { UNUSED(level); const i64 current_value = movsx_64le(p, size); DWORD num_children; if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_CHILDRENCOUNT, &num_children)) goto name_unavailable; // alloc an array to hold child IDs const size_t MAX_CHILDREN = 1000; char child_buf[sizeof(TI_FINDCHILDREN_PARAMS)+MAX_CHILDREN*sizeof(DWORD)]; TI_FINDCHILDREN_PARAMS* fcp = (TI_FINDCHILDREN_PARAMS*)child_buf; fcp->Start = 0; fcp->Count = MIN(num_children, MAX_CHILDREN); if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_FINDCHILDREN, fcp)) goto name_unavailable; for(uint i = 0; i < fcp->Count; i++) { DWORD child_data_idx = fcp->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. OLE DLL is already pulled in anyway. VARIANT v; SymGetTypeInfo(hProcess, mod_base, child_data_idx, TI_GET_VALUE, &v); 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)) goto name_unavailable; out(L"%s", name); LocalFree(name); return 0; } } name_unavailable: // we can produce reasonable output (the numeric value), // but weren't able to retrieve the matching enum name. out(L"%I64d", current_value); return 1; } ////////////////////////////////////////////////////////////////////////////// // is a SymTagArrayType; output its value. // called by dump_type_sym; lock is held. static int dump_array(DWORD type_idx, const u8* p, size_t size, uint level) { DWORD elements; if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_COUNT, &elements)) return -1; DWORD el_type_idx = 0; if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_TYPEID, &el_type_idx)) return -1; size_t stride = (size_t)(size / elements); // // special case for character arrays, i.e. strings // u64 addr = (u64)p; // .. char[] if(stride == sizeof(char) && is_string_ptr(addr, stride)) { out(L"\"%hs\"", p); return 0; } // .. WCHAR[] (don't use wchar_t, since that might be 4 bytes) if(stride == sizeof(WCHAR) && is_string_ptr(addr, stride)) { out(L"\"%s\"", p); return 0; } // // regular array output // int err = 0; out(L"{ "); const uint elements_to_show = MIN(32, elements); for(uint i = 0; i < elements_to_show; i++) { int ret = dump_type_sym(el_type_idx, p + i*stride, level+1); // skip trailing comma if(i != elements_to_show-1) out(L", "); // remember first error if(err == 0) err = ret; } // we truncated some if(elements != elements_to_show) out(L" ..."); out(L" }"); return err; } ////////////////////////////////////////////////////////////////////////////// // is a SymTagUDT; output its value. // called by dump_type_sym; lock is held. static int dump_udt(DWORD type_idx, const u8* p, size_t size, uint level) { UNUSED(size); out(L"\r\n"); DWORD num_children; if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_CHILDRENCOUNT, &num_children)) return -1; // alloc an array to hold child IDs const size_t MAX_CHILDREN = 1000; char child_buf[sizeof(TI_FINDCHILDREN_PARAMS)+MAX_CHILDREN*sizeof(DWORD)]; TI_FINDCHILDREN_PARAMS* fcp = (TI_FINDCHILDREN_PARAMS*)child_buf; fcp->Start = 0; fcp->Count = MIN(num_children, MAX_CHILDREN); if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_FINDCHILDREN, fcp)) return -1; int err = 0; // recursively display each child (call back to dump_data) for(uint i = 0; i < fcp->Count; i++) { DWORD child_data_idx = fcp->ChildId[i]; DWORD ofs = 0; if(!SymGetTypeInfo(hProcess, mod_base, child_data_idx, TI_GET_OFFSET, &ofs)) debug_warn("dump_udt: warning: TI_GET_OFFSET query failed"); int ret = dump_data_sym(child_data_idx, p+ofs, level+1); // remember first error if(err == 0) err = ret; } return err; } ////////////////////////////////////////////////////////////////////////////// // // // ////////////////////////////////////////////////////////////////////////////// // given a data symbol's type identifier, output its type name (if // applicable), determine what kind of variable it describes, and // call the appropriate dump_* routine. // lock is held. static int dump_type_sym(DWORD type_idx, const u8* p, uint level) { DWORD type_tag; if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_SYMTAG, &type_tag)) return -1; // get "type name" (only available for SymTagUDT, SymTagEnum, and // SymTagTypedef types). // note: can't use SymFromIndex to get tag as well as name, because it // fails when name isn't available (e.g. if this is a SymTagBaseType). WCHAR* type_name; if(SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_SYMNAME, &type_name)) { out(L"(%s)", type_name); LocalFree(type_name); } ULONG64 size; if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_LENGTH, &size)) return -1; switch(type_tag) { case SymTagPointerType: return dump_ptr(p, (size_t)size); case SymTagEnum: return dump_enum(type_idx, p, (size_t)size, level); case SymTagBaseType: return dump_base_type(type_idx, p, (size_t)size, level); case SymTagUDT: return dump_udt(type_idx, p, (size_t)size, level); case SymTagArrayType: return dump_array(type_idx, p, (size_t)size, level); case SymTagTypedef: if(!SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_TYPEID, &type_idx)) return -1; return dump_type_sym(type_idx, p, level); default: debug_out("Unknown tag: %d\n", type_tag); return -1; } } ////////////////////////////////////////////////////////////////////////////// // indent to current nesting level, display name, and output value via // dump_type_sym. // // split out of dump_sym_cb so dump_udt can call back here for its members. // lock is held. static int dump_data_sym(DWORD data_idx, const u8* p, uint level) { SYMBOL_INFO_PACKAGEW sp; SYMBOL_INFOW* sym = &sp.si; sym->SizeOfStruct = sizeof(sp.si); sym->MaxNameLen = MAX_SYM_NAME; if(!SymFromIndexW(hProcess, mod_base, data_idx, sym)) return -1; assert(sym->Tag == SymTagData); // indent for(uint i = 0; i <= level+1; i++) out(L" "); out(L"%s = ", sym->Name); int ret = dump_type_sym(sym->TypeIndex, p, level); // couldn't produce any reasonable output; value = "?" if(ret < 0) out(L"?"); out(L"\r\n"); return ret; } ////////////////////////////////////////////////////////////////////////////// struct DumpSymParams { STACKFRAME64* frame; bool locals_active; }; // get actual address of what the symbol represents (may be relative // to frame pointer); demarcate local/param sections; output name+value via // dump_data_sym. // // called from dump_frame_cb for each local symbol; lock is held. static BOOL CALLBACK dump_sym_cb(SYMBOL_INFO* sym, ULONG sym_size, void* ctx) { UNUSED(sym_size); DumpSymParams* p = (DumpSymParams*)ctx; mod_base = (uintptr_t)sym->ModBase; // get address ULONG64 addr = sym->Address; // .. relative to a register; we assume it's the frame pointer, // since sym->Register is undocumented. if(sym->Flags & SYMF_REGREL || sym->Flags & SYMF_FRAMEREL) addr += p->frame->AddrFrame.Offset; // .. in register; we can't reliably retrieve it (since undocumented) else if(sym->Flags & SYMF_REGISTER) return 1; // .. global variable (address already set) // demarcate local / parameter report sections - this is nicer than // printing e.g. "local" in front of each variable. we assume that // symbols are sorted by group (local/param); if not, we waste space // with redundant "locals:"/"params:" tags. // note that both flags can be set, so we can't combine the if pairs. if(sym->Flags & SYMF_PARAMETER) { if(p->locals_active) { out(L"params:\r\n"); p->locals_active = false; } } else if(sym->Flags & SYMF_LOCAL) { if(!p->locals_active) { out(L"locals:\r\n "); p->locals_active = true; } } dump_data_sym(sym->Index, (const u8*)addr, 0); return TRUE; } ////////////////////////////////////////////////////////////////////////////// struct DumpFrameParams { int left_to_skip; }; // called by walk_stack for each stack frame static int dump_frame_cb(STACKFRAME64* frame, void* ctx) { DumpFrameParams* p = (DumpFrameParams*)ctx; // not the one we want yet if(p->left_to_skip > 0) { p->left_to_skip--; return 1; // keep calling } lock(); void* func = (void*)frame->AddrPC.Offset; char func_name[1000]; char file[100]; int line; if(debug_resolve_symbol(func, func_name, file, &line) == 0) out(L"%hs (%hs:%lu)", func_name, file, line); else out(L"%p", func); out(L"\r\n"); // 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_FRAME imghlp_frame; imghlp_frame.InstructionOffset = (DWORD64)func; SymSetContext(hProcess, &imghlp_frame, 0); // last param is ignored DumpSymParams params = { frame, true }; SymEnumSymbols(hProcess, 0, 0, dump_sym_cb, ¶ms); // 2nd and 3rd params indicate scope set by SymSetContext // should be used. out(L"\r\n"); unlock(); return 1; // keep calling } ////////////////////////////////////////////////////////////////////////////// // most recent stack frames will be skipped // (we don't want to show e.g. GetThreadContext / this call) static void dump_stack(uint skip, CONTEXT* thread_context = NULL) { out(L"\r\nCall stack:\r\n\r\n"); DumpFrameParams params = { (int)skip+2 }; // skip dump_stack and walk_stack walk_stack(dump_frame_cb, ¶ms, thread_context); } ////////////////////////////////////////////////////////////////////////////// // // "program error" dialog with stack trace (triggered by assert and exception) // ////////////////////////////////////////////////////////////////////////////// enum DialogType { ASSERT, EXCEPTION }; static int CALLBACK dlgproc(HWND hDlg, unsigned int msg, WPARAM wParam, LPARAM lParam) { switch(msg) { // return TRUE to set default keyboard focus case WM_INITDIALOG: { DialogType type = (DialogType)lParam; // disable inappropriate buttons if(type != ASSERT) { HWND h; h = GetDlgItem(hDlg, IDC_CONTINUE); EnableWindow(h, FALSE); h = GetDlgItem(hDlg, IDC_SUPPRESS); EnableWindow(h, FALSE); h = GetDlgItem(hDlg, IDC_BREAK); EnableWindow(h, FALSE); } SetDlgItemTextW(hDlg, IDC_EDIT1, buf); return TRUE; } // return 0 if processed, otherwise break 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; } break; // return 0 if processed, otherwise break case WM_COMMAND: switch(wParam) { case IDC_COPY: clipboard_set(buf); return 0; case IDC_CONTINUE: // 2000 case IDC_SUPPRESS: case IDC_BREAK: // 2002 EndDialog(hDlg, wParam-2000); return 0; case IDC_EXIT: exit(0); return 0; default: break; } 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 buf[]) // exits directly if 'exit' is clicked. // // return values: // 0 - continue // 1 - suppress // 2 - break static int dialog(DialogType type) { // we don't know if the enclosing app even has a Window. const HWND hParent = GetDesktopWindow(); return (int)DialogBoxParam(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), hParent, dlgproc, (LPARAM)type); } /*---------------------------------------------------------------------------*/ static PTOP_LEVEL_EXCEPTION_FILTER prev_except_filter; static long CALLBACK except_filter(EXCEPTION_POINTERS* except) { PEXCEPTION_RECORD except_record = except->ExceptionRecord; uintptr_t addr = (uintptr_t)except_record->ExceptionAddress; DWORD code = except_record->ExceptionCode; const char* except_str; #define X(e) case EXCEPTION_##e: except_str = #e; break; switch(code) { X(ACCESS_VIOLATION) X(DATATYPE_MISALIGNMENT) X(BREAKPOINT) X(SINGLE_STEP) X(ARRAY_BOUNDS_EXCEEDED) X(FLT_DENORMAL_OPERAND) X(FLT_DIVIDE_BY_ZERO) X(FLT_INEXACT_RESULT) X(FLT_INVALID_OPERATION) X(FLT_OVERFLOW) X(FLT_STACK_CHECK) X(FLT_UNDERFLOW) X(INT_DIVIDE_BY_ZERO) X(INT_OVERFLOW) X(PRIV_INSTRUCTION) X(IN_PAGE_ERROR) X(ILLEGAL_INSTRUCTION) X(NONCONTINUABLE_EXCEPTION) X(STACK_OVERFLOW) X(INVALID_DISPOSITION) X(GUARD_PAGE) X(INVALID_HANDLE) default: except_str = "(unknown)"; } #undef X MEMORY_BASIC_INFORMATION mbi; char module_buf[100]; const char* module = "???"; uintptr_t base = 0; if(VirtualQuery((void*)addr, &mbi, sizeof(mbi))) { base = (uintptr_t)mbi.AllocationBase; if(GetModuleFileName((HMODULE)base, module_buf, sizeof(module_buf))) module = strrchr(module_buf, '\\')+1; // GetModuleFileName returns fully qualified path => // trailing '\\' exists } pos = buf; out(L"Exception %hs at %hs!%08lX\r\n", except_str, module, addr-base); dump_stack(0, except->ContextRecord); dialog(EXCEPTION); if(prev_except_filter) return prev_except_filter(except); return EXCEPTION_CONTINUE_EXECUTION; } static void set_exception_handler() { prev_except_filter = SetUnhandledExceptionFilter(except_filter); } #ifdef LOCALISED_TEXT // Split this into a separate function because destructors and __try don't mix void i18n_display_fatal_msg(const wchar_t* errortext) { CStrW title = translate(L"Pyrogenesis Failure"); CStrW message = translate(L"A fatal error has occurred: $msg. Please report this to http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files from your 'data' folder.") << I18n::Raw(errortext); wdisplay_msg(title.c_str(), message.c_str()); } #endif // LOCALISED_TEXT // PT: Alternate version of the exception handler, which makes // the crash log more useful, and takes the responsibility of // suiciding away from main.cpp. void abort_timer(); // from wtime.cpp int debug_main_exception_filter(unsigned int UNUSEDPARAM(code), PEXCEPTION_POINTERS ep) { // If something crashes after we've already crashed (i.e. when shutting // down everything), don't bother logging it, because the first crash // is the most important one to fix. static bool already_crashed = false; if (already_crashed) { return EXCEPTION_EXECUTE_HANDLER; } already_crashed = true; // The timer thread sometimes dies from EXCEPTION_PRIV_INSTRUCTION // when debugging this exception handler code (which gets quite // annoying), so kill it before it gets a chance. __try { abort_timer(); } __except (EXCEPTION_EXECUTE_HANDLER) { } const wchar_t* error = NULL; // C++ exceptions put a pointer to the exception object // into ExceptionInformation[1] -- so see if it looks like // a PSERROR*, and use the relevant message if it is. __try { if (ep->ExceptionRecord->NumberParameters == 3) { /*/* PSERROR* err = (PSERROR*) ep->ExceptionRecord->ExceptionInformation[1]; if (err->magic == 0x45725221) { int code = err->code; error = GetErrorString(code); } */ } } __except (EXCEPTION_EXECUTE_HANDLER) { // Presumably it wasn't a PSERROR and resulted in // accessing invalid memory locations. error = NULL; } // Try getting nice names for other types of error: if (! error) { switch (ep->ExceptionRecord->ExceptionCode) { case EXCEPTION_ACCESS_VIOLATION: error = L"Access violation"; break; case EXCEPTION_DATATYPE_MISALIGNMENT: error = L"Datatype misalignment"; break; case EXCEPTION_BREAKPOINT: error = L"Breakpoint"; break; case EXCEPTION_SINGLE_STEP: error = L"Single step"; break; case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: error = L"Array bounds exceeded"; break; case EXCEPTION_FLT_DENORMAL_OPERAND: error = L"Float denormal operand"; break; case EXCEPTION_FLT_DIVIDE_BY_ZERO: error = L"Float divide by zero"; break; case EXCEPTION_FLT_INEXACT_RESULT: error = L"Float inexact result"; break; case EXCEPTION_FLT_INVALID_OPERATION: error = L"Float invalid operation"; break; case EXCEPTION_FLT_OVERFLOW: error = L"Float overflow"; break; case EXCEPTION_FLT_STACK_CHECK: error = L"Float stack check"; break; case EXCEPTION_FLT_UNDERFLOW: error = L"Float underflow"; break; case EXCEPTION_INT_DIVIDE_BY_ZERO: error = L"Integer divide by zero"; break; case EXCEPTION_INT_OVERFLOW: error = L"Integer overflow"; break; case EXCEPTION_PRIV_INSTRUCTION: error = L"Privileged instruction"; break; case EXCEPTION_IN_PAGE_ERROR: error = L"In page error"; break; case EXCEPTION_ILLEGAL_INSTRUCTION: error = L"Illegal instruction"; break; case EXCEPTION_NONCONTINUABLE_EXCEPTION:error = L"Noncontinuable exception"; break; case EXCEPTION_STACK_OVERFLOW: error = L"Stack overflow"; break; case EXCEPTION_INVALID_DISPOSITION: error = L"Invalid disposition"; break; case EXCEPTION_GUARD_PAGE: error = L"Guard page"; break; case EXCEPTION_INVALID_HANDLE: error = L"Invalid handle"; break; default: error = L"[unknown exception]"; } } wchar_t* errortext = (wchar_t*) error; // Handle access violations specially, because we can see // whether and where they were reading/writing. if (ep->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) { errortext = new wchar_t[256]; if (ep->ExceptionRecord->ExceptionInformation[0]) swprintf(errortext, 256, L"Access violation writing 0x%08X", ep->ExceptionRecord->ExceptionInformation[1]); else swprintf(errortext, 256, L"Access violation reading 0x%08X", ep->ExceptionRecord->ExceptionInformation[1]); } bool localised_successfully = false; #ifdef LOCALISED_TEXT // In case this is called before/after the i18n system is // alive, make sure it's actually a valid pointer if (g_CurrentLocale) { __try { i18n_display_fatal_msg(errortext); localised_successfully = true; } __except (EXCEPTION_EXECUTE_HANDLER) { } } #endif // LOCALISED_TEXT if (!localised_successfully) { wchar_t message[1024]; swprintf(message, 1024, L"A fatal error has occurred: %ls.\nPlease report this to http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files from your 'data' folder.", errortext); message[1023] = 0; wdisplay_msg(L"Pyrogenesis failure", message); } __try { debug_write_crashlog("crashlog.txt", errortext, ep->ContextRecord); } __except (EXCEPTION_EXECUTE_HANDLER) { wdisplay_msg(L"Pyrogenesis failure", L"Error generating crash log."); } __try { HANDLE hFile = CreateFile("crashlog.dmp", GENERIC_WRITE, FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0); if (hFile == INVALID_HANDLE_VALUE) throw; DumpMiniDump(hFile, ep); CloseHandle(hFile); } __except (EXCEPTION_EXECUTE_HANDLER) { wdisplay_msg(L"Pyrogenesis failure", L"Error generating crash dump."); } // Disable memory-leak reporting, because it's going to // leak like a bucket with a missing bottom when it crashes. #ifdef HAVE_DEBUGALLOC uint flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG); _CrtSetDbgFlag(flags & ~_CRTDBG_LEAK_CHECK_DF); #endif exit(EXIT_FAILURE); } int debug_assert_failed(const char* file, int line, const char* expr) { pos = buf; out(L"Assertion failed in %hs, line %d: \"%hs\"\r\n", file, line, expr); dump_stack(1); // skip this function's frame return dialog(ASSERT); } // from http://www.codeproject.com/debug/XCrashReportPt3.asp static void DumpMiniDump(HANDLE hFile, PEXCEPTION_POINTERS excpInfo) { /* if (excpInfo == NULL) { // Generate exception to get proper context in dump __try { OutputDebugString("RaiseException for MinidumpWriteDump\n"); RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL); } __except(DumpMiniDump(hFile, GetExceptionInformation()), EXCEPTION_CONTINUE_EXECUTION) { } } else {*/ MINIDUMP_EXCEPTION_INFORMATION eInfo; eInfo.ThreadId = GetCurrentThreadId(); eInfo.ExceptionPointers = excpInfo; eInfo.ClientPointers = FALSE; // TODO: Store the crashlog.txt inside the UserStreamParam // so that people only have to send us one file? // note: MiniDumpWithIndirectlyReferencedMemory does not work on Win98 if (!MiniDumpWriteDump( GetCurrentProcess(), GetCurrentProcessId(), hFile, MiniDumpNormal, excpInfo ? &eInfo : NULL, NULL, NULL)) { throw; // fail noisily } // } } static void cat_atow(FILE* in, FILE* out) { const int bufsize = 1024; char buffer[bufsize+1]; // bufsize+1 so there's space for a \0 at the end while (!feof(in)) { int r = (int)fread(buffer, 1, bufsize, in); if (!r) break; buffer[r] = 0; // ensure proper NULLness fwprintf(out, L"%hs", buffer); } } // Imported from sysdep.cpp extern wchar_t MicroBuffer[]; extern size_t MicroBuffer_off; int debug_write_crashlog(const char* file, const wchar_t* header, void* ctx) { pos = buf; out(L"Undefined state reached.\r\n"); CONTEXT* context = (CONTEXT*)ctx; const uint skip = context? 0 : 2; dump_stack(skip, context); FILE* f = fopen(file, "wb"); u16 BOM = 0xFEFF; fwrite(&BOM, 2, 1, f); if (header) { fwrite(header, wcslen(header), sizeof(wchar_t), f); fwrite(L"\r\n\r\n", 4, sizeof(wchar_t), f); } fwrite(buf, pos-buf, 2, f); const wchar_t* divider = L"\r\n\r\n====================================\r\n\r\n"; fwrite(divider, wcslen(divider), sizeof(wchar_t), f); // HACK: Insert the contents from a couple of other log files // and one memory log. These really ought to be integrated better. // Copy the contents of ../logs/systeminfo.txt FILE* in; in = fopen("../logs/system_info.txt", "rb"); if (in) { fwprintf(f, L"System info:\r\n\r\n"); cat_atow(in, f); fclose(in); } fwrite(divider, wcslen(divider), sizeof(wchar_t), f); // Copy the contents of ../logs/mainlog.html in = fopen("../logs/mainlog.html", "rb"); if (in) { fwprintf(f, L"Main log:\r\n\r\n"); cat_atow(in, f); fclose(in); } fwrite(divider, wcslen(divider), sizeof(wchar_t), f); fwprintf(f, L"Last known activity:\r\n\r\n"); fwrite(MicroBuffer, MicroBuffer_off, sizeof(wchar_t), f); fclose(f); return 0; }