| name | bfd-debug-interface |
| description | Use when debugging STM32 applications with J-Link or ST-Link, including breakpoints, watchpoints, register or memory inspection, HardFault analysis, and automated fault-context capture. |
BFD Debug Interface
Self-Improvement Loop (Required)
- When this skill encounters a build, flash, debug, script-usage, or other workflow problem, do not stop at the local workaround after the issue is solved.
- Record the resolved issue and lesson in
BFD-Kit/.learnings/ERRORS.md and/or BFD-Kit/.learnings/LEARNINGS.md; unresolved capability gaps go to BFD-Kit/.learnings/FEATURE_REQUESTS.md.
- Promote reusable fixes into the affected BFD-Kit asset in the same task when feasible: update the relevant
SKILL.md, script, wrapper, or resource so the next run benefits by default.
- When a learning is promoted into a skill or script, append a short entry to
BFD-Kit/.learnings/CHANGELOG.md and mention the improvement in the task close-out.
Overview
Use this skill to run standardized STM32 debug sessions with repeatable CLI and GDB commands.
Required Precheck
Run bootstrap before any debug action:
python3 ./.codex/skills/bfd-project-init/scripts/bootstrap.py --project-root . --mode check
Log and Evidence Policy (Required)
- Save debug session logs and register snapshots under repository paths.
- Use:
logs/debug/ for session and register logs
logs/hw_error/ for fault records
- Do not keep final artifacts in
/tmp.
- Fail-fast when
.codex/bfd/active_profile.env is missing.
Tool Resolution
| Tool | Rule |
|---|
| J-Link | Resolve from PATH (JLinkExe, JLinkGDBServerCLExe) |
| ST-Link | Resolve from PATH (STM32_Programmer_CLI, ST-LINK_gdbserver) |
| SVD Files | Resolve from STM32_SVD in bootstrap profile |
RTT note:
- J-Link RTT capture uses the existing
build_tools/jlink/rtt.sh flow.
- ST-Link-specific debug and memory control should use the independent
bfd-stlink-interface skill.
- ST-Link RTT capture should use the independent
bfd-strtt-rtt skill.
- Native HSS remains J-Link-only.
Service Timeout With Missing Upstream Status
If the host can send a service/action request but never sees the expected upstream status packets, do not jump straight into application FSM debugging:
- Verify whether
SYSTEM_STATUS, ODOM, and other routine upstream packets have stopped globally, not just the service response.
- Capture RTT and look for repeated USB warnings such as
W:[usb] tx fail ... status=1; on STM32 USB CDC this usually means USBD_BUSY.
- If this signature is present, classify the problem as an upstream transport blockage first.
- For firmware that reuses a shared static TX buffer, confirm the USB path serializes senders and waits for
TxState == 0 both before reusing the buffer and after CDC_Transmit_*() succeeds.
This check is often faster than instrumenting the service FSM and prevents misdiagnosing a transport stall as a control-plane logic bug.
Zephyr USB CDC ACM Fault Triage
If a Zephyr firmware only faults after enabling a service that can select different transports, do not immediately blame missing downstream peripherals such as motors or sensors. Isolate the failing path first:
- Compare
service disabled, service enabled + UART forced, and service enabled + USB selected.
- If only the USB-selected path faults, treat it as a transport-init problem until disproven.
- Capture
CFSR, current PC, and the smallest useful readiness globals such as s_comm_ready, g_is_initialized, g_usb_ready, g_usb_enabled, and the selected transport pointer.
- On legacy Zephyr USB CDC ACM stacks, avoid writing
DCD/DSR line-control signals immediately after usb_enable(). Prefer plain usb_enable() first, then verify host enumeration and /dev/ttyACM* payload separately.
This staged isolation is faster and more reliable than starting from a hardware-absence hypothesis.
Immediate HardFault Right After Flash/Reset
If a freshly flashed STM32 firmware lands in HardFault_Handler before you can reach main, do not start with business logic. First verify the image and memory layout that actually reached the chip:
- Read the live vector table at
0x08000000 and compare the initial MSP / reset vector against the ELF .isr_vector.
- Check the linked
_estack in the map file against the target's real SRAM topology, not just the copied linker script filename. On STM32F4 migrations, CCM RAM is separate and does not extend the 0x20000000 AHB SRAM window.
- Inspect the active link flags in generated build files such as
build.ninja or CMakeFiles/CMakeConfigureLog.yaml; a copied toolchain file can still force -T STM32F427XX_FLASH.ld even when a correct STM32F407XX_FLASH.ld already exists in the repo.
- Only after the live vector table,
_estack, and linker-script family all match the target MCU should you continue with runtime HardFault triage.
FreeRTOS Scheduler HardFault Heuristic
If the decoded fault site lands in FreeRTOS internals such as vListInsertEnd, vListInsert, or xTaskIncrementTick, do not assume the kernel is the root cause. First classify whether the list pointers themselves have already been corrupted:
- Capture
CFSR/HFSR/BFAR/MMFAR and the stacked fault PC/LR before reset.
- If
CFSR shows PRECISERR + BFARVALID and BFAR is an obviously invalid application address, treat it as prior memory corruption.
- Audit recently executed variadic log calls first, especially
%f passed to RTT/SEGGER logs, or %d/%x accidentally fed with addresses/pointers.
- Prioritize hot task loops, delay/error-report paths, and ISR-adjacent logs; on ARM hard-float ABIs, wrong variadic argument types can silently corrupt scheduler/list state and only explode later inside FreeRTOS.
Use the kernel frame as the symptom location and keep tracing outward until you find the application-side memory stomp.
Quick Session (5 Steps)
- Start GDB server.
JLinkGDBServerCLExe -device "${STM32_DEVICE}" -if "${STM32_IF}" -speed "${STM32_SPEED_KHZ}" -port 2331
ST-LINK_gdbserver -p 61234 -m 1
- Connect GDB.
arm-none-eabi-gdb "${STM32_ELF}"
(gdb) target remote localhost:2331
(gdb) target remote localhost:61234
- Initialize target.
(gdb) monitor reset halt
(gdb) load
- Set critical breakpoints.
(gdb) break main
(gdb) break HardFault_Handler
- Run and capture evidence.
(gdb) continue
(gdb) backtrace
(gdb) info registers
Full Command Reference
1) Connect and Load
target remote localhost:2331 connect to J-Link
target remote localhost:61234 connect to ST-Link
file ${STM32_ELF} load symbols
load load program to target
2) Execution Control
run start execution (with load)
continue continue execution
continue <count> continue, ignore count-1 breaks
step step into (source line)
stepi step into (instruction)
next step over (source line)
nexti step over (instruction)
finish run until function returns
until run until line/address
jump *0x08001000 jump to address
3) Breakpoint Management
Set Breakpoints
break main break at function
break file.c:123 break at line
break *0x08001234 break at address
break func if var > 10 conditional breakpoint
tbreak main temporary breakpoint
hbreak HardFault_Handler hardware breakpoint
thbreak func temporary hardware breakpoint
Manage Breakpoints
info breakpoints list all breakpoints
delete <num> delete breakpoint
disable <num> disable breakpoint
enable <num> enable breakpoint
clear main clear by function
clear file.c:123 clear by line
ignore <num> <count> ignore breakpoint N times
condition <num> <expr> set condition
commands <num> on-hit command list
> print var
> continue
> end
Hardware vs Software Breakpoints
| Type | Limit | Use Case |
|---|
| Hardware | 6 (Cortex-M) | Flash memory |
| Software | Unlimited | RAM only |
4) Watchpoints and Variable Display
watch variable_name break on write
rwatch variable_name break on read
awatch variable_name break on read/write
info watchpoints list watchpoints
delete <num> delete watchpoint
print variable_name
print/x variable_name
print variable_name[0]@10
display variable_name
display/x variable_name
undisplay <num>
5) Register Read and Write
Core Registers
info registers
info all-registers
print $r0
print $pc
print $sp
print $lr
print $xpsr
Write Registers
set $r0 = 0x12345678
set $pc = 0x08001000
set $sp = 0x2001FFF0
Cortex-M Fault Registers
| Register | Address | Purpose |
|---|
| CFSR | 0xE000ED28 | Configurable Fault Status |
| HFSR | 0xE000ED2C | HardFault Status |
| DFSR | 0xE000ED30 | Debug Fault Status |
| MMFAR | 0xE000ED34 | MemManage Fault Address |
| BFAR | 0xE000ED38 | Bus Fault Address |
monitor memU32 0xE000ED28 1 CFSR
monitor memU32 0xE000ED2C 1 HFSR
monitor memU32 0xE000ED34 1 MMFAR
monitor memU32 0xE000ED38 1 BFAR
6) Memory Read and Write
Read
x/10x 0x20000000
x/20b 0x20000000
x/5i $pc
x/s 0x20001000
x/10wx 0x20000000
Format Specifiers
| Specifier | Size |
|---|
| b | byte (8-bit) |
| h | halfword (16-bit) |
| w | word (32-bit) |
| g | giant (64-bit) |
Write
set {int}0x20000000 = 0x12345678
set {char}0x20000000 = 0x55
set {short}0x20000000 = 0x1234
set {int[10]}0x20000000 = {0}
7) Stack and Call Chain Analysis
backtrace
backtrace full
frame <num>
info frame
info locals
info args
x/20x $sp
8) Flash, Reset, and Semihosting
Flash Programming
JLinkExe -device "${STM32_DEVICE}" -if "${STM32_IF}" -speed "${STM32_SPEED_KHZ}"
J-Link> loadfile firmware.hex
J-Link> r
J-Link> g
ST-LINK_CLI -P firmware.hex -V
Reset
monitor reset
monitor reset halt
monitor halt
monitor go
Semihosting
monitor semihosting enable
monitor semihosting disable
HardFault Playbook
- Break at fault entry.
(gdb) break HardFault_Handler
(gdb) continue
- Read core and fault status.
(gdb) info registers
(gdb) print/x *(uint32_t*)0xE000ED28 CFSR
(gdb) print/x *(uint32_t*)0xE000ED2C HFSR
(gdb) print/x *(uint32_t*)0xE000ED34 MMFAR
(gdb) print/x *(uint32_t*)0xE000ED38 BFAR
- Decode stack frame.
(gdb) x/8x $sp
R0, R1, R2, R3, R12, LR, PC, xPSR
- Save evidence.
logs/debug/hardfault_<timestamp>.log
logs/hw_error/<date>/...
- Hand off to
bfd-fault-logger.
Error Detection and Auto-Trigger
RTT Keyword Monitor
| Keyword | Severity | Action |
|---|
ERROR | High | Log and analyze |
FATAL | Critical | Halt and log |
HardFault | Critical | Capture fault state |
BusFault | Critical | Capture fault state |
MemManage | Critical | Capture fault state |
UsageFault | High | Log and analyze |
Trigger Conditions
- HardFault breakpoint is hit.
- CFSR indicates BusFault-related bits.
- Peripheral status registers expose error flags.
- RTT stream contains high-risk keywords.
Minimal Automation Snippet
break HardFault_Handler
commands
silent
info registers
print/x *(uint32_t*)0xE000ED28
print/x *(uint32_t*)0xE000ED2C
print/x *(uint32_t*)0xE000ED34
print/x *(uint32_t*)0xE000ED38
x/8x $sp
continue
end
Context Fields for bfd-fault-logger
{
"error_type": "HardFault|BusFault|PeripheralError|LogError",
"timestamp": "<debug timestamp>",
"registers": {
"pc": "<program counter>",
"lr": "<link register>",
"sp": "<stack pointer>",
"cfsr": "<fault status>",
"hfsr": "<hard fault status>",
"bfar": "<bus fault address>",
"mmfar": "<memmanage fault address>"
},
"stack_trace": "<backtrace output>",
"rtt_log": "<relevant log lines>",
"peripheral": "<optional peripheral>"
}
Hard Rules
- Capture evidence before proposing fixes.
- Fault analysis must include core registers, fault registers, and stack context.
- Save session logs under
logs/ subdirectories.
- Use
bfd-rtt-logger for RTT capture to avoid session conflicts.
- Use
bfd-fault-logger for structured fault archival.
Scripts
| Script | Purpose |
|---|
scripts/var_monitor.py | Real-time variable monitoring via RTT |
scripts/register_view.py | Peripheral register viewer |
scripts/svd_parser.py | SVD parser for register definitions |
Reference Documentation
references/debug-interface-reference.md
Related Skills
bfd-project-init
bfd-debug-executor
bfd-fault-logger
bfd-rtt-logger