Skip to content

An unaligned word or halfword access stitches bytes instead of taking the HardFault ARMv6-M requires #69

Description

@begeistert

An unaligned word or halfword access is a HardFault on ARMv6-M. The emulator instead stitches bytes across the two neighbouring words and carries on in Thread mode, so firmware that computes a bad address keeps running on a plausible, wrong value rather than stopping where the real chip stops.

This is not a theoretical gap. It is what made #68 invisible: a two-byte arithmetic error in ADD (register) handed mpy_init a GOT pointer at 0x20013F7A, and instead of the HardFault a Pico takes there, the emulated ldr returned a word assembled from the tail of one GOT slot and the head of the next. The import reported success and the module came back empty. The alignment fault would have pointed at the faulting instruction on the first run.

Where

BusInterconnect performs every access with Unsafe.ReadUnaligned / Unsafe.WriteUnaligned and never checks the low address bits:

  • ReadHalfWord (src/RP2040Sharp/Core/Memory/BusInterconnect.cs:87), ReadWord (:104)
  • WriteWord (:138), WriteHalfWord (:177)

The CPU handlers in Core/Cpu/Instructions/MemoryOps.cs call straight into them, so no layer raises the fault. LDRB/STRB are byte accesses and are never affected.

Reproduction

A console app referencing RP2040Sharp, no firmware needed:

using var bus = new BusInterconnect();
var cpu = new CortexM0Plus(bus);

bus.WriteWord(0x20000100, 0xAABBCCDD);
bus.WriteWord(0x20000104, 0x11223344);

// LDR r0, [r1, r2]  with r1 + r2 = 0x20000102 (halfword-aligned, not word-aligned)
bus.WriteHalfWord(0x20000000, 0x5800 | (2 << 6) | (1 << 3) | 0);
cpu.Registers.PC = 0x20000000;
cpu.Registers[1] = 0x20000102;
cpu.Registers[2] = 0;
cpu.Step();
// r0 = 0x3344AABB, PC = 0x20000002, IPSR = 0, IsLockedUp = false

// STR r0, [r1, r2]  with r1 + r2 = 0x20000112
bus.WriteHalfWord(0x20000020, 0x5000 | (2 << 6) | (1 << 3) | 0);
cpu.Registers.PC = 0x20000020;
cpu.Registers[0] = 0xDEADBEEF;
cpu.Registers[1] = 0x20000112;
cpu.Registers[2] = 0;
cpu.Step();
// mem[0x20000110] = 0xBEEF0000, mem[0x20000114] = 0x0000DEAD, IPSR = 0

Measured output:

LDR from 0x20000102 -> r0=0x3344AABB  PC=0x20000002  IPSR 0->0  lockedUp=False
STR to  0x20000112 -> mem[0x20000110]=0xBEEF0000 mem[0x20000114]=0x0000DEAD  PC=0x20000022  IPSR=0  lockedUp=False

The store is the worse half: one misaligned str silently corrupts two unrelated words that no instruction addressed.

Expected

ARMv6-M ARM §A3.2 (Alignment support): the architecture does not support unaligned data accesses. Every word access must be word-aligned and every halfword access halfword-aligned; an access that is not generates an alignment fault. ARMv6-M has no UsageFault, so the fault is taken as HardFault (exception 3) via the usual escalation, with the exception taken before the access has any effect, so no memory is written and the destination register is unchanged.

So the emulator should, for LDR/STR/LDRH/STRH and the SP-relative and literal forms, raise EXC_HARDFAULT when the effective address is not naturally aligned, leaving registers and memory untouched. LDM/STM/PUSH/POP and the vector table fetch have the same requirement on their word addresses.

Notes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions