How do debuggers guarantee correctness when using INT 3 (0xCC) software breakpoint even though an instruction was patched?
Asked Answered
L

3

23

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?

Lorrinelorry answered 19/9, 2010 at 23:9 Comment(1)
Will someone please quote GDB source code? :-)Mordecai
H
27

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.

Hibernicism answered 20/9, 2010 at 0:40 Comment(1)
Also mentioned on the GDB internals wiki: "When the user says to continue, GDB will restore the original instruction, single-step, re-insert the trap, and continue on."Mordecai
A
4

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).

Actuality answered 19/9, 2010 at 23:29 Comment(1)
single stepping is INT 1 IIRCPronto
A
2

The usual solution is for the debugger to modify the address on the stack (and restore the instruction that was overwritten by the trap), so it does execute the patched instruction.

Adigranth answered 19/9, 2010 at 23:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.