Change Note 2026-02-19

Tools and debugging support.

Tools

  • Updated versions of
    • makeuf2
    • makeelf
  • New tools
    • picoload
    • makerdb
  • As usual, running the tools with the -h option displays the usage message.
  • Documentation is missing or possibly out of date, sorry.

makeuf2

  • 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.

makeelf

  • 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 RP240 and RP2350.
  • Uses picotool in lieu of file copying (for now, I may restore the file copying for the RP2040).

makerdb

  • Create listing files for debugging, see below.

Astrobe Config Files

  • The project-specific Astrobe config files had started to clutter config/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 makeelf 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 makerdb to be used with the Astrobe IDE.

A Few Details

Procedure calls

UART.GetBaseCfg(uartCfg);
.   154  00C0044C6  0F11D0004  adds.w    r0,sp,#4
.   158  00C0044CA  0F8DF10AC  ldr.w     r1,[pc,#172] -> 332
.   162  00C0044CE  0F7FFFCF7  bl.w      UART.GetBaseCfg [-777 -> 0C003EC0]
.   166  00C0044D2      0E000  b         0 -> 170
.   168  00C0044D4      0003A  <LineNo: 58>
.   170  00C0044D6      02001  movs      r0,#1
   ...
.   332  00C004578  00C003D18  <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,#172] -> 332 instruction fetches the value, or where b 0 -> 170 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 [-777 -> 0C003EC0], 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

.   328  00C004574  009896800  <Const:  160000000>
.   332  00C004578  00C003D18  <Global: UART code>
.   336  00C00457C  00C0043A8  <Global: UARTstr code>
.   340  00C004580  00C0043F8  <Global: UARTstr code>
.   344  00C004584  0300BFE84  <Global: Terminals data>
.   348  00C004588  0300BFE74  <Global: Terminals data>
.   352  00C00458C  0300BFE7C  <Global: Terminals data>
.   356  00C004590  00C003C04  <Global: RuntimeErrorsOut code>
  • 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 reliably, 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.
  • My Python skills are a bit better now, but I am still learning and trying new stuff. Don’t expect much consistency in the code from one tool to the other. (I would not want to write a parser for Python.)

Full Example

. <tool: Astrobe for RP2350>
. <prog: C:\Users\gray\Projects\oberon\dev\oberon-rtk\examples\v3.0\stm\u585i-iot\SignalSync\SignalSync.mod>

.     0  00C00442C              <Pad: 0>
MODULE Main;
  IMPORT
    Config, Memory, RuntimeErrors, MCU := MCU2, Clocks,
    CLK, RuntimeErrorsOut, Terminals, Out, In, GPIO, UART, UARTstr, FPUctrl;

  CONST
    Baudrate0 = 115200; (* terminal 0 *)
    UARTt0 = UART.USART1;
    UARTt0_TxPinNo = 9; (* GPIOA *)
    UARTt0_RxPinNo = 10;
    TERM0 = Terminals.TERM0;


  PROCEDURE cfgPins(txPin, rxPin: INTEGER);
    CONST AF = 7;
    VAR padCfg: GPIO.PadCfg;
  BEGIN
.     4  00C004430      0B503  push      { r0, r1, lr }
.     6  00C004432      0B084  sub       sp,#16
    CLK.EnableBusClock(MCU.DEV_GPIOA);
.     8  00C004434      02020  movs      r0,#32
.    10  00C004436  0F7FCF927  bl.w      CLK.EnableBusClock [-7897 -> 0C000688]
.    14  00C00443A      0E000  b         0 -> 18
.    16  00C00443C      0001F  <LineNo: 31>
    padCfg.mode := GPIO.ModeAlt;
.    18  00C00443E      02002  movs      r0,#2
.    20  00C004440      09000  str       r0,[sp]
    padCfg.type := GPIO.TypePushPull;
.    22  00C004442      02000  movs      r0,#0
.    24  00C004444      09001  str       r0,[sp,#4]
    padCfg.speed := GPIO.SpeedHigh;
.    26  00C004446      02002  movs      r0,#2
.    28  00C004448      09002  str       r0,[sp,#8]
    padCfg.pulls := GPIO.PullUp;
.    30  00C00444A      02001  movs      r0,#1
.    32  00C00444C      09003  str       r0,[sp,#12]
    GPIO.ConfigurePad(MCU.PORTA, txPin, padCfg);
.    34  00C00444E  0F8DF004C  ldr.w     r0,[pc,#76] -> 112
.    38  00C004452      09904  ldr       r1,[sp,#16]
.    40  00C004454      0466A  mov       r2,sp
.    42  00C004456  0F8DF3048  ldr.w     r3,[pc,#72] -> 116
.    46  00C00445A  0F7FCFEBF  bl.w      GPIO.ConfigurePad [-6465 -> 0C0011DC]
.    50  00C00445E      0E000  b         0 -> 54
.    52  00C004460      00024  <LineNo: 36>
    GPIO.ConfigurePad(MCU.PORTA, rxPin, padCfg);
.    54  00C004462  0F8DF0038  ldr.w     r0,[pc,#56] -> 112
.    58  00C004466      09905  ldr       r1,[sp,#20]
.    60  00C004468      0466A  mov       r2,sp
.    62  00C00446A  0F8DF3034  ldr.w     r3,[pc,#52] -> 116
.    66  00C00446E  0F7FCFEB5  bl.w      GPIO.ConfigurePad [-6475 -> 0C0011DC]
.    70  00C004472      0E000  b         0 -> 74
.    72  00C004474      00025  <LineNo: 37>
    GPIO.SetFunction(MCU.PORTA, txPin, AF);
.    74  00C004476  0F8DF0024  ldr.w     r0,[pc,#36] -> 112
.    78  00C00447A      09904  ldr       r1,[sp,#16]
.    80  00C00447C      02207  movs      r2,#7
.    82  00C00447E  0F7FCFF17  bl.w      GPIO.SetFunction [-6377 -> 0C0012B0]
.    86  00C004482      0E000  b         0 -> 90
.    88  00C004484      00026  <LineNo: 38>
    GPIO.SetFunction(MCU.PORTA, rxPin, AF)
.    90  00C004486  0F8DF0014  ldr.w     r0,[pc,#20] -> 112
.    94  00C00448A      09905  ldr       r1,[sp,#20]
.    96  00C00448C      02207  movs      r2,#7
  END cfgPins;
.    98  00C00448E  0F7FCFF0F  bl.w      GPIO.SetFunction [-6385 -> 0C0012B0]
.   102  00C004492      0E000  b         0 -> 106
.   104  00C004494      00027  <LineNo: 39>
.   106  00C004496      0B006  add       sp,#24
.   108  00C004498      0BD00  pop       { pc }
.   110  00C00449A      0BF00  nop
.   112  00C00449C  042020000  <Const:  1107427328>
.   116  00C0044A0  00C0011C8  <Global: GPIO code>


  PROCEDURE init;
    CONST Core0 = 0;
    VAR
      uartDev: UART.Device;
      uartCfg: UART.DeviceCfg;
  BEGIN
.   120  00C0044A4      0B500  push      { lr }
.   122  00C0044A6      0B086  sub       sp,#24
    Clocks.Configure;
.   124  00C0044A8  0F7FDFFA2  bl.w      Clocks.Configure [-4190 -> 0C0023F0]
.   128  00C0044AC      0E000  b         0 -> 132
.   130  00C0044AE      00031  <LineNo: 49>

    (* init vector table *)
    RuntimeErrors.Install(Core0);
.   132  00C0044B0      02000  movs      r0,#0
.   134  00C0044B2  0F7FDFE39  bl.w      RuntimeErrors.Install [-4551 -> 0C002128]
.   138  00C0044B6      0E000  b         0 -> 142
.   140  00C0044B8      00034  <LineNo: 52>

    (* config UART pins and pads *)
    cfgPins(UARTt0_TxPinNo, UARTt0_RxPinNo);
.   142  00C0044BA      02009  movs      r0,#9
.   144  00C0044BC      0210A  movs      r1,#10
.   146  00C0044BE  0F7FFFFB7  bl.w      cfgPins [-73 -> 0C004430]
.   150  00C0044C2      0E000  b         0 -> 154
.   152  00C0044C4      00037  <LineNo: 55>

    (* define UART cfg *)
    UART.GetBaseCfg(uartCfg);
.   154  00C0044C6  0F11D0004  adds.w    r0,sp,#4
.   158  00C0044CA  0F8DF10AC  ldr.w     r1,[pc,#172] -> 332
.   162  00C0044CE  0F7FFFCF7  bl.w      UART.GetBaseCfg [-777 -> 0C003EC0]
.   166  00C0044D2      0E000  b         0 -> 170
.   168  00C0044D4      0003A  <LineNo: 58>
    uartCfg.fifoEn := UART.Enabled;
.   170  00C0044D6      02001  movs      r0,#1
.   172  00C0044D8      09001  str       r0,[sp,#4]
    uartCfg.over8En := UART.Disabled;
.   174  00C0044DA      02000  movs      r0,#0
.   176  00C0044DC      09002  str       r0,[sp,#8]
    uartCfg.clkSel := UART.CLK_SYSCLK;
.   178  00C0044DE      02001  movs      r0,#1
.   180  00C0044E0      09003  str       r0,[sp,#12]
    uartCfg.presc := UART.Presc_16;
.   182  00C0044E2      02007  movs      r0,#7
.   184  00C0044E4      09004  str       r0,[sp,#16]
    uartCfg.clkFreq := Clocks.SYSCLK_FRQ;
.   186  00C0044E6  0F8DF008C  ldr.w     r0,[pc,#140] -> 328
.   190  00C0044EA      09005  str       r0,[sp,#20]

    (* open text IO to/from serial terminal *)
    Terminals.InitUART(UARTt0, uartCfg, Baudrate0, uartDev);
.   192  00C0044EC      02000  movs      r0,#0
.   194  00C0044EE  0F11D0104  adds.w    r1,sp,#4
.   198  00C0044F2  0F8DF2084  ldr.w     r2,[pc,#132] -> 332
.   202  00C0044F6  0F44F33E1  mov.w     r3,#001C200H
.   206  00C0044FA      0466C  mov       r4,sp
.   208  00C0044FC  0F7FFFD34  bl.w      Terminals.InitUART [-716 -> 0C003F68]
.   212  00C004500      0E000  b         0 -> 216
.   214  00C004502      00042  <LineNo: 66>
    Terminals.Open(TERM0, uartDev, UARTstr.PutString, UARTstr.GetString);
.   216  00C004504      02000  movs      r0,#0
.   218  00C004506      09900  ldr       r1,[sp]
.   220  00C004508  0F8DF2070  ldr.w     r2,[pc,#112] -> 336
.   224  00C00450C  0F8DF3070  ldr.w     r3,[pc,#112] -> 340
.   228  00C004510  0F7FFFD52  bl.w      Terminals.Open [-686 -> 0C003FB8]
.   232  00C004514      0E000  b         0 -> 236
.   234  00C004516      00043  <LineNo: 67>

    (* init Out and In to use terminal *)
    Out.Open(Terminals.W[0]);
.   236  00C004518  0F8DF0068  ldr.w     r0,[pc,#104] -> 344
.   240  00C00451C      06800  ldr       r0,[r0]
.   242  00C00451E  0F7FFFE7F  bl.w      Out.Open [-385 -> 0C004220]
.   246  00C004522      0E000  b         0 -> 250
.   248  00C004524      00046  <LineNo: 70>
    In.Open(Terminals.R[0]);
.   250  00C004526  0F8DF0060  ldr.w     r0,[pc,#96] -> 348
.   254  00C00452A      06800  ldr       r0,[r0]
.   256  00C00452C  0F7FFFEEC  bl.w      In.Open [-276 -> 0C004308]
.   260  00C004530      0E000  b         0 -> 264
.   262  00C004532      00047  <LineNo: 71>

    (* init run-time error printing to serial terminal *)
    (* use error output writer *)
    Terminals.OpenErr(TERM0, UARTstr.PutString);
.   264  00C004534      02000  movs      r0,#0
.   266  00C004536  0F8DF1044  ldr.w     r1,[pc,#68] -> 336
.   270  00C00453A  0F7FFFDE5  bl.w      Terminals.OpenErr [-539 -> 0C004108]
.   274  00C00453E      0E000  b         0 -> 278
.   276  00C004540      0004B  <LineNo: 75>
    RuntimeErrorsOut.SetWriter(Core0, Terminals.Werr[0]);
.   278  00C004542      02000  movs      r0,#0
.   280  00C004544  0F8DF1044  ldr.w     r1,[pc,#68] -> 352
.   284  00C004548      06809  ldr       r1,[r1]
.   286  00C00454A  0F7FFFBC9  bl.w      RuntimeErrorsOut.SetWriter [-1079 -> 0C003CE0]
.   290  00C00454E      0E000  b         0 -> 294
.   292  00C004550      0004C  <LineNo: 76>
    RuntimeErrors.InstallErrorHandler(Core0, RuntimeErrorsOut.ErrorHandler);
.   294  00C004552      02000  movs      r0,#0
.   296  00C004554  0F8DF1038  ldr.w     r1,[pc,#56] -> 356
.   300  00C004558  0F7FDFDBA  bl.w      RuntimeErrors.InstallErrorHandler [-4678 -> 0C0020D0]
.   304  00C00455C      0E000  b         0 -> 308
.   306  00C00455E      0004D  <LineNo: 77>

    (* core init *)
    RuntimeErrors.EnableFaults;
.   308  00C004560  0F7FDFDD0  bl.w      RuntimeErrors.EnableFaults [-4656 -> 0C002104]
.   312  00C004564      0E000  b         0 -> 316
.   314  00C004566      00050  <LineNo: 80>
    FPUctrl.Init;
.   316  00C004568  0F7FFFF4C  bl.w      FPUctrl.Init [-180 -> 0C004404]
.   320  00C00456C      0E000  b         0 -> 324
.   322  00C00456E      00051  <LineNo: 81>
  END init;
.   324  00C004570      0B006  add       sp,#24
.   326  00C004572      0BD00  pop       { pc }
.   328  00C004574  009896800  <Const:  160000000>
.   332  00C004578  00C003D18  <Global: UART code>
.   336  00C00457C  00C0043A8  <Global: UARTstr code>
.   340  00C004580  00C0043F8  <Global: UARTstr code>
.   344  00C004584  0300BFE84  <Global: Terminals data>
.   348  00C004588  0300BFE74  <Global: Terminals data>
.   352  00C00458C  0300BFE7C  <Global: Terminals data>
.   356  00C004590  00C003C04  <Global: RuntimeErrorsOut code>

BEGIN
.   360  00C004594      0B500  push      { lr }
  init
END Main.
.   362  00C004596  0F7FFFF85  bl.w      init [-123 -> 0C0044A4]
.   366  00C00459A      0E000  b         0 -> 370
.   368  00C00459C      0005B  <LineNo: 91>
.   370  00C00459E      0BD00  pop       { pc }
Updated: 2026-02-21