- The Unified Address Space Model
- ARM vexpress-a9 Memory Layout in QEMU
- Memory Aliasing
- Execute-in-Place vs Copy-to-RAM
- Inspecting Memory with the QEMU Monitor
- Conclusion
Bare-metal programming runs without operating-system support. There is no program loader, no virtual memory, and no runtime safety layer - only the CPU, the physical memory map, and the code placed into that memory. So a clear understanding of the physical memory layout - where flash, RAM, and peripheral registers reside is essential. This layout acts as a hardware contract that governs startup behavior, linker scripts, and instruction flow.
This post examines the memory map of the ARM vexpress-a9 platform as modeled by QEMU, focusing on how real hardware organizes memory and how software is expected to interact with it.
The Unified Address Space Model
ARM processors interact with memory and peripherals through an address bus, treating all devices as regions within a single addressable space. From the CPU’s perspective, there is no inherent distinction between RAM, flash, or peripheral registers - it simply reads from and writes to addresses placed on the bus. The behavior depends entirely on what hardware responds at those addresses.
Some important terms:
- Physical Address: The actual address driven onto the bus by the CPU. Hardware devices decode these addresses and respond accordingly.
-
Symbols and Labels: Toolchain abstractions such as
_startormain. These exist only at build time; the CPU never sees symbols - only addresses. - Memory Aliasing: A single physical memory device responding to multiple physical address ranges.
This unified model is powerful, but a wrong address does not fail gracefully - it simply talks to the wrong hardware.
ARM vexpress-a9 Memory Layout in QEMU
Different SoCs place RAM, flash, and peripherals in very different locations. With QEMU configured for 128 MB of RAM, the layout looks like this:
| Address Range | Size | Device | Access |
|---|---|---|---|
| 0x00000000–0x03FFFFFF | 64 MB | Flash | Read, Execute |
| 0x10000000–0x1FFFFFFF | 256 MB | Peripherals | Device-specific |
| 0x60000000–0x67FFFFFF | 128 MB | RAM | Read, Write, Execute |
Flash (0x00000000–0x03FFFFFF)
- Non-volatile storage for code.
- Persists across reset and power cycles.
- Typically readable and executable, but not writable at runtime.
- In QEMU, this region is modeled as an emulated NOR flash device backed by a file.
On the vexpress-a9 platform, QEMU aliases flash to address 0x00000000 at reset, allowing the CPU to fetch its initial instructions from this location.
RAM (0x60000000–0x67FFFFFF)
- Volatile, writable memory used at runtime.
- Holds stacks, global variables, heap, and optionally executable code.
- Contents are lost on reset or power loss.
- Faster to access than flash in most systems.
Most bare-metal programs eventually transition to executing from RAM, even if initial boot code starts in flash.
Peripherals (0x10000000 and above):
- Memory-mapped device registers (UART, GPIO, timers, interrupt controllers). For more info on MMIO Memory Mapped IO (MMIO)
Writing to these addresses is how bare-metal software communicates with hardware.
Summary:
| Region | Address Range | Purpose |
|---|---|---|
| Reset Flash Alias | 0x00000000–0x03FFFFFF | CPU reset entry; aliased flash |
| Peripherals | 0x10000000–0x1FFFFFFF | MMIO registers (UART, timers, sysctl, etc.) |
| A9 Private Region | 0x1E000000–0x1E001FFF | SCU, GIC CPU interface, timers |
| L2 Cache Controller | 0x1E00A000–0x1E00AFFF | L2 cache control registers |
| Flash0 / Flash1 | 0x40000000–0x47FFFFFF | Actual flash devices |
| On-Chip SRAM | 0x48000000–0x49FFFFFF | Fast internal SRAM |
| Main RAM | 0x60000000–0x67FFFFFF | Runtime data, stacks, heaps, and optionally executable code |
Memory Aliasing
The vexpress-a9 platform implements physical memory aliasing. The same flash device is accessible through more than one physical address range.
At reset, flash is aliased to 0x00000000 so the CPU can fetch its reset vector from a well-known location. The same flash may also exist at another address used internally by the platform. Both addresses refer to the same physical storage.
This technique simplifies reset behavior without constraining the rest of the memory map.
Execute-in-Place vs Copy-to-RAM
Bare-metal systems typically use one of two strategies for placing executable code.
Execute-in-Place (XIP)
The CPU fetches instructions directly from flash.
- Minimal RAM usage
- Simple startup logic
- Slower execution due to flash access latency
XIP is well suited for small systems or early boot code.
Copy-to-RAM
Code is stored in flash but copied into RAM by startup code before execution continues.
- Faster instruction execution
- Requires RAM for code storage
- Introduces a copy step during startup
Many systems use a hybrid approach: minimal startup code executes from flash, then hands control to a RAM-resident program.
Inspecting Memory with the QEMU Monitor
QEMU provides a monitor interface that allows inspection of memory and device state while the system is running or paused—even before guest code executes.
Launching QEMU with a Telnet Monitor
qemu-system-arm -M vexpress-a9 -cpu cortex-a9 -m 128M -nographic \
-monitor telnet:127.0.0.1:4444,server,nowait
This opens a QEMU monitor accessible via Telnet:
telnet 127.0.0.1 4444
Examining Physical Memory with the xp Command
Basic syntax:
(qemu) xp /[count][format][size] address
Example: read four 32-bit words from 0x60000000 after running the minimal startup code from From ARM Assembly to Machine Code: A Bare-Metal Primer
(qemu) xp /4xw 0x60000000
0000000060000000: 0xe3a00000 0xe59f1004 0xe59f2004 0xe59ff004
Common format options include:
-
/4xw: four 32-bit words in hexadecimal -
/16xb: sixteen bytes in hexadecimal -
/8xh: eight 16-bit halfwords in hexadecimal
Viewing Memory Hierarchy with info mtree command
(qemu) info mtree
Example output:
address-space: memory
0000000000000000-ffffffffffffffff (prio 0, i/o): system
0000000000000000-0000000003ffffff (prio 0, romd):
alias vexpress.flashalias @vexpress.flash0
→ 0000000040000000-0000000043ffffff
0000000040000000-0000000043ffffff (prio 0, romd):
vexpress.flash0
0000000044000000-0000000047ffffff (prio 0, romd):
vexpress.flash1
0000000060000000-0000000067ffffff (prio 0, ram):
vexpress.highmem
Conclusion
Bare-metal systems are defined by their memory maps. On the vexpress-a9 platform, flash is aliased at reset, RAM resides at a fixed high address range, and peripherals are accessed through memory-mapped registers. These characteristics directly shape startup code, linker scripts, and execution flow.
With the physical layout established, the next step is examining how toolchains represent this memory. The upcoming post will explore ELF files, sections, and segments, showing how compiler and linker decisions result in code and data being placed into flash and RAM.
Top comments (0)