Memory Layout
Understanding the ARM Cortex-M memory map is essential for working with mcjtag safely. The memory map explains which addresses are safe to read and write, and why mcjtag restricts write operations to SRAM by default.
ARM Cortex-M Memory Map
Section titled “ARM Cortex-M Memory Map”ARM defines a fixed memory map for all Cortex-M processors. The 4 GB address space is divided into regions with specific purposes:
| Region | Address Range | Purpose |
|---|---|---|
| Code | 0x00000000 — 0x1FFFFFFF | Flash memory, vector table, boot ROM |
| SRAM | 0x20000000 — 0x3FFFFFFF | RAM — stack, heap, global variables |
| Peripherals | 0x40000000 — 0x5FFFFFFF | Memory-mapped I/O registers |
| External RAM | 0x60000000 — 0x9FFFFFFF | External memory interfaces (FSMC, FMC) |
| External Device | 0xA0000000 — 0xDFFFFFFF | External peripheral bus |
| System | 0xE0000000 — 0xFFFFFFFF | Debug, SysTick, NVIC, CoreSight |
Not all of this space is populated on any given chip. An STM32F103C8 (Blue Pill), for example, has:
- 64 KB flash at
0x08000000—0x0800FFFF - 20 KB SRAM at
0x20000000—0x20004FFF - Peripherals at
0x40000000+
Reading from an unpopulated address typically returns a bus fault. Writing to an unpopulated address does the same.
Flash Memory (Code Region)
Section titled “Flash Memory (Code Region)”Flash is non-volatile storage where firmware lives. On STM32, flash is mapped at 0x08000000 and aliased to 0x00000000 at boot.
Flash has important properties that make it dangerous to write carelessly:
- Erase-before-write: Flash cells must be erased (set to
0xFF) before they can be programmed. Writing without erasing produces corrupted data. - Sector granularity: Erase operations work on entire sectors (typically 1—128 KB), not individual bytes.
- Limited endurance: Flash cells wear out after 10,000—100,000 erase cycles.
- Bricking risk: Corrupting the vector table or bootloader can make the chip unbootable without a full erase.
For these reasons, mcjtag provides flash_program as the only sanctioned way to write flash. It handles erase, write, and verify as an atomic operation.
SRAM (The Safe Zone)
Section titled “SRAM (The Safe Zone)”SRAM occupies 0x20000000—0x3FFFFFFF in the memory map. On a real chip, only a fraction of this range is populated (e.g., 20 KB on STM32F103, 128 KB on STM32F407).
SRAM is the default safe write zone because:
- Volatile: Contents are lost on power cycle. The worst case for a bad write is corrupted variables that disappear on reset.
- No erase required: Any address can be written at any time without a preceding erase step.
- No wear: SRAM can be written an unlimited number of times.
- No bricking risk: Corrupted SRAM cannot prevent the chip from booting.
mcjtag defaults MCJTAG_SAFE_WRITE_RANGES to 0x20000000-0x20100000 (1 MB of address space), which covers the SRAM of virtually all Cortex-M chips.
Expanding the Safe Zone
Section titled “Expanding the Safe Zone”Some chips have additional RAM regions:
# STM32F4: Main SRAM + CCM RAM (core-coupled memory)export MCJTAG_SAFE_WRITE_RANGES="0x20000000-0x20020000,0x10000000-0x10010000"
# STM32H7: Multiple SRAM banksexport MCJTAG_SAFE_WRITE_RANGES="0x20000000-0x20020000,0x24000000-0x24080000,0x30000000-0x30048000"
# With external SDRAM (FMC)export MCJTAG_SAFE_WRITE_RANGES="0x20000000-0x20100000,0xD0000000-0xD1000000"Unrestricted Mode
Section titled “Unrestricted Mode”For advanced use (register poking, peripheral testing, etc.), write restrictions can be disabled entirely:
export MCJTAG_SAFE_WRITE_RANGES="none"This allows writing to any address, including peripheral registers and system space. Use with extreme caution on safety-critical hardware.
Peripheral Registers
Section titled “Peripheral Registers”The peripheral region (0x40000000—0x5FFFFFFF) contains memory-mapped I/O registers. Writing to a peripheral register does not store data — it changes hardware behavior.
Examples of what peripheral writes can do:
| Write target | Effect |
|---|---|
| GPIO output register | Toggle physical pins (could drive motors, relays, LEDs) |
| USART data register | Transmit a byte over serial |
| Timer control register | Start or stop a hardware timer |
| ADC control register | Begin an analog-to-digital conversion |
| RCC clock register | Change the system clock (can crash firmware) |
| Flash control register | Erase or program flash sectors |
| Watchdog register | Reset the system |
Because peripheral writes have real-world consequences, they are excluded from the default safe write ranges. Use svd_inspect to read and understand peripheral registers before deciding to modify them.
System Region
Section titled “System Region”The system region (0xE0000000—0xFFFFFFFF) contains ARM’s CoreSight debug infrastructure:
| Address | Block | Purpose |
|---|---|---|
0xE000E000 | System Control Block (SCB) | Fault status, vector table offset, system reset |
0xE000E010 | SysTick | System tick timer |
0xE000E100 | NVIC | Nested Vectored Interrupt Controller |
0xE000ED00 | CPUID | Processor identification (read by probe_diagnostics) |
0xE000EDF0 | Debug Control | Halt, step, breakpoint control |
0xE0040000 | TPIU | Trace output |
0xE0041000 | ETM | Embedded Trace Macrocell |
Writing to debug control registers can lock out the debugger itself, requiring a power cycle to recover. These addresses are excluded from default write ranges.
The Vector Table
Section titled “The Vector Table”The vector table is the first structure in flash, at the base of the code region. On STM32, this is at 0x08000000 (aliased to 0x00000000).
| Offset | Word | Content |
|---|---|---|
0x00 | 0 | Initial stack pointer (top of SRAM) |
0x04 | 1 | Reset handler address |
0x08 | 2 | NMI handler |
0x0C | 3 | HardFault handler |
0x10 | 4 | MemManage handler |
0x14 | 5 | BusFault handler |
0x18 | 6 | UsageFault handler |
0x1C—0x28 | 7—10 | Reserved |
0x2C | 11 | SVCall handler |
0x30 | 12 | Debug monitor |
0x34 | 13 | Reserved |
0x38 | 14 | PendSV handler |
0x3C | 15 | SysTick handler |
0x40+ | 16+ | IRQ handlers (device-specific) |
The vector table is a goldmine of information:
- Word 0 (initial SP) tells you where SRAM ends. If it reads
0x20005000, the chip has 20 KB of SRAM (0x20000000—0x20004FFF, SP points one past the end). - Word 1 (reset vector) tells you where code execution begins. This is typically the start of the firmware’s
main()or startup code. - Fault handler addresses reveal whether the firmware has custom fault handlers or uses the default infinite-loop handler.
Read the vector table with:
# Read first 8 vectors (32 bytes)read_memory(address="0x08000000", count=8, width=32)The memory_map prompt automates this analysis. See Prompts for the full workflow.
Practical Implications
Section titled “Practical Implications”Reading is (mostly) safe
Section titled “Reading is (mostly) safe”Reading memory through read_memory is non-destructive for most addresses. The exceptions are hardware registers with read-clear semantics — reading them changes their state. Common examples:
- USART status registers (reading clears flags)
- Interrupt pending registers
- DMA transfer complete flags
mcjtag builds hexdumps from already-read values (it does not re-read for the formatted view) specifically to avoid double-reading registers with side effects.
Writing requires thought
Section titled “Writing requires thought”Before writing to any address outside SRAM, consider:
- What lives at this address? Use
svd_inspectto decode it. - What will the write do? Peripheral writes change hardware state.
- Is the firmware expecting this? Firmware may be actively using the peripheral.
- Can you recover? Flash writes can brick the device. Peripheral writes may require a reset.
The safe write range default is deliberately conservative. Expand it only for addresses you understand.