SetThreadContext Fail on x64
On all 64-bit versions of Windows I have been experiencing weird crashes with apps running in a debugger, caused by all sorts of access violations in ntdll. I figured it must have been my code doing something incorrectly which works on x86 but breaks on 64-bit Windows.
The exceptions would usually happen after the break at ntdll!DbgBreakPoint, somewhere around RtlUserThreadBase on Vista SP2. I even got DEP access violations at random memory addresses outside of ntdll. This behaviour seemed to be consistent on all 64-bit Windows versions. I got similar exceptions on XP SP2, Vista SP2 and Win7 RC1.
I was able to nail it down to a call to SetThreadContext which was responsible for setting the DRX registers. The code did nothing more than call GetThreadContext, set DR0 to DR3 + DR7 and call SetThreadContext. I suspected the DRX values to be corrupted, so I removed everything between the two APIs. Surprisingly, the error remained. When I just removed SetThreadContext, it worked flawlessly.
Since I didn’t need to read and update anything other than the debug registers, I set the context flags to CONTEXT_DEBUG_REGISTERS. Because I lacked any logical solutions I played around with the flags and tried CONTEXT_ALL. All of a sudden the exceptions were gone and the debuggee started up normally. Wow.
Turns out that combining CONTEXT_DEBUG_REGISTERS with any other valid flag fixes this problem.
Just to be sure, I added this piece of code to the CREATE_PROCESS_DEBUG_EVENT handler:
CONTEXT Context; Context.ContextFlags = CONTEXT_DEBUG_REGISTERS; HANDLE Thread = Event.u.CreateProcessInfo.hThread; GetThreadContext(Thread, &Context); SetThreadContext(Thread, &Context);
This perfectly reproduced all the crashes I was experiencing before. Changing the second line to:
Context.ContextFlags = CONTEXT_CONTROL | CONTEXT_DEBUG_REGISTERS;
made it work like a charm.
I had another bunch of DEP violations after every INT1 but didn’t realize it was the same problem. I forgot to adapt the flags for one piece of code which read and updated the debug registers after a hardware breakpoint or single step. After fixing that, I had a perfectly working debugger.
I can’t remember how much time I spent finding that stupid bug. Bill, you owe me big-time.