Hacker News new | ask | show | jobs
by hinoki 992 days ago
If you revert the int 3 to the original instruction’s byte, when do you put it back? The breakpoint could still be active.

In a trivial example, the breaking instruction could be a jump to itself, which you’d expect to immediately break into the debugger again.

I thought the debugger had to emulate the instruction instead, but it’s not like I’ve ever implemented one…

4 comments

I believe when you resume the debugger, you can tell the process/thread to single-step over one instruction. So it's something like this:

1. Overwrite instruction with int 3.

2. When you hit the breakpoint, restore the original instruction.

3. Single-step over the original instruction by changing the thread's EFlags (Intel).

4. Restore the breakpoint with int 3.

5. Resume normally.

Wouldn’t that race against any other thread in the process? I guess you could stop all threads when you hit the breakpoint and start them again after you restore the breakpoint, but the synchronisation of that would be really tricky too.
Yes. And yes that is one of the ways to solve it.

You could also do something like have a clean mapping table (i.e. the code with no breakpoints installed) that you install for just the thread doing the step. You then revert back to the normal mapping table with the breakpoint after the step. As you are only modifying the executable section, as long as you are not using self-modifying code, there should be no data inconsistency with having a multiple copys of the executable transiently.

Emulation is an option, rotating hardware debug registers is another option, detecting self-jumps is another option.

I really only implemented a debugger for the esp8266 and it was just good enough for me and my team to get our job done so it didn't handle many edge cases like that

As i_don_t_know stated, if the CPU has the ability to single step an instruction, you use that. Otherwise:

* Restore the original instruction byte.

* Find the next instruction, and set a temporary software breakpoint there.

* Resume the one instruction

* Restore the original instruction byte at the temporary software breakpoint.

* Set the software breakpoint in the original instruction

* Resume running

The other thing to keep in mind is dealing with JMP, CALL and conditional branch instructions. It can get pretty messy pretty quick, which is why I find low level debuggers on old 8-bit CPUs a marvel as they had to deal with only software breakpoints.

Yes, software breakpoints are difficult to get correct (the main reason why I started with hardware breakpoints). It gets more complicated with kernel debugging, where a single step (trap flag) could get pre-empted by an interrupt handler. And you can't always single-step a CPU and leave all other CPUs frozen.