Software Breakpoints

Software Breakpoints

The mechanism by which a debugger pauses a program at a chosen instruction. The clever-but-simple trick: the debugger overwrites the instruction at the target address with a single-byte trap (INT3 on x86, 0xCC), then restores the original byte after the trap fires.

The flow

You set a breakpoint at line 42, which DWARF tells the debugger lives at address 0x4011a8. The first byte of that instruction is, say, 0x55 (start of push rbp).

  1. Debugger uses ptrace (PTRACE_PEEKDATA) to read the byte at 0x4011a8. Reads 0x55.
  2. Debugger uses ptrace (PTRACE_POKEDATA) to overwrite that byte with 0xCC. 0xCC is the x86 instruction INT3 — a 1-byte software interrupt.
  3. Debugger remembers: "address 0x4011a8 originally held 0x55."

Now the program runs. Eventually the CPU's instruction pointer reaches 0x4011a8 and executes INT3.

  1. CPU raises a software interrupt. Kernel converts it to a SIGTRAP signal aimed at the debuggee.
  2. Kernel pauses the debuggee. Debugger (which had PTRACE_ATTACH'd) gets notified.
  3. Debugger checks RIP (instruction pointer): it's at 0x4011a8. "That's our breakpoint."

The user inspects state, then presses continue. Now the debugger has to execute the original instruction (0x55) and put the breakpoint back so we can hit it again next time:

  1. Restore: PTRACE_POKEDATA(0x4011a8, 0x55).
  2. Single-step: PTRACE_SINGLESTEP. The CPU executes one instruction (push rbp) and pauses.
  3. Re-insert: PTRACE_POKEDATA(0x4011a8, 0xCC).
  4. Resume: PTRACE_CONT.

That's a complete software-breakpoint cycle.

Why a single byte

Different architectures have different trap instructions, but all are short (often 1–4 bytes) and chosen so that overwriting any aligned instruction's first byte with the trap is safe. On x86, INT3 is exactly 1 byte (0xCC), which means you can replace the first byte of any instruction without disturbing the next instruction's location. Critical: the instruction stream is variable-length on x86; longer trap instructions could overflow.

ARM uses BKPT (16-bit on Thumb, 32-bit on ARM); RISC-V uses EBREAK. Same idea.

Hardware breakpoints

The CPU also has dedicated debug registers (DR0–DR7 on x86) that can pause execution when a specific address is accessed. These are hardware breakpoints:

Used for things like watchpoints (pause when memory at address X is written) where software breakpoints don't work cleanly. Most line breakpoints in normal source code are software breakpoints.

What can go wrong

See also