Change Note 2026-02-19
Tools and debugging support
Tools
- Updated versions of
make-uf2make-elf
- New tools
picoloadgen-rdb
- As usual, running the tools with the
-hoption 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
picotoolin 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..initprocedures):- 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
.lstlisting 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
.lstfiles, together with the.mapand.binfile, to create what I callabsolute 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
.alstinto the new standalone toolgen-rdbto 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] -> 672instruction fetches the value, or whereb 0 -> 532branches 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
.alstfiles 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