Making SVD-Loader for Ghidra play nice with RP2040
Problem statement
When reversing ARM-based firmware in Ghidra, it’s very useful to be able to load up peripheral / memory blocks from the supplied SVD file1.
To that end, Leveldown security created the SVD Loader Ghidra script.
But, the original script didn’t work for RP2040 at all due to a bug2. And, more importantly, Raspberry Pi’s Pico board (that uses RP2040 chip) has a rather unique feature in its address space: Atomic Register Access:
The section 2.1.2 Atomic Register Access in the RP2040 datasheet explains:
Each peripheral register block is allocated 4kB of address space, with registers accessed using one of 4 methods, selected by address decode.
Addr + 0x0000
: normal read write accessAddr + 0x1000
: atomic XOR on writeAddr + 0x2000
: atomic bitmask set on writeAddr + 0x3000
: atomic bitmask clear on writeThis allows individual fields of a control register to be modified without performing a read-modify-write sequence in software: instead the changes are posted to the peripheral, and performed in-situ. Without this capability, it is difficult to safely access IO registers when an interrupt service routine is concurrent with code running in the foreground, or when the two processors are running code in parallel.
And the original script doesn’t support that either, resulting in missing references (red) and broken decompile:
missing references
broken decompile
So I set out to make the SVD-Loader play nice with RP2040.
Solution
tl;dr: Get it from my github fork, what follows is a brief write-up about the change, and “after” pictures.
So apart from fixing the initial bug, (with a one-liner) I decided to basically replicate all the peripherals at the above mentioned offsets when RP2040’s SVD is detected.
Downside of that is that the memory map:
(part of) memory map prior the ARA mod
ends up looking more crowded:
(part of) memory map after the ARA mod
Fortunately with a little bit of code restructuring (and help of the derivedFrom
field from the SVD), I was able to re-use the data structures (so e.g. both UARTs
reference single data structure). Plus as an additional tweak, I aliased names
ending with zero (UART0
, PIO0
, I2C0
, …) to a name without (UART
, …).
The end result are correct references and working decompile:
correct references
working decompile
I also:
- auto-added pointers for the created structures
- added better instal instructions (in the repo)
- made the script run idempotent (subsequent runs should just add missing peripherials/blocks, instead of throwing exception)
All in all, I think this should make RP2040 firmware reversing in Ghidra a tad easier.
Read the full delta (so far), if that’s interesting to you.
Closing words
This is a relatively quick hack to improve quality of life for anyone reverse-engineering a RP2040 (Raspberry Pico) firmware in Ghidra.
What is still missing (and unrelated) is:
- support for bitfields in Ghidra
- auto-import of some structures (vector map, things like
pio_program_t
, etc)
Maybe someone will come up with those… in time3. :-)
-
CMSIS-SVD, System View Description, is a description of ARM-based MCUs. The SVD file for RP2040 is in the SDK. ↩
-
TypeError: unsupported operand type(s) for /: 'NoneType' and 'int'
(incalculate_peripheral_size
) ↩ -
You, dear reader? No pressure. ↩