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
Visual Studio C/C++ Breakpoint conditions
Debuggers have a choice to its own software breakpoints with the help of INT 1 handler and other operating system supported debugging features. VC++ implements following conditional breakpoints to help
1. Breaking when it reaches a function: Set a breakpoint for a specific function by providing a full function name. If it’s C++ code, then provide along with class name.
Line number should be relative to Function. Line number which you provide here should have valid code and you cannot mention to any blank line. You need to select programming language if you are debugging program for more than one language.
2. Breaking in a specific Location in a file: You can set a breakpoint to specific line within source file by inserting breakpoint.
Specify source filename along with line number. This line number is relative to source file. You can also specify different version of source file for this breakpoint.
3. Breaking when it reaches specified Address: You need to mention runtime address of a function. To use this type of breakpoint, you start a debugging with some breakpoint, then go to Dissembler window, look for address and insert breakpoint.
4. Breaking when specified variables changes: You can specify to break at a specific location based on two conditions “Is TRUE” and “Has Changed”.
If you want to check the condition if some pointer variable is NULL, then enter expression as “ptrBuff == 0” and check “Is True” option, it breaks it when variable is 0. Enter expression in “Condition” field and check “Has changed” option for breaking into code when expression changes.
5. If you have loop and if you want to break inside loop based on pre-conditions you can use this conditional breakpoint. It provides four options:
- Break always
- Break when hit count is equal to “Some number”
- Break when hit count is multiple of “Some number”
- Break when hit count is greater or equal to “Some number”
6. Breakpoint filter based on Machine name, ProcessID, ProcessName, ThreadId, ThreadName with combination of one or more conditions using ||, & and !
7. You can print TRACE info in debug window along with built-in macro condition. You have option to continue execution automatically or you can execute the macro and break into code.