diff --git a/source/lib/sysdep/win/assert_dlg.h b/source/lib/sysdep/win/assert_dlg.h new file mode 100755 index 0000000000..cebbc0915b --- /dev/null +++ b/source/lib/sysdep/win/assert_dlg.h @@ -0,0 +1,22 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by assert_dlg.rc +// +#define IDD_DIALOG1 101 +#define IDC_EDIT1 1001 +#define IDC_COPY 1002 +#define IDC_CONTINUE 2000 +#define IDC_SUPPRESS 2001 +#define IDC_BREAK 2002 +#define IDC_EXIT 2003 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/source/lib/sysdep/win/assert_dlg.rc b/source/lib/sysdep/win/assert_dlg.rc new file mode 100755 index 0000000000..440edacb4a --- /dev/null +++ b/source/lib/sysdep/win/assert_dlg.rc @@ -0,0 +1,91 @@ +// Microsoft Visual C++ generated resource script. +// +#include "assert_dlg.h" + +#define APSTUDIO_READONLY_SYMBOLS +#include +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "assert_dlg.h\0" +END + +2 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_DIALOG1 DIALOGEX 0, 0, 327, 260 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | + WS_CAPTION | WS_SYSMENU +CAPTION "Program Error" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + PUSHBUTTON "&Continue",IDC_CONTINUE,7,239,50,14 + EDITTEXT IDC_EDIT1,7,7,313,204,ES_MULTILINE | ES_AUTOHSCROLL | + ES_READONLY | ES_WANTRETURN | WS_VSCROLL + PUSHBUTTON "Copy",IDC_COPY,270,212,50,14 + PUSHBUTTON "&Suppress",IDC_SUPPRESS,59,239,50,14 + PUSHBUTTON "&Break",IDC_BREAK,111,239,50,14 + PUSHBUTTON "E&xit",IDC_EXIT,163,239,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_DIALOG1, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 320 + TOPMARGIN, 7 + BOTTOMMARGIN, 253 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/source/lib/sysdep/win/dbghelp_funcs.h b/source/lib/sysdep/win/dbghelp_funcs.h new file mode 100755 index 0000000000..5061c97a7d --- /dev/null +++ b/source/lib/sysdep/win/dbghelp_funcs.h @@ -0,0 +1,13 @@ +FUNC(void, SymCleanup, (HANDLE)) +FUNC(BOOL, SymInitialize, (HANDLE, char*, int)) +FUNC(DWORD, SymSetOptions, (DWORD)) +FUNC(DWORD64, SymGetModuleBase64, (HANDLE, DWORD64)) +FUNC(PVOID, SymFunctionTableAccess64, (HANDLE, DWORD64)) +FUNC(BOOL, StackWalk64, (DWORD, HANDLE, HANDLE, STACKFRAME64*, void*, PREAD_PROCESS_MEMORY_ROUTINE64, PFUNCTION_TABLE_ACCESS_ROUTINE64, PGET_MODULE_BASE_ROUTINE64, PTRANSLATE_ADDRESS_ROUTINE64)) +FUNC(BOOL, SymGetLineFromAddr64, (HANDLE, DWORD64, DWORD*, IMAGEHLP_LINE64*)) +FUNC(IMAGE_NT_HEADERS*, ImageNtHeader, (void*)) + +FUNC(BOOL, SymGetTypeInfo, (HANDLE, DWORD64, ULONG, IMAGEHLP_SYMBOL_TYPE_INFO, void*)) +FUNC(BOOL, SymFromAddr, (HANDLE, DWORD64, DWORD64*, SYMBOL_INFO*)) +FUNC(ULONG, SymSetContext, (HANDLE, IMAGEHLP_STACK_FRAME*, IMAGEHLP_CONTEXT*)) +FUNC(BOOL, SymEnumSymbols, (HANDLE, ULONG64, const char*, PSYM_ENUMERATESYMBOLS_CALLBACK, void*)) diff --git a/source/lib/sysdep/win/wdbg.cpp b/source/lib/sysdep/win/wdbg.cpp new file mode 100755 index 0000000000..021686aff4 --- /dev/null +++ b/source/lib/sysdep/win/wdbg.cpp @@ -0,0 +1,602 @@ +/* + * 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 "debug.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; + +/* + * function pointers + * + * we need to link against dbghelp dynamically - some systems may not + * have v5.1. (if <= 5.0, type information is unavailable). + * can't skip the dbghelp.h function prototypes, so we need to + * name the function pointers differently (prepend _). Grr. + */ +#define FUNC(ret, name, params) ret (WINAPI *_##name) params; +#include "dbghelp_funcs.h" +#undef FUNC + + +static HANDLE hProcess; +static uintptr_t mod_base; + +static char buf[8192]; /* buffer for stack trace */ +static char* out; /* current pos in buf */ + + +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) +{ + // ? + BasicType type; + u32 base_idx = 0; + if(!_SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_BASETYPE, &type)) + if(_SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_TYPEID, &base_idx)) + _SymGetTypeInfo(hProcess, mod_base, base_idx, TI_GET_BASETYPE, &type); + + u64 len; + _SymGetTypeInfo(hProcess, mod_base, level? base_idx : type_idx, TI_GET_LENGTH, &len); + + u32 elements; + _SymGetTypeInfo(hProcess, mod_base, level? base_idx : type_idx, TI_GET_COUNT, &elements); + + char* fmt = "%I64X"; /* default: 64 bit integer */ + + out += sprintf(out, "= "); + + /* + * single string (character array) + * len == elements => bool array | character array (probably string) + */ + if(len == elements && is_string_ptr(addr)) + { + out += sprintf(out, "\"%s\"\r\n", (char*)addr); + return; + } + + /* array */ + if(elements) + { + len /= elements; + out += sprintf(out, "{ "); + } + +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 = "%lf"; + } + /* string (char*) */ + else if(len == sizeof(void*) && is_string_ptr(data)) + { + fmt = "\"%s\""; + } + + out += sprintf(out, fmt, data); + + /* ascii char */ + if(data < 0x100 && isprint((int)data)) + out += sprintf(out, " ('%c')", (char)data); + + if(elements) + { + addr += len; /* add stride */ + elements--; + + if(elements == 0) + out += sprintf(out, " }"); + else + { + out += sprintf(out, ", "); + goto next_element; + } + } + + out += sprintf(out, "\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) +{ + u32 num_children = 0; + _SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_CHILDRENCOUNT, &num_children); + + /* print type name / member name */ + WCHAR* type_name; + if(_SymGetTypeInfo(hProcess, mod_base, type_idx, TI_GET_SYMNAME, &type_name)) + { + out += sprintf(out, "%ls ", (char*)type_name); /* HACK: get rid of ICC warning */ + LocalFree(type_name); + } + + /* built-in type? yes - show its value, terminate recursion */ + if(!num_children) + { + print_var(type_idx, level, addr); + return; + } + + /* get children's type indices */ + static char child_buf[sizeof(TI_FINDCHILDREN_PARAMS) + 256*4]; + TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*)child_buf; + children->Count = num_children; + _SymGetTypeInfo(hProcess, mod_base, type_idx, TI_FINDCHILDREN, children); + + out += sprintf(out, "\r\n"); + + /* recurse for each child */ + for(uint child_idx = 0; child_idx < num_children; child_idx++) + { + u32 child_id = children->ChildId[child_idx]; + + /* indent */ + for(uint i = 0; i < level+3; i++) + out += sprintf(out, " "); + + 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 += sprintf(out, " params:\r\n"); + } + } + else + if(var_type != LOCALS) + { + var_type = LOCALS; + out += sprintf(out, " locals:\r\n"); + } + + out += sprintf(out, " %s ", 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 += sprintf(out, "\r\nError reading symbol %s!\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, char* out_buf) +{ + dbg_init(); + + bool type_avail = _SymFromAddr != 0; + + out = out_buf; + out += sprintf(out, "\r\nCall stack:\r\n"); + + if(!type_avail) + out += sprintf(out, "warning: older dbghelp.dll loaded; no type information available.\r\n"); + + HANDLE hThread = GetCurrentThread(); + + static CONTEXT context; + context.ContextFlags = CONTEXT_FULL; + GetThreadContext(hThread, &context); + + static STACKFRAME64 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; + + static char sym_buf[sizeof(SYMBOL_INFO) + 1024]; + SYMBOL_INFO* sym = (SYMBOL_INFO*)sym_buf; + sym->SizeOfStruct = sizeof(SYMBOL_INFO); + sym->MaxNameLen = 1024; + + static IMAGEHLP_LINE64 line = { sizeof(IMAGEHLP_LINE64) }; + static 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 += sprintf(out, "%I64X\r\n", pc); + continue; + } + + /* function name / address */ + u64 sym_disp; + if(type_avail && _SymFromAddr(hProcess, pc, &sym_disp, sym)) + out += sprintf(out, "%s", sym->Name); + else + out += sprintf(out, "%I64X", pc); + + /* source file + line number */ + DWORD line_disp; + if(_SymGetLineFromAddr64(hProcess, pc, &line_disp, &line)) + out += sprintf(out, " (%s, line %lu)", line.FileName, line.LineNumber); + + out += sprintf(out, "\r\n"); + + /* params + vars */ + var_type = NONE; + + imghlp_frame.InstructionOffset = pc; + _SymSetContext(hProcess, &imghlp_frame, 0); + _SymEnumSymbols(hProcess, 0, 0, sym_callback, &frame); + + out += sprintf(out, "\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); + } + + SetDlgItemText(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: + if(OpenClipboard(NULL)) + { + EmptyClipboard(); + HANDLE text = GlobalAlloc(GMEM_FIXED, strlen(buf)+1); + strcpy((char*)text, buf); + SetClipboardData(CF_TEXT, text); + CloseClipboard(); + } + 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); + + +void dbg_init_except_handler() +{ + prev_except_filter = SetUnhandledExceptionFilter(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 = sprintf(buf, "Exception %s at %s!%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; +} + + +int show_assert_dlg(char* file, int line, char* expr) +{ + int pos = sprintf(buf, "Assertion failed in %s, line %d: %s\r\n", file, line, expr); + walk_stack(2, buf+pos); + + return dialog(ASSERT); +} diff --git a/source/lib/sysdep/win/wdbg.h b/source/lib/sysdep/win/wdbg.h new file mode 100755 index 0000000000..ac0027f0aa --- /dev/null +++ b/source/lib/sysdep/win/wdbg.h @@ -0,0 +1,43 @@ +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +#ifdef __cplusplus +extern "C" { +#endif + + +/* + * exception handler and assert with stack trace + * (including variable / parameter types and values) + * shown in a dialog, which offers + * continue, break, suppress (ignore this assert), and exit + */ + +/* register exception handler */ +extern void dbg_init_except_handler(); + +/* recommended use: assert2(expr && "descriptive string") */ +#define assert2(expr)\ +{\ + static int suppress;\ + if(!suppress && !expr)\ + switch(show_assert_dlg(__FILE__, __LINE__, #expr))\ + {\ + case 1:\ + suppress = 1;\ + break;\ +\ + case 2:\ + __asm { int 3 } /* x86 specific; Win alternative: DebugBreak */\ + break;\ + }\ +} + +extern int show_assert_dlg(char* file, int line, char* expr); + + +#ifdef __cplusplus +} +#endif + +#endif // #ifndef __DEBUG_H__