1
0
forked from 0ad/0ad

breakpoint WIP

This was SVN commit r2280.
This commit is contained in:
janwas 2005-05-11 04:37:05 +00:00
parent b8532df921
commit 0e4624397a
2 changed files with 303 additions and 65 deletions

View File

@ -1,5 +1,5 @@
#ifndef DEBUG_H__
#define DEBUG_H__
#ifndef DEBUG_H_INCLUDED
#define DEBUG_H_INCLUDED
// we need to include the platform-specific version here, so it can
// define debug_break. if it can be implemented as a macro (e.g. on ia32),
@ -13,29 +13,7 @@
//
// logging
//
// output to the debugger (may take ~1 ms!)
extern void debug_out(const char* fmt, ...);
// log to memory buffer (fast)
#define MICROLOG debug_microlog
extern void debug_microlog(const wchar_t* fmt, ...);
const size_t DBG_SYMBOL_LEN = 1000;
const size_t DBG_FILE_LEN = 100;
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
// assert
//
// notify the user that an assertion failed.
@ -44,16 +22,9 @@ extern int debug_resolve_symbol(void* ptr_of_interest, char* sym_name, char* fil
// .. or exits the program if the user so chooses.
extern int debug_assert_failed(const char* source_file, int line, const char* assert_expr);
extern int debug_write_crashlog(const char* file, const wchar_t* header, void* context);
extern void debug_check_heap(void);
// superassert
// recommended use: assert2(expr && "descriptive string")
#define assert2(expr) STMT(\
#define assert2(expr)\
STMT(\
static int suppress__ = 0;\
if(!suppress__ && !(expr))\
switch(debug_assert_failed(__FILE__, __LINE__, #expr))\
@ -68,15 +39,74 @@ extern void debug_check_heap(void);
)
//
// output
//
// output to the debugger (may take ~1 ms!)
extern void debug_out(const char* fmt, ...);
// log to memory buffer (fast)
#define MICROLOG debug_microlog
extern void debug_microlog(const wchar_t* fmt, ...);
#define debug_warn(str) assert2(0 && (str))
#ifdef _WIN32
# define debug_warn(str) assert2(0 && (str))
#else
# define debug_warn(str) debug_out("Debug Warning Fired at %s:%d: %s\n", __FILE__, __LINE__, str)
#endif
//
// breakpoints
//
//
//#define debug_break()
// sometimes mmgr's 'fences' (making sure padding before and after the
// allocation remains intact) aren't enough to catch hard-to-find
// memory corruption bugs. another tool is to trigger a debug exception
// when the later to be corrupted variable is accessed; the problem should
// then become apparent.
// the VC++ IDE provides such 'breakpoints', but
#endif // #ifndef DEBUG_H__
// values chosen to match IA-32 bit defs, so compiler can optimize.
// this isn't required, it'll work regardless.
enum DbgBreakType
{
DBG_BREAK_CODE = 0,
DBG_BREAK_DATA_WRITE = 1,
DBG_BREAK_DATA = DBG_BREAK_DATA_WRITE|2
};
extern int debug_set_break(void* addr, DbgBreakType type);
//
// memory
//
// independent of mmgr, endeavor to
extern void debug_check_heap(void);
//
// symbol access
//
const size_t DBG_SYMBOL_LEN = 1000;
const size_t DBG_FILE_LEN = 100;
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

View File

@ -104,6 +104,20 @@ static int wdbg_shutdown(void)
}
// 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);
}
//////////////////////////////////////////////////////////////////////////////
@ -151,22 +165,216 @@ void wdebug_out(const wchar_t* fmt, ...)
//////////////////////////////////////////////////////////////////////////////
//
// dbghelp support routines for walking the stack
// code and data breakpoints
//
//////////////////////////////////////////////////////////////////////////////
// dbghelp isn't thread-safe. lock is taken by walk_stack,
// debug_resolve_symbol, and while dumping a stack frame (dump_frame_cb).
static void lock()
#if 0
// rationale: 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.
static struct BreakInfo
{
win_lock(DBGHELP_CS);
// real (not pseudo) handle of thread whose context we will change
HANDLE hThread;
uintptr_t addr;
DbgBreakType type;
}
break_info;
// Local Enable bits of all registers we enabled (used to restore all).
static DWORD break_local_enables;
static bool break_want_all_disabled;
int debug_set_break(void* p, DbgBreakType type)
{
lock();
BreakInfo* bi = &break_info;
bi->addr = (uintptr_t)p;
bi->type = type;
helper();
unlock();
}
static void unlock()
static void debug_remove_all_breaks()
{
win_unlock(DBGHELP_CS);
lock();
break_want_all_disabled = true;
unlock()
}
static void* break_disable_all(BreakInfo* bi, CONTEXT* context)
{
context->Dr7 &= ~break_local_enables;
return (void*)1; // success
}
static void* break_enable(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 < 4; reg++)
{
LE = 1u << (reg * 2);
if((context->Dr7 & LE) == 0) // currently not in use
goto have_reg;
}
debug_warn("break_enable: no break register available");
return 0;
have_reg:
// mark as enabled (and in use) ASAP
break_local_enables |= LE;
context->Dr7 |= LE;
// write address
switch(reg)
{
case 0: context.Dr0 = bi->addr; break;
case 1: context.Dr1 = bi->addr; break;
case 2: context.Dr2 = bi->addr; break;
case 3: context.Dr3 = bi->addr; break;
}
// build Debug Control Register
// IA32 requires code breakpoints have len=1
uint len = 1;
if(bi->type != DBG_BREAK_CODE)
len = (bi->addr-1) & 3;
uint rw;
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("break_enable: invalid type");
return 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 (void*)1; // success
}
static void* break_thread_func(void* arg)
{
DWORD err;
BreakInfo* bi = (BreakInfo*)arg;
err = SuspendThread(bi->hThread);
// abort, since GetThreadContext only works if the target is suspended.
if(err == (DWORD)-1)
{
debug_warn("break_thread_func: SuspendThread failed");
return -1;
}
// target is now guaranteed to be suspended,
// since the Windows counter never goes negative.
//////////////////////////////////////////////////////////////////////////
// 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("break_thread_func: GetThreadContext failed");
return 0;
}
void* ret;
#if defined(_M_IX86)
if(break_want_all_disabled)
ret = break_do_disable_all(bi, &context)
else
ret = break_do_enable(bi, &context);
if(!SetThreadContext(bi->hThread, &context))
{
debug_warn("break_thread_func: GetThreadContext failed");
return 0;
}
#else
#error "port"
#endif
//////////////////////////////////////////////////////////////////////////
err = ResumeThread(hThread);
assert(err != 0);
return ret;
}
static void helper()
{
int err;
BreakInfo* bi = &break_info;
// 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_SUSPEND_RESUME;
bi->hThread = OpenThread(access, FALSE, GetCurrentThreadId());
if(bi->hThread == INVALID_HANDLE_VALUE)
{
debug_warn("debug_set_break: OpenThread failed");
return -1;
}
err = pthread_create(&thread, 0, break_thread_func, bi);
assert2(err == 0);
void* ret;
err = pthread_join(thread, &ret);
assert2(err == 0 && ret == 0);
}
// We can only safely alter the thread context of a suspended thread,
// so we create a worker thread which suspends us, alters our context, and
// restarts us. It update m_bp_num.
#endif
//////////////////////////////////////////////////////////////////////////////
//
// dbghelp support routines for walking the stack
//
//////////////////////////////////////////////////////////////////////////////
// iterate over a call stack.
// if <thread_context> != 0, we start there; otherwise, at the current
// stack frame. call <cb> for each stack frame found, also passing the
@ -798,7 +1006,7 @@ static int dump_udt(DWORD type_idx, const u8* p, size_t size, uint level)
//////////////////////////////////////////////////////////////////////////////
//
//
// stack trace
//
//////////////////////////////////////////////////////////////////////////////
@ -1037,7 +1245,7 @@ static void dump_stack(uint skip, CONTEXT* thread_context = NULL)
//////////////////////////////////////////////////////////////////////////////
//
// "program error" dialog with stack trace (triggered by assert and exception)
// "program error" dialog (triggered by assert and exception)
//
//////////////////////////////////////////////////////////////////////////////
@ -1054,7 +1262,7 @@ enum DialogType
//
static POINTS dlg_client_origin;
static POINTS prev_dlg_client_size;
static POINTS dlg_prev_client_size;
const int ANCHOR_LEFT = 0x01;
const int ANCHOR_RIGHT = 0x02;
@ -1062,7 +1270,7 @@ const int ANCHOR_TOP = 0x04;
const int ANCHOR_BOTTOM = 0x08;
const int ANCHOR_ALL = 0x0f;
static void resize_control(HWND hDlg, int dlg_item, int dx,int dy, int anchors)
static void dlg_resize_control(HWND hDlg, int dlg_item, int dx,int dy, int anchors)
{
HWND hControl = GetDlgItem(hDlg, dlg_item);
RECT r;
@ -1095,7 +1303,7 @@ static void resize_control(HWND hDlg, int dlg_item, int dx,int dy, int anchors)
}
static void onSize(HWND hDlg, WPARAM wParam, LPARAM lParam)
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.
@ -1106,23 +1314,23 @@ static void onSize(HWND hDlg, WPARAM wParam, LPARAM lParam)
// 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 prev_dlg_client_size for the next call before exiting.
bool first_call = (prev_dlg_client_size.y == 0);
// 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 - prev_dlg_client_size.x;
int dy = dlg_client_size.y - prev_dlg_client_size.y;
prev_dlg_client_size = dlg_client_size;
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;
resize_control(hDlg, IDC_CONTINUE, dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
resize_control(hDlg, IDC_SUPPRESS, dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
resize_control(hDlg, IDC_BREAK , dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
resize_control(hDlg, IDC_EXIT , dx,dy, ANCHOR_LEFT|ANCHOR_BOTTOM);
resize_control(hDlg, IDC_COPY , dx,dy, ANCHOR_RIGHT|ANCHOR_BOTTOM);
resize_control(hDlg, IDC_EDIT1 , dx,dy, ANCHOR_ALL);
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);
}
@ -1134,7 +1342,7 @@ static int CALLBACK dlgproc(HWND hDlg, unsigned int msg, WPARAM wParam, LPARAM l
{
// need to reset for new instance of dialog
dlg_client_origin.x = dlg_client_origin.y = 0;
prev_dlg_client_size.x = prev_dlg_client_size.y = 0;
dlg_prev_client_size.x = dlg_prev_client_size.y = 0;
// disable inappropriate buttons
DialogType type = (DialogType)lParam;
@ -1202,7 +1410,7 @@ static int CALLBACK dlgproc(HWND hDlg, unsigned int msg, WPARAM wParam, LPARAM l
}
case WM_SIZE:
onSize(hDlg, wParam, lParam);
dlg_resize(hDlg, wParam, lParam);
break;
default: