Oberon RTK

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-LINK
  • RTK_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.json
  • examples/v3.1/rpi/pico2/SignalSync/.vscode/launch.json

Typical Session

  1. open the project directory in VS Code (File > Open Folder)
  2. in the Run and Debug view (Ctrl+Shift+D), select Load (or whichever matches your setup) and press F5
  3. 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 .alst file in the rdb/ 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