Debugging
Debugging Astrobe Oberon programs on the target hardware with GDB and OpenOCD
Programs built with Astrobe can be debugged on the target hardware using GDB. GDB can be used directly from the command line, or through a GUI front-end such as VS Code with the Cortex-Debug extension.
Debugging requires an ELF file with DWARF debug data, built by gen-rdb and make-elf.
For the full build workflow see the Build & Load pages.
Prerequisites
Before debugging on hardware, complete the debug set-up for your
target: Set-up: Debugging on RP (RP2040 / RP2350) or
Set-up: Debugging on STM32 (STM32U585 / STM32H573). Those guides cover
tool installation and RTK_* environment variables configuration; this page picks up at
the point where the toolchain is in place and a debug-enabled ELF
file has been built.
How It Works
Hardware debugging uses two cooperating programs:
-
GDB server (OpenOCD) – connects to the target MCU via a debug probe (ST-LINK, J-Link, CMSIS-DAP). It translates GDB commands into the target's debug interface: reading/writing memory and registers, setting breakpoints, halting and stepping the CPU.
-
GDB (
arm-none-eabi-gdb-py3) – the debugger. It connects to the GDB server over a TCP port, sends commands, and interprets the results. GDB reads the ELF file for symbols and debug data, but it does not talk to the hardware directly.
Both approaches described below – VS Code with Cortex-Debug, and the GDB command line – use this same two-program architecture. Cortex-Debug is a graphical front-end that runs GDB internally and manages the GDB server on the user's behalf.
VS Code + Cortex-Debug provides source navigation across multiple files, variable inspection with automatic type display, memory views, and peripheral register browsing via SVD files (the SVD file maps register names and field definitions to addresses, so individual peripheral registers can be inspected by name without knowing their addresses). Cortex-Debug also launches and configures the GDB server automatically.
GDB command line is useful for quick checks, scripted operations,
and environments where VS Code is not available. All debugging
capabilities are the same – GDB has full access to symbols, types,
and DWARF data – but peripheral registers must be accessed by address
(eg. x/1xw 0x40021000), since GDB has no SVD support.
The ELF file is the single artefact that ties everything together: it
carries the binary for flashing (load), the symbols for breakpoints
and variable inspection (file), and the DWARF data for source-level
stepping and type-aware display.
GDB Server
Cortex-Debug launches its own OpenOCD instance automatically. For command-line GDB, start OpenOCD manually before connecting – the framework ships ready-made server scripts; see External Tools § GDB Server Scripts for the catalogue and which script matches which target/probe combination.
The server listens on port 3333. Leave it running in a separate terminal. (Cortex-Debug uses its own port assignment.)
VS Code + Cortex-Debug
The Cortex-Debug extension provides a graphical debugging environment with variable views, peripheral register displays (via SVD files), and memory inspection. See External Tools for installation; Set-up: Debugging on RP and Set-up: Debugging on STM32 cover the env-var wiring it relies on.
Settings
Two settings are required at user level in VS Code. Per-folder
settings.json files are not picked up when using .code-workspace
files, so these must be set in the global settings
(File > Preferences > Settings, or edit settings.json directly):
{
"cortex-debug.variableUseNaturalFormat": false,
"debug.allowBreakpointsEverywhere": true
}
The first setting displays variables in hexadecimal rather than decimal. The second allows breakpoints on any line, which is needed for Oberon assembly listings.
Launch Configurations
Debug configurations live in a .vscode/launch.json file at the
root of the workspace. All RTK example projects include a
.vscode/launch.json in their project directory. The recommended
workflow is to open each project directory as its own VS Code workspace
(File > Open Folder). Other setups are possible (eg. a common root
workspace for several projects), but one project per workspace is
simple and straightforward: to debug a program, open its directory.
Each configuration specifies:
-
"cwd": "${workspaceFolder}"– working directory is the project folder -
"executable": "Prog.elf"– the ELF file name for this project -
the GDB server type (OpenOCD), debug probe, adapter speed, device name, and SVD file (all via environment variables)
Two configurations are typical:
-
Load – flashes the ELF file to the target, resets, and halts at symbol
main(whichmake-elfdefines at the entry address of the program) -
Attach – connects to a running target without flashing; use this when the program was loaded via picotool or STM32CubeProgrammer
Working examples:
-
<repo>/examples/v3.1/stm/u585i-iot/SignalSync/.vscode/launch.json -
<repo>/examples/v3.1/rpi/pico2/SignalSync/.vscode/launch.json -
<repo>/examples/v3.1/rpi/pico/SignalSync/.vscode/launch.json
Typical Session
-
open the project directory in VS Code (File > Open Folder)
-
in the Run and Debug view (Ctrl+Shift+D), select Load (or whichever matches your set-up) and press F5
-
the debugger flashes the ELF file, resets the target, and halts at
main
From here you can:
-
step through code (F10 for step over, F11 for step into)
-
set breakpoints by clicking in the gutter of an
.alstfile in therdb/directory (the assembly listing files are the debug source) -
inspect variables in the Variables panel – local variables, arguments, and global variables are shown with their current values
-
view peripheral registers in the Cortex-Debug peripheral panel (requires an SVD file in the launch configuration)
-
inspect memory using the Memory view
Other GDB Front-Ends
Any GDB front-end that supports remote debugging can be used with the
ELF files. The DWARF debug data is standard and not tied to a specific
tool. Configure the front-end to connect to localhost:3333 and load
the ELF file.
GDB Command Line
Single-Image Programs
STM32
arm-none-eabi-gdb-py3
(gdb) file Prog.elf
(gdb) target extended-remote localhost:3333
(gdb) monitor reset halt
(gdb) load
(gdb) break main
(gdb) continue
RP2350
A second monitor reset halt after load is required. The reset
runs the boot ROM, which discovers the IMAGE_DEF block and
initialises the XIP interface. This applies to both Path A
(prepended via make-elf --image-def, the framework default) and
Path B (embedded via the ImageDef module) – see
IMAGE_DEF.
arm-none-eabi-gdb-py3
(gdb) file Prog.elf
(gdb) target extended-remote localhost:3333
(gdb) monitor reset halt
(gdb) load
(gdb) monitor reset halt
(gdb) break main
(gdb) continue
file loads the ELF file's symbols and debug data into GDB. load writes
the binary to flash on the target. These are separate operations.
RP2040
The same flow as RP2350 (above). On RP2040 the prepended block is
the boot2 stage-2 loader (via make-elf --boot2); the second
monitor reset halt is required because the boot ROM runs boot2
to initialise the QSPI flash interface.
S/NS Dual-Image Programs
For S/NS programs, the S and NS ELF files use symbol prefixes
(--sym-prefix S and --sym-prefix NS) to avoid name collisions.
file and add-symbol-file load symbols into GDB. load and
load <file> write binaries to flash. Both images must be flashed.
STM32
arm-none-eabi-gdb-py3
(gdb) file sec/S.elf
(gdb) add-symbol-file nonsec/NS.elf
(gdb) target extended-remote localhost:3333
(gdb) monitor reset halt
(gdb) load
(gdb) load nonsec/NS.elf
(gdb) monitor reset halt
(gdb) break S_main
(gdb) continue
load (without argument) flashes the primary ELF file (sec/S.elf).
load nonsec/NS.elf flashes the NS image to its address range.
RP2350 (without partition table)
arm-none-eabi-gdb-py3
(gdb) file sec/S.elf
(gdb) add-symbol-file nonsec/NS.elf
(gdb) target extended-remote localhost:3333
(gdb) monitor reset halt
(gdb) load
(gdb) load nonsec/NS.elf
(gdb) monitor reset halt
(gdb) break S_main
(gdb) continue
OpenOCD performs sector-level flash erase, so the second load
preserves the first image. The second monitor reset halt runs the
boot ROM to initialise XIP.
With symbol prefixes, procedure names become S_Module_Proc and
NS_Module_Proc (eg. S_Main__init, NS_Main__init).
Loading Without Flashing (Attach)
When the images were loaded via picotool or STM32CubeProgrammer, use
file and add-symbol-file to load symbols only — do not use load:
Single-image (Non-S/NS)
arm-none-eabi-gdb-py3
(gdb) file Prog.elf
(gdb) target extended-remote localhost:3333
(gdb) monitor reset halt
(gdb) break main
(gdb) continue
Dual-image (S/NS)
arm-none-eabi-gdb-py3
(gdb) file sec/S.elf
(gdb) add-symbol-file nonsec/NS.elf
(gdb) target extended-remote localhost:3333
(gdb) monitor reset halt
(gdb) break S_main
(gdb) continue
In VS Code, use the Attach configuration instead of Load.
Warning: On the RP2350, load writes to flash at absolute
addresses. If the target uses a partition table, load will overwrite
the partition table. In that case, load both images via picotool
(which respects partition boundaries) and use GDB in attach mode.
Useful GDB Commands
(gdb) list -- show source around current PC
(gdb) step -- step one source line
(gdb) next -- step over procedure call
(gdb) print period -- print a variable
(gdb) info locals -- show all local variables
(gdb) break Kernel_SetPeriod -- set breakpoint (underscore form)
(gdb) continue -- run until next breakpoint
(gdb) info registers -- show CPU registers
(gdb) x/4xw 0x20000000 -- examine 4 words of memory
Note: procedure names in the symbol table use underscores instead of
dots (Kernel_SetPeriod not Kernel.SetPeriod), because some GDB
expression evaluators interpret dots as structure-member operators.
Restarting the Target (RP2040 / RP2350)
On RP2040 and RP2350, a hardware reset (monitor reset halt) runs
the boot ROM, which initialises the XIP interface to flash memory
(via the boot2 stage-2 loader on RP2040, via the IMAGE_DEF block on
RP2350). Without this, flash is not accessible and GDB cannot read
program code or set breakpoints.
A full monitor reset halt followed by load is slow when you just
want to re-run the same program. The restart-rp command avoids the
hardware reset entirely: it restores the CPU state from the vector
table while keeping the XIP interface active.
Add this command to ~/.gdbinit:
define restart-rp
set $msp = *(unsigned int *)0x10000100
set $pc = *(unsigned int *)0x10000104
set *(unsigned int *)0xE000ED08 = 0x10000100
end
This restores the initial stack pointer and program counter from
the vector table at 10000100H, and resets the VTOR register.
Run restart-rp followed by continue at the GDB prompt.
The address 0x10000100 matches RP2040 (always, after the boot2
prologue) and RP2350 Path A (the framework default, with IMAGE_DEF
prepended). On RP2350 Path B (ImageDef module embedded; code
starts at 10000000H), substitute 0x10000000 for all three
occurrences. See IMAGE_DEF.
In VS Code + Cortex-Debug, the standard restart button performs a
monitor reset halt, which works correctly. To use the faster
register-restore approach instead, add overrideRestartCommands
to the launch configuration:
"overrideRestartCommands": [
"set $msp = *(unsigned int *)0x10000100",
"set $pc = *(unsigned int *)0x10000104",
"set *(unsigned int *)0xE000ED08 = 0x10000100"
]
Troubleshooting
GDB cannot connect to the server
Check that the GDB server is running and listening on port 3333. Check that no other process is using the port.
"No symbol table" or missing variables
The ELF file was built without --debug. Rebuild with --debug.
Breakpoints not hit
Hardware breakpoints are limited in number. If you see "Cannot insert breakpoint", reduce the number of active breakpoints. OpenOCD uses hardware breakpoints for flash addresses; JLink GDB Server can emulate unlimited flash breakpoints.
User-defined GDB commands not recognised
GDB reads ~/.gdbinit at start-up, but on Windows ~ resolves via the
HOME environment variable. Windows does not set HOME by default, so
GDB cannot find the init file. Set HOME permanently with:
setx HOME "%USERPROFILE%"
This takes effect in new terminals.
Variables show unexpected values
Check that the ELF file matches the binary on the target. If the program was recompiled, repeat the full workflow (gen-rdb, make-elf, load).
See Also
Set-up: Debugging on RP, Set-up: Debugging on STM32, Compile and Link: RP2040, Compile and Link: RP2350, Compile and Link: STM32, IMAGE_DEF, External Tools, Environment Variables, make-elf, gen-rdb
Last updated: 8 May 2026