0ad/source/lib/sysdep/win/wdbg.cpp

2809 lines
71 KiB
C++
Executable File

// 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 <stdlib.h>
#include <stdio.h>
#include "lib.h"
#include "win_internal.h"
#define _NO_CVCONST_H // request SymTagEnum be defined
#include "dbghelp.h"
#include <OAIdl.h> // VARIANT
#include "posix.h"
// 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")
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.
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,
};
// return localized version of <text>, if i18n functionality is available.
// this is used to translate the "unhandled exception" dialog strings.
// WARNING: leaks memory returned by wcsdup, but that's ok since the
// program will terminate soon after. fixing this is hard and senseless.
static const wchar_t* translate(const wchar_t* text)
{
#ifdef HAVE_I18N
// make sure i18n system is (already|still) initialized.
if(g_CurrentLocale)
{
// be prepared for this to fail, because translation potentially
// involves script code and the JS context might be corrupted.
__try
{
const wchar_t* text2 = wcsdup(I18n::translate(text).c_str());
// only overwrite if wcsdup succeeded, i.e. not out of memory.
if(text2)
text = text2;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
}
#endif
return text;
}
// convenience wrapper using translate.
static void translate_and_display_msg(const wchar_t* caption, const wchar_t* text)
{
wdisplay_msg(translate(caption), translate(text));
}
//////////////////////////////////////////////////////////////////////////////
// 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.
// max # characters (including \0) output by debug_(w)printf in one call.
static const int MAX_CNT = 512;
void debug_printf(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 debug_wprintf(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);
}
void debug_check_heap()
{
__try
{
_heapchk();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
}
//////////////////////////////////////////////////////////////////////////////
// to avoid deadlock, be VERY CAREFUL to avoid anything that may block,
// including locks taken by the OS (e.g. malloc, GetProcAddress).
typedef int(*WhileSuspendedFunc)(HANDLE hThread, void* user_arg);
struct WhileSuspendedParam
{
HANDLE hThread;
WhileSuspendedFunc func;
void* user_arg;
};
static void* while_suspended_thread_func(void* user_arg)
{
DWORD err;
WhileSuspendedParam* param = (WhileSuspendedParam*)user_arg;
err = SuspendThread(param->hThread);
// abort, since GetThreadContext only works if the target is suspended.
if(err == (DWORD)-1)
{
debug_warn("while_suspended_thread_func: SuspendThread failed");
goto fail;
}
// target is now guaranteed to be suspended,
// since the Windows counter never goes negative.
int ret = param->func(param->hThread, param->user_arg);
err = ResumeThread(param->hThread);
assert(err != 0);
return (void*)(intptr_t)ret;
fail:
return (void*)(intptr_t)-1;
}
static int call_while_suspended(WhileSuspendedFunc func, void* user_arg)
{
int err;
// we need a real HANDLE to the target thread for use with
// Suspend|ResumeThread and GetThreadContext.
// alternative: DuplicateHandle on the current thread pseudo-HANDLE.
// this way is a bit more obvious/simple.
const DWORD access = THREAD_GET_CONTEXT|THREAD_SET_CONTEXT|THREAD_SUSPEND_RESUME;
HANDLE hThread = OpenThread(access, FALSE, GetCurrentThreadId());
if(hThread == INVALID_HANDLE_VALUE)
{
debug_warn("OpenThread failed");
return -1;
}
WhileSuspendedParam param = { hThread, func, user_arg };
pthread_t thread;
err = pthread_create(&thread, 0, while_suspended_thread_func, &param);
assert2(err == 0);
void* ret;
err = pthread_join(thread, &ret);
assert2(err == 0 && ret == 0);
return (int)(intptr_t)ret;
}
//////////////////////////////////////////////////////////////////////////////
//
// breakpoints
//
//////////////////////////////////////////////////////////////////////////////
// breakpoints are set by storing the address of interest in a
// debug register and marking it 'enabled'.
//
// the first problem is, they are only accessible from Ring0;
// we get around this by updating their values via SetThreadContext.
// that in turn requires we suspend the current thread,
// spawn a helper to change the registers, and resume.
// parameter passing to helper thread. currently static storage,
// but the struct simplifies switching to a queue later.
static struct BreakInfo
{
uintptr_t addr;
DbgBreakType type;
// determines what brk_thread_func will do.
// set/reset by debug_remove_all_breaks.
bool want_all_disabled;
}
brk_info;
// Local Enable bits of all registers we enabled (used when restoring all).
static DWORD brk_all_local_enables;
static const uint MAX_BREAKPOINTS = 4;
// IA-32 limit; if this changes, make sure brk_enable still works!
// (we assume CONTEXT has contiguous Dr0..Dr3 register fields)
// remove all breakpoints enabled by debug_set_break from <context>.
// called while target is suspended.
static int brk_disable_all_in_ctx(BreakInfo* bi, CONTEXT* context)
{
context->Dr7 &= ~brk_all_local_enables;
return 0;
}
// find a free register, set type according to <bi> and
// mark it as enabled in <context>.
// called while target is suspended.
static int brk_enable_in_ctx(BreakInfo* bi, CONTEXT* context)
{
int reg; // index (0..3) of first free reg
uint LE; // local enable bit for <reg>
// find free debug register.
for(reg = 0; reg < MAX_BREAKPOINTS; reg++)
{
LE = BIT(reg*2);
// .. this one is currently not in use.
if((context->Dr7 & LE) == 0)
goto have_reg;
}
debug_warn("brk_enable_in_ctx: no register available");
return ERR_LIMIT;
have_reg:
// set value and mark as enabled.
(&context->Dr0)[reg] = (DWORD)bi->addr; // see MAX_BREAKPOINTS
context->Dr7 |= LE;
brk_all_local_enables |= LE;
// build Debug Control Register value.
// .. type
uint rw = 0;
switch(bi->type)
{
case DBG_BREAK_CODE:
rw = 0; break;
case DBG_BREAK_DATA:
rw = 1; break;
case DBG_BREAK_DATA_WRITE:
rw = 3; break;
default:
debug_warn("brk_enable_in_ctx: invalid type");
}
// .. length (determined from addr's alignment).
// note: IA-32 requires len=0 for code breakpoints.
uint len = 0;
if(bi->type != DBG_BREAK_CODE)
{
const uint alignment = (uint)(bi->addr % 4);
// assume 2 byte range
if(alignment == 2)
len = 1;
// assume 4 byte range
else if(alignment == 0)
len = 3;
// else: 1 byte range; len already set to 0
}
const uint shift = (16 + reg*4);
const uint field = (len << 2) | rw;
// clear previous contents of this reg's field
// (in case the previous user didn't do so on disabling).
const uint mask = 0xFu << shift;
context->Dr7 &= ~mask;
context->Dr7 |= field << shift;
return 0;
}
// carry out the request stored in the BreakInfo* parameter.
// called while target is suspended.
static int brk_do_request(HANDLE hThread, void* arg)
{
int ret;
BreakInfo* bi = (BreakInfo*)arg;
CONTEXT context;
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
if(!GetThreadContext(hThread, &context))
{
debug_warn("brk_do_request: GetThreadContext failed");
goto fail;
}
#if defined(_M_IX86)
if(bi->want_all_disabled)
ret = brk_disable_all_in_ctx(bi, &context);
else
ret = brk_enable_in_ctx (bi, &context);
if(!SetThreadContext(hThread, &context))
{
debug_warn("brk_do_request: SetThreadContext failed");
goto fail;
}
#else
#error "port"
#endif
return 0;
fail:
return -1;
}
// arrange for a debug exception to be raised when <addr> is accessed
// according to <type>.
// for simplicity, the length (range of bytes to be checked) is
// derived from addr's alignment, and is typically 1 machine word.
// breakpoints are a limited resource (4 on IA-32); abort and
// return ERR_LIMIT if none are available.
int debug_set_break(void* p, DbgBreakType type)
{
lock();
brk_info.addr = (uintptr_t)p;
brk_info.type = type;
int ret = call_while_suspended(brk_do_request, &brk_info);
unlock();
return ret;
}
// remove all breakpoints that were set by debug_set_break.
// important, since these are a limited resource.
int debug_remove_all_breaks()
{
lock();
brk_info.want_all_disabled = true;
int ret = call_while_suspended(brk_do_request, &brk_info);
brk_info.want_all_disabled = false;
unlock();
return ret;
}
//////////////////////////////////////////////////////////////////////////////
//
// 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 <user_arg>.
// 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 <pcontext> != 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 <addr>?
// set <stride> 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 <idx> 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 <skip> 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)&params);
// 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;
if(!GetImpersonationToken(&hImpersonationToken.m_h))
{
return FALSE;
}
// We need the SeDebugPrivilege to be able to run MiniDumpWriteDump
TOKEN_PRIVILEGES tp;
BOOL bPrivilegeEnabled = EnablePriv(SE_DEBUG_NAME, hImpersonationToken, &tp);
// DBGHELP.DLL is not thread safe
EnterCriticalSection(pCS);
bRet = pDumpFunction(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpWithDataSegs, &stInfo, NULL, NULL);
LeaveCriticalSection(pCS);
if(bPrivilegeEnabled)
{
// Restore the privilege
RestorePriv(hImpersonationToken, &tp);
}
static BOOL GetImpersonationToken(HANDLE* phToken)
{
*phToken = NULL;
if(!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, TRUE, phToken))
{
if(GetLastError() == ERROR_NO_TOKEN)
{
// No impersonation token for the curren thread available - go for the process token
if(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, phToken))
{
return FALSE;
}
}
else
{
return FALSE;
}
}
return TRUE;
}
static BOOL EnablePriv(LPCTSTR pszPriv, HANDLE hToken, TOKEN_PRIVILEGES* ptpOld)
{
BOOL bOk = FALSE;
TOKEN_PRIVILEGES tp;
tp.PrivilegeCount = 1;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
bOk = LookupPrivilegeValue( 0, pszPriv, &tp.Privileges[0].Luid);
if(bOk)
{
DWORD cbOld = sizeof(*ptpOld);
bOk = AdjustTokenPrivileges(hToken, FALSE, &tp, cbOld, ptpOld, &cbOld);
}
return (bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()));
}
static BOOL RestorePriv(HANDLE hToken, TOKEN_PRIVILEGES* ptpOld)
{
BOOL bOk = AdjustTokenPrivileges(hToken, FALSE, ptpOld, 0, 0, 0);
return (bOk && (ERROR_NOT_ALL_ASSIGNED != GetLastError()));
}
BOOL SetPrivilege(
HANDLE hToken, // token handle
LPCTSTR Privilege, // Privilege to enable/disable
BOOL bEnablePrivilege // TRUE to enable. FALSE to disable
)
{
TOKEN_PRIVILEGES tp;
LUID luid;
TOKEN_PRIVILEGES tpPrevious;
DWORD cbPrevious=sizeof(TOKEN_PRIVILEGES);
if(!LookupPrivilegeValue( NULL, Privilege, &luid )) return FALSE;
//
// first pass. get current privilege setting
//
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = 0;
AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
&tpPrevious,
&cbPrevious
);
if (GetLastError() != ERROR_SUCCESS) return FALSE;
//
// second pass. set privilege based on previous setting
//
tpPrevious.PrivilegeCount = 1;
tpPrevious.Privileges[0].Luid = luid;
if(bEnablePrivilege) {
tpPrevious.Privileges[0].Attributes |= (SE_PRIVILEGE_ENABLED);
}
else {
tpPrevious.Privileges[0].Attributes ^= (SE_PRIVILEGE_ENABLED &
tpPrevious.Privileges[0].Attributes);
}
AdjustTokenPrivileges(
hToken,
FALSE,
&tpPrevious,
cbPrevious,
NULL,
NULL
);
if (GetLastError() != ERROR_SUCCESS) return FALSE;
return TRUE;
}
BOOL SetPrivilege2(
HANDLE hToken, // token handle
LPCTSTR Privilege, // Privilege to enable/disable
BOOL bEnablePrivilege // TRUE to enable. FALSE to disable
)
{
TOKEN_PRIVILEGES tp = { 0 };
// Initialize everything to zero
LUID luid;
DWORD cb=sizeof(TOKEN_PRIVILEGES);
if(!LookupPrivilegeValue( NULL, Privilege, &luid ))
return FALSE;
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
if(bEnablePrivilege) {
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
} else {
tp.Privileges[0].Attributes = 0;
}
AdjustTokenPrivileges( hToken, FALSE, &tp, cb, NULL, NULL );
if (GetLastError() != ERROR_SUCCESS)
return FALSE;
return TRUE;
}
extern WINBASEAPI LANGID WINAPI GetSystemDefaultLangID (void);
void DisplayError(
LPTSTR szAPI // pointer to failed API name
)
{
LPTSTR MessageBuffer;
DWORD dwBufferLength;
fprintf(stderr,"%s() error!\n", szAPI);
/*
if(dwBufferLength=FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
GetSystemDefaultLangID(),
(LPTSTR) &MessageBuffer,
0,
NULL
))
{
DWORD dwBytesWritten;
//
// Output message string on stderr
//
WriteFile(
GetStdHandle(STD_ERROR_HANDLE),
MessageBuffer,
dwBufferLength,
&dwBytesWritten,
NULL
);
//
// free the buffer allocated by the system
//
LocalFree(MessageBuffer);
}
}
static int screwaround()
{
HANDLE hProcess;
HANDLE hToken;
int dwRetVal=RTN_OK; // assume success from main()
if(!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &hToken))
{
if (GetLastError() == ERROR_NO_TOKEN)
{
if (!ImpersonateSelf(SecurityImpersonation))
return RTN_ERROR;
if(!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, FALSE, &hToken)){
DisplayError("OpenThreadToken");
return RTN_ERROR;
}
}
else
return RTN_ERROR;
}
// enable SeDebugPrivilege
if(!SetPrivilege(hToken, SE_DEBUG_NAME, TRUE))
{
DisplayError("SetPrivilege");
// close token handle
CloseHandle(hToken);
// indicate failure
return RTN_ERROR;
}
// disable SeDebugPrivilege
SetPrivilege(hToken, SE_DEBUG_NAME, FALSE);
}
*/
//
// analyze exceptions; determine their type and locus
//
// storage for strings built by get_SEH_exception_description and get_cpp_exception_description.
static wchar_t description[128];
// VC++ exception handling internals.
// see http://www.codeproject.com/cpp/exceptionhandler.asp
struct XTypeInfo
{
DWORD _;
const std::type_info* ti;
// ..
};
struct XTypeInfoArray
{
DWORD count;
const XTypeInfo* types[1];
};
struct XInfo
{
DWORD _[3];
const XTypeInfoArray* array;
};
// does the given SEH exception look like a C++ exception?
// (compiler-specific).
static bool isCppException(const EXCEPTION_RECORD* er)
{
#ifdef _MSC_VER
// note: representation of 'msc' isn't specified, so use FOURCC
if(er->ExceptionCode != FOURCC(0xe0, 'm','s','c'))
return false;
// exception info = (magic, &thrown_Cpp_object, &XInfo)
if(er->NumberParameters != 3)
return false;
// MAGIC_NUMBER1 from exsup.inc
if(er->ExceptionInformation[0] != 0x19930520)
return false;
return true;
#else
# error "port"
#endif
}
// if <er> is not a C++ exception, return 0. otherwise, return a description
// of the exception type and cause (in English). uses static storage.
static const wchar_t* get_cpp_exception_description(const EXCEPTION_RECORD* er)
{
if(!isCppException(er))
return 0;
// see above for interpretation
const ULONG_PTR* const ei = er->ExceptionInformation;
// note: we can't share a __try below - the failure of
// one attempt must not abort the others.
// get std::type_info
char type_buf[100] = {'\0'};
const char* type_name = type_buf;
__try
{
const XInfo* xi = (XInfo*)ei[2];
const XTypeInfoArray* xta = xi->array;
const XTypeInfo* xti = xta->types[0];
const std::type_info* ti = xti->ti;
// strip "class " from start of string (clutter)
strcpy_s(type_buf, ARRAY_SIZE(type_buf), ti->name());
if(!strncmp(type_buf, "class ", 6))
type_name += 6;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
// std::exception.what()
char what[100] = {'\0'};
__try
{
std::exception* e = (std::exception*)ei[1];
strcpy_s(what, ARRAY_SIZE(what), e->what());
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
}
// we got meaningful data; format and return it.
if(type_name[0] != '\0' || what[0] != '\0')
{
swprintf(description, ARRAY_SIZE(description), L"%hs(\"%hs\")", type_name, what);
return description;
}
// not a C++ exception; we can't say anything about it.
return 0;
}
// return a description of the exception type (in English).
// uses static storage.
static const wchar_t* get_SEH_exception_description(const EXCEPTION_RECORD* er)
{
const DWORD code = er->ExceptionCode;
const ULONG_PTR* ei = er->ExceptionInformation;
// special case for access violations: display type and address.
if(code == EXCEPTION_ACCESS_VIOLATION)
{
const wchar_t* op = (ei[0])? L"writing" : L"reading";
const wchar_t* fmt = L"Access violation %s 0x%08X";
swprintf(description, ARRAY_SIZE(description), translate(fmt), translate(op), ei[1]);
return description;
}
// rationale: we don't use FormatMessage because it is unclear whether
// NTDLL's symbol table will always include English-language strings
// (we don't want crashlogs in foreign gobbledygook).
// it also adds unwanted formatting (e.g. {EXCEPTION} and trailing .).
switch(code)
{
// case EXCEPTION_ACCESS_VIOLATION: return L"Access violation";
case EXCEPTION_DATATYPE_MISALIGNMENT: return L"Datatype misalignment";
case EXCEPTION_BREAKPOINT: return L"Breakpoint";
case EXCEPTION_SINGLE_STEP: return L"Single step";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return L"Array bounds exceeded";
case EXCEPTION_FLT_DENORMAL_OPERAND: return L"FPU denormal operand";
case EXCEPTION_FLT_DIVIDE_BY_ZERO: return L"FPU divide by zero";
case EXCEPTION_FLT_INEXACT_RESULT: return L"FPU inexact result";
case EXCEPTION_FLT_INVALID_OPERATION: return L"FPU invalid operation";
case EXCEPTION_FLT_OVERFLOW: return L"FPU overflow";
case EXCEPTION_FLT_STACK_CHECK: return L"FPU stack check";
case EXCEPTION_FLT_UNDERFLOW: return L"FPU underflow";
case EXCEPTION_INT_DIVIDE_BY_ZERO: return L"Integer divide by zero";
case EXCEPTION_INT_OVERFLOW: return L"Integer overflow";
case EXCEPTION_PRIV_INSTRUCTION: return L"Privileged instruction";
case EXCEPTION_IN_PAGE_ERROR: return L"In page error";
case EXCEPTION_ILLEGAL_INSTRUCTION: return L"Illegal instruction";
case EXCEPTION_NONCONTINUABLE_EXCEPTION: return L"Noncontinuable exception";
case EXCEPTION_STACK_OVERFLOW: return L"Stack overflow";
case EXCEPTION_INVALID_DISPOSITION: return L"Invalid disposition";
case EXCEPTION_GUARD_PAGE: return L"Guard page";
case EXCEPTION_INVALID_HANDLE: return L"Invalid handle";
}
// anything else => unknown; display its exception code.
// we don't punt to get_exception_description because anything
// we get called for will actually be a SEH exception.
swprintf(description, ARRAY_SIZE(description), L"Unknown (0x%08X)", code);
return description;
}
// return a description of the exception <er> (in English).
// it is only valid until the next call, since static storage is used.
static const wchar_t* get_exception_description(const EXCEPTION_POINTERS* ep)
{
const EXCEPTION_RECORD* const er = ep->ExceptionRecord;
// note: more specific than SEH, so try it first.
const wchar_t* d = get_cpp_exception_description(er);
if(d)
return d;
return get_SEH_exception_description(er);
}
// return an indication of where the exception <er> occurred (lang. neutral).
// it is only valid until the next call, since static storage is used.
static const wchar_t* get_exception_locus(const EXCEPTION_POINTERS* ep)
{
// HACK: <ep> provides no useful information - ExceptionAddress always
// points to kernel32!RaiseException. we use dump_stack to determine the
// real location.
out_reset();
const wchar_t* stack_trace = dump_stack(+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);
wchar_t* end = wcschr(locus, '\r');
if(end)
*end = '\0';
return locus;
}
// 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.
//
// 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:
// - 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().
//
// 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.
// we can still get at the C++ information (std::exception.what()) by
// examining the internal exception data structures. these are
// compiler-specific, but haven't changed from VC5-VC7.1.
// alternatively, _set_se_translator could be used to translate all
// SEH exceptions to C++. this way is more reliable/documented, but has
// several drawbacks:
// - it wouldn't work at all in C programs,
// - 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()
{
void* prev_filter = SetUnhandledExceptionFilter(unhandled_exception_filter);
if(prev_filter)
assert2("conflict with SetUnhandledExceptionFilter. must implement chaining to previous handler");
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 };
// 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()))
{
}
}
static int wdbg_init()
{
RETURN_ERR(sym_init());
// 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();
}