01 Connecting: OpenOCD, st-util, J-Link GDB server
GDB never talks to the chip directly. A GDB server owns the SWD/JTAG probe and exposes the GDB Remote Serial Protocol over a TCP port; arm-none-eabi-gdb is the client. Your whole workflow is: start a server, then target extended-remote host:port.
Use target extended-remote (not plain target remote) whenever the server supports it: the extended protocol lets you kill, re-run and reconnect without dropping the server, which is what you want for iterative flash-debug cycles.
# OpenOCD (ST-LINK v2/v3 on-board or standalone) — GDB port 3333
openocd -f interface/stlink.cfg -f target/stm32l4x.cfg
# texane/stlink st-util — GDB port 4242
st-util
# SEGGER J-Link — GDB port 2331, device name is mandatory
JLinkGDBServer -device STM32L4R5ZI -if SWD -speed 4000
# ST official ST-LINK GDB server (ships with STM32CubeIDE) — default port 61234
# -cp points at the STM32CubeProgrammer bin dir it needs for flashing
ST-LINK_gdbserver -p 61234 -e -d -cp /opt/st/stm32cubeide/.../tools/bin
arm-none-eabi-gdb build/firmware.elf
(gdb) target extended-remote localhost:3333 # OpenOCD
# (gdb) target extended-remote localhost:4242 # st-util
# (gdb) target extended-remote localhost:2331 # J-Link
(gdb) monitor reset halt # 'monitor' forwards a raw command to the server
(gdb) info registers pc sp # confirm the core is halted and reachable
| Server | Launch command | GDB port | monitor dialect |
|---|---|---|---|
| OpenOCD | openocd -f interface/stlink.cfg -f target/stm32l4x.cfg | 3333 | rich (reset halt, flash, semihosting) |
| st-util | st-util | 4242 | minimal |
| J-Link GDB server | JLinkGDBServer -device STM32L4R5ZI -if SWD | 2331 | J-Link (monitor reset, monitor halt) |
| ST-LINK_gdbserver | ST-LINK_gdbserver -p 61234 -cp <progr-dir> | 61234 | ST GDB server |
monitor <cmd> is a passthrough to the server, so the syntax differs per server. monitor reset halt is OpenOCD; J-Link splits it into monitor reset + monitor halt. Everything else in this guide (break, x/, info registers) is pure GDB and identical across servers.
The STM32L4R5 debug port is SWD (SWDIO/PA13, SWCLK/PA14) plus optional SWO trace (PB3). If monitor reset halt fails with "target not halted", the firmware may be reconfiguring PA13/PA14 as GPIO or entering a low-power mode — attach with monitor reset halt under connect-under-reset (OpenOCD: add -c "reset_config srst_only srst_nogate connect_assert_srst").
02 Flashing & loading firmware from GDB
GDB's load command downloads the ELF's loadable sections straight into flash through the server, then you verify and reset. You rarely need a separate flasher during a debug session.
(gdb) monitor reset halt # stop the core before touching flash
(gdb) load # program all LOAD sections into flash @ 0x08000000
Loading section .isr_vector, size 0x30c lma 0x8000000
Loading section .text, size 0x5f14 lma 0x800030c
Loading section .data, size 0x88 lma 0x8006220
Start address 0x0800030c, load size 25228
(gdb) compare-sections # read back flash and diff against the ELF
Section .text: matched.
(gdb) monitor reset halt # re-run the vector table / SP from the fresh image
(gdb) break main
(gdb) continue
Flashing outside a debug session (build pipelines, quick reflash) is faster with the dedicated tools:
# st-flash writes a raw binary at an absolute address (flash base = 0x08000000)
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
st-flash write firmware.bin 0x08000000
# OpenOCD one-shot: program + verify + reset + exit
openocd -f interface/stlink.cfg -f target/stm32l4x.cfg \
-c "program firmware.elf verify reset exit"
# J-Link via GDB script (see .gdbinit section) or J-Flash
load writes bytes and refreshes symbols. If the target was already flashed by another tool and you only need the debug symbols, use file firmware.elf (or start GDB with the ELF) and skip load. Debugging with an ELF that does not match the flashed image gives you wrong source lines and phantom breakpoints — the classic "why is it stepping into the wrong function" bug.
If your linker script places code in SRAM1 (0x20000000), load writes directly to RAM (no flash-erase latency) and you set $pc/$sp manually or via a RAM vector table. Handy for fast edit-compile-debug loops. For flash images, always monitor reset halt after load so the Cortex-M reloads MSP and the reset vector from 0x08000000.
03 Breakpoints & watchpoints (break, hbreak, watch)
The Cortex-M4 has finite debug hardware: a Flash Patch & Breakpoint (FPB) unit with a handful of instruction comparators (typically 6 on STM32 Cortex-M4) and a Data Watchpoint & Trace (DWT) unit with 4 comparators. GDB juggles these for you, but you hit hard limits fast.
Breakpoints
(gdb) break main # by symbol
(gdb) break usart.c:142 # by file:line
(gdb) break *0x08001a3c # by absolute address
(gdb) break HardFault_Handler # by handler name
(gdb) tbreak SystemInit # temporary: auto-deletes after first hit
(gdb) hbreak process_packet # FORCE a hardware (FPB) breakpoint
(gdb) info breakpoints # list, with hit counts
(gdb) delete 2 # remove breakpoint #2
(gdb) disable 3 # keep but deactivate
# conditional + counted breakpoints (evaluated on-target each pass)
(gdb) break adc_isr if channel == 7
(gdb) condition 4 count > 100
(gdb) ignore 4 50 # skip the next 50 hits of bp #4
# run a command list automatically at a breakpoint
(gdb) commands 1
> printf "tick=%u sp=0x%08x\n", g_ticks, $sp
> continue
> end
A software breakpoint replaces the instruction with BKPT, which means writing the target memory. In flash that is expensive and not always possible per-instruction, so OpenOCD transparently promotes flash breakpoints to the FPB hardware anyway. Use hbreak explicitly when: (a) debugging code executing from flash/ROM and plain break silently fails to stop, or (b) the code region is not yet loaded. Every hbreak consumes one of the ~6 FPB slots — GDB reports Cannot insert hardware breakpoint N when they run out.
Watchpoints (DWT data breakpoints)
(gdb) watch g_state # break on WRITE to g_state
(gdb) rwatch g_state # break on READ
(gdb) awatch g_state # break on READ or WRITE (access)
# watch a raw address — perfect for "who is corrupting this?"
(gdb) watch *(uint32_t*)0x20000100
# scope-limited: only while a local is live
(gdb) watch buf[i] if i == 3
(gdb) continue
Hardware watchpoint 2: g_state
Old value = 0
New value = 3
process_event () at events.c:88
Watchpoints map to DWT comparators (4 on Cortex-M4), and each can cover 1/2/4/8/16... bytes but must be size- and alignment-compatible. Watching a large struct or a 64-byte buffer can fail with Cannot insert hardware watchpoints because it needs more comparators than exist. Watch a single word or a specific field instead. Never rely on GDB "software watchpoints" on a Cortex-M — single-stepping every instruction to check a value is unusably slow.
04 Stepping, backtrace & frame navigation
Once halted, you drive execution one source line, one instruction, or one function at a time, and you climb the call stack to read locals in any frame.
(gdb) next / n # step over — do not descend into calls
(gdb) step / s # step into — descend into called function
(gdb) nexti / ni # step over one machine instruction
(gdb) stepi / si # step into one machine instruction
(gdb) finish # run until current function returns, print retval
(gdb) until 210 # run until source line 210 (skips loop iterations)
(gdb) continue / c # resume until next breakpoint / fault
(gdb) return 0 # force-return from current frame (careful!)
(gdb) backtrace / bt # full call chain, innermost first
#0 i2c_wait_flag (tmo=1000) at i2c.c:73
#1 0x08002c1a in i2c_write (addr=80) at i2c.c:120
#2 0x08003044 in sensor_read () at bme280.c:55
#3 0x080008e0 in main () at main.c:64
(gdb) bt full # backtrace + every frame's local variables
(gdb) frame 2 / f 2 # select frame #2 (sensor_read)
(gdb) up # move one frame toward main
(gdb) down # move one frame toward the fault
(gdb) info args # arguments of the selected frame
(gdb) info locals # locals of the selected frame
(gdb) info frame # CFA, saved regs, return address of this frame
With -O2 the backtrace can look wrong: inlined functions collapse, locals show <optimized out>, and stepping jumps around. Build the code under test with -Og -g3 for faithful stepping while keeping realistic timing. -g3 additionally gives you macro expansion in GDB.
05 Examining memory & core registers (x/, info registers)
The two workhorses: x/ (examine memory) reads arbitrary addresses in any format; info registers dumps the core state. On a Cortex-M, OpenOCD also exposes the special registers (MSP, PSP, PRIMASK, CONTROL...) as GDB convenience registers.
x/ — examine memory
Syntax is x/NFU addr: N = count, F = format letter, U = unit size.
| Format F | Meaning | Unit U | Size |
|---|---|---|---|
| x | hex | b | byte (1) |
| d / u | signed / unsigned decimal | h | halfword (2) |
| t | binary | w | word (4) |
| c / s | char / string | g | giant (8) |
| i | disassembled instruction | ||
| f / a | float / address (with symbol) |
(gdb) x/8xw 0x20000000 # 8 words of SRAM1 in hex
(gdb) x/16xb &rx_buffer # 16 raw bytes of a buffer
(gdb) x/s device_name # a C string
(gdb) x/t 0x48000010 # GPIOA_IDR as binary
(gdb) x/8i $pc # disassemble 8 instructions at PC
(gdb) x/i $pc # the exact instruction about to execute
# print expressions with explicit format
(gdb) p/x $lr # link register in hex
(gdb) p/t status_reg # a variable in binary
(gdb) p (rx_len * 2) + 1 # arithmetic on target values
(gdb) p sizeof(struct config) # types resolved from DWARF
Registers
(gdb) info registers # r0-r12, sp, lr, pc, xpsr
(gdb) info all-registers # + FPU s0-s31, fpscr, and special regs
(gdb) info registers pc sp lr # just these three
# Cortex-M special registers (exposed by OpenOCD)
(gdb) p/x $msp # Main stack pointer
(gdb) p/x $psp # Process stack pointer
(gdb) p/x $primask # global IRQ mask (1 = IRQs off)
(gdb) p/x $basepri # priority threshold mask
(gdb) p/x $faultmask
(gdb) p/x $control # bit0 nPRIV, bit1 SPSEL, bit2 FPCA
# write a register (e.g. redirect execution or fix a stuck flag)
(gdb) set $pc = 0x08001a3c
(gdb) set $r0 = 0
(gdb) set var g_error_flag = 0
(gdb) set *(uint32_t*)0x20000100 = 0xDEADBEEF
$xpsr low 9 bits are ISR_NUMBER: 0 = thread mode, 3 = HardFault, 11 = SVCall, 15 = SysTick, and 16 + IRQn for peripheral IRQs. So a non-zero low byte in $xpsr means you are inside an exception handler — decode which one before trusting the stack.
06 Peripheral registers by address (SVD-less)
No SVD, no CMSIS headers, no problem. Every STM32 peripheral is memory-mapped: compute base + offset from RM0432 and read/write it with x/ or a cast. This is how you debug bring-up before any driver exists.
| Peripheral | Base address | Bus |
|---|---|---|
| Flash memory | 0x08000000 | — |
| SRAM1 | 0x20000000 | — |
| GPIOA / GPIOB / GPIOC | 0x48000000 / 0x48000400 / 0x48000800 | AHB2 |
| RCC | 0x40021000 | AHB1 |
| PWR | 0x40007000 | APB1 |
| USART2 | 0x40004400 | APB1 |
| USART1 | 0x40013800 | APB2 |
| NVIC ISER0 | 0xE000E100 | PPB |
| SysTick CTRL | 0xE000E010 | PPB |
| DWT CYCCNT | 0xE0001004 | PPB |
| SCB base | 0xE000ED00 | PPB |
GPIO port register offsets (add to the port base, e.g. GPIOA_ODR = 0x48000000 + 0x14 = 0x48000014):
| Offset | Register | Purpose |
|---|---|---|
| 0x00 | MODER | 2 bits/pin: 00 input, 01 output, 10 alt-func, 11 analog |
| 0x08 | OSPEEDR | output speed |
| 0x0C | PUPDR | pull-up / pull-down |
| 0x10 | IDR | input data (read pin levels) |
| 0x14 | ODR | output data |
| 0x18 | BSRR | atomic set (low 16) / reset (high 16) |
| 0x20 / 0x24 | AFRL / AFRH | alternate-function select, 4 bits/pin |
# Is the GPIOA clock even enabled? RCC_AHB2ENR = 0x40021000 + 0x4C, bit0 = GPIOAEN
(gdb) p/t *(uint32_t*)0x4002104C
$1 = 1 # bit0 set -> GPIOA clocked. If 0, your writes do nothing.
# Read GPIOA_MODER, check PA5 (LED) is output (bits [11:10] = 01)
(gdb) x/t 0x48000000
# Read the pin levels on port A
(gdb) x/t 0x48000010 # GPIOA_IDR
# Toggle PA5 atomically via BSRR (set bit 5)
(gdb) set *(uint32_t*)0x48000018 = (1 << 5) # BS5: drive high
(gdb) set *(uint32_t*)0x48000018 = (1 << 21) # BR5: drive low
# Read a USART2 status/data register set (base 0x40004400)
(gdb) x/8xw 0x40004400 # ISR/RDR/TDR/BRR... dump the block
# Free-running cycle counter for micro-benchmarks (must enable DWT first)
(gdb) p/d *(uint32_t*)0xE0001004 # DWT_CYCCNT
Casting to volatile uint32_t* everywhere is noisy. In your .gdbinit define helpers like define gpioa that print MODER/IDR/ODR by name (see the automation section). GDB also honors set $g = (uint32_t*)0x48000000, after which p/x $g[5] reads offset 0x14.
Some registers clear flags or pop FIFOs on read (e.g. a UART RDR read consumes a byte, reading certain status registers clears sticky bits). Using x/ on those from GDB will silently change device state and hide the bug you are chasing. Know which registers are read-to-clear before you examine them.
07 Catching HardFault: SCB CFSR / HFSR / BFAR
When a Cortex-M4 faults, it stacks 8 words and vectors to a handler. The why lives in the System Control Block fault status registers. These addresses are architectural (ARMv7-M), identical on every Cortex-M4 including the STM32L4R5.
| Register | Address | Tells you |
|---|---|---|
| CFSR | 0xE000ED28 | Configurable Fault Status = UFSR[31:16] | BFSR[15:8] | MMFSR[7:0] |
| HFSR | 0xE000ED2C | HardFault Status: FORCED (escalated) / VECTTBL |
| MMFAR | 0xE000ED34 | faulting data address (valid if MMFSR.MMARVALID) |
| BFAR | 0xE000ED38 | faulting bus address (valid if BFSR.BFARVALID) |
| SHCSR | 0xE000ED24 | enable bits for the separate fault handlers |
| CCR | 0xE000ED14 | DIV_0_TRP (bit4), UNALIGN_TRP (bit3) |
| Sub-reg | Bit | Flag | Cause |
|---|---|---|---|
| MMFSR | 0 / 1 | IACCVIOL / DACCVIOL | MPU instruction / data access violation |
| MMFSR | 7 | MMARVALID | MMFAR holds the faulting address |
| BFSR | 0 / 1 | IBUSERR / PRECISERR | instruction bus err / precise data bus err (BFAR valid) |
| BFSR | 2 | IMPRECISERR | imprecise (buffered write) bus error — BFAR NOT valid |
| BFSR | 7 | BFARVALID | BFAR holds the faulting address |
| UFSR | 0 / 1 | UNDEFINSTR / INVSTATE | undefined instruction / illegal EPSR (Thumb bit cleared) |
| UFSR | 2 / 3 | INVPC / NOCP | bad EXC_RETURN / coprocessor (FPU) disabled or absent |
| UFSR | 8 / 9 | UNALIGNED / DIVBYZERO | unaligned access / divide-by-zero (needs trap enabled in CCR) |
| HFSR | 30 / 1 | FORCED / VECTTBL | escalated from a lower fault / bad vector table read |
Recipe A — decode a live fault from GDB
You are stopped in HardFault_Handler (set break HardFault_Handler beforehand). Read the fault registers and recover the stacked frame:
(gdb) p/x *(uint32_t*)0xE000ED28 # CFSR
$1 = 0x00000082 # MMFSR: DACCVIOL(1) + MMARVALID(7) -> bad data access
(gdb) p/x *(uint32_t*)0xE000ED2C # HFSR
$2 = 0x40000000 # FORCED -> escalated from the config fault above
(gdb) p/x *(uint32_t*)0xE000ED34 # MMFAR = the address that faulted
$3 = 0x00000000 # NULL-pointer write
# Which SP was in use on entry? EXC_RETURN (in $lr) bit 2: 0 = MSP, 1 = PSP
(gdb) p/x $lr
$4 = 0xfffffff9 # ...9 -> returns to MSP, thread mode
# The 8-word exception frame sits at that SP. Dump it:
(gdb) x/8xw $msp
(gdb) set $frame = (uint32_t*)$msp
(gdb) printf "stacked PC = 0x%08x\n", $frame[6] # the faulting instruction
(gdb) printf "stacked LR = 0x%08x\n", $frame[5]
(gdb) printf "stacked xPSR= 0x%08x\n", $frame[7]
(gdb) list *$frame[6] # jump to the guilty source line
| SP offset | Stacked reg | Note |
|---|---|---|
| +0x00 | R0 | frame[0] |
| +0x04 | R1 | frame[1] |
| +0x08 | R2 | frame[2] |
| +0x0C | R3 | frame[3] |
| +0x10 | R12 | frame[4] |
| +0x14 | LR (R14) | frame[5] — caller return addr |
| +0x18 | Return address (PC) | frame[6] — the faulting instruction |
| +0x1C | xPSR | frame[7] |
Recipe B — a handler that captures the frame
Put this in firmware so faults are dissectable even without a debugger attached (log ctx to flash/RTT). The naked prologue picks the correct stack pointer from EXC_RETURN and hands it to C.
#include <stdint.h>
/* Fault sub-registers, architectural addresses (ARMv7-M). */
#define SCB_CFSR (*(volatile uint32_t*)0xE000ED28u)
#define SCB_HFSR (*(volatile uint32_t*)0xE000ED2Cu)
#define SCB_MMFAR (*(volatile uint32_t*)0xE000ED34u)
#define SCB_BFAR (*(volatile uint32_t*)0xE000ED38u)
typedef struct {
uint32_t r0, r1, r2, r3, r12, lr, pc, xpsr;
} ExcFrame;
/* Set a GDB breakpoint here, or log the fields, then inspect. */
void hard_fault_report(ExcFrame *ctx)
{
volatile uint32_t cfsr = SCB_CFSR; /* why */
volatile uint32_t hfsr = SCB_HFSR; /* escalated? */
volatile uint32_t mmfar = SCB_MMFAR; /* valid iff CFSR bit7 */
volatile uint32_t bfar = SCB_BFAR; /* valid iff CFSR bit15 */
(void)cfsr; (void)hfsr; (void)mmfar; (void)bfar;
(void)ctx; /* ctx->pc = faulting instruction */
__asm volatile ("bkpt #0"); /* trap into the debugger */
for (;;) { }
}
__attribute__((naked)) void HardFault_Handler(void)
{
__asm volatile (
"tst lr, #4 \n" /* EXC_RETURN bit2: 0=MSP, 1=PSP */
"ite eq \n"
"mrseq r0, msp \n"
"mrsne r0, psp \n"
"b hard_fault_report\n"
);
}
By default MemManage, BusFault and UsageFault are disabled and everything escalates to HardFault (that is why HFSR.FORCED is so common). To fault precisely: set SHCSR (0xE000ED24) bits MEMFAULTENA(16)/BUSFAULTENA(17)/USGFAULTENA(18), and set CCR (0xE000ED14) DIV_0_TRP(4) to trap divide-by-zero. Imprecise bus faults (IMPRECISERR) point nowhere useful — insert a DSB after suspect writes to force precision, or set ACTLR.DISDEFWBUF (0xE000E008 bit1) to make all bus faults precise.
08 TUI & .gdbinit automation
The Text User Interface turns GDB into a split-pane debugger in your terminal; .gdbinit scripts collapse the connect-flash-inspect ritual into one command and give you named helpers for peripherals and fault registers.
TUI layouts
(gdb) tui enable # or start gdb with: arm-none-eabi-gdb -tui
(gdb) layout src # source + command panes
(gdb) layout asm # disassembly pane
(gdb) layout regs # registers pane on top of src/asm
(gdb) layout split # source AND disassembly together
(gdb) focus cmd # send arrow keys to the command window
(gdb) focus src # scroll the source window instead
# keybindings
# Ctrl-x a toggle TUI on/off
# Ctrl-x 1 single window
# Ctrl-x 2 two windows (cycle)
# Ctrl-l redraw (fixes a garbled screen)
# Ctrl-p/Ctrl-n command history while a non-cmd window has focus
A project .gdbinit
set pagination off
set confirm off
set print pretty on
set history save on
# --- connect + reset + flash in one word ---
define reload
monitor reset halt
load
monitor reset halt
end
document reload
Reset target, reflash the current ELF, reset again.
end
# --- dump the Cortex-M fault registers by name ---
define faultregs
printf "CFSR = 0x%08x\n", *(unsigned int*)0xE000ED28
printf "HFSR = 0x%08x\n", *(unsigned int*)0xE000ED2C
printf "MMFAR = 0x%08x\n", *(unsigned int*)0xE000ED34
printf "BFAR = 0x%08x\n", *(unsigned int*)0xE000ED38
printf "SHCSR = 0x%08x\n", *(unsigned int*)0xE000ED24
end
# --- decode GPIOA by name (base 0x48000000) ---
define gpioa
printf "MODER=0x%08x IDR=0x%04x ODR=0x%04x\n", \
*(unsigned int*)0x48000000, \
*(unsigned int*)0x48000010, \
*(unsigned int*)0x48000014
end
# --- auto-print PC + instruction every time we stop ---
define hook-stop
printf "-- stopped @ 0x%08x --\n", $pc
x/i $pc
end
# --- connect on startup ---
target extended-remote localhost:3333
monitor reset halt
load
break main
tui enable
layout src
For security, modern GDB refuses to source a .gdbinit from the current directory unless the path is trusted. Either run arm-none-eabi-gdb -x debug.gdb firmware.elf explicitly, or add to your ~/.gdbinit: add-auto-load-safe-path /path/to/project (or the blunt set auto-load safe-path /). Otherwise you will see "File ... auto-loading has been declined" and your defines never register.
To route target printf to the GDB console via semihosting (OpenOCD): monitor arm semihosting enable, then link with the semihosting stubs. For lower overhead, use the SWO/ITM trace pin (PB3) with monitor tpiu ... in OpenOCD, or SEGGER RTT with J-Link. Semihosting halts the core on each I/O call, so never leave it enabled in timing-sensitive code.
09 Gotchas & common mistakes
The bugs that eat an afternoon are almost never in the code you are stepping through — they are in the debug setup itself.
Debugging with an ELF that does not match the image on the chip is the number-one time sink. Symptoms: breakpoints that never hit, stepping into the "wrong" function, garbage locals. Always load (or compare-sections) before you trust source lines. If another tool flashed the board, reflash from GDB or confirm with compare-sections.
Cannot insert hardware breakpoint N / Cannot insert hardware watchpoints means you exhausted the FPB (~6) or DWT (4) comparators. delete stale breakpoints, watch a single word instead of a whole struct, and remember every hbreak and every watch/rwatch/awatch permanently holds a slot until deleted.
Reading a peripheral whose clock is gated returns a bus error, not zeros. Check RCC_AHBxENR/APBxENR first. Also: the core must be halted to read most memory over SWD; if x/ fails, you are probably running — Ctrl-C or monitor halt. And you cannot read flash addresses that the linker never populated.
If BFSR.IMPRECISERR is set, BFARVALID is 0 and the stacked PC points past the real culprit because the write was buffered. Force precision: insert a DSB after suspect stores, or set ACTLR.DISDEFWBUF (0xE000E008 bit1). Then re-run and the fault becomes PRECISERR with a valid BFAR.
<optimized out> and jumpy stepping come from -O2/-Os with inlining. Rebuild with -Og -g3 for debugging. Also make sure you did not strip symbols and that -g reached the linker; a Release ELF with no DWARF gives you address-only debugging.
Single-stepping into __WFI() or a STOP-mode entry can hang the debug connection because the core stops clocking the debug logic. Set the DBGMCU_CR DBG_STOP/DBG_STANDBY bits (or the OpenOCD keep-alive options) to keep the debug clock alive in low-power modes. Conversely, if continue immediately re-breaks in SysTick_Handler, you set a breakpoint in a 1 kHz ISR — use a condition or delete it.
Copy-pasting monitor reset halt against J-Link fails — it wants monitor reset then monitor halt. When a "monitor" command is rejected, you are aiming an OpenOCD idiom at a different server. The GDB-side commands (break, x/, info registers, watch) are identical everywhere; only monitor changes.
One-screen cheat sheet
- Connect:
target extended-remote :3333(OpenOCD) /:4242(st-util) /:2331(J-Link) - Flash:
monitor reset halt→load→compare-sections→monitor reset halt - Stop:
hbreak fn,watch *(uint32_t*)0xADDR,break fn if cond - Look:
x/8xw addr,x/i $pc,info registers,p/x $msp,bt full - Fault: CFSR 0xE000ED28, HFSR 0xE000ED2C, MMFAR 0xE000ED34, BFAR 0xE000ED38; stacked PC =
((uint32_t*)$msp)[6]