rambo
ROM Area Memory Behavior Observer — a CLI for mapping the SRAM collateral damage a Cortex-M boot ROM leaves behind.
rambo connects to a target MCU over SWD using probe-rs, primes its on-chip
SRAM with a known pattern, issues a reset, halts the core before it executes
any user firmware, and then classifies every block of RAM to show you exactly
what the boot ROM clobbered, what it left alone, and what looks suspiciously
like leftover data structures.
It is intentionally not a TUI — output is plain stdout with ANSI colors, designed to be readable in a terminal, piped to a file, or pasted into a bug report.
Why?
When you write low-level firmware (bootloaders, secure-boot stages, RTOS ports),
it matters which parts of RAM are safe to use at startup. Vendor reference
manuals may not document which scratch areas the on-chip ROM uses, and the
layout often differs between silicon revisions. rambo answers the question
empirically: "if I want my firmware's first instruction to find untouched
memory, which addresses can I trust?"
How it works
For each RAM region reported by probe-rs's chip database:
- Write a deterministic pattern (
addr-as-data: each 32-bit word stores its own address) across the entire region. - Reset the target and halt it immediately on the vector catch, before any user code runs.
- Read back the region and classify each block:
- SAFE — pattern survived intact (ROM did not touch this block).
- ZERO — block was zeroed.
- ONES — block was filled with
0xFF. - CHANGED — block was rewritten with something else (likely ROM scratch / stack / data structures).
- Render a heatmap, a run-length summary, and per-region totals.
Optional modes layer extra analyses on top of this core loop:
- Fingerprint changed blocks (
--fingerprint) — detect constants, dominant values, ascending counters, repeating motifs, address-with-offset patterns, or noise. - Dual-pattern sweep (
--dual-pattern) — writeaddrthen!addrin two separate reset cycles to distinguish undriven RAM from actively modified RAM. - Write-readback (
--write-readback) — no reset between write and read; detects aliasing and unmapped windows in reserved address ranges. - Stability (
--reset-cycles N) — re-read after each of N resets to see whether post-ROM state is deterministic or drifts.
Installation
From source
Pre-built binaries
Pre-built binaries for Linux, macOS (Intel + Apple Silicon), and Windows are attached to each tagged release on the Releases page.
System dependencies
probe-rs needs libudev on Linux:
Quick start
Survey all RAM regions of an MCXA276 with default settings (4 KiB blocks, single reset cycle):
Use a smaller block size and fingerprint anything ROM modified:
Decide whether a region is driven or undriven via the dual-pattern sweep:
Detect aliasing or unmapped windows in a reserved RAM range (no reset between write and read):
Check determinism across five resets:
Select a specific debug probe by VID:PID or serial:
CLI reference
| Flag | Default | Description |
|---|---|---|
--chip <NAME> |
— | Required. probe-rs target identifier (e.g. MCXA276). |
--block <BYTES> |
0x1000 |
Classification block size. Decimal / 0x / 0o / 0b / _ accepted. Must be a non-zero multiple of 4. |
--reset-cycles <N> |
1 |
Number of read-after-reset iterations. N>1 enables stability analysis. Incompatible with --dual-pattern. |
--fingerprint |
off | Fingerprint each CHANGED block (bit density, top values, pattern matches). |
--dual-pattern |
off | Two-pass addr / !addr sweep to distinguish undriven from modified RAM. |
--write-readback |
off | Write then immediately read back (no reset). Reveals aliasing / unmapped windows. |
--probe <SEL> |
first | Probe selector, VID:PID or serial. |
Run rambo --help for the full clap-generated help text.
Classification legend
| Class | Glyph | Meaning |
|---|---|---|
| SAFE | █ green | Pattern survived. ROM did not touch this block. |
| ZERO | █ blue | Block was cleared to 0x00000000. |
| ONES | █ magenta | Block was filled with 0xFFFFFFFF. |
| CHANGED | █ red | Block was rewritten with something else (ROM working data). |
The heatmap is laid out as 64 cells per row, where each cell aggregates one
block (1 KiB for the survey heatmap, --block bytes elsewhere). Below the
heatmap, rambo prints a run-length-encoded list of contiguous regions sharing
a class, followed by totals.
Output anatomy
A typical survey output for a single region looks like:
═══ RAM @ 0x20000000 .. 0x2003c000 (240 KiB) ═══
[heatmap, 64 cells per row]
Runs
┌────────────────────────┬─────────┬──────────┐
│ Range │ Size │ Class │
├────────────────────────┼─────────┼──────────┤
│ 0x20000000..0x20001000 │ 4 KiB │ CHANGED │
│ 0x20001000..0x2003c000 │ 236 KiB │ SAFE │
└────────────────────────┴─────────┴──────────┘
Totals
SAFE: 236 KiB
CHANGED: 4 KiB
Hardware notes
rambo has been smoke-tested against an NXP MCXA276 development board with
the on-board MCU-Link probe. Any Cortex-M chip supported by probe-rs should
work, but only chips whose target description in probe-rs exposes RAM regions in
its memory_map will be useful (which is essentially all of them).
⚠️ The CMSIS-Pack chip descriptions that probe-rs consumes occasionally list code-bus aliases of the system-bus RAM as separate regions.
rambotreats every region in the map as independent — if you see the same physical RAM surveyed twice under different addresses, that is why.
Development
CI runs the same three checks on every push and PR (Ubuntu/macOS/Windows for
tests, Ubuntu for lints). The CI badges at the top of this README reflect the
current status of main.
License
MIT © Felipe Balbi