Oberon RTK

Change Note 2026-02-19

Tools and debugging support

Tools

  • Updated versions of
    • make-uf2
    • make-elf
  • New tools
    • picoload
    • gen-rdb
  • As usual, running the tools with the -h option displays the usage message.
  • Documentation is missing or possibly out of date, sorry.

make-uf2

  • Removed the automatic upload functionality, since if we have several binaries to upload to different partitions, we better use a targeted approach (see picoload).

  • For RP2350:

    • can create UF2 binaries tagged for Non-secure programs;
    • can omit the meta data block for images that don't boot on reset, such as Non-secure programs and Non-secure Callable veneer blocks.
  • The UF2 binaries can still be copied manually, of course, or with a small shell script.

make-elf

  • Can combine several binary images into one ELF file.

  • Can include DWARF debug data and a symbol table (see section Debugging below).

picoload

  • Utility for uploading UF2 and other images to the RP2040 and RP2350.

  • Uses picotool in lieu of file copying (for now, I may restore the file copying for the RP2040).

gen-rdb

  • Create listing files for debugging, see below.

Astrobe Config Files

  • The project-specific Astrobe config files had started to clutter config/astrobe-rp2350, and for Secure/Non-secure programs selecting the corresponding config file for each build had become tedious (I am lazy).

  • I have move the project-specific config files directly into the directory with the corresponding example/test programs.

  • My scripts pick these config files up automatically now (laziness, but also avoiding wrong builds). I will think about, and implement a solution to again collect them into a central location to be used with the Astrobe IDE via the GUI. For now, I suggest to copy them manually. Sorry about that.

Debugging

Overview

Having a debugger at hand is always useful, but in particular to get started with a new MCU, when terminal printing is not even available yet, to inspect the many configuration aspects and registers for a Secure/Non-secure program separation, or starting a program in a different image. I have been using SEGGER's Ozone debugger, but limited to the very basic functionality, since the uploaded binaries did not include any debug data.

The new version of make-elf now includes a first set of meta and DWARF debug data, in particular a symbol table to match addresses to module and procedure names, line data, which matches addresses to lines in source files, so that the program steps can be visualised right in the program text, and stack frame data.

Existing Functionality

  • Modules and procedures are known by name everywhere in the debugger (including the invisible Module..init procedures):

    • the debugger's find functionality can search for source files (ie. modules) or procedures;
    • we can look up a procedure by a pop-up menu quasi anywhere.
  • We can step through the program, while the current instruction is highlighted in the source file. Calling a procedure in a different module automatically opens the corresponding file.

  • Breakpoints can be set by simply clicking left of a source line.

  • We have a call stack view.

  • All registers can be inspected:

    • processor core registers;
    • processor registers on the Private Peripheral Bus, such as the SAU or the NVIC;
    • all memory mapped peripheral device registers, eg. for UARTs, or timers, etc.
  • We have other views, such as memory layout, procedure call hierarchy with stack depth calcluation, etc.

  • I have deliberately chosen to match addresses to assembly lines, since for low level work that's what I want to see usually, not least as we do not yet have a good data visibility (locals, globals).

  • There is a mode for Oberon source line debugging too, which works astonishingly well, considering the little time I have invested into this, but which needs some more work and polish.

  • Everything I have not discovered and evaluated yet. :)

Missing/future Functionality

  • The biggest missing functionality is a better view of the data.

  • We do have raw memory inspection, for example in the call stack view we can request to display the stack frame, but that's simply in raw hex data, without any symbolic reference to variables and such.

  • Unfortunately this might be trickier, since data types come into play. Not sure how far I'll get for this with the currently employed very simple regex-based scanning.

  • Of course, there's always the CPU's register view.

How Debug Data Is Created

  • The Astrobe compiler creates .lst listing files for every successfully compiled module, right next to the source, symbol, and object files. These listing files represent the relocatable object files, ie. they contain module-local code addresses, and external references to procedures and addresses in other modules are encoded for the linker to resolve to absolute addresses, together with the code addresses themselves.

  • For some time my build system has used these .lst files, together with the .map and .bin file, to create what I call absolute listing files (.alst), where all addresses are absolute, all references are resolved – after the linking process.

  • These absolute listing files form the basis to extract the DWARF debug data and the symbol table.

  • I have now extracted the functionality for the creation of these .alst into the new standalone tool gen-rdb to be used with the Astrobe IDE.

A Few Details

Procedure calls

UART.GetBaseCfg(uartCfg);
.   516  00C0048BC  0F11D0004  adds.w    r0,sp,#4
.   520  00C0048C0  0F8DF1094  ldr.w     r1,[pc,#148] -> 672
.   524  00C0048C4  0F7FDFE18  bl.w      UART.GetBaseCfg [-4584 -> 0C0024F8]
.   528  00C0048C8      0E000  b         0 -> 532
    uartCfg.fifoEn := UART.Enabled;
.   532  00C0048CC      02001  movs      r0,#1
.   534  00C0048CE      09001  str       r0,[sp,#4]
   ...
.   672  00C004958  00C002228  <Global: UART code>
  • The leftmost column still shows the module local addresses in decimal, since they are useful to read the code, which indicates where, say, a ldr.w r1,[pc,#148] -> 672 instruction fetches the value, or where b 0 -> 532 branches to.

  • The second column shows the absolute address in hexademimal as linked.

  • The "Ext Proc" indicators are replaced with the actual module and procedure name, bl.w UART.GetBaseCfg [-4584 -> 0C0024F8], with the offset and the target address indicated in brackets; the corresponding op-code is corrected to its real value as found in the binary (and by the processor).

Constants & Globals

.   672  00C004958  00C002228  <Global: UART code>
.   676  00C00495C  00C002214  <Global: UART code>
.   680  00C004960  00C0026AC  <Global: UARTstr code>
.   684  00C004964  00C0026FC  <Global: UARTstr code>
.   688  00C004968  0300BFE68  <Global: Console data>
.   692  00C00496C  0300BFE60  <Global: Console data>
.   696  00C004970  0300BFE64  <Global: Console data>
  • The encoded addresses, which are used as arguments in the code, are resolved.

  • They also indicate to which module they belong, and if they are in this module's code or data space.

Debugging Source Files

  • The .alst files is what I use as source files for debugging. The integrated Oberon source code helps me to understand and follow the assembly code, but for the execution I can follow the latter. With easily set breakpoints – just a mouse click – it is easy to run through the code quickly to step over lines that are not interesting and relevant at the moment.

  • As said, there is a some Oberon source level debugging functionality, which works, but will need some work regarding the allocation of instruction addresses to Oberon source lines for an easier understanding during debugging.

Supported MCUs

  • I have only had time to work with an STM32U585 MCU. I have started with this device, since I knew things would be more straight forward.

  • RP2350 will be next, but I expect again some surprises, as with the Secure/Non-secure separation (see last two change notes).

A Note…

  • I had some help to figure out the DWARF debug data generation. The DWARF spec is some 500 pages. Ayo.

  • There still a lot I do not know or understand. Debugging and debuggers are hard.

Full Example

. <tool: Astrobe for RP2350>
. <prog: SignalSync.mod>
. <mcu: STM32U585/Cortex-M33>

.     0  00C0049F8              <Pad: 0>
MODULE Main;

  IMPORT (* keep first three imports in this order  *)
    Startup, MemMap, Memory, Clocks, Console,
    RuntimeErrors, RuntimeErrorsOut, LED, FPU;

  PROCEDURE run;
  BEGIN
.     4  00C0049FC      0B500  push      { lr }
    ASSERT(Startup.Done);
.     6  00C0049FE  0F8DF0074  ldr.w     r0,[pc,#116] -> 124
.    10  00C004A02      07800  ldrb      r0,[r0]
.    12  00C004A04  0F0100F01  tst.w     r0,#1
.    16  00C004A08      0D101  bne.n     2 -> 22
.    18  00C004A0A      0DF00  svc       0
.    20  00C004A0C      00016  <LineNo: 22>
    ASSERT(MemMap.Done);
.    22  00C004A0E  0F8DF0068  ldr.w     r0,[pc,#104] -> 128
.    26  00C004A12      07800  ldrb      r0,[r0]
.    28  00C004A14  0F0100F01  tst.w     r0,#1
.    32  00C004A18      0D101  bne.n     2 -> 38
.    34  00C004A1A      0DF00  svc       0
.    36  00C004A1C      00017  <LineNo: 23>
    ASSERT(Memory.Done);
.    38  00C004A1E  0F8DF005C  ldr.w     r0,[pc,#92] -> 132
.    42  00C004A22      07800  ldrb      r0,[r0]
.    44  00C004A24  0F0100F01  tst.w     r0,#1
.    48  00C004A28      0D101  bne.n     2 -> 54
.    50  00C004A2A      0DF00  svc       0
.    52  00C004A2C      00018  <LineNo: 24>
    Clocks.Config;
.    54  00C004A2E  0F7FDFA23  bl.w      Clocks.Config [-5597 -> 0C001E78]
.    58  00C004A32      0E000  b         0 -> 62
.    60  00C004A34      00019  <LineNo: 25>
    LED.Config;
.    62  00C004A36  0F7FEFB25  bl.w      LED.Config [-3291 -> 0C003084]
.    66  00C004A3A      0E000  b         0 -> 70
.    68  00C004A3C      0001A  <LineNo: 26>
    RuntimeErrors.Install;
.    70  00C004A3E  0F7FEFC69  bl.w      RuntimeErrors.Install [-2967 -> 0C003314]
.    74  00C004A42      0E000  b         0 -> 78
.    76  00C004A44      0001B  <LineNo: 27>
    Console.Install(Clocks.SYSCLK_FRQ);
.    78  00C004A46  0F8DF0028  ldr.w     r0,[pc,#40] -> 120
.    82  00C004A4A  0F7FFFF2F  bl.w      Console.Install [-209 -> 0C0048AC]
.    86  00C004A4E      0E000  b         0 -> 90
.    88  00C004A50      0001C  <LineNo: 28>
    RuntimeErrors.InstallErrorHandler(RuntimeErrorsOut.ErrorHandler);
.    90  00C004A52  0F8DF002C  ldr.w     r0,[pc,#44] -> 136
.    94  00C004A56  0F7FEFC39  bl.w      RuntimeErrors.InstallErrorHandler [-3015 -> 0C0032CC]
.    98  00C004A5A      0E000  b         0 -> 102
.   100  00C004A5C      0001D  <LineNo: 29>
    RuntimeErrors.EnableFaults;
.   102  00C004A5E  0F7FEFC47  bl.w      RuntimeErrors.EnableFaults [-3001 -> 0C0032F0]
.   106  00C004A62      0E000  b         0 -> 110
.   108  00C004A64      0001E  <LineNo: 30>
    FPU.Enable
    (*Security.Config *) (* for S/NS programs, import Security is used *)
  END run;
.   110  00C004A66  0F7FFFF9B  bl.w      FPU.Enable [-101 -> 0C0049A0]
.   114  00C004A6A      0E000  b         0 -> 118
.   116  00C004A6C      0001F  <LineNo: 31>
.   118  00C004A6E      0BD00  pop       { pc }
.   120  00C004A70  009896800  <Const:  160000000>
.   124  00C004A74  0300BFFD3  <Global: LinkOptions data>
.   128  00C004A78  0300BFF8B  <Global: MemMap data>
.   132  00C004A7C  0300BFEDB  <Global: Memory data>
.   136  00C004A80  00C0045C8  <Global: RuntimeErrorsOut code>

BEGIN
.   140  00C004A84      0B500  push      { lr }
  run
END Main.
.   142  00C004A86  0F7FFFFB9  bl.w      run [-71 -> 0C0049FC]
.   146  00C004A8A      0E000  b         0 -> 150
.   148  00C004A8C      00024  <LineNo: 36>
.   150  00C004A8E      0BD00  pop       { pc }

Last updated: 21 February 2026