# Tracking invalid memory accesses

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

## Recommended Posts

Hey everybody, Can anyone suggest a good application that can track accesses to invalid memory locations (eg. by writing through uninitialized pointers, etc), even in mapped memory ? We have an extremly obscure bug, where something in our code corrupts data directly on the video cards memory. The bug is very hard to reproduce, and we're basically stuck by the fact that we can't watch a memory location on mapped VRAM from within a debugger. The bug manifested itself once in system RAM as well, but of course the application wasn't running on a debugger at this point, and we were never able to reproduce it. Boundschecker doesn't find anything. gDEBugger doesn't find anything. Anyone could suggest some alternatives ? Language is C/C++ (no .NET), IDE is MSVS 2003 (or alternatively 2005, if required). Thanks !

##### Share on other sites
Hi,

there is a WinAPI function, that is able to determine whether a pointer is valid or not.

Maybe that helps.

georg

##### Share on other sites
I don't think that'll be of much help, as Yann's problem is access to momry that's, say, outside the bounds of an array, but still mapped to the program. This is tricky, as in, raises no exception.

Yann, forgive me if I isunderstood you, but if it's heap corruption occuring in VRAM, then... Good luck. you're gonna need that by the truckloads.

If it's in the system RAM, on the other hand, there're a few ways to track that. I assume this is via some sort of Array initialized by new[]. If so, you can do the following trick (PAINFUL trick, If I may add, but it's a last resort):

make a template class, say watched_array<class T> (yeah, there are millions of better names. back when I did that I had a breakdown from the bug I was having), have a static map of void*s and unsigned ints (unsigned int is the bounds of that pointer). overload the type's new[] to record the number of items and the beginning pointer into that map. watched_array's operator[] checks for the passed index being within bounds (by checking the global map[this]). This is crude, and classified as second degree evil hack :D.

Again, forgive me for any mistakes, and if you'd like me to *gulp* dig the old code up, I'd be more than glad to.

##### Share on other sites
There is a commercially available software from Rational called Purify. An open source version called 'checker' is available from gnu. Basic idea is to write your own heap manangement routines (new/malloc/free) and on each access check if it is within an allocated block. I think Purify and Checker work the same way.

I think the Visual Studio 2005 heavy duty editions have such tools for static (source code) and runtime analysis. Not sure what they are called though.

##### Share on other sites
I dont think there is any tool that detects that your own memory space has been written to, that is a perfectly valid operation.

Perhaps Yann, you'll have to convert your code so that you can log each write operation by a pointer, perhaps overloading a pointer class?

Then you can map memmory, and if any pointer writes to an area you mapped as do-not-write, you can trigger an event...

I have some SofIce debugging experience from my driver writing days, but I don't remember even that powerful tool having the ability of arbitrarily detecting mem writes.

The last thing you can try to do is a runtime opcode-replacement run, where you replace each mem-writing opcode with a call to a specific function who's task it is to monitor such things, but this will probably kill any performance you have.

Hope it helped...

##### Share on other sites
I thought Purify was more like Valgrind, which is great but sadly Linux only. It might be possible to run under Valgrind+Wine but I'd be surprised if you'd get much useful output (you'll only get a memory address of the instruction rather than a file/line number for a start). It does track use of uninitialized pointers but if both of those are out of the question then there may be one other possibility: it's possible on Windows to use VirtualProtect to set a guard status on a page. That may let you track every single memory access into the range you're trying to nail (I'm assuming you can find that range), so you could track the source of the problem that way. Then you want to figure out whether it's coming from inside your program of the graphics drivers which will need some range checking on the accessing instruction or something, I'm not sure how you'd do that.

##### Share on other sites
Thanks for the suggestions so far. I will be a little more specific about what happens.

The bug happens (sometimes) after a certain combination of user operations in the application. The result is always the same type of corruption, if you stay on the same test machine. If we modify (unrelated) parts of the application code, the corruption changes slightly, but stays within the same memory region.

So far so good. Since I know when it happens (approximately), and where the corruption occurs (exactly), I could just set a write-on-memory watch, that will break execution when the address is written to. A short stack backtrace, and I'd have the bug.

But there is a big problem: the corruption happens in the VRAM that is currently mapped to my process. I have no direct monitoring access to this memory through the debugger, I don't even know where in virtual address space it is being mapped to (since that is the business of the driver).

An example of the bug in action: After performing the critical operation chain, a VBO is suddendly partially corrupted (ie. the vertices fly away in arbitrary directions). After modifying the code, the corruption suddendly kills off parts of a texture instead. If the code is not changed, the bug will always corrupt the same video resource (which is a little strange by itself, since those resources are dynamically handled by the driver, ie. will theroretically end up in different location in VRAM everytime the program is started). It's not a driver issue, since it happens on some ATI and nVidia setups.

So in essence, we're stuck. That is the strangest bug that I ever encountered. We already use a custom memory manager that will track simple bound overwriting, but not arbitrary accesses to parts well outside the allocated region, yet still mapped to the process. We can't overload new, * and [] for every possible type either, that would be a complete nightmare (we're talking about around 600k LOCs here !)

The best option would be a compiler plugin, that would add a check infront of every memory dereference in the compiled ASM, and test the pointer address against the list of allocated blocks. Of course that would compelely kill performance, but this is not an issue as long as we find the reason for the bug.

I'll check out Purify. Also, VirtualProtect sounds interesting. Could it be used to monitor VRAM mapped into the processes address space as well, assuming I find a way to determine where the exact address range is located ?

##### Share on other sites
Maybe you could set a breakpoint condition on registers having that exact memory location? Might not work, but worth a shot...

Also, I am confused, you say that you know the exact memory location but don't know the virtual address?

Maybe you can try Process Explorer from www.sysinternals.com which can give you a map of the memory of the process (I think). Also, Windows probably has memory profilers which you can use for looking at the memory usage and virtual memory map.

##### Share on other sites
Purify looks pretty good. I will just buy it, and see if it can find something. I'm really desperate, any more suggestions for other applications are more than welcome !

Thanks !

Quote:
 Also, I am confused, you say that you know the exact memory location but don't know the virtual address?

Sorry, I wasn't precise here. I know an exact relative offset where the corruption occurs within the VRAM itself (or at least, within the resource - but I can find out where the resource is located). But I don't know where the VRAM is being mapped to, so I don't know the exact memory address from the point of view of my applications address space.

##### Share on other sites
have u tried paul nettles memory checker?
also try running the app without VBOs or DLs ie standard VAs or immediate
ive had some strange errors like this -> "a VBO is suddendly partially corrupted (ie. the vertices fly away in arbitrary directions)"
though if youre seeing it with nvidia + ati is seems unlikely is a driver bug

##### Share on other sites
These memory errors can very, very annoying - I know the feeling.
I also suggest Paul Nettle's Manager - it helped me out on several occasions.

You could try to slowly 'take apart' your app - removing calls to subsystems
of code (especially code that was added recently) to see if you can 'magically'
get the problem to disappear.

Eg, don't render models anymore, or disable the soundsystem and so on.
Not really effective, but if you really have no clue, that might be
at least sth to try to give you a hint into what code is causing
the memory overwrite.

Regards

##### Share on other sites
With something that's as much of a corner case as that, I'd suggest a small custom-written tool, a poor man's data breakpoint debugger which simply keeps on checking the memory, very very often. Have it record the stack each time and keep around the stack trace from the last check as well. If it runs at high enough resolution, you should be able to narrow down the offending code pretty precisely between the two stack traces. Of course, this will make your program horribly slow... but it'll probably still be usable enough to debug.

##### Share on other sites
When you say the VRAM is mapped, do you mean the videocard hardware mapping of the VRAM to the PCs physical address space or the mapping of that physical memory space into your user (game) process space?

I'm inclined to believe the bug is in a driver (perhaps brought to light by abuse from your game) if its corrupting VRAM.

WndDbg is the NT kernel debugger. The Intel CPUs can break on memory reads and writes; if you know the location its writing to you ought to be able to set a break-point on write to it. It's only accurate to an aligned 4 bytes IIRC.

You need a second computer and a null-modem cable to use WndDbg, and you have to boot the kernel with some extra parameters to enable debugging... You don't have to use the checked build though. That's another thought, you could try running on a checked build of XP.

Does this happen on more than one PC?

##### Share on other sites
There was a lot of text there so I might have missed this is someone said it.

Anyway, if you have a copy of softice, you can load up your application and set some small number of memory access breakpoints (like 4 or 8 or something?). Whenever that memory is accessed, it will trigger the break and softice will tell you your EIP. It takes a bit of know how to use softice (and it's not free) but it should find your bug.

##### Share on other sites
Quote:
 Original post by Shannon Barber[...]You need a second computer and a null-modem cable to use WndDbg[...]
Not Quite
Mark Russinovich is awesome =-)

Yann L: If nothing else, perhaps you could try contacting the aforementioned guru to see if he has any ideas..? It seems likely that even if there isn't an existing tool to do what you want, Dr. Russinovich could either create such a tool, point you to somebody else that would be willing to do so (though I would expect any such custom software to cost quite a bit), or point you to the relevant chapter(s) in his book so that you could construct such a tool yourself.

##### Share on other sites
One thing that come's to mind is, are you *sure* the data is getting corrupted while it's in VRAM and not before it's transferred there? This could explain the apparant consistency in the relative memory location that's getting trashed, system memory gets trashed and then transferred into the same resource in VRAM.

##### Share on other sites
Wow, this sounds hairy ;)
OK, so you know the offset within VBO. Why not determine address of the video mem mappings via PCI config registers, then scan for known VBO data to determine its exact address? You could then place hardware breakpoints.
Unfortunately, I'm not sure if WinXP will still let you at the PCI BIOS; when I was doing this, it was back in Win95 days ;) It may be easier to use some configuration tool or the Windows hardware information to hard-code the mappings for your test machine.

Failing that, Sneftel's poor man's data checker sounds good :) We have each type of resource validate itself on every access in paranoia builds, which has saved my sanity at least once.

##### Share on other sites
You say the bug starts in a VBO, you could replace the Vertex information with a bitmap that identifies each vertex buffer you

this way you know atleast which resources are affected and can limit the problem to some point in your code

##### Share on other sites
Yann L, if I understand you right your wrong memory access is still at a valid memory address. If you are outside the valid blocks the system will throw an exception. Because you don’t get a exception and don’t know the address there is no automatic way to find the place in your program were this wrong access happened.

I am not very deep in OpenGL driver details (I am primary a DirectX guy) but it is highly believable that the driver try to keep all the data at the same place in memory all the time. The first step to find the bug is to know which of your video resource it will destroy. If you know this you can get the memory address for this resource form the point of your program were you fill it with data (I am hope that it is a static and not a dynamic resource). Than you can set a breakpoint inside VS that monitor this memory block for changes. Works very well for me in the past with a similar problem.

##### Share on other sites
Quote:
 Original post by zedzeekhave u tried paul nettles memory checker?

As I said, we already have our own memory manager, that does pretty much the same thing as Pauls. Unfortunately, what we need is runtime range checking on every memory access in the code, and this can't be done with a simple memory manager.

Quote:
 Original post by Kitt3nYou could try to slowly 'take apart' your app - removing calls to subsystems of code (especially code that was added recently) to see if you can 'magically'get the problem to disappear.

That's not so easy, unfortunately. Those bugs that rely on dereferencing a memory location with an arbitrary undefined value (and that's probably what it is, considering its random behaviour) tend to be higly volatile. You remove one part of the code, and you can be pretty sure that the bug goes away - just to reappear somewhere else later. The 'taking apart' approach is not impossible, but very, very time intensive, and exremely frustrating ;)

Quote:
 Original post by SneftelWith something that's as much of a corner case as that, I'd suggest a small custom-written tool, a poor man's data breakpoint debugger which simply keeps on checking the memory, very very often.

That's a good idea, but I don't know where the offending memory is. I did some research, and it seems that the physical VRAM isn't actually mapped in one piece to the process address space. The driver continously maps in and out single pages of the physical address space, according to its current needs. So even if I somehow manage to identify the location of the corrupted resource, it might be mapped out again a few milliseconds later. GAHH...

Quote:
 Original post by Shannon BarberWhen you say the VRAM is mapped, do you mean the videocard hardware mapping of the VRAM to the PCs physical address space or the mapping of that physical memory space into your user (game) process space?

The latter.

Quote:
 I'm inclined to believe the bug is in a driver (perhaps brought to light by abuse from your game) if its corrupting VRAM.

That was also my thought. But I'm actually not so sure anymore. Actually, our customer first reported this bug while beta testing our application, on a mobility Radeon 9-something. We all know the quality of ATIs OpenGL drivers, so we immediately yelled: "driver bug, update your drivers". They did, and it went away.

A couple of months later, they got the very same error on a GF6800. And that's where we started to get seriously worried. We have since found one single machine in all of our company (with an old GF3 !) that was prone to the bug.

Quote:
 Original post by Puzzler183Anyway, if you have a copy of softice, you can load up your application and set some small number of memory access breakpoints (like 4 or 8 or something?). Whenever that memory is accessed, it will trigger the break and softice will tell you your EIP. It takes a bit of know how to use softice (and it's not free) but it should find your bug.

As I said above, I don't know where that memory exactly is, since it is dynamically mapped.

Quote:
 Original post by ExtrariusNot QuiteMark Russinovich is awesome =-)

Interesting. I'll have a look at that.

Quote:
 Original post by joanusdmentiaOne thing that come's to mind is, are you *sure* the data is getting corrupted while it's in VRAM and not before it's transferred there? This could explain the apparant consistency in the relative memory location that's getting trashed, system memory gets trashed and then transferred into the same resource in VRAM.

Was also my first idea. Unfortunately, the data copy in system RAM is completely clean. In fact, the bug happened on two static resources: a VBO that is only allocated and filled once at program startup, and a texture that is only loaded once at scene load time. Both resources render fine, as long as the "critical operation chain" isn't done. This operation chain doesn't involve any uploading or modifications of any video resource at all. Once it was performed, there is a chance (doesn't happen each time), that the static VRAM resource gets corrupted. A simple reupload of the resource from system RAM fixes it again. Till the next corruption.

Quote:
 Original post by Jan WassenbergWow, this sounds hairy ;)OK, so you know the offset within VBO. Why not determine address of the video mem mappings via PCI config registers, then scan for known VBO data to determine its exact address? You could then place hardware breakpoints.

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 ;)

Quote:
 Original post by BasirorYou say the bug starts in a VBO, you could replace the Vertex information with a bitmap that identifies each vertex buffer youthis way you know atleast which resources are affected and can limit the problem to some point in your code

I know exactly the resource that gets corrupted, including the precise offset. The problem is, that it is not getting corrupted in system RAM, but in video memory.

Quote:
 Original post by DemirugYann L, if I understand you right your wrong memory access is still at a valid memory address. If you are outside the valid blocks the system will throw an exception. Because you don’t get a exception and don’t know the address there is no automatic way to find the place in your program were this wrong access happened.

That's right. My thread title is misleading, sorry. I actually tried to modify and shuffle around code, initialize the heap with zeros, etc, in the attempt to get the bug to access memory not mapped to my process, maybe even to dereference a NULL pointer. If it finally raised an exception, then I would get it. But till now, no success. Corruption is always in video memory. Really weird.

OK, current battleplan:

* Supposedly, VC 2005 is very picky about things like uninitialized variables & co. So I'll download VC2005 Express and see if it finds something to nitpick on.

* I'll try to run Purify on the code.

* If these don't find anything, I'll try to reproduce the bug with Mesa instead of hardware OGL. If the bug reappears, and can place a breakpoint within the Mesa source, and also watch the resource memory, as everything is in system RAM.

* I'll try to continuously read back the corrupted resource using glGet* and compare it to the original copy. However, I'm afraid that the driver will just give me back its own memory copy, instead of the corrupted VRAM copy.

* WinDbg & friends. Oh dear, we're supposed to ship in 3 weeks... (runs away screaming into the next wall)...

* Or we'll just ship the whole thing, and blame it onto the drivers ;)

If everything fails, I can still move to Lappland and become a reindeer farmer, trying to hide from the hitman our customer is going to send after us when he loses a \$100 million contract because of our bug... [grin]

Seriously though, thanks for your help guys, much appreciated. I'll keep you updated.

##### Share on other sites
Maybe try STLport, it's debug mode does wonders in finding errors.

##### Share on other sites
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 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;}

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 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 on other sites
open source nvidia gpu driver?