I've read that the INT 3 (0xCC) is used for software breakpoints.
It is set by (for instance) a debugger by overwriting the actual program code in memory.
I've also read that INT 3 is a "trap" not "fault" exception meaning the address pushed on the stack is the address of the instruction following the INT3 instruction.
How does the debugger guarantee correctness if the patched instruction is not re-executed?
To set a breakpoint in source code: Click in the far left margin next to a line of code. You can also select the line and press F9, select Debug > Toggle Breakpoint, or right-click and select Breakpoint > Insert breakpoint. The breakpoint appears as a red dot in the left margin.
The debugger loads the program into memory and provides you some input prompt. You tell the debugger to add a breakpoint. It may use some information to figure out where in memory your breakpoint actually corresponds to the in-memory representation of the program.
In software development, a breakpoint is an intentional stopping or pausing place in a program, put in place for debugging purposes. It is also sometimes simply referred to as a pause.
A breakpoint helps to speed up the debugging process in a large program by allowing the execution to continue up to a desired point before debugging begins. This is more efficient than stepping through the code on a line-by-line basis.
When you want to continue execution after the breakpoint fires, you have two possibilities: either the breakpoint was only supposed to fire once, or it was supposed to be persistent. If it was only supposed to fire once, you restore the original value you overwrote with your breakpoint instruction, manually adjust the address to that instruction's address (remember, regardless of what instruction was there, what executed was your single-byte breakpoint, so the adjustment is always trivial). Then you continue execution.
If it was supposed to be a persistent breakpoint, there's one added wrinkle: before you continue execution, you set the single-step (aka trap) bit in the flags on the stack. That means only the one instruction where the breakpoint was set will execute, then you'll get a breakpoint interrupt again. You respond to that by restoring the int 3 byte you had just patched to the first byte of the original instruction, and (again) continue execution.
It's been a while since I've delved into that sort of stuff, but assuming you are correct that the following address is pushed on the stack, the debugger can pop the return address and use it to figure out where the breakpoint was (the return address minus one, since the INT 3 instruction is one byte long) [edited].
In other words, the debugger doesn't necessarily need to return to the address on the stack. It can restore the original instruction, and then execute it at the original location. If the breakpoint is to remain set, it can use the "trap bit" in the flags to execute only a single instruction - the original one that was overwritten - before another trap will be generated (INT 3 again I think); then the INT 3 instruction can be re-established before continuing execution properly.
Most of the time, though, debuggers are operating under a system where they're not directly handling the trap anyway; they might be delivered a signal, for instance, telling them where the trap occurred. Most likely they still need to figure out the "real" address (i.e. the address of the INT 3 instruction) from the trap address, as the OS has no way to do this.
Things get complicated, too, if there are multiple threads involved; in that case restoring the original instruction "in place" may lead to the breakpoint being missed if it is hit by another thread. One solution might be stopping all the other threads before restoring the instruction (and starting them again afterwards).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With