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 and Load pages for Build and Load: RP2350, Build and Load: RP2350 (S/NS), Build and Load: STM32, and Build and Load: STM32 (S/NS).
Prerequisites
- Python 3 (3.9 or later)
- xPack ARM GNU Toolchain (
arm-none-eabi-gdb-py3) - OpenOCD matched to your debug probe:
- Raspberry Pi OpenOCD with a CMSIS-DAP probe (Picoprobe or debugprobe) – for RP2350
- xPack OpenOCD with an ST-LINK or J-Link probe – for STM32 targets
- VS Code with Cortex-Debug (for graphical debugging), or use GDB directly from the command line
See External Tools for installation details and Environment Variables for configuration.
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
(e.g. 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. Ready-made
scripts are in tools/scripts/, each with .sh and .cmd variants.
The scripts use environment variables to locate the server executable:
RTK_OPENOCD_XPACK– xPack OpenOCD executable (STM32 scripts)RTK_OPENOCD_RPI– Raspberry Pi OpenOCD executable (RP2350 scripts)RTK_OPENOCD_CFG_STLINK– OpenOCD interface config for ST-LINKRTK_OPENOCD_CFG_JLINK– OpenOCD interface config for J-Link
Set these as permanent user environment variables. On Windows:
setx RTK_OPENOCD_XPACK "C:\path\to\xpack\openocd.exe"
setx RTK_OPENOCD_RPI "C:\path\to\rpi\openocd.exe"
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.
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 (e.g. 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 to the target, resets, and halts at
main - Attach – connects to a running target without flashing; use this when the program was loaded via picotool or STM32CubeProgrammer
Working examples:
examples/v3.1/stm/u585i-iot/SignalSync/.vscode/launch.jsonexamples/v3.1/rpi/pico2/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 setup) and press F5
- the debugger flashes the ELF, 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 (with --image-def): 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:
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's symbols and debug data into GDB. load writes
the binary to flash on the target. These are separate operations.
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 (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 (e.g. 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:
arm-none-eabi-gdb-py3
(gdb) file Prog.elf
(gdb) target extended-remote localhost:3333
(gdb) monitor reset halt
(gdb) break main
(gdb) continue
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 (RP2350)
On the RP2350, a hardware reset (monitor reset halt) runs the boot
ROM, which initialises the XIP interface to flash memory. 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. Then type
restart-rp followed by continue at the GDB prompt.
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 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 startup, 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 matches the binary on the target. If the program was recompiled, repeat the full workflow (gen-rdb, make-elf, load).
See Also
Build RTK-based Programs, External Tools, Environment Variables, make-elf, gen-rdb
Last updated: 18 April 2026