Data breakpoints are based on the linear address, not the physical address

When you ask the debugger to set a read or write breakpoint, the breakpoint fires only if the address is read from or written to by the address you specify. If the memory is mapped to another address and modified at that other address, then your breakpoint won't see it.

For example, if you have multiple views on the same data, then modifications to that data via alternate addresses will not trigger the breakpoint.

The hardware breakpoint status is part of the processor context, which is maintained on a per-thread basis. Each thread maintains its own virtualized hardware breakpoint status. You don't notice this in practice because debuggers are kind enough to replicate the breakpoint state across all threads in a process so that the breakpoint fires regardless of which thread triggers it. But that replication typically doesn't extend beyond the process you're debugging; the debugger doesn't bother replicating your breakpoints into other processes! This means that if you set a write breakpoint on a block of shared memory, and the write occurs in some other process, your breakpoint won't fire since it's not your process that wrote to it.

When you call into kernel mode, there is another context switch, this time between user mode and kernel mode, and the kernel mode context of course doesn't have your data breakpoint. Which is a good thing, because if that data breakpoint fired in kernel mode, how is your user-mode debugger expected to be able to make any sense of it? The breakpoint fired when executing code that user mode doesn't have permission to access, and it may have fired while the kernel mode code owned an important critical section or spinlock, a critical section the debugger itself may very well need. Imagine if the memory were accessed by the keyboard driver. Oops, now your keyboard processing has been suspended. Even worse, what if the memory were accessed by a a hardware interrupt handler? Hardware interrupt handlers can't even access paged memory, much less allow user-mode code to run.

This "program being debugged takes a lock that the debugger itself needs" issue isn't usually a problem when a user-mode debugger debugs a user-mode process, because the locks held by a user-mode process typically affect only that process. If a process takes a critical section, sure that may deadlock the process, but the debugger is not part of the process, so it doesn't care.

Of course, the "debugger is its own world" principle falls apart if the debugger is foolish enough to require a lock that the program being debugged also uses. Debugger authors therefore must be careful to avoid these sorts of cross-process dependencies. (News flash: Writing a debugger is hard.) You can still run into trouble if the program being debugged has done something with global consequences like create a fullscreen topmost window (thereby covering the debugger) or installed a global keyboard hook (thereby interfering with typing). If you've tried debugging a system service, you may have run into this sort of cross-process deadlock. For example, if you debug the service that is responsible for the networking client, and the debugger tries to access the network (for example, to load symbols), you've created a deadlock since the debugger needs to access the network, which it can't do because the networking service is stopped in the debugger.

Hardware debugging breakpoints are a very convenient tool for chasing down bugs, but you have to understand their limitations.

Additional reading: Data breakpoint oddities.

Comments (6)
  1. Greg Geldorp says:

    A couple of weeks back, I was scratching my head on how the value of a certain memory location could change without the data breakpoint I had set on it firing. In the end it turned out that the location was part of a buffer passed to ReadFile, so it was changed in kernel mode.

  2. MadQ1 says:

    This reminds of trying to debug a an application that used a global (out-of-process) low-level mouse hook (yes, yes, heresy! global hooks are Evil™!) The mouse behaved very strangly when the process was paused by the debugger. Kudos to the designers of the hook APIs for taking into account badly-behaved handlers; they appear to bypass the hook after a timeout. On the bright side, I did get more profficient at using shortcut keys in Visual Studio.

  3. Yuhong Bao says:

    An example of this, caused by the TSF:

  4. Raymundo Chennai says:

    Thanks for this post.  And thank you for saying, "Hardware debugging breakpoints are a very convenient tool for chasing down bugs, but you have to understand their limitations."  Instead of the usual variation of, "If you didn’t intuitively realize all of the preceding when the concept of a debugger was first described to you, the world would be a better place if your mother had aborted you."

  5. f0dder says:

    Writing a debugger is hard. Writing a system-level debugger using VMX is worse :)

  6. Dave Harris says:

    On occasion I’ve had to debug code that handles Pasting from the clipboard into my app. I’ve learned not to use the debugger as the Copy source because it hangs, presumably because of the kind of issues mentioned here.

    It’s so tempting, though, because my debugger is the IDE (VC++) and there’s usually some selected text right there just begging to be copied.

Comments are closed.

Skip to main content