A breakpoint is a mechanism to pause a program execution for further analysis during runtime to identify run-time errors. Pausing is required to examine the run-time values of variables. Examining these variables is possible only if they are valid in the current context. Various options will be provided for developer by debugger to examine the details of running program to identify run-time errors including CPU registers, call stack, memory dump and other debugger supported features.
x86 Hardware provided
x86 architecture provides interrupt INT 1, INT 3 and Debug registers which helps debuggers to implement breakpoints.
INT 1 – 0xCD01 :
Single step interrupt provided by x86, where program can be debugged single instruction at a time. This handler is implemented by operating system and provides high level API for debugger development.
To perform single step on process, debugger need to attach with admin privilege. Debugger need to set Trap Flag (TF) in EFLAGS register. By enabling this flag, debugger gets control from the CPU before executing each instruction. Debugger will give control to developer either in assembly instructions or source code level if it has debugging information. Debugger will give various options for developer for continuing the execution of code after examining the variable values.
Trap Flag(TF) is fully under control of Debugger, it can enable or disable this flag based on various options provided by debugger including software and conditional breakpoints.
INT 3 – 0xCC :
Break point interrupt is provided by x86, where debugger can place this anywhere inside the program execution. This opcode 0xCC can be placed by developer directly inside a program ( ex: __asm int 3 in a C/C++ code ), this instruction will be identified by CPU during execution of a program and transfers control to a debugger if its running. If the program is not running inside a program, operating system will execute default handler i.e to terminate the program.
Opcode 0xCC can be used by IDE debugger to implement software based breakpoints. If a developer inserts IDE based breakpoint, internally IDE can this one byte instruction in the code, once IDE gets control from CPU, it will remove this one byte instruction and replace with original instruction and allow developer to examine the program variables. One byte opcode will make easy to insert instruction anywhere in program without much difficulty.
Debugger Registers:
x86 provides 8 debug registers DB0 to DB7 and 2 model specific registers (MSR). These registers can store either memory address or I/O locations to break the execution for analysis of a program in a debugger. To perform action on these registers modifier code must be running kernel mode.
Only 4 registers can be used to store the memory location address to pause the execution i.e DRO to DR3, Each of these registers stores the memory address. After the debugger assigns the memory address, based on the address CPU will automatically pause the execution and provide the control to the debugger to help developer to examine the state of the program. These 4 registers functions based on flags set at DR6 and DR7 registers. DR6 is used as status register and DR7 is used as control register. DR6 flags will not be cleared by CPU, callback program need to clear the stack and this will happen only after exception handler gets the control.
DR6 has following flags:
- Bit 0 to 3 (Breakpoint condition detected): Each of this bit indicated which DR register has triggered breakpoint. If bit 0 is set, it means breakpoint is set using DR0 register.
- Bit 13 (Debug register detected): This bit indicates next instruction in the execution flow is going to access the debug registers from DR0 to DR7.
- Bit 14 (Single Step): This bit indicates that debug exception has generated since Trap Flag (TF) is set in EFLAGS register.
- Bit 15 (Task Switch): This bit indicates debug exception if triggered from a task switch where Trap Flag (TF) is set.
DR7 register is used as control register. This register will enable or disable breakpoints with breakpoint conditions. This register has following flags:
- Bit 0, 2, 4 and 6 (Local Breakpoint): These bits enable the breakpoint for current task. After breakpoint exception, CPU will clear this flag to prevent breakpoint in other task.
- Bit 1, 3, 5 and 7 (Global breakpoint): These bits enable the breakpoint in all tasks in the system, after breakpoint exception CPU will not clear this flag to break in any task globally.
- Bit 13 (General detect enable): This bit enables debug register protection which causes a debug exception before executing MOV instruction to move data to debug registers.
- Bit 16, 17, 20, 21, 24, 25, 28 and 29 (Read/Write): This flag specifies a condition for each breakpoint. Debug Extension (DE) flag in control register CR4 determines how to interpret these flags. DE flag has following types:
- 00 – Break on instruction execution only
- 01 – Break on data writes only
- 10 – Break on I/O reads or writes
- 11 – Break on data reads or writes but not instruction fetches
- Bit 18, 19, 22, 23, 26, 27, 30 and 31 : These bits helps in identifying the size of memory location specified in debug registers with following options:
- 00 – 1 byte
- 01 – 2 byte
- 10 – undefined
- 11 – 4 byte
For details: http://www.yashks.com/2011/01/210/
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:
|
24-31 |
2 bits for each register, defining the size of the breakpoint:
|
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.
Setting the Breakpoint
HANDLE SetHardwareBreakpoint(HANDLE hThread,HWBRK_TYPE Type,HWBRK_SIZE Size,void* s);
hThread
- Handle to the thread for which the breakpoint is to be set.Type
- Type of the breakpoint:HWBRK_TYPE_CODE
HWBRK_TYPE_READWRITE
HWBRK_TYPE_WRITE
Size
- Size of the breakpoint:HWBRK_SIZE_1
HWBRK_SIZE_2
HWBRK_SIZE_4
HWBRK_SIZE_8
addr
- The address of the breakpoint.
The function returns a handle to the breakpoint, to be used later in RemoveHardwareBreakpoint
. It can return 0 if:
- You do not have access to the thread.
- You have set the maximum number of breakpoints for that thread (4).
Removing the Breakpoint
bool RemoveHardwareBreakpoint(HANDLE hBrk);
Removes the breakpoint, returning true on success.
Sample
All source code down here:
hwbrk.h
hwbrk.cpp
test.cpp
For details: http://www.codeproject.com/KB/debug/hardwarebreakpoint.aspx
Also another article and a C++wrapper: http://www.morearty.com/code/breakpoint/
This is a debugging helper class which lets you set breakpoints on the fly from within code.
This is mainly useful for the case where you have a variable that you know is getting trashed,
but you have no idea who is trashing it. You can cause the debugger to break in at the very moment the variable is changed.
The really cool thing is that this makes use of the Intel Pentium's built-in debug registers,
which means that it really will stop no matter what code is executing,
even if it's down in the NT kernel, in a different thread, or whatever.
(Visual C++ has the ability to set a breakpoint when a variable's value changes,
but these breakpoints can be very hard to use, especially on local variables.)
Usage
The class is called CBreakpoint. If, for example, you have a DWORD called "x" and
you want to break the next time it's written to, do this:
DWORD x = 1;CBreakpoint bp;bp.Set(&x, sizeof(x), CBreakpoint::Write);As your code continues to execute, if Visual C++ stops at a location where
you didn't have a VC++ breakpoint set, it's possible that the CBreakpoint triggered.
If it did, the assembly instruction immediately before the instruction pointer is the one that caused it to trigger.
The breakpoint will be cleared automatically when the CBreakpoint object
falls out of scope. Or, to clear it explicitly, do this:
bp.Clear();You can also break the moment any code even tries to read the value of the variable x!
(Actually, this will break when someone tries to read or write it.)
bp.Set(&x, sizeof(x), CBreakpoint::Read);Notes
This code is Intel-specific. It will run on Win95/98/ME and on WinNT/2000/XP,
as long as the machine is running an Intel or compatible chip (e.g. not Alpha).
There are certain limitations when the breakpoint is triggered inside system code
(these same limitations apply to hardware breakpoints set by Visual C++ or any other debugger):
· On WinNT/2000/XP, some system code runs in ring 3 (with user privileges), and
other system code runs in ring 0. If a hardware breakpoint triggers inside ring 0 code,
the debugger will stop, but not exactly when the data write happens --
it will stop after execution transitions back to ring 3 code.
In practice, this is not a problem at all -- when the breakpoint triggers,
it's usually quite easy to figure out what happened.
· On Win95/98/ME, the situation is not as good. All system code is treated by
the debugger as untouchable. Worse, whenever any hardware breakpoint triggers
inside system code, you'll never see the breakpoint trigger.
For example, if a call to strcpy() would cause your breakpoint to trigger, you'll see it,
because strcpy() is just a regular part of your application.
But if a call to lstrcpy() would cause your breakpoint to trigger,
you won't see it, because lstrcpy() is system code.·
You can do "bp.Clear()" from the QuickWatch dialog to clear a breakpoint from within the debugger.
The second parameter is the number of bytes to watch. This must be either 1, 2, or 4.
Also, the variable being watched must be aligned appropriately (e.g. if you're watching 4 bytes,
they must be DWORD-aligned). If you don't do this, you'll get an error when you try to set the breakpoint.
There is a limit of 4 hardware breakpoints. Because of this (and also because of common sense),
don't check in code that uses breakpoints -- just write the code temporarily to find bugs,
but delete it before checking in your source into your source control system.
It's important to pass in the address that you actually want to watch.
Consider these two similar but different cases:
long *my_array;my_array = new long[3];CBreakpoint bpArrayPtr, bpArrayFirstElement;// break if someone changes the POINTERbpArrayPtr.Set(&my_array, sizeof(long*), CBreakpoint::Write);// break if someone changes the FIRST ELEMENT POINTED TObpArrayFirstElement.Set(&my_array[0], sizeof(long), CBreakpoint::Write);How does it work?
The Intel x86 CPUs have some special registers which are intended for debugging use only.
By storing special values into these registers, a program can ask the CPU to execute
an INT 1 (interrupt 1) instruction immediately whenever a specified memory location
is read from or written to.
(They can also stop when a memory address is about to be executed as code,
but my CBreakpoint class doesn't use that functionality.)
INT 1 also happens to be the interrupt that's executed by the CPU after
a debugger asks the CPU to single-step one assembly line of the program.
And by good fortune, when Visual C++ encounters an INT 1 that it wasn't expecting to see
(as is the case when a CBreakpoint breakpoint is triggered),
it responds gracefully, stopping the debugger at the appropriate instruction.