Change Note 2026-02-19
Tools
- Updated versions of
makeuf2makeelf
- New tools
picoloadmakerdb
- As usual, running the tools with the
-hoption 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
picotoolin 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..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 toolmakerdbto 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] -> 332instruction fetches the value, or whereb 0 -> 170branches 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
.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 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 }