/* * 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" #include #include "wdbg.h" #include "assert_dlg.h" /* * voodoo programming. PDB debug info is a poorly documented mess. * see http://msdn.microsoft.com/msdnmag/issues/02/03/hood/default.aspx */ /* from DIA cvconst.h */ typedef enum { btNoType = 0, btVoid = 1, btChar = 2, btWChar = 3, btInt = 6, btUInt = 7, btFloat = 8, btBCD = 9, btBool = 10, btLong = 13, btULong = 14, btCurrency = 25, btDate = 26, btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31 } BasicType; static void DumpMiniDump(HANDLE hFile, PEXCEPTION_POINTERS excpInfo); // function pointers // // we need to link against dbghelp dynamically - some systems may not // have v5.1. (if <= 5.0, type information is unavailable). // // don't delay-load: requires the .lib file, which may not be present // on all compilers. // // can't skip the dbghelp.h function prototypes, so we need to // name the function pointers differently (prepend _). Grr. // #define FUNC(ret, name, params) static ret (WINAPI *_##name) params; #include "dbghelp_funcs.h" #undef FUNC static HANDLE hProcess; static uintptr_t mod_base; const int bufsize = 64000; static wchar_t buf[bufsize]; /* buffer for stack trace */ static wchar_t* pos; /* current pos in buf */ static void out(wchar_t* fmt, ...) { // Don't overflow the buffer (and abort if we're about to) if (pos-buf+1000 > bufsize) throw; va_list args; va_start(args, fmt); pos += vswprintf(pos, 1000, fmt, args); va_end(args); } static void dbg_cleanup(void) { _SymCleanup(hProcess); } static void dbg_init() { /* already initialized */ if(hProcess) return; HMODULE hdbghelp = LoadLibrary("dbghelp.dll"); // import functions #define FUNC(ret, name, params) *(void**)&_##name = (void*)GetProcAddress(hdbghelp, #name); #include "dbghelp_funcs.h" #undef FUNC hProcess = GetCurrentProcess(); _SymSetOptions(SYMOPT_DEFERRED_LOADS); _SymInitialize(hProcess, 0, TRUE); atexit(dbg_cleanup); } /* * guess if addr points to a string * algorithm: * scan string; count # text chars vs. garbage */ static bool is_string_ptr(u64 addr) { /* early out for x86 */ #ifdef _WIN32 if(addr < 0x10000 || addr > 0x80000000) return false; #endif const char* str = (const char*)addr; __try { int score = 0; for(;;) { const int c = *str++ & 0xff; /* signed char => promoted */ if(isalnum(c)) score += 1; else if(!c) return (score > 0); /* end of string reached */ else if(!isprint(c)) score -= 5; /* give up */ if(score <= -10) return false; } } __except(1) { return false; } } /* * output the value of 'basic' types: string, (array of) float, int * * not reliably supported, due to broken debug info / bad documentation: * - multidimensional arrays (e.g. int[2][2] => len 16, elements 2) * - arrays of character arrays (would have rely on is_string_ptr) * - nested structures (invalid base type index) */ static void print_var(u32 type_idx, uint level, u64 addr) { // successful with index we were given? BasicType type = btNoType; u32 real_typeid = 0; if(!_SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_BASETYPE, &type)) { // no; try to get the "real" index first BOOL ok = _SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_TYPEID, &real_typeid); if(ok && real_typeid != type_idx) _SymGetTypeInfo(hProcess, mod_base, real_typeid, TI_GET_BASETYPE, &type); } u64 len; _SymGetTypeInfo(hProcess, mod_base, level? real_typeid : type_idx, TI_GET_LENGTH, &len); u32 elements; _SymGetTypeInfo(hProcess, mod_base, level? real_typeid : type_idx, TI_GET_COUNT, &elements); wchar_t* fmt = L"%I64X"; /* default: 64 bit integer */ out(L"= "); /* * single string (character array) * len == elements => bool array | character array (probably string) */ if(len == elements && is_string_ptr(addr)) { out(L"\"%hs\"\r\n", (char*)addr); return; } /* array */ if(elements) { len /= elements; out(L"{ "); } u32 totalelements = elements; next_element: /* zero extend to 64 bit (little endian) */ u64 data = 0; for(u64 i = 0; i < MIN(len,8); i++) data |= (u64)(*(u8*)(addr+i)) << (i*8); /* float / double */ if(type == btFloat) { /* convert floats to double (=> single printf call) */ if(len == 4) { double double_data = (double)(*(float*)addr); data = *(u64*)&double_data; } fmt = L"%lf"; } /* string (char*) */ else if(len == sizeof(void*) && is_string_ptr(data)) { fmt = L"\"%hs\""; } out(fmt, data); /* ascii char */ if(data < 0x100 && isprint((int)data)) out(L" ('%hc')", (char)data); if(elements) { addr += len; /* add stride */ elements--; /* don't display too many elements of huge arrays */ if (totalelements-elements > 32) out(L" ... }"); else if(elements == 0) out(L" }"); else { out(L", "); goto next_element; } } out(L"\r\n"); } /* print sym name; if not a basic type, recurse for each child */ static void dump_sym(u32 type_idx, uint level, u64 addr) { /* print type name / member name */ WCHAR* type_name; if(_SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_SYMNAME, &type_name)) { /* ignore things that look like member functions, because there's no point in outputting them */ if (wcsstr(type_name, L"::")) { LocalFree(type_name); return; } /* indent */ for(uint i = 0; i < level+2; i++) out(L" "); out(L"%ls ", type_name); /* exclude some specific variable types, because they just fill the stack trace with rubbish */ if (!wcscmp(type_name, L"JSType") || !wcscmp(type_name, L"JSVersion") ) { LocalFree(type_name); out(L"\r\n"); return; } LocalFree(type_name); } u32 num_children = 0; /* built-in type? yes - show its value, terminate recursion */ if (! _SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_CHILDRENCOUNT, &num_children) || num_children == 0) { print_var(type_idx, level, addr); return; } // bogus if(num_children > 50) return; /* get children's type indices */ char child_buf[sizeof(TI_FINDCHILDREN_PARAMS) + 256*4]; TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*)child_buf; children->Count = num_children; children->Start = 0; _SymGetTypeInfo(hProcess, mod_base, type_idx, TI_FINDCHILDREN, children); out(L"\r\n"); /* recurse for each child */ for(uint child_idx = 0; child_idx < num_children; child_idx++) { u32 child_id = children->ChildId[child_idx]; u32 member_ofs; _SymGetTypeInfo(hProcess, mod_base, child_id, TI_GET_OFFSET, &member_ofs); dump_sym(child_id, level+1, addr+member_ofs); } } static enum { NONE, PARAMS, LOCALS } var_type; #define SYM_FLAG(flag) (sym->Flags & (IMAGEHLP_SYMBOL_INFO_##flag)) /* called for each local symbol */ static BOOL CALLBACK sym_callback(SYMBOL_INFO* sym, ULONG /* SymbolSize */, void* ctx) { STACKFRAME64* frame = (STACKFRAME64*)ctx; mod_base = (uintptr_t)sym->ModBase; __try { if(SYM_FLAG(PARAMETER)) { if(var_type != PARAMS) { var_type = PARAMS; out(L" params:\r\n"); } } else if(var_type != LOCALS) { var_type = LOCALS; out(L" locals:\r\n"); } out(L" %hs ", sym->Name); u64 addr = sym->Address; /* -> var's contents */ if(SYM_FLAG(REGRELATIVE)) addr += frame->AddrFrame.Offset; else if(SYM_FLAG(REGISTER)) return 0; dump_sym(sym->TypeIndex, 0, addr); } __except(1) { out(L"\r\nError reading symbol %hs!\r\n", sym->Name); } return 1; } /* * most recent stack frames will be skipped * (we don't want to show e.g. GetThreadContext / this call) */ static void walk_stack(int skip, wchar_t* out_buf, CONTEXT* override_context = NULL) { dbg_init(); bool type_avail = _SymFromAddr != 0; pos = out_buf; out(L"\r\nCall stack:\r\n\r\n"); if(!type_avail) out(L"warning: older dbghelp.dll loaded; no type information available.\r\n"); HANDLE hThread = GetCurrentThread(); CONTEXT context; if(override_context) { memcpy(&context, override_context, sizeof(CONTEXT)); } else { memset(&context, 0, sizeof(context)); context.ContextFlags = CONTEXT_FULL; GetThreadContext(hThread, &context); } STACKFRAME64 frame; memset(&frame, 0, sizeof(frame)); #ifdef _M_IX86 /* x86 only: init STACKFRAME for first StackWalk call (undocumented) */ frame.AddrPC.Offset = context.Eip; frame.AddrPC.Mode = AddrModeFlat; frame.AddrStack.Offset = context.Esp; frame.AddrStack.Mode = AddrModeFlat; frame.AddrFrame.Offset = context.Ebp; frame.AddrFrame.Mode = AddrModeFlat; #endif u64 base = _SymGetModuleBase64(hProcess, (u64)&walk_stack); IMAGE_NT_HEADERS* header = _ImageNtHeader((void*)base); u32 machine = header->FileHeader.Machine; char sym_buf[sizeof(SYMBOL_INFO) + 1024]; memset(sym_buf, 0, sizeof(sym_buf)); SYMBOL_INFO* sym = (SYMBOL_INFO*)sym_buf; sym->SizeOfStruct = sizeof(SYMBOL_INFO); sym->MaxNameLen = 1024; IMAGEHLP_LINE64 line = { sizeof(IMAGEHLP_LINE64) }; IMAGEHLP_STACK_FRAME imghlp_frame; for(;;) { if(!_StackWalk64(machine, hProcess, hThread, &frame, &context, 0, _SymFunctionTableAccess64, _SymGetModuleBase64, 0)) break; if(skip-- > 0) continue; u64 pc = frame.AddrPC.Offset; if(!type_avail) { out(L"%I64X\r\n", pc); continue; } /* function name / address */ u64 sym_disp; if(type_avail && _SymFromAddr(hProcess, pc, &sym_disp, sym)) out(L"%hs", sym->Name); else out(L"%I64X", pc); /* source file + line number */ DWORD line_disp; if(_SymGetLineFromAddr64(hProcess, pc, &line_disp, &line)) out(L" (%hs, line %lu)", line.FileName, line.LineNumber); out(L"\r\n"); /* params + vars */ var_type = NONE; imghlp_frame.InstructionOffset = pc; _SymSetContext(hProcess, &imghlp_frame, 0); _SymEnumSymbols(hProcess, 0, 0, sym_callback, &frame); out(L"\r\n"); } } /*---------------------------------------------------------------------------*/ #include "resource.h" typedef enum { ASSERT, EXCEPTION } DLG_TYPE; static int CALLBACK dlgproc(HWND hDlg, unsigned int msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_INITDIALOG: { DLG_TYPE type = (DLG_TYPE)lParam; /* disable inappropriate buttons */ if(type != ASSERT) { HWND h; h = GetDlgItem(hDlg, IDC_CONTINUE); EnableWindow(h, 0); h = GetDlgItem(hDlg, IDC_SUPPRESS); EnableWindow(h, 0); h = GetDlgItem(hDlg, IDC_BREAK); EnableWindow(h, 0); } SetDlgItemTextW(hDlg, IDC_EDIT1, buf); return 1; /* allow focus to be set */ } case WM_SYSCOMMAND: if(wParam == SC_CLOSE) EndDialog(hDlg, 0); return 0; /* allows dragging; don't understand why. */ case WM_COMMAND: switch(wParam) { case IDC_COPY: clipboard_set(buf); break; case IDC_CONTINUE: /* 2000 */ case IDC_SUPPRESS: case IDC_BREAK: /* 2002 */ EndDialog(hDlg, wParam-2000); break; case IDC_EXIT: exit(0); } return 1; } return 0; /* do default processing for msg */ } /* * 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(DLG_TYPE type) { return (int)DialogBoxParam(0, MAKEINTRESOURCE(IDD_DIALOG1), 0, 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; char* except_str = 0; #define X(e) case EXCEPTION_##e: except_str = #e; 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) } #undef X MEMORY_BASIC_INFORMATION mbi; char module_buf[100]; 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, '\\'); } int pos = swprintf(buf, 1000, L"Exception %hs at %hs!%08lX\r\n", except_str, module, addr-base); walk_stack(0, buf+pos); 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); } // 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 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]); } 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"Prometheus failure", message); __try { debug_write_crashlog("crashlog.txt", errortext, ep->ContextRecord); } __except (EXCEPTION_EXECUTE_HANDLER) { wdisplay_msg(L"Prometheus 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"Prometheus 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 _CrtSetDbgFlag( _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) & ~_CRTDBG_LEAK_CHECK_DF ); #endif exit(EXIT_FAILURE); return EXCEPTION_EXECUTE_HANDLER; } static void init() { ONCE(dbg_init()); } int debug_assert_failed(const char* file, int line, const char* expr) { init(); int pos = swprintf(buf, 1000, L"Assertion failed in %hs, line %d: %hs\r\n", file, line, expr); walk_stack(2, buf+pos); return dialog(ASSERT); } // main.cpp will have an exception handler, but this covers low-level init // (before main is called) static int wdbg_init() { set_exception_handler(); return 0; } #pragma data_seg(".LIB$WIB") // first WIN_REGISTER_FUNC(wdbg_init); #pragma data_seg() // 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; // 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 int MicroBuffer_off; int debug_write_crashlog(const char* file, wchar_t* header, void* context) { init(); int len = swprintf(buf, 1000, L"Undefined state reached.\r\n"); __try { if (context) walk_stack(0, buf+len, (CONTEXT*)context); else walk_stack(2, buf+len); } __except(EXCEPTION_EXECUTE_HANDLER) { // Probably caught a buffer overflow } FILE* f = fopen(file, "wb"); int 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; }