0ad/source/lib/sysdep/win/wdbg.cpp
janwas c4e3037e60 adts: add LL_OPT_* defines that enable optimizations (some only make sense if there are tons of files, and need to be able to disable them for thesis). realized this would be perfect application of policy template classes, which will replace this.
fix crashdumps: was failing to write out to file (underlying cause:
current directory no longer being set)
app_hooks: add get_log_dir; used by debug and wdbg_sym

minor improvements/documentation in archive+compression+file_cache+zip

main: remove dead ScEd code

This was SVN commit r3498.
2006-02-11 22:49:09 +00:00

833 lines
25 KiB
C++
Executable File

// Windows debug backend
// 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"
#include "posix.h"
#include "sysdep/cpu.h"
#include "wdbg.h"
#include "byte_order.h" // FOURCC
#include "app_hooks.h"
#pragma data_seg(WIN_CALLBACK_PRE_MAIN(b))
WIN_REGISTER_FUNC(wdbg_init);
#pragma data_seg()
// used to prevent the vectored exception handler from taking charge when
// an exception is raised from the main thread (allows __try blocks to
// get control). latched in wdbg_init.
static DWORD main_thread_id;
// protects the breakpoint helper thread.
static void lock()
{
win_lock(WDBG_CS);
}
static void unlock()
{
win_unlock(WDBG_CS);
}
// used in a desperate attempt to avoid deadlock in wdbg_exception_handler.
static bool is_locked()
{
return win_is_locked(WDBG_CS) == 1;
}
void debug_puts(const char* text)
{
OutputDebugStringA(text);
}
//////////////////////////////////////////////////////////////////////////////
// inform the debugger of the current thread's description, which it then
// displays instead of just the thread handle.
void wdbg_set_thread_name(const char* name)
{
// we pass information to the debugger via a special exception it
// swallows. if not running under one, bail now to avoid
// "first chance exception" warnings.
if(!IsDebuggerPresent())
return;
// presented by Jay Bazuzi (from the VC debugger team) at TechEd 1999.
const struct ThreadNameInfo
{
DWORD type;
const char* name;
DWORD thread_id; // any valid ID or -1 for current thread
DWORD flags;
}
info = { 0x1000, name, (DWORD)-1, 0 };
__try
{
RaiseException(0x406D1388, 0, sizeof(info)/sizeof(DWORD), (DWORD*)&info);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
// if we get here, the debugger didn't handle the exception.
debug_warn("thread name hack doesn't work under this debugger");
}
}
//-----------------------------------------------------------------------------
// debug memory allocator
//-----------------------------------------------------------------------------
// check heap integrity (independently of mmgr).
// errors are reported by the CRT or via debug_display_error.
void debug_heap_check()
{
int ret;
__try
{
ret = _heapchk();
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
ret = _HEAPBADNODE;
}
if(ret != _HEAPOK)
DISPLAY_ERROR(L"debug_heap_check: heap is corrupt");
}
// call at any time; from then on, the specified checks will be performed.
// if not called, the default is DEBUG_HEAP_NONE, i.e. do nothing.
void debug_heap_enable(DebugHeapChecks what)
{
// note: if MMGR is enabled, crtdbg.h will not have been included.
// in that case, we do nothing here - this interface is too basic to
// control mmgr; use its API instead.
#if !CONFIG_USE_MMGR && HAVE_VC_DEBUG_ALLOC
uint flags = _CrtSetDbgFlag(_CRTDBG_REPORT_FLAG);
switch(what)
{
case DEBUG_HEAP_NONE:
// note: do not set flags to zero because we might trash some
// important flag value.
flags &= ~(_CRTDBG_CHECK_ALWAYS_DF|_CRTDBG_DELAY_FREE_MEM_DF |
_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
break;
case DEBUG_HEAP_NORMAL:
flags |= _CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF;
break;
case DEBUG_HEAP_ALL:
flags |= (_CRTDBG_CHECK_ALWAYS_DF|_CRTDBG_DELAY_FREE_MEM_DF |
_CRTDBG_ALLOC_MEM_DF|_CRTDBG_LEAK_CHECK_DF);
break;
default:
debug_assert("debug_heap_enable: invalid what");
}
_CrtSetDbgFlag(flags);
#else
UNUSED2(what);
#endif // HAVE_DEBUGALLOC
}
//-----------------------------------------------------------------------------
// to avoid deadlock, be VERY CAREFUL to avoid anything that may block,
// including locks taken by the OS (e.g. malloc, GetProcAddress).
typedef LibError (*WhileSuspendedFunc)(HANDLE hThread, void* user_arg);
struct WhileSuspendedParam
{
HANDLE hThread;
WhileSuspendedFunc func;
void* user_arg;
};
static void* while_suspended_thread_func(void* user_arg)
{
debug_set_thread_name("suspender");
WhileSuspendedParam* param = (WhileSuspendedParam*)user_arg;
DWORD 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");
return (void*)(intptr_t)-1;
}
// target is now guaranteed to be suspended,
// since the Windows counter never goes negative.
LibError ret = param->func(param->hThread, param->user_arg);
WARN_IF_FALSE(ResumeThread(param->hThread));
return (void*)(intptr_t)ret;
}
static LibError 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 ERR_FAIL;
}
WhileSuspendedParam param = { hThread, func, user_arg };
pthread_t thread;
WARN_ERR(pthread_create(&thread, 0, while_suspended_thread_func, &param));
void* ret;
err = pthread_join(thread, &ret);
debug_assert(err == 0 && ret == 0);
return (LibError)(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;
// IA32 limit; will need to update brk_enable_in_ctx if this changes.
static const uint MAX_BREAKPOINTS = 4;
// remove all breakpoints enabled by debug_set_break from <context>.
// called while target is suspended.
static LibError brk_disable_all_in_ctx(BreakInfo* UNUSED(bi), CONTEXT* context)
{
context->Dr7 &= ~brk_all_local_enables;
return ERR_OK;
}
// find a free register, set type according to <bi> and
// mark it as enabled in <context>.
// called while target is suspended.
static LibError brk_enable_in_ctx(BreakInfo* bi, CONTEXT* context)
{
uint 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:
// store breakpoint address in debug register.
DWORD addr = (DWORD)bi->addr;
// .. note: treating Dr0..Dr3 as an array is unsafe due to
// possible struct member padding.
switch(reg)
{
case 0: context->Dr0 = addr; break;
case 1: context->Dr1 = addr; break;
case 2: context->Dr2 = addr; break;
case 3: context->Dr3 = addr; break;
default: UNREACHABLE;
}
// choose breakpoint settings:
// .. 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("invalid type");
}
// .. length (determine 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
}
// update Debug Control register
const uint shift = (16 + reg*4);
// .. clear previous contents of this reg's field
// (in case the previous user didn't do so on disabling).
context->Dr7 &= ~(0xFu << shift);
// .. write settings
context->Dr7 |= ((len << 2)|rw) << shift;
// .. mark as enabled
context->Dr7 |= LE;
brk_all_local_enables |= LE;
return ERR_OK;
}
// carry out the request stored in the BreakInfo* parameter.
// called while target is suspended.
static LibError brk_do_request(HANDLE hThread, void* arg)
{
LibError 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 CPU_IA32
if(bi->want_all_disabled)
ret = brk_disable_all_in_ctx(bi, &context);
else
ret = brk_enable_in_ctx (bi, &context);
#else
#error "port"
#endif
if(!SetThreadContext(hThread, &context))
{
debug_warn("brk_do_request: SetThreadContext failed");
goto fail;
}
return ret;
fail:
return ERR_FAIL;
}
// 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.
LibError debug_set_break(void* p, DbgBreakType type)
{
lock();
brk_info.addr = (uintptr_t)p;
brk_info.type = type;
LibError 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.
LibError debug_remove_all_breaks()
{
lock();
brk_info.want_all_disabled = true;
LibError ret = call_while_suspended(brk_do_request, &brk_info);
brk_info.want_all_disabled = false;
unlock();
return ret;
}
//////////////////////////////////////////////////////////////////////////////
//
// exception handler
//
//////////////////////////////////////////////////////////////////////////////
//
// 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_buf[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)
{
#if MSC_VERSION
// notes:
// - value of multibyte character constants (e.g. 'msc') aren't
// specified by C++, so use FOURCC instead.
// - "MS C" compiler is the only interpretation of this magic value that
// makes sense, so it is apparently stored in big-endian format.
if(er->ExceptionCode != FOURCC_BE(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_buf, ARRAY_SIZE(description_buf), L"%hs(\"%hs\")", type_name, what);
return description_buf;
}
// 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_buf, ARRAY_SIZE(description_buf), ah_translate(fmt), ah_translate(op), ei[1]);
return description_buf;
}
// 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 to receive 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_buf, ARRAY_SIZE(description_buf), L"Unknown (0x%08X)", code);
return description_buf;
}
// 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 debug_get_nth_caller to
// determine the real location.
void* func = debug_get_nth_caller(1, ep->ContextRecord);
// skip RaiseException
char func_name[DBG_SYMBOL_LEN];
char file[DBG_FILE_LEN] = {0};
int line = 0;
(void)debug_resolve_symbol(func, func_name, file, &line);
// note: file is the base path only (no drive letter), so there are
// no problems with wdbg_exception_filter's "%[^:]" format string.
// note: keep formatting in sync with wdbg_exception_filter, which
// extracts file/line for use with debug_display_error.
static wchar_t locus[256];
swprintf(locus, ARRAY_SIZE(locus), L"%hs (%hs:%d)", func_name, file, line);
return locus;
}
// called* when an SEH exception was not caught by the app;
// provides detailed debugging information and exits.
// (via win.cpp!entry's __except or vectored_exception_handler; see below)
//
// note: keep memory allocs and locking to an absolute minimum, because
// they may deadlock the process!
//
// rationale:
// we want to replace the OS "program error" dialog box because
// it is not all too helpful in debugging. to that end, there are
// 5 ways to make sure unhandled SEH exceptions are caught:
// - via WaitForDebugEvent; the app is run from a separate debugger process.
// this complicates analysis, since the exception is in another
// address space. also, we are basically implementing a full-featured
// debugger - overkill.
// - by wrapping all threads in __try (necessary since the handler chain
// is in TLS). this is very difficult to guarantee.
// - with a vectored exception handler. this works across threads, but
// is never called when the process is being debugged
// (messing with the PEB flag doesn't help; root cause is the
// Win32 KiUserExceptionDispatcher implementation).
// worse, it's only available on WinXP (unacceptable).
// - by setting the per-process unhandled exception filter. as above,
// this works across threads and isn't called while a debugger is active;
// it is at least portable across Win32. unfortunately, some Win32 DLLs
// appear to register their own handlers, so this isn't reliable.
// - by hooking the exception dispatcher. this isn't future-proof.
//
// so, SNAFU. we compromise and register a regular __except filter at
// program entry point and add a vectored exception handler
// (if supported by the OS) to cover other threads.
// we steer clear of the process-wide unhandled exception filter,
// because it is not understood in what cases it can be overwritten;
// this precludes reliable use.
//
// since C++ exceptions are implemented via SEH, we can also catch those here;
// it's nicer than a global try{} and avoids duplicating this code.
// 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.
extern void wdbg_write_minidump(EXCEPTION_POINTERS* ep);
LONG WINAPI wdbg_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_CONTINUE_SEARCH;
// someone is already holding the dbghelp lock - this is bad.
// we'll report this problem first and then try to display the
// exception info regardless (maybe dbghelp won't blow up).
if(is_locked())
DISPLAY_ERROR(L"Exception raised while critical section is held - may deadlock..");
// extract details from ExceptionRecord.
const wchar_t* description = get_exception_description(ep);
const wchar_t* locus = get_exception_locus (ep);
wchar_t fmt[50]; wchar_t func_name[DBG_SYMBOL_LEN] = L"?"; char file[DBG_FILE_LEN] = "?"; int line = 0;
swprintf(fmt, ARRAY_SIZE(fmt), L"%%%ds (%%%dh[^:]:%%d)", DBG_SYMBOL_LEN, DBG_FILE_LEN);
// bake in the string limits (future-proof)
(void)swscanf(locus, fmt, func_name, file, &line);
// don't care whether all 3 fields were filled (they default to "?")
// this must happen before the error dialog because user could choose to
// exit immediately there.
wdbg_write_minidump(ep);
wchar_t buf[500];
const wchar_t* msg_fmt =
L"Much to our regret we must report the program has encountered an error.\r\n"
L"\r\n"
L"Please let us know at http://bugs.wildfiregames.com/ and attach the crashlog.txt and crashlog.dmp files.\r\n"
L"\r\n"
L"Details: unhandled exception (%s at %s)\r\n";
swprintf(buf, ARRAY_SIZE(buf), msg_fmt, description, locus);
int flags = 0;
if(ep->ExceptionRecord->ExceptionFlags & EXCEPTION_NONCONTINUABLE)
flags = DE_NO_CONTINUE;
ErrorReaction er = debug_display_error(buf, flags, 1,ep->ContextRecord, file,line);
debug_assert(er > 0);
// invoke the Win32 default handler - it calls ExitProcess for
// most exception types.
return EXCEPTION_CONTINUE_SEARCH;
}
static LONG WINAPI vectored_exception_handler(EXCEPTION_POINTERS* ep)
{
// since we're called from the vectored handler chain,
// ignore exceptions from the main thread. this allows
// __try blocks to take charge; entry() catches all exceptions with a
// standard filter and relays them to wdbg_exception_filter.
if(main_thread_id == GetCurrentThreadId())
return EXCEPTION_CONTINUE_SEARCH;
return wdbg_exception_filter(ep);
}
static LibError wdbg_init(void)
{
// see decl
main_thread_id = GetCurrentThreadId();
// add vectored exception handler (if supported by the OS).
// see rationale above.
#if _WIN32_WINNT >= 0x0500 // this is how winbase.h tests for it
const HMODULE hKernel32Dll = LoadLibrary("kernel32.dll");
PVOID (WINAPI *pAddVectoredExceptionHandler)(IN ULONG FirstHandler, IN PVECTORED_EXCEPTION_HANDLER VectoredHandler);
*(void**)&pAddVectoredExceptionHandler = GetProcAddress(hKernel32Dll, "AddVectoredExceptionHandler");
FreeLibrary(hKernel32Dll);
// make sure the reference is released so BoundsChecker
// doesn't complain. it won't actually be unloaded anyway -
// there is at least one other reference.
if(pAddVectoredExceptionHandler)
pAddVectoredExceptionHandler(TRUE, vectored_exception_handler);
#endif
return ERR_OK;
}
// return 1 if the pointer appears to be totally bogus, otherwise 0.
// this check is not authoritative (the pointer may be "valid" but incorrect)
// but can be used to filter out obviously wrong values in a portable manner.
int debug_is_pointer_bogus(const void* p)
{
#if CPU_IA32
if(p < (void*)0x10000)
return true;
if(p >= (void*)(uintptr_t)0x80000000)
return true;
#endif
// notes:
// - we don't check alignment because nothing can be assumed about a
// string pointer and we mustn't reject any actually valid pointers.
// - nor do we bother checking the address against known stack/heap areas
// because that doesn't cover everything (e.g. DLLs, VirtualAlloc).
// - cannot use IsBadReadPtr because it accesses the mem
// (false alarm for reserved address space).
return false;
}
bool debug_is_code_ptr(void* p)
{
uintptr_t addr = (uintptr_t)p;
// totally invalid pointer
if(debug_is_pointer_bogus(p))
return false;
// comes before load address
static const HMODULE base = GetModuleHandle(0);
if(addr < (uintptr_t)base)
return false;
return true;
}
static NT_TIB* get_tib()
{
NT_TIB* tib;
__asm
{
mov eax, fs:[NT_TIB.Self]
mov [tib], eax
}
return tib;
}
bool debug_is_stack_ptr(void* p)
{
uintptr_t addr = (uintptr_t)p;
// totally invalid pointer
if(debug_is_pointer_bogus(p))
return false;
// not aligned
if(addr % sizeof(void*))
return false;
// out of bounds (note: IA32 stack grows downwards)
NT_TIB* tib = get_tib();
if(!(tib->StackLimit < p && p < tib->StackBase))
return false;
return true;
}