UNIT 06 · LESSON 2 OF 6

Breakpoints, Stepping, and Watchpoints

How can a debugger stop a program at an exact instruction or on an exact memory access, and why does it sometimes say it cannot?

INTERACTIVEWhich breakpoints and watchpoints fit
Hardware breakpoint and watchpoint comparators and which requests fitbreakpoint comparators (4)watchpoint comparators (2)3 flash breakpoints in hardware1 RAM breakpoint as BKPT instructions written into RAM (no comparator used)1 watchpoint in hardware
Hardware breakpoint and watchpoint comparators and which requests fitbreakpoint comparators (4)watchpoint comparators (2)3 flash breakpoints in hardware1 RAM breakpoint as BKPT instructions written intoRAM (no comparator used)1 watchpoint in hardware

Try this

Hardware breakpoint comparators
Data watchpoint comparators
3 hardware and 1 software breakpoints, 1 watchpoints; everything fits.

A breakpoint in RAM can be a software breakpoint: the debugger writes a BKPT instruction over the code and restores it later. Code in flash cannot be rewritten that way, so each breakpoint there needs one of the core’s hardware comparators (the Flash Patch and Breakpoint unit), and each watchpoint one data comparator (the DWT). How many exist is chip-specific; debuggers read it from FPCTRL and DWT_CTRL. GDB picks hardware or software from the target’s memory map.

What you will be able to do
  • Explain how a software breakpoint works and why it cannot be used in flash code without reprogramming the flash.
  • Predict how many breakpoints and watchpoints fit on a chip, given its comparator counts and where the code runs.
  • Predict where step, next and finish stop in a short program.
  • Use a watchpoint to find the code that writes a variable, and say where the debugger reports the hit.
  • Explain why stepping through optimised code jumps around.
Before you start
  • Connecting a debugger to the target (lesson 1).
  • Instructions, registers and the call stack (unit 3, lessons 2 and 5).
Steps in this lesson
  1. Software breakpoints rewrite the code
  2. Hardware breakpoints and watchpoints use comparators
  3. step, next and finish
  4. Watchpoints: stop on data
  5. Worked example: finding who corrupts a flag
  6. Common misconceptions

The puzzle

A variable holds the wrong value by the time you look at it. Somewhere, some code wrote it. You could scatter prints through the program, or you could tell the chip “stop the moment anyone writes this address”. How can a debugger stop a program at an exact instruction or on an exact memory access, and why does it sometimes say it cannot?

STEP 1

Software breakpoints rewrite the code

The classic breakpoint replaces the instruction at the chosen address with a special trap instruction (BKPT on Arm). When the core executes it, it halts and the debugger takes over; before resuming, the debugger puts the original instruction back, steps over it and re-inserts the trap. There is no limit on how many you can set.

The catch is the word replace. It works only where the debugger can write the code with an ordinary memory write: RAM. Code in flash cannot be patched that way (some commercial debug servers do it anyway by erasing and reprogramming a flash sector, which is slow and wears the flash).

STEP 2

Hardware breakpoints and watchpoints use comparators

For code in flash, the core provides a few comparators that watch the instruction fetch address (on Cortex-M, the Flash Patch and Breakpoint unit) and a few that watch data accesses (the Data Watchpoint and Trace unit, DWT). Each hardware breakpoint or watchpoint occupies one comparator, and the counts are small and chip-specific; debuggers read them from FPCTRL and DWT_CTRL when they connect.

↑ This step uses the figure at the top of the page.

GDB chooses automatically from the target’s memory map: software breakpoints in RAM, hardware ones in read-only memory. When the comparators run out, the extra breakpoints or watchpoints cannot be inserted, and GDB may only report it when you resume. There are two hidden users of comparators to remember:

  • next and finish work by setting temporary breakpoints (after the call, at the return address). In flash code they need a free comparator too.
  • An older Flash Patch unit (revision 1) can only place hardware breakpoints below address 0x2000_0000, in the code region.

STEP 3

step, next and finish

The three stepping commands are defined in terms of source lines and stack frames:

  • step runs until control reaches a different source line, entering any called function that has line information.
  • next also stops at the next line, but runs any called functions to completion without stopping.
  • finish runs until the current function returns, stops in the caller and prints the returned value.
INTERACTIVEstep, next and finish
A short program with the current line and the line where the chosen command stops10  int main(void) {11      int a = f(2);now12      led(a);13  }20  int f(int x) {21      int y = g(x);stops here22      return y + 1;23  }30  int g(int x) { return 3 * x; }step at line 11: f has line information, so step enters it and stops at its firstline.
A short program with the current line and the line where the chosen command stops10  int main(void) {11      int a = f(2);now12      led(a);13  }20  int f(int x) {21      int y = g(x);stops here22      return y + 1;23  }30  int g(int x) { return 3 * x; }step at line 11: f has line information, so stepenters it and stops at its first line.
Stopped at line
Command
step at line 11: stops at line 21. f has line information, so step enters it and stops at its first line.

GDB’s three most used commands, applied to a small program of three functions. step stops at the next source line, entering any called function that has line information; next treats calls as one step; finish runs until the current function returns and prints its value. Choose where the program is stopped and a command.

stepi and nexti do the same per machine instruction. Optimised code makes source stepping look erratic: the compiler reorders and merges lines, keeps variables in registers that are reused, and inlines small functions, so the “current line” jumps backwards and some variables show as <optimized out>. Building with -Og keeps debugging usable while still optimising.

STEP 4

Watchpoints: stop on data

watch counter stops when counter changes, rwatch when it is read and awatch on any access. A hardware watchpoint stops right at the write, which is the whole point: the debugger shows the offending code, not a symptom later (on Cortex-M the core halts just after the store completes, possibly an instruction or two later). With a remote debug server GDB does not know how many comparators exist: it accepts every watch as a hardware watchpoint and reports “Could not insert watchpoint” when you resume. Only if you tell it the limit (set remote hardware-watchpoint-limit 2) does it fall back to a software watchpoint instead: it single-steps the program and compares the value after each step, hundreds of times slower, and reports only at the next source line. rwatch and awatch have no software fallback at all.

A comparator also watches a limited shape: on Cortex-M a region whose size is a power of two and whose address is aligned to that size (the hardware allows masks up to 32 KiB). A 12-byte structure, or an 8-byte value aligned only to 4 bytes, needs a larger covering region or several comparators.

STEP 5

Worked example: finding who corrupts a flag

A flag ready at 0x2000_0418 turns to 0 unexpectedly. The chip has 4 breakpoint and 2 watchpoint comparators, and two breakpoints are already set in flash.

  1. watch ready takes one DWT comparator. Two breakpoint comparators remain free for next and finish.
  2. The program stops just after the store that changed it, with GDB reporting the old and new value; bt shows the call chain that led to it (lesson 3).
  3. The culprit is a memset of a neighbouring 16-byte buffer with length 20: bytes 0x2000_0408 to 0x2000_041B, four bytes past the buffer’s end at 0x2000_0417, into ready.
last byte written=0x2000_0408+20−1=0x2000_041B≥0x2000_0418=&ready\text{last byte written} = 0x2000\_0408 + 20 - 1 = 0x2000\_041B \geq 0x2000\_0418 = \&\text{ready}

Without the watchpoint, the only symptom was a flag that was sometimes wrong.

MYTHS AND FACTS

Common misconceptions

Breakpoints are free and unlimited

In RAM, yes; in flash each one uses one of a handful of comparators.

next skips over the function without running it

It runs it completely; it only does not stop inside.

A watchpoint stops before the write

It stops once the write has happened, just after the instruction that made it, showing the new value.

If GDB accepted the watchpoint, it will work

With a remote target GDB cannot know the comparator count; an extra watchpoint fails only when you resume, unless you set the limit, in which case GDB uses a much slower software watchpoint.

Stepping shows the program exactly as written

Optimised code is reordered and inlined; stepping follows the machine code, not the source text.

Check yourself

Answer in your head, then open the card.

A chip has 6 hardware breakpoint comparators. You set 5 breakpoints in flash and then type next. Why might next fail, and what fixes it?

If the line calls a function, next places a temporary breakpoint at the call's return address, which in flash takes a sixth comparator; if one is already used, for example by another hardware breakpoint, none is left. Delete or disable a breakpoint.

Why can the debugger set unlimited breakpoints in a function copied to RAM?

It can write a BKPT instruction over the RAM code with an ordinary memory write and restore it later, so no comparator is needed.

You type finish inside f(), which was called from main line 11. Where does the program stop, and what is printed?

In main, just after the call returns, still on line 11 (the assignment of the result may not have completed); GDB prints the value f returned.

A watchpoint on a 12-byte structure fails to insert, but one on a 32-bit field of it works. Why?

A Cortex-M data comparator matches a power-of-two size aligned to that size. 12 bytes is not a power of two, so the debugger needs a 16-byte aligned region or several comparators, which may not be available.

Sources (4)
  1. GDB manual, “Setting Breakpoints” (hbreak, set breakpoint auto-hw) and “Setting Watchpoints” — GDB “normally implements breakpoints by replacing the program code at the breakpoint address with a special instruction”; with auto-hw it uses “the target memory map to decide if software or hardware breakpoint must be used”, including for “internal breakpoints set by commands like next and finish”; hardware watchpoints report “a change in value at the exact instruction where the change occurs”, software watchpoints single-step and are “hundreds of times slower”; “Hardware watchpoint num: Could not insert watchpoint” is reported on resume (read from gdb/doc/gdb.texinfo in the riscv-collab/riscv-binutils-gdb mirror)
  2. GDB manual, “Continuing and Stepping” (step, next, finish, stepi) — step “steps inside any functions called within the line” but “only enters a function if there is line number information for the function. Otherwise it acts like the next command”; next executes calls “without stopping”; finish runs “until just after function in the selected stack frame returns” and prints the returned value
  3. OpenOCD, src/target/cortex_m.c and cortex_m.h — the number of hardware breakpoint comparators is read from FPCTRL (fp_num_code) and of DWT comparators from DWT_CTRL bits 31:28; “Can not find free FPB Comparator!” when none is left; Flash Patch rev. 1 “cannot handle HW breakpoint above address 0x1FFFFFFE”; software breakpoints write a BKPT instruction (not BKPT 0xAB, reserved for semihosting)
  4. Arm, CMSIS 6, CMSIS/Core/Include/core_cm4.h — DWT_CTRL NUMCOMP in bits 31:28 (the number of comparators is implementation-defined and read at run time)