janwas
3e874c54cf
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.
444 lines
11 KiB
C++
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, ¶m));
|
|
|
|
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");
|
|
}
|
|
}
|