slight revisions to comments.
breakpoint code looks to work, but self-test isn't yet complete. This was SVN commit r2290.
This commit is contained in:
parent
add0a0aa94
commit
e4d1454fd8
@ -114,7 +114,7 @@ static void simplify_stl_name(char* name)
|
||||
// end of a loop would require a goto, instead of continue
|
||||
// (there are several paths through the loop, for speed).
|
||||
// therefore, preincrement. when skipping strings, subtract
|
||||
// 1 from the offset (since src is advanced directlry after).
|
||||
// 1 from the offset (since src is advanced directly after).
|
||||
|
||||
// end of string reached - we're done.
|
||||
if(c == '\0')
|
||||
|
@ -25,6 +25,12 @@
|
||||
#endif
|
||||
|
||||
|
||||
// check heap integrity (independently of mmgr).
|
||||
// errors are reported by the CRT, e.g. via assert.
|
||||
extern void debug_check_heap(void);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// assert
|
||||
//
|
||||
@ -69,6 +75,7 @@ STMT(\
|
||||
)
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// output
|
||||
//
|
||||
@ -77,12 +84,17 @@ STMT(\
|
||||
extern void debug_printf(const char* fmt, ...);
|
||||
|
||||
// write to memory buffer (fast)
|
||||
// used for "last activity" reporting in the crashlog.
|
||||
extern void debug_wprintf_mem(const wchar_t* fmt, ...);
|
||||
|
||||
// warn of unexpected state. less error-prone than assert(!"text");
|
||||
#define debug_warn(str) assert2(0 && (str))
|
||||
|
||||
// TODO
|
||||
extern int debug_write_crashlog(const char* file, const wchar_t* header, void* context);
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// breakpoints
|
||||
//
|
||||
@ -101,7 +113,7 @@ extern void debug_wprintf_mem(const wchar_t* fmt, ...);
|
||||
// then become apparent.
|
||||
// the VC++ IDE provides such 'breakpoints', but can only detect write access.
|
||||
// additionally, it can't resolve symbols in Release mode (where this would
|
||||
// be most useful), so we provide direct access to hardware breakpoints.
|
||||
// be most useful), so we provide a breakpoint API.
|
||||
|
||||
// values chosen to match IA-32 bit defs, so compiler can optimize.
|
||||
// this isn't required, it'll work regardless.
|
||||
@ -116,22 +128,22 @@ enum DbgBreakType
|
||||
// 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.
|
||||
extern int debug_set_break(void* addr, DbgBreakType type);
|
||||
|
||||
|
||||
//
|
||||
// memory
|
||||
//
|
||||
|
||||
// check heap integrity (independently of mmgr).
|
||||
// errors are reported by the CRT, e.g. via assert.
|
||||
extern void debug_check_heap(void);
|
||||
// remove all breakpoints that were set by debug_set_break.
|
||||
// important, since these are a limited resource.
|
||||
extern int debug_remove_all_breaks();
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// symbol access
|
||||
//
|
||||
|
||||
// TODO: rationale+comments
|
||||
|
||||
const size_t DBG_SYMBOL_LEN = 1000;
|
||||
const size_t DBG_FILE_LEN = 100;
|
||||
|
||||
@ -140,11 +152,4 @@ extern void* debug_get_nth_caller(uint n);
|
||||
extern int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* file, int* line);
|
||||
|
||||
|
||||
//
|
||||
// crash notification
|
||||
//
|
||||
|
||||
extern int debug_write_crashlog(const char* file, const wchar_t* header, void* context);
|
||||
|
||||
|
||||
#endif // #ifndef DEBUG_H_INCLUDED
|
||||
|
@ -31,6 +31,10 @@
|
||||
#include "wdbg.h"
|
||||
#include "assert_dlg.h"
|
||||
|
||||
#ifndef PERFORM_SELF_TEST
|
||||
#define PERFORM_SELF_TEST 0
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "dbghelp.lib")
|
||||
@ -172,22 +176,24 @@ void debug_wprintf(const wchar_t* fmt, ...)
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// hardware breakpoints
|
||||
// breakpoints
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// breakpoints are set by storing the address of interest in a
|
||||
// debug register and marking it 'enabled'.
|
||||
//
|
||||
// the first problem is, these are privileged; 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.
|
||||
// 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.
|
||||
//
|
||||
// we don't reuse similar code from the profiler in wcpu.cpp:
|
||||
// it is designed to interrupt the main thread periodically,
|
||||
// whereas what we need here is to interrupt any thread on-demand.
|
||||
|
||||
// parameter passing to helper thread. currently static storage,
|
||||
// but the struct simplifies switching to a queue later.
|
||||
static struct BreakInfo
|
||||
{
|
||||
// real (not pseudo) handle of thread whose context we will change
|
||||
@ -195,21 +201,25 @@ static struct BreakInfo
|
||||
|
||||
uintptr_t addr;
|
||||
DbgBreakType type;
|
||||
|
||||
bool want_all_disabled;
|
||||
}
|
||||
brk_info;
|
||||
|
||||
// Local Enable bits of all registers we enabled (used to restore all).
|
||||
// Local Enable bits of all registers we enabled (used when restoring all).
|
||||
static DWORD brk_all_local_enables;
|
||||
static bool brk_want_all_disabled;
|
||||
|
||||
|
||||
// called from brk_thread_func; return error code as void*.
|
||||
static void* brk_disable_all(BreakInfo* bi, CONTEXT* context)
|
||||
{
|
||||
context->Dr7 &= ~brk_all_local_enables;
|
||||
return (void*)1; // success
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
|
||||
// find a free register, set type according to <bi> and enable it.
|
||||
// called from brk_thread_func; return error code as void*.
|
||||
static void* brk_enable(BreakInfo* bi, CONTEXT* context)
|
||||
{
|
||||
int reg; // index (0..3) of first free reg
|
||||
@ -218,25 +228,31 @@ static void* brk_enable(BreakInfo* bi, CONTEXT* context)
|
||||
// find free debug register
|
||||
for(reg = 0; reg < 4; reg++)
|
||||
{
|
||||
LE = 1u << (reg * 2);
|
||||
if((context->Dr7 & LE) == 0) // currently not in use
|
||||
LE = BIT(reg*2);
|
||||
// .. currently not in use.
|
||||
if((context->Dr7 & LE) == 0)
|
||||
goto have_reg;
|
||||
}
|
||||
debug_warn("break_enable: no break register available");
|
||||
return 0;
|
||||
debug_warn("brk_enable: no register available");
|
||||
return (void*)(intptr_t)ERR_LIMIT;
|
||||
have_reg:
|
||||
|
||||
// set and mark as enabled/in use ASAP
|
||||
(&context->Dr0)[reg] = (DWORD)bi->addr;
|
||||
brk_all_local_enables |= LE;
|
||||
// set and mark as enabled/in use ASAP.
|
||||
context->Dr7 |= LE;
|
||||
brk_all_local_enables |= LE;
|
||||
switch(reg) // for safety; could use (&context->Dr0)[reg]
|
||||
{
|
||||
case 0: context->Dr0 = (DWORD)bi->addr; break;
|
||||
case 1: context->Dr1 = (DWORD)bi->addr; break;
|
||||
case 2: context->Dr2 = (DWORD)bi->addr; break;
|
||||
case 3: context->Dr3 = (DWORD)bi->addr; break;
|
||||
default:
|
||||
debug_warn("brk_enable: invalid reg");
|
||||
}
|
||||
|
||||
// build Debug Control Register
|
||||
// IA32 requires code breakpoints have len=1
|
||||
uint len = 1;
|
||||
if(bi->type != DBG_BREAK_CODE)
|
||||
len = (uint)((bi->addr-1) & 3);
|
||||
uint rw;
|
||||
// build Debug Control Register value.
|
||||
// .. type
|
||||
uint rw = 0;
|
||||
switch(bi->type)
|
||||
{
|
||||
case DBG_BREAK_CODE:
|
||||
@ -246,8 +262,21 @@ have_reg:
|
||||
case DBG_BREAK_DATA_WRITE:
|
||||
rw = 3; break;
|
||||
default:
|
||||
debug_warn("break_enable: invalid type");
|
||||
return 0;
|
||||
debug_warn("brk_enable: 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;
|
||||
@ -258,14 +287,16 @@ have_reg:
|
||||
context->Dr7 &= ~mask;
|
||||
|
||||
context->Dr7 |= field << shift;
|
||||
return (void*)1; // success
|
||||
return 0; // success
|
||||
}
|
||||
|
||||
|
||||
// return 1 to indicate success.
|
||||
// carry out the request stored in the BreakInfo* parameter.
|
||||
// return error code as void*.
|
||||
static void* brk_thread_func(void* arg)
|
||||
{
|
||||
DWORD err;
|
||||
void* ret;
|
||||
BreakInfo* bi = (BreakInfo*)arg;
|
||||
|
||||
err = SuspendThread(bi->hThread);
|
||||
@ -273,7 +304,7 @@ static void* brk_thread_func(void* arg)
|
||||
if(err == (DWORD)-1)
|
||||
{
|
||||
debug_warn("brk_thread_func: SuspendThread failed");
|
||||
return 0;
|
||||
goto fail;
|
||||
}
|
||||
// target is now guaranteed to be suspended,
|
||||
// since the Windows counter never goes negative.
|
||||
@ -282,40 +313,46 @@ static void* brk_thread_func(void* arg)
|
||||
|
||||
// to avoid deadlock, be VERY CAREFUL to avoid anything that may block,
|
||||
// including locks taken by the OS (e.g. malloc, GetProcAddress).
|
||||
{
|
||||
|
||||
CONTEXT context;
|
||||
context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
|
||||
if(!GetThreadContext(bi->hThread, &context))
|
||||
{
|
||||
debug_warn("brk_thread_func: GetThreadContext failed");
|
||||
return 0;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
void* ret;
|
||||
#if defined(_M_IX86)
|
||||
if(brk_want_all_disabled)
|
||||
if(bi->want_all_disabled)
|
||||
ret = brk_disable_all(bi, &context);
|
||||
else
|
||||
ret = brk_enable(bi, &context);
|
||||
|
||||
if(!SetThreadContext(bi->hThread, &context))
|
||||
{
|
||||
debug_warn("brk_thread_func: GetThreadContext failed");
|
||||
return 0;
|
||||
debug_warn("brk_thread_func: SetThreadContext failed");
|
||||
goto fail;
|
||||
}
|
||||
#else
|
||||
#error "port"
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
err = ResumeThread(bi->hThread);
|
||||
assert(err != 0);
|
||||
|
||||
return ret;
|
||||
|
||||
fail:
|
||||
return (void*)(intptr_t)-1;
|
||||
}
|
||||
|
||||
|
||||
// latch current thread and carry out the request stored in brk_info.
|
||||
static int brk_run_thread()
|
||||
{
|
||||
int err;
|
||||
@ -325,7 +362,7 @@ static int brk_run_thread()
|
||||
// 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_SUSPEND_RESUME;
|
||||
const DWORD access = THREAD_GET_CONTEXT|THREAD_SET_CONTEXT|THREAD_SUSPEND_RESUME;
|
||||
bi->hThread = OpenThread(access, FALSE, GetCurrentThreadId());
|
||||
if(bi->hThread == INVALID_HANDLE_VALUE)
|
||||
{
|
||||
@ -339,23 +376,24 @@ static int brk_run_thread()
|
||||
|
||||
void* ret;
|
||||
err = pthread_join(thread, &ret);
|
||||
assert2(err == 0 && ret == (void*)1);
|
||||
assert2(err == 0 && ret == 0);
|
||||
|
||||
return (ret == (void*)1)? 0 : -1;
|
||||
return (int)(intptr_t)ret;
|
||||
}
|
||||
|
||||
|
||||
// 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.
|
||||
// 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();
|
||||
|
||||
BreakInfo* bi = &brk_info;
|
||||
bi->addr = (uintptr_t)p;
|
||||
bi->type = type;
|
||||
brk_info.addr = (uintptr_t)p;
|
||||
brk_info.type = type;
|
||||
int ret = brk_run_thread();
|
||||
|
||||
unlock();
|
||||
@ -363,11 +401,13 @@ int debug_set_break(void* p, DbgBreakType type)
|
||||
}
|
||||
|
||||
|
||||
// remove all breakpoints that were set by debug_set_break.
|
||||
// important, since these are a limited resource.
|
||||
int debug_remove_all_breaks()
|
||||
{
|
||||
lock();
|
||||
|
||||
brk_want_all_disabled = true;
|
||||
brk_info.want_all_disabled = true;
|
||||
int ret = brk_run_thread();
|
||||
|
||||
unlock();
|
||||
@ -375,6 +415,44 @@ int debug_remove_all_breaks()
|
||||
}
|
||||
|
||||
|
||||
#if PERFORM_SELF_TEST
|
||||
|
||||
template<class T> static void verify_natural_alignment(T* t)
|
||||
{
|
||||
uintptr_t addr = (uintptr_t)t;
|
||||
assert2(addr % sizeof(T) == 0);
|
||||
}
|
||||
|
||||
#pragma optimize("", off)
|
||||
|
||||
static void brk_self_test()
|
||||
{
|
||||
volatile char c = 0x01;
|
||||
volatile short s = 0x0202;
|
||||
volatile int i = 0x03030303;
|
||||
volatile char c4[4] = { 0x04, 0x05, 0x06, 0x07 };
|
||||
verify_natural_alignment(&c);
|
||||
verify_natural_alignment(&s);
|
||||
verify_natural_alignment(&i);
|
||||
verify_natural_alignment(&c4[0]);
|
||||
|
||||
debug_printf("brk_self_test: should trigger");
|
||||
debug_set_break((void*)&c, DBG_BREAK_DATA_WRITE);
|
||||
c = 0x0a;
|
||||
debug_remove_all_breaks();
|
||||
debug_set_break((void*)&c, DBG_BREAK_DATA);
|
||||
c = 0x0b;
|
||||
debug_remove_all_breaks();
|
||||
|
||||
debug_printf("brk_self_test: should not trigger");
|
||||
debug_set_break((void*)&c, DBG_BREAK_DATA_WRITE);
|
||||
}
|
||||
|
||||
#pragma optimize("", on)
|
||||
|
||||
#endif // #if PERFORM_SELF_TEST
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// dbghelp support routines for walking the stack
|
||||
@ -598,7 +676,7 @@ static void out(const wchar_t* fmt, ...)
|
||||
// algorithm: scan the "string" and count # text chars vs. garbage.
|
||||
static bool is_string_ptr(u64 addr, size_t stride = 1)
|
||||
{
|
||||
// completely bogus on IA32; save ourselves the segfault (slow).
|
||||
// completely bogus on IA-32; save ourselves the segfault (slow).
|
||||
#ifdef _M_IX86
|
||||
if(addr < 0x10000 || addr > 0xc0000000)
|
||||
return false;
|
||||
@ -1851,3 +1929,25 @@ LONG WINAPI debug_main_exception_filter(PEXCEPTION_POINTERS ep)
|
||||
|
||||
#endif // #ifdef EXCEPTION_HACK_0AD
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// built-in self test
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
namespace test {
|
||||
|
||||
#if PERFORM_SELF_TEST
|
||||
|
||||
static int run_tests()
|
||||
{
|
||||
brk_self_test();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dummy = run_tests();
|
||||
|
||||
#endif // #if PERFORM_SELF_TEST
|
||||
|
||||
} // namespace test
|
Loading…
Reference in New Issue
Block a user