Jump to content
  • Advertisement
Sign in to follow this  
Yann L

Tracking invalid memory accesses

This topic is 4896 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Advertisement
In vs2003 you have an option in the project-settings:
C/C++: Code Generation / Buffer Security Check.

Maybe that'll give you an exception if the overwrite occurs...

Share this post


Link to post
Share on other sites
Quote:
Sounds good. But will that work on mapped AGP memory ? And how do I do that on Windows XP ? I'm not so much of a kernel hacking guru ;)

*g*

Analysis of why HW breakpoints should work on mapped AGP mem:
Before giving up on an OS/video driver project (silly me..), I had a mode switcher+HW mouse cursor+framebuffer setup going for GF2s. The gfx card maps FB and command buffers into your address space, which you can modify with standard integer instructions (hence the term memory-mapped IO). HW breakpoints work by comparing all such memory accesses against the addresses of interest; the CPU doesn't care what kind of memory it is actually touching.
Code to set BPs follows: (license=GPL)

// to avoid deadlock, be VERY CAREFUL to avoid anything that may block,
// including locks taken by the OS (e.g. malloc, GetProcAddress).
typedef int(*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");

DWORD err;
WhileSuspendedParam* param = (WhileSuspendedParam*)user_arg;

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.

int ret = param->func(param->hThread, param->user_arg);

err = ResumeThread(param->hThread);
debug_assert(err != 0);

return (void*)(intptr_t)ret;
}


static int 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 -1;
}

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 (int)(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 int brk_disable_all_in_ctx(BreakInfo* UNUSED(bi), CONTEXT* context)
{
context->Dr7 &= ~brk_all_local_enables;
return 0;
}


// find a free register, set type according to <bi> and
// mark it as enabled in <context>.
// called while target is suspended.
static int 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 0;
}


// carry out the request stored in the BreakInfo* parameter.
// called while target is suspended.
static int brk_do_request(HANDLE hThread, void* arg)
{
int 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 0;
fail:
return -1;
}


// 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.
int debug_set_break(void* p, DbgBreakType type)
{
lock();

brk_info.addr = (uintptr_t)p;
brk_info.type = type;
int 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.
int debug_remove_all_breaks()
{
lock();

brk_info.want_all_disabled = true;
int ret = call_while_suspended(brk_do_request, &brk_info);
brk_info.want_all_disabled = false;

unlock();
return ret;
}



Now about finding MMIO base address:
in case you want to try the PCI approach, the relevant PCI BIOS call is INT 1A, AX = B00Ah (search for that and "read configuration dword"). There are 6 possible base addresses starting at offset 0x10 in the config area.
However if it's just for testing and only one machine can duplicate the problem anyway, just check the Windows Device Manager -> Display Adapters -> Properties -> Resources. My Radeon 9700 only has one large mapping and one smaller 64KB, so you probably don't have to guess which is which (IIRC GF2s had 2 large ranges, so I had to pick one and cross my fingers :P).

Share this post


Link to post
Share on other sites
there s a custom nvidia driver out there which works pretty well maybe you could compile it with some build in features to check memory access


on the other side did you run it on different hardware setups? this could be a hardware bug as well or maybe the resouce you transfer to the card is too large

and they probably get a integer overflow inside the api

you could however test if the content of the resource gets corrupted right after submission
just read it back and run a memcmp

or an unsupported format type

Share this post


Link to post
Share on other sites
i don t have a link since i can remember the name but i have installed it once a year ago and it worked pretty well

it was tuned for quake engine i think maybe someone else here knows its name?

Share this post


Link to post
Share on other sites
A longshot to add another twist to the problem.
Maybe your resource ISN'T corrupted, what if your resource is DELETED and then the memory is overwritten by a new resource.
What I mean is that somewhere you destroy a legal resource (not the corrupted one) and the address that's getting passed to the destory funtion is corrupted (all cpu side).
I've seen stranger things happen :)

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!