Introduction

I decided to write this article about hardware breakpoints for the following reasons:

Visual C++ only supports write-only data breakpoints. You might want to trigger a break when data is read as well.

You might not be using Visual C++, so chances are that your debugger uses some slow software-based mechanism.

You might want to set/remove a breakpoint programmatically.

You may be interested in low level CPU stuff!

Features

Works for x86 and x64.

Supports upto 4 hardware breakpoints per thread.

Debug Registers

x86/x64 contains a set of debug registers, named DR0, DR1, DR2, DR3, DR6, and DR7. These registers are 32-bit when in 32-bit mode, and 64-bit when in long mode. DR0, DR1, DR2, and DR3 contain the linear addresses of the breakpoint, and DR7 contains the bits explained here:

Bits

Meaning

0-7

Flags for each of the 4 debug registers (2 for each). The first flag is set to specify a local breakpoint (so the CPU resets the flag when switching tasks), and the second flag is set to specify a global breakpoint. In Windows, obviously, you can only use the first flag (although I haven't tried the second).

16-23

2 bits for each register, defining when the breakpoint will be triggered:

00b - Triggers when code is executed

01b - Triggers when data is written

10b - Reserved

11b - Triggers when data is read or written

24-31

2 bits for each register, defining the size of the breakpoint:

00b - 1 byte

01b - 2 bytes

10b - 8 bytes

11b - 4 bytes

We use SetThreadContext to set the necessary flags for the thread. After that, when the breakpoint is triggered, an exception of the value EXCEPTION_SINGLE_STEP is raised.

Comments and Discussions

Are data breakpoints supposed to trigger when you're running code in the debugger? When I compile your test program and run it by double-clicking the EXE, I see the four MessageBox windows pop up in sequence as expected. If I run through the debugger, it just runs past all four try/except blocks without stopping.

For that matter, are the try/except blocks strictly necessary? It seems to me that the real power of this technique would be having the debugger break right when it hits the line that triggered the breakpoint, rather than in some except clause way up the call stack. In other words, I would expect it to behave just like the debugger's built-in data breakpoint, except controlled programatically). Breakpoints that only trigger when I'm OUTSIDE the debugger (and only from within an exception handler) don't sound terribly useful

Data breakpoints are triggered in any context, no matter if you are debugging or not. The try/catch simply allows the application to catch the exception and continue. When debugging, the exception goes into the debugger which (in case of VS) does NOT break because it knows there's a hardware breakpoint.

Of course the try/except is not needed. It's there only to demonstrate that the code actually throws. In real life, you set the breakpoint using SHB() and you wait for the debugger to break when the breakpoint is hit.

Okay, that's what I thought. However, I tried commenting out the __try, __except, and MessageBoxA() lines of test.cpp. If I run in the debugger, it seems to get stuck in SomeFunc(), as described in a previous question (I'm using VS2008). So, to work around that, I also commented out the calls to SHB(hX3) and RHB(hX3).

Now, if I run in the debugger, it once again runs past all three remaining breakpoints without stopping, and exits (normally) immediately. If I double-click the EXE, I get a "The application test64 has stopped working" dialog. Either way, not ideal.

Can you think of anything else I could be doing wrong? It's clear that this code has been working correctly for many other readers; could it be my environment (VS2008, Win64)?

I ran some tests on additional systems, and it looks like the problem is definitely in the 64-bit version of the test project. The 32-bit version worked correctly (except for the hang in SomeFunc() mentioned earlier) on all systems I tried, with both VS2008 and VS2010.

I'll do some more digging, but please chime in if you have any suggestions. I'm vaguely aware of some subtle differences in GetThreadContext()'s behavior on Win64 vs. Win32, but I don't recall the specifics.

Well, the only thing out-of-the-ordinary I noticed was that if I insert a call to GetLastError() at the very beginning of th() before calling any other function, and then build & run test64.exe in the debugger with no additional modifications, the new call to GetLastError() returns 87 (ERROR_INVALID_PARAMETER) immediately upon entering th(). None of the preceding Windows functions in the main thread return a non-successful error code.

And with that, I declare myself way out of my depth. I hope somebody else can get to the bottom of this!