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:
nextandfinishwork 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.
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.
watch readytakes one DWT comparator. Two breakpoint comparators remain free fornextandfinish.- The program stops just after the store that changed it, with GDB reporting the old and new value;
btshows the call chain that led to it (lesson 3). - The culprit is a
memsetof a neighbouring 16-byte buffer with length 20: bytes 0x2000_0408 to 0x2000_041B, four bytes past the buffer’s end at 0x2000_0417, intoready.
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)
- 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)
- 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
- 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)
- 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)