1
0
forked from 0ad/0ad
0ad/source/lib/sysdep/win/wdbg.cpp
janwas 3e874c54cf manual build.
aken: add provision for copying physical memory (safer than mapping)
acpi uses that to avoid problems due to nonaligned ACPI tables (bonus:
wastes less memory)

wdbg: remove unnecessary headers
wseh: add comments
wstartup: fix return type (since we're now called by _initterm_e)

This was SVN commit r5159.
2007-06-10 13:25:09 +00:00

444 lines
11 KiB
C++

/**
* =========================================================================
* File : wdbg.cpp
* Project : 0 A.D.
* Description : Win32 debug support code and exception handler.
* =========================================================================
*/
// license: GPL; see lib/license.txt
#include "precompiled.h"
#include "wdbg.h"
#include "lib/bits.h"
#include "win.h"
#include "wutil.h"
// protects the breakpoint helper thread.
static void lock()
{
win_lock(WDBG_CS);
}
static void unlock()
{
win_unlock(WDBG_CS);
}
static NT_TIB* get_tib()
{
#if CPU_IA32
NT_TIB* tib;
__asm
{
mov eax, fs:[NT_TIB.Self]
mov [tib], eax
}
return tib;
#endif
}
//-----------------------------------------------------------------------------
// 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
}
//-----------------------------------------------------------------------------
// thread suspension
// suspend a thread, execute a user callback, revive the thread.
// 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)
WARN_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.
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;
};
static BreakInfo brk_info;
// Local Enable bits of all registers we enabled (used when restoring all).
static DWORD brk_all_local_enables;
// IA-32 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 INFO::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;
}
WARN_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;
NODEFAULT;
}
// 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 INFO::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))
WARN_RETURN(ERR::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))
WARN_RETURN(ERR::FAIL);
RETURN_ERR(ret);
return INFO::OK;
}
// 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;
}
//-----------------------------------------------------------------------------
// 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;
}
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: IA-32 stack grows downwards)
NT_TIB* tib = get_tib();
if(!(tib->StackLimit < p && p < tib->StackBase))
return false;
return true;
}
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");
}
}