Project 7: AArch64 Exception Level Lab

Observe and transition between AArch64 exception levels.

Quick Reference

Attribute Value
Difficulty Level 4
Time Estimate 16-24 hours
Main Programming Language Assembly + C (Alternatives: Rust)
Alternative Programming Languages Rust
Coolness Level Level 4
Business Potential Level 3
Prerequisites Concept 1: Profiles & Execution States, Concept 5: Boot & Exceptions
Key Topics EL0-EL3, privilege transitions

1. Learning Objectives

By completing this project, you will:

  1. Translate ARM concepts into observable outputs you can verify.
  2. Explain why each toolchain or hardware step is necessary.
  3. Detect and fix at least one realistic failure mode.
  4. Communicate the result clearly in a technical review or interview.

2. All Theory Needed (Per-Concept Breakdown)

Profiles & Execution States

Fundamentals ARM is a family of architectures organized into profiles optimized for different constraints. The A-profile targets application processors that run rich OSes (phones, laptops, servers), the M-profile targets microcontrollers with tight power and memory budgets, and the R-profile targets deterministic real-time systems. citeturn0search0turn0search1turn0search2 Each profile implies a different set of instructions, privilege models, and system features. Within a profile, ARM defines execution states (such as AArch64 or AArch32) that determine register width, instruction encoding, and address space. AArch64 is the 64-bit execution state introduced in ARMv8-A, while AArch32 is the 32-bit state; M-profile uses Thumb encodings for compact code density and simpler decode logic. citeturn0search2 This is why “ARM assembly” is not a single language: the same mnemonic can encode differently, or even be invalid, depending on profile and state.

Deep Dive The profile split is the most important high-level idea in ARM. A-profile cores are built to host complex operating systems with virtual memory, multi-core scheduling, and high performance. That means features like exception levels, MMUs, and richer instruction sets matter. In contrast, M-profile focuses on minimal latency, low power, and deterministic behavior: it strips away many features to reduce silicon cost and simplify real-time response. R-profile sits in-between: it retains more predictability than A-profile but includes stronger real-time guarantees than M-profile. citeturn0search0turn0search1 When you choose to write assembly, you’re implicitly choosing a profile, and that choice changes everything from the boot flow to the toolchain arguments you use.

Execution states deepen the split. In ARMv8-A, AArch64 brings a new 64-bit register file and 32-bit fixed-length instruction encoding (A64). AArch32 keeps the 32-bit model (A32/T32). This means that for A-profile hardware, your code must declare its intended execution state; otherwise, even valid mnemonics may assemble into the wrong encoding or fail. citeturn0search2 M-profile, by contrast, uses Thumb encodings by design, favoring compact instructions and simpler decode paths. These constraints are not academic. They drive register availability, calling convention differences, and even the structure of your interrupt handlers. If you write code that assumes AArch64 but run on Cortex-M, the encoding and semantics are incompatible.

Another subtle but critical effect of profile and state is the system-level context. A-profile expects multiple privilege levels and potentially a hypervisor. M-profile’s exception model is simpler, its vector table is fixed and immediate, and it typically boots directly into a single firmware image. R-profile targets systems where real-time guarantees trump throughput; this affects interrupt priority, memory latency assumptions, and peripheral access patterns. Understanding profile choice lets you reason about why an instruction exists, why a particular addressing mode is missing, and why certain system registers are visible or hidden.

Finally, architecture profiles determine the ecosystem around your work. A-profile benefits from abundant tooling, standardized ABIs, and OS integration, while M-profile leans on vendor SDKs, board-specific memory maps, and smaller toolchains. This guide intentionally spans both because many real-world systems combine them: a Linux-capable application processor for high-level features and a microcontroller for deterministic control. Once you see that split, you can design experiments and projects that map to the right target without confusion.

How this fits on projects

  • Shapes target selection in P01 (Toolchain Pipeline Explorer) and P07 (Exception Level Lab).
  • Determines encoding assumptions in P03 (Thumb Encoder/Decoder).

Definitions & key terms

  • Profile: A family of ARM features optimized for a market segment (A/M/R). citeturn0search0turn0search1turn0search2
  • Execution state: The architectural mode (AArch64, AArch32, Thumb) that defines register width and instruction encoding. citeturn0search2
  • AArch64: 64-bit execution state introduced in ARMv8-A.
  • AArch32: 32-bit execution state in ARMv8-A.
  • Thumb: Compact instruction encoding used by M-profile.

Mental model diagram

ARM Architecture Evolution:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

                              ┌─────────────────────────────────────────────┐
                              │            ARM Holdings (IP owner)           │
                              │   Designs architectures, licenses to others  │
                              └─────────────────────┬───────────────────────┘
                                                    │
           ┌────────────────────────────────────────┼────────────────────────────┐
           │                                        │                            │
           ▼                                        ▼                            ▼
    ┌──────────────┐                    ┌────────────────┐              ┌────────────────┐
    │   M-Profile  │                    │    A-Profile   │              │   R-Profile    │
    │ Microcontrollers                  │  Applications  │              │   Real-Time    │
    │ (Embedded)   │                    │ (Phones, PCs)  │              │   (Automotive) │
    └──────────────┘                    └────────────────┘              └────────────────┘
           │                                        │
    ┌──────┴──────┐                    ┌────────────┼────────────┐
    │             │                    │            │            │
    ▼             ▼                    ▼            ▼            ▼
 ┌───────┐   ┌────────┐          ┌─────────┐  ┌─────────┐  ┌─────────┐
 │Cortex │   │Cortex  │          │Cortex-A7│  │Cortex-A │  │Cortex-A │
 │ -M0+  │   │-M4/M7  │          │Cortex-A9│  │53/55/72 │  │76/78/X  │
 │       │   │        │          │(32-bit) │  │(64-bit) │  │(64-bit) │
 └───────┘   └────────┘          └─────────┘  └─────────┘  └─────────┘
     │            │                   │            │            │
     │            │                   │            │            │
  Thumb       Thumb-2              ARM32      AArch64      AArch64
  only        + DSP               + Thumb    + NEON       + SVE2
                + FPU

┌──────────────────────────────────────────────────────────────────────────────┐
│ YOUR TARGETS:                                                                 │
│                                                                               │
│ Raspberry Pi Pico (RP2040)     Raspberry Pi 3/4/5                            │
│ ├─ Dual Cortex-M0+ cores       ├─ Cortex-A53/A72/A76 cores                   │
│ ├─ ARMv6-M architecture        ├─ ARMv8-A architecture (AArch64)             │
│ ├─ Thumb instruction set       ├─ A64 instruction set                        │
│ ├─ 16 registers (r0-r15)       ├─ 31 registers (x0-x30)                      │
│ ├─ 133 MHz max clock           ├─ 1.5-2.4 GHz clock                          │
│ └─ 264 KB RAM, no OS           └─ 1-8 GB RAM, Linux capable                  │
└──────────────────────────────────────────────────────────────────────────────┘

ARM Architecture Family Tree

How it works (step-by-step, with invariants and failure modes)

  1. Choose the target profile (A/M/R) based on system constraints and OS expectations.
  2. Select execution state (AArch64, AArch32, Thumb) based on ISA and toolchain output.
  3. Assemble and link with profile/state-specific flags; encoding mismatches yield invalid opcodes.
  4. Boot into the expected privilege level; if the firmware expects EL2 and you start at EL1, early setup fails.
  5. Validate on target or emulator; incorrect profile assumptions manifest as illegal instruction faults or boot hangs.

Minimal concrete example (pseudo-assembly, not runnable)

Select Target = {Profile: M, State: Thumb}
Assemble([LOAD R0, [ADDR]], Target)
If Target != CPU_State → Fault: Illegal Instruction

Common misconceptions

  • “ARM assembly is one language” → It is a family with profile/state splits.
  • “Thumb is only a compact mode” → It also shapes register access and available instructions.
  • “AArch64 is just ARM32 with bigger registers” → It changes the register file and encoding model.

Check-your-understanding questions

  1. Why can Cortex-M code not run on a Cortex-A core without translation?
  2. What is the difference between AArch64 and AArch32?
  3. How does the profile choice affect your toolchain flags?

Check-your-understanding answers

  1. Cortex-M uses the M-profile with Thumb encodings and a different system model; Cortex-A expects A-profile with AArch64/AArch32 states.
  2. AArch64 is a 64-bit execution state with a new register file and A64 encoding; AArch32 is 32-bit with different encodings. citeturn0search2
  3. The assembler and linker must emit instructions for the correct ISA and object format; mismatches produce illegal opcodes or link errors.

Real-world applications

  • Microcontroller firmware (M-profile) in sensors, robotics, and embedded control. citeturn0search0
  • Application processors (A-profile) in mobile, desktop, and servers. citeturn0search2

Where you’ll apply it

  • This project: see §3.1 and §5.4 in P07-aarch64-exception-level-lab.md
  • P01 Toolchain Pipeline Explorer
  • P03 Thumb Instruction Encoder/Decoder
  • P07 AArch64 Exception Level Lab

References

  • Arm M-profile overview. citeturn0search0
  • Arm R-profile overview. citeturn0search1
  • Arm A-profile overview and execution states. citeturn0search2

Key insights Your “ARM assembly” only makes sense once you name the profile and execution state.

Summary Profiles and execution states are the root of every other difference in ARM assembly. When you get this right, the rest of the system becomes predictable.

Homework/Exercises to practice the concept

  1. Pick two devices (one microcontroller, one phone) and identify their ARM profile and execution state.
  2. Write a one-paragraph explanation of why Thumb exists.

Solutions to the homework/exercises

  1. Example: RP2040 is M-profile with Thumb; a modern smartphone SoC is A-profile with AArch64.
  2. Thumb improves code density and decoder simplicity, which is crucial for small embedded systems.

Boot, Exceptions & Interrupts

Fundamentals Boot and exception handling define how control flow starts and changes when the system is interrupted. On Cortex-M, reset reads a vector table at a fixed address to obtain the initial stack pointer and reset handler. On AArch64, exception levels (EL0–EL3) define privilege and isolation across kernel, hypervisor, and secure monitor. citeturn0search6turn2search4 Interrupts are structured events with defined entry and exit behavior; when misunderstood, they cause the most common low-level failures (silent lockups, corrupted stacks, and unacknowledged interrupts).

Deep Dive Boot flow is architecture-specific, but it always starts with the hardware choosing a program counter and stack pointer. In Cortex-M, the vector table is a literal list of addresses at the start of flash (or a remapped location). The CPU loads the initial SP from offset 0 and the reset handler from offset 4; execution begins there. This is why vector tables are so critical: a single incorrect address prevents boot. In AArch64 systems, boot is more complex. Firmware (or a ROM) selects the initial exception level and execution state, then transfers control to your image. This can occur at EL2 or EL1 depending on platform; understanding the starting level is essential for setting up the MMU and interrupt controller. citeturn0search6turn2search4

Exceptions and interrupts are structured transitions. On Cortex-M, hardware automatically saves a register frame on the stack and switches to handler mode. This means your ISR is effectively running on a known stack layout; if you violate it, return from interrupt fails. AArch64 exceptions follow a different path: they trap into higher exception levels and use banked registers and exception vector tables that differ per EL. This makes exception handling on A-profile both more powerful and more complex. In practice, you must know which registers are saved by hardware and which you must save manually, and you must understand the difference between synchronous exceptions (e.g., illegal instruction) and asynchronous interrupts (e.g., timer). citeturn0search6turn2search4

Interrupt latency is also a systems-level trade-off. M-profile is designed for low-latency, deterministic responses, which is why it dominates microcontroller workloads. citeturn0search0 This is a critical difference from A-profile, where throughput and virtualization might be prioritized. When you design firmware, you need to decide which tasks are best done in an ISR versus in the main loop; an ISR that does too much can starve other interrupts and introduce jitter.

Finally, exceptions connect directly to debugging. Many “mysterious” crashes are just unhandled faults. On Cortex-M, a hard fault may indicate an invalid memory access or misaligned stack. On AArch64, synchronous exceptions reveal illegal instructions or permission violations. By understanding the exception model, you can interpret fault codes and correlate them to your assembly-level behavior, which is a core skill in systems programming and security analysis.

How this fits on projects

  • Central to P05 (Vector Table Builder), P06 (Interrupt-Driven UART), and P07 (Exception Level Lab).

Definitions & key terms

  • Vector table: Table of exception handler addresses used at reset or interrupt.
  • Exception level: Privilege tier in AArch64 (EL0–EL3). citeturn0search6turn2search4
  • ISR: Interrupt service routine.
  • HardFault: Cortex-M fault handler for severe errors.

Mental model diagram

Cortex-M Boot Sequence:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    Power Applied
         │
         ▼
    ┌─────────────────────────────────────────────────────────────────┐
    │  1. CPU comes out of reset                                      │
    │     - All registers undefined (except SP and PC)                │
    │     - Processor in Thread mode, privileged                      │
    │     - Using Main Stack Pointer (MSP)                            │
    └─────────────────────────────────────────────────────────────────┘
         │
         ▼
    ┌─────────────────────────────────────────────────────────────────┐
    │  2. CPU reads address 0x00000000 (or VTOR)                      │
    │     - Loads INITIAL STACK POINTER value                         │
    │     - This value goes into SP/r13                               │
    └─────────────────────────────────────────────────────────────────┘
         │
         ▼
    ┌─────────────────────────────────────────────────────────────────┐
    │  3. CPU reads address 0x00000004                                │
    │     - Loads RESET HANDLER address                               │
    │     - This value goes into PC/r15                               │
    │     - Bit 0 MUST be 1 (Thumb mode indicator)                    │
    └─────────────────────────────────────────────────────────────────┘
         │
         ▼
    ┌─────────────────────────────────────────────────────────────────┐
    │  4. Execution begins at Reset_Handler                           │
    │     - Your code starts running!                                 │
    │     - Stack is ready to use                                     │
    │     - All peripherals need initialization                       │
    └─────────────────────────────────────────────────────────────────┘


Vector Table Structure (first 16 entries are standard Cortex-M):
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    Offset   │  Exception #  │  Contents
    ─────────┼───────────────┼────────────────────────────────────────
    0x0000   │  -            │  Initial Stack Pointer value
    0x0004   │  1 (Reset)    │  Reset_Handler address (| 1 for Thumb)
    0x0008   │  2 (NMI)      │  NMI_Handler address
    0x000C   │  3 (HardFault)│  HardFault_Handler address
    0x0010   │  4            │  Reserved (M0+ doesn't use)
    ...      │  ...          │  ...
    0x003C   │  15 (SysTick) │  SysTick_Handler address
    0x0040   │  16 (IRQ0)    │  First peripheral interrupt
    0x0044   │  17 (IRQ1)    │  Second peripheral interrupt
    ...      │  ...          │  (RP2040 has 26 IRQs)


Example minimal vector table in assembly:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    .section .vectors, "a"
    .align 2

    .word   _stack_top          // 0x00: Initial SP
    .word   Reset_Handler + 1   // 0x04: Reset (bit 0 = Thumb)
    .word   NMI_Handler + 1     // 0x08: NMI
    .word   HardFault_Handler+1 // 0x0C: HardFault
    .word   0                   // 0x10: Reserved
    // ... more entries ...

NOTE: On RP2040, flash is at 0x10000000, so your vector table
lives there. The boot ROM copies the SP and PC from flash.

Cortex-M Boot Sequence

AArch64 Exception Levels:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    ┌─────────────────────────────────────────────────────────────────┐
    │  EL3: Secure Monitor                                            │
    │       - Highest privilege, manages secure/non-secure worlds     │
    │       - TrustZone firmware lives here                           │
    ├─────────────────────────────────────────────────────────────────┤
    │  EL2: Hypervisor                                                │
    │       - Virtualization support                                  │
    │       - Controls virtual machines                               │
    ├─────────────────────────────────────────────────────────────────┤
    │  EL1: OS Kernel                                                 │
    │       - Where Linux kernel runs                                 │
    │       - Your bare-metal code runs here!                         │
    ├─────────────────────────────────────────────────────────────────┤
    │  EL0: User Applications                                         │
    │       - Lowest privilege                                        │
    │       - Normal programs run here under Linux                    │
    └─────────────────────────────────────────────────────────────────┘

    On Raspberry Pi boot:
    ┌──────────────────────────────────────────────────────────────┐
    │ GPU firmware starts at EL3, then drops to EL2,               │
    │ loads your kernel8.img, and jumps to 0x80000 at EL2.         │
    │ Your bare-metal code typically runs at EL1 after setup.      │
    └──────────────────────────────────────────────────────────────┘

AArch64 Exception Levels

Interrupt Flow on Cortex-M:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    Main Code Running
           │
           │ ← UART receives byte
           │   Hardware sets interrupt flag
           │   NVIC sees enabled interrupt
           ▼
    ┌──────────────────────────────────────────────────────────────────┐
    │  AUTOMATIC HARDWARE ACTIONS (you don't write code for this):    │
    │  1. Finish current instruction                                   │
    │  2. Push 8 registers to stack: r0-r3, r12, LR, PC, xPSR         │
    │  3. Load new PC from vector table (exception #)                  │
    │  4. Load 0xFFFFFFF9 into LR (EXC_RETURN)                        │
    │  5. Enter Handler mode (privileged)                              │
    └──────────────────────────────────────────────────────────────────┘
           │
           ▼
    ┌──────────────────────────────────────────────────────────────────┐
    │  YOUR ISR EXECUTES:                                              │
    │  - Must save r4-r11 if you use them (push {r4-r7})              │
    │  - Read UART data register (clears interrupt flag)               │
    │  - Process byte (store in buffer, set flag, etc.)                │
    │  - Restore r4-r11 if saved                                       │
    │  - Return with: BX LR (the magic EXC_RETURN value)               │
    └──────────────────────────────────────────────────────────────────┘
           │
           ▼
    ┌──────────────────────────────────────────────────────────────────┐
    │  AUTOMATIC HARDWARE ACTIONS:                                     │
    │  1. Hardware detects EXC_RETURN in LR                            │
    │  2. Pop 8 registers from stack                                   │
    │  3. Resume execution exactly where interrupted                   │
    │  4. Return to Thread mode                                        │
    └──────────────────────────────────────────────────────────────────┘
           │
           ▼
    Main Code Continues (unaware anything happened!)


Stack During Interrupt:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

    BEFORE interrupt:           AFTER entry, BEFORE ISR code:
    ┌──────────────┐           ┌──────────────┐
    │  (old data)  │           │  (old data)  │
    │              │           ├──────────────┤
    │              │           │  xPSR        │ ← +0x1C from new SP
    │              │           ├──────────────┤
    │              │           │  PC (return) │ ← +0x18
    │              │           ├──────────────┤
    │              │           │  LR          │ ← +0x14
    │              │           ├──────────────┤
    │              │           │  r12         │ ← +0x10
    │              │           ├──────────────┤
    │              │           │  r3          │ ← +0x0C
    │              │           ├──────────────┤
    │              │           │  r2          │ ← +0x08
    │              │           ├──────────────┤
    │              │           │  r1          │ ← +0x04
    │              │           ├──────────────┤
SP →│              │        SP→│  r0          │ ← +0x00 (new SP)
    └──────────────┘           └──────────────┘

    The 32 bytes (8 × 4) are pushed automatically by hardware!

Cortex-M Interrupt Flow

How it works (step-by-step, with invariants and failure modes)

  1. Boot loads initial SP and PC from the vector table (Cortex-M) or firmware-defined entry (AArch64). citeturn0search6turn2search4
  2. An interrupt triggers hardware context save and branches to the handler.
  3. Handler restores context and returns using the architecture-specific mechanism.
  4. Failure mode: wrong vector address or corrupted stack → boot hang or fault loop.

Minimal concrete example (pseudo, not runnable)

VECTOR_TABLE[Reset] -> Reset_Handler
On interrupt: push context, branch handler, restore, return

Common misconceptions

  • “Interrupt handlers are just normal functions” → They obey different entry/exit rules.
  • “Boot is just jump to main” → Boot is a structured sequence with strict alignment rules.

Check-your-understanding questions

  1. Why must the reset handler address be Thumb-aligned on Cortex-M?
  2. What does EL1 represent in AArch64?
  3. Why must ISRs be short?

Check-your-understanding answers

  1. Bit 0 of the address indicates Thumb state; if it’s wrong, the CPU faults.
  2. EL1 is the kernel-level privilege where OS code typically runs. citeturn0search6turn2search4
  3. Long ISRs increase latency and can block higher-priority interrupts.

Real-world applications

  • Firmware bootloaders, interrupt-driven I/O, and OS exception handling. citeturn0search0turn0search6

Where you’ll apply it

  • This project: see §3.1 and §5.4 in P07-aarch64-exception-level-lab.md
  • P05 Vector Table Builder
  • P06 Interrupt-Driven UART
  • P07 AArch64 Exception Level Lab

References

  • AArch64 exception model and privilege levels. citeturn0search6turn2search4
  • Cortex-M profile emphasis on low-latency interrupt response. citeturn0search0

Key insights Boot and exceptions are not features you add later; they are the foundation of control flow.

Summary Once you understand boot and exceptions, most “mysterious” bare-metal failures become obvious.

Homework/Exercises to practice the concept

  1. Draw the Cortex-M vector table layout and label the first 8 entries.
  2. Explain how an interrupt differs from a synchronous exception.

Solutions to the homework/exercises

  1. The first entry is the initial SP, followed by reset, NMI, HardFault, and system handlers.
  2. Interrupts are asynchronous hardware events; synchronous exceptions are triggered by the current instruction.

3. Project Specification

3.1 What You Will Build

A lab that reports current EL and safely transitions to EL1.

3.2 Functional Requirements

  1. Requirement 1: Read current exception level
  2. Requirement 2: Configure target EL and transition
  3. Requirement 3: Log outcomes in deterministic order

3.3 Non-Functional Requirements

  • Clear diagnostics on failure

3.4 Example Usage / Output

$ aarch64-el-lab
EL at entry: EL2
Dropped to EL1
EL now: EL1

$ aarch64-el-lab --invalid-el
error: unsupported target EL
exit code: 2

3.5 Data Formats / Schemas / Protocols

  • EL log: timestamp, EL value

3.6 Edge Cases

  • Unexpected starting EL
  • Traps during transition

3.7 Real World Outcome

This is the golden reference for success:

  • You can explain why a platform boots at EL2 or EL3.

3.7.1 How to Run (Copy/Paste)

  • Build: follow the toolchain steps defined in this guide
  • Run: use the CLI examples in §3.4 with fixed inputs
  • Expected directory: project root

3.7.2 Golden Path Demo (Deterministic)

Run with a fixed input set and confirm output matches §3.4 exactly.

3.7.3 If CLI: Exact Terminal Transcript

$ aarch64-el-lab
EL at entry: EL2
Dropped to EL1
EL now: EL1

$ aarch64-el-lab --invalid-el
error: unsupported target EL
exit code: 2

4. Solution Architecture

4.1 High-Level Design

┌──────────────┐     ┌──────────────┐     ┌──────────────┐
│ Input Layer  │───▶│ Core Logic   │───▶│ Output Layer │
└──────────────┘     └──────────────┘     └──────────────┘

4.2 Key Components

Component Responsibility Key Decisions
Input Parser Validate and normalize input Strict error handling
Core Engine Perform the main computation Deterministic paths
Reporter Produce user-facing output Stable formatting

4.3 Data Structures (No Full Code)

Record Entry {
  name: string
  fields: list
  notes: text
}

4.4 Algorithm Overview

Key Algorithm: Core Flow

  1. Parse input and validate parameters.
  2. Execute the core transformation or analysis.
  3. Emit deterministic output or error summary.

Complexity Analysis:

  • Time: O(n) in the size of input records
  • Space: O(n) for stored mappings and logs

5. Implementation Guide

5.1 Development Environment Setup

# Install toolchain and verify versions
toolchain --version

5.2 Project Structure

project-root/
├── src/
│   ├── core
│   └── io
├── tests/
│   └── fixtures
├── docs/
└── README.md

5.3 The Core Question You’re Answering

“Observe and transition between AArch64 exception levels.”

5.4 Concepts You Must Understand First

Stop and research these before coding:

  1. Profiles & Execution States
    • What is the key invariant you must preserve?
  2. Boot, Exceptions & Interrupts
    • What is the key invariant you must preserve?

5.5 Questions to Guide Your Design

  1. Data Flow
    • How does input become output?
    • Which steps must be deterministic?
  2. Validation
    • What is the simplest test that proves correctness?
    • How will you detect regressions?

5.6 Thinking Exercise

Trace the Critical Path

Write a step-by-step trace of the most important workflow in this project.

Questions to answer:

  • Where could a subtle bug hide?
  • What would you log to prove correctness?

5.7 The Interview Questions They’ll Ask

  1. “What is the core invariant this project relies on?”
  2. “How would you debug a failure in this workflow?”
  3. “What trade-offs did you make in design?”
  4. “How does this map to real hardware or toolchains?”
  5. “How do you prove your output is correct?”

5.8 Hints in Layers

Hint 1: Start small Focus on the smallest input that still demonstrates the concept.

Hint 2: Make output deterministic Fix inputs and produce stable logs before expanding functionality.

Hint 3: Validate against a known reference Compare with a known-good output or specification.

Hint 4: Add instrumentation Log internal steps so you can verify each phase explicitly.

5.9 Books That Will Help

Topic Book Chapter
Core concept “ARM Assembly Language” by William Hohl Ch. 3-5
Binary formats “Linkers and Loaders” by John R. Levine Ch. 1-3

5.10 Implementation Phases

Phase 1: Foundation (2-4 hours)

Goals:

  • Establish a minimal working pipeline
  • Validate one end-to-end path Tasks:
    1. Build the smallest viable input and output
    2. Verify outputs against a reference Checkpoint: Output matches expected golden path

Phase 2: Core Functionality (4-8 hours)

Goals:

  • Implement main logic and validation
  • Add structured error handling Tasks:
    1. Implement the core transformation
    2. Add deterministic reporting Checkpoint: Core tests pass reliably

Phase 3: Polish & Edge Cases (2-4 hours)

Goals:

  • Cover edge cases
  • Improve output clarity Tasks:
    1. Add negative tests
    2. Document limitations Checkpoint: All edge cases handled gracefully

5.11 Key Implementation Decisions

Decision Options Recommendation Rationale
Input format Free-form vs structured Structured Easier validation
Output format Human vs machine Both Supports verification and tooling

6. Testing Strategy

6.1 Test Categories

Category Purpose Examples
Unit Tests Validate core logic Field parsing, bounds checks
Integration Tests Validate full flow End-to-end CLI runs
Edge Case Tests Validate boundaries Empty input, invalid flags

6.2 Critical Test Cases

  1. Golden path: Fixed input produces known output.
  2. Invalid input: Error path triggers correct exit code.
  3. Boundary case: Maximum supported value handled correctly.

6.3 Test Data

Input: fixed seed or fixed fixture
Expected: exact output text from §3.4

7. Common Pitfalls & Debugging

Pitfall Symptom Solution
Misaligned assumptions Unexpected output Re-check invariants
Missing validation Silent failures Add explicit checks
Non-determinism Flaky output Fix inputs and seeds

7.2 Debugging Strategies

  • Trace everything: Log each step with stable ordering
  • Compare against reference: Use known-good outputs

7.3 Performance Traps

  • Avoid repeated parsing of the same input; cache results when possible

8. Extensions & Challenges

8.1 Beginner Extensions

  • Add one extra output format
  • Add a help screen with examples

8.2 Intermediate Extensions

  • Add a verification mode that compares two outputs
  • Add structured JSON output

8.3 Advanced Extensions

  • Add a batch mode for large inputs
  • Add cross-target comparisons (M vs A profile)

9. Real-World Connections

9.1 Industry Applications

  • Firmware bring-up: use the same checks to validate early boot images
  • Security audits: analyze binaries for ABI or control-flow correctness
  • binutils: source of many ARM tooling workflows
  • QEMU: emulator used for ARM testing

9.3 Interview Relevance

  • Explains why ARM behavior differs across profiles
  • Demonstrates toolchain literacy and debugging rigor

10. Resources

10.1 Essential Reading

  • “ARM Assembly Language” by William Hohl - practical instruction usage
  • “Linkers and Loaders” by John R. Levine - binary layout

10.2 Video Resources

  • ARM architecture overview talks and lectures

10.3 Tools & Documentation

  • GNU binutils documentation
  • Arm developer documentation
  • This project connects with: P01-toolchain-pipeline-explorer.md, P02-register-stack-visualizer.md, P03-thumb-encoder-decoder.md

11. Self-Assessment Checklist

11.1 Understanding

  • I can explain the core concept without notes
  • I can explain why my design choices were necessary
  • I can describe one realistic failure mode

11.2 Implementation

  • All functional requirements are met
  • Tests pass deterministically
  • Edge cases are documented

11.3 Growth

  • I can describe what I would improve next time
  • I can explain this project in an interview

12. Submission / Completion Criteria

Minimum Viable Completion:

  • Core functionality works on reference inputs
  • Deterministic golden path is documented
  • At least one failure path is demonstrated

Full Completion:

  • All minimum criteria plus:
  • Edge cases are covered with tests
  • Output format is stable and documented

Excellence (Going Above & Beyond):

  • Add a comparison against a second target
  • Provide a short write-up of lessons learned