---
title: Memory Layout
description: ARM Cortex-M memory map and why SRAM is the default safe zone
sidebar:
  order: 3
---

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

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)

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)

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

Some chips have additional RAM regions:

```bash
# STM32F4: Main SRAM + CCM RAM (core-coupled memory)
export MCJTAG_SAFE_WRITE_RANGES="0x20000000-0x20020000,0x10000000-0x10010000"

# STM32H7: Multiple SRAM banks
export 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

For advanced use (register poking, peripheral testing, etc.), write restrictions can be disabled entirely:

```bash
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

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

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

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:

```python
# Read first 8 vectors (32 bytes)
read_memory(address="0x08000000", count=8, width=32)
```

The `memory_map` prompt automates this analysis. See [Prompts](/reference/prompts/) for the full workflow, or [Target Identification](/guides/target-identification/) for using the vector table to identify unknown chips.

---

## Practical Implications

### 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

Before writing to any address outside SRAM, consider:

1. **What lives at this address?** Use `svd_inspect` to decode it.
2. **What will the write do?** Peripheral writes change hardware state.
3. **Is the firmware expecting this?** Firmware may be actively using the peripheral.
4. **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.
