Get the Professional Embedded Starter Kit: Production-ready templates and architectural cheat sheets for your firmware projects.
Understanding the ARMv8-A Vector Table and Exception Handling
1.
Introduction
When an exception occurs on an ARMv8-A processor, the hardware needs to know where to jump. It does not call a function through a pointer registered in software at runtime. Instead it consults a fixed structure in memory called the vector table. The vector table maps each exception type to a handler address, and the processor jumps there automatically when the exception is taken.
Setting up the vector table correctly is one of the first things bare metal firmware and operating system kernels must do. A misconfigured vector table means exceptions jump to garbage code, causing crashes or silent failures that are extremely difficult to debug.
2.
What the Vector Table Is
2.1 Purpose and Location
The vector table is a block of memory that contains the entry points for exception handlers. Unlike the ARMv7 vector table which contained absolute addresses, the ARMv8-A vector table contains actual instructions. When an exception is taken, the processor does not read a pointer and jump to it. It jumps directly to the vector table entry and begins executing instructions there.
Each entry in the ARMv8-A vector table is 128 bytes wide, giving enough room for a short handler sequence or a branch instruction to a full handler elsewhere in memory.
The location of the vector table is not fixed. It is configured through a Vector Base Address Register, one per exception level.
2.2 VBAR_EL1, VBAR_EL2, VBAR_EL3
Each exception level that can receive exceptions has its own Vector Base Address Register:
- VBAR_EL1: base address of the EL1 vector table, used when exceptions are taken to EL1
- VBAR_EL2: base address of the EL2 vector table, used when exceptions are taken to EL2
- VBAR_EL3: base address of the EL3 vector table, used when exceptions are taken to EL3
The vector table must be aligned to a 2KB boundary. This is a hardware requirement. A misaligned VBAR will cause incorrect vector table lookups.
/* install vector table at EL1 */ adr x0, vector_table_el1 msr vbar_el1, x0 isb /* instruction sync barrier */ |
Struggling to implement this for a professional project?
If you need to master full-scale firmware architecture, security, and build automation, join our 1-D or 4-Day Live Implemnetation Workshops. I'll show you the exact direct path to production-ready firmware without the trial and error.
3.
Vector Table Structure
3.1 Four Categories of Exceptions
The ARMv8-A vector table is divided into four categories based on the source and type of the exception:
- Synchronous: exceptions caused by the instruction being executed, such as system calls, data aborts, instruction aborts, and undefined instructions.
- IRQ: asynchronous interrupt requests from external hardware.
- FIQ: fast interrupt requests, typically routed to EL3 for secure world use.
- SError: system errors, typically asynchronous aborts from the memory system such as uncorrectable ECC errors.
3.2 Four Source Levels Per Category
For each of the four exception categories, the vector table has four entries depending on where the exception came from and what execution state the processor was in:
- Current EL with SP_EL0: exception taken at the current level using the EL0 stack pointer.
- Current EL with SP_ELn: exception taken at the current level using the current level's own stack pointer.
- Lower EL using AArch64: exception taken from a lower level running in AArch64 state.
- Lower EL using AArch32: exception taken from a lower level running in AArch32 state.
3.3 The 16 Entries and Their Offsets
The vector table has 4 categories times 4 sources equals 16 entries. Each entry is 128 bytes. The offsets from VBAR_ELn are fixed by the architecture:
Offset Source Type
0x000 Current EL, SP_EL0 Synchronous
0x080 Current EL, SP_EL0 IRQ
0x100 Current EL, SP_EL0 FIQ
0x180 Current EL, SP_EL0 SError
0x200 Current EL, SP_ELn Synchronous
0x280 Current EL, SP_ELn IRQ
0x300 Current EL, SP_ELn FIQ
0x380 Current EL, SP_ELn SError
0x400 Lower EL, AArch64 Synchronous
0x480 Lower EL, AArch64 IRQ
0x500 Lower EL, AArch64 FIQ
0x580 Lower EL, AArch64 SError
0x600 Lower EL, AArch32 Synchronous
0x680 Lower EL, AArch32 IRQ
0x700 Lower EL, AArch32 FIQ
0x780 Lower EL, AArch32 SError
Fig
1. Vector table layout: 16 entries at fixed offsets from VBAR_ELn.
4.
Exception Categories Explained
4.1 Synchronous Exceptions
Synchronous exceptions are caused directly by the instruction stream. They are precise: the processor knows exactly which instruction caused them and the exception is taken before any side effects beyond that instruction occur.
Common synchronous exceptions include system calls via SVC, hypervisor calls via HVC, secure monitor calls via SMC, data aborts from memory access failures, instruction aborts from failed instruction fetches, and undefined instruction exceptions.
Inside a synchronous handler, the ESR_ELn register, the Exception Syndrome Register, contains a code that identifies the exact cause of the exception. The EC field in bits 31 to 26 encodes the exception class.
/* read exception syndrome at EL1 */ define ESR_EL1_EC_SHIFT 26U define ESR_EL1_EC_MASK 0x3FUL define ESR_EL1_EC_SVC64 0x15UL /* SVC from AArch64 */ define ESR_EL1_EC_DABT 0x24UL /* data abort from lower EL */ define ESR_EL1_EC_IABT 0x20UL /* instruction abort */
static inline uint32_t esr_get_ec(uint64_t esr) { return (uint32_t)((esr >> ESR_EL1_EC_SHIFT) & ESR_EL1_EC_MASK); } |
4.2 IRQ
IRQ is the standard external interrupt mechanism. When an interrupt controller signals an interrupt to the processor, an IRQ exception is generated. The processor saves its state and jumps to the IRQ entry in the vector table.
IRQ is asynchronous. It can be taken between any two instructions regardless of what the processor was executing. The kernel interrupt handler identifies the interrupt source by querying the interrupt controller, typically a GIC on ARMv8-A systems, and dispatches the appropriate driver handler.
4.3 FIQ
FIQ is a fast interrupt with higher priority than IRQ. On ARMv8-A systems with TrustZone, FIQ is typically routed to EL3 and used exclusively by the secure world. The SCR_EL3 register controls whether FIQ is routed to EL3 or allowed to be handled at lower levels.
4.4 SError
SError, System Error, is an asynchronous exception generated by the memory system for errors that cannot be attributed to a specific instruction. The most common source is an uncorrectable ECC error in RAM detected after the instruction that triggered the access has already retired.
SError is the hardest exception to handle correctly because by the time it arrives, the processor state may not directly correspond to the instruction that caused the problem. Many production systems treat SError as fatal and trigger a system reset.
Fig
2. The four exception types: synchronous, IRQ, FIQ, and SError.
5.
Writing a Minimal Vector Table in Assembly
5.1 Aligning the Vector Table to 2KB
The vector table must be 2KB aligned. In assembly, the .align directive specifies the alignment as a power of two. 2KB is 2048 bytes which is 2 to the power of 11.
.section .vectors, "ax" .align 11 /* 2^11 = 2048 byte alignment */
vector_table_el1:
/* Current EL with SP_EL0 */ .org 0x000 b sync_handler_sp0 .org 0x080 b irq_handler_sp0 .org 0x100 b fiq_handler_sp0 .org 0x180 b serror_handler_sp0
/* Current EL with SP_EL1 */ .org 0x200 b sync_handler_spn .org 0x280 b irq_handler_spn .org 0x300 b fiq_handler_spn .org 0x380 b serror_handler_spn
/* Lower EL using AArch64 */ .org 0x400 b sync_handler_lower64 .org 0x480 b irq_handler_lower64 .org 0x500 b fiq_handler_lower64 .org 0x580 b serror_handler_lower64
/* Lower EL using AArch32 */ .org 0x600 b sync_handler_lower32 .org 0x680 b irq_handler_lower32 .org 0x700 b fiq_handler_lower32 .org 0x780 b serror_handler_lower32 |
5.2 Installing VBAR_EL1
After defining the vector table, the kernel installs it by writing its address to VBAR_EL1 followed by an instruction synchronization barrier to ensure the write takes effect before any exception could be taken.
adr x0, vector_table_el1 msr vbar_el1, x0 isb
|
5.3 A Minimal Handler Stub
Each handler entry needs to save registers, identify and handle the exception, restore registers, and return with ERET. A minimal stub for a synchronous exception from a lower EL looks like this:
sync_handler_lower64: /* save general purpose registers onto the EL1 stack */ stp x0, x1, [sp, -16]! stp x2, x3, [sp, -16]! stp x29, x30, [sp, -16]!
/* read exception syndrome to identify cause */ mrs x0, esr_el1 mrs x1, elr_el1 mrs x2, far_el1
/* call C handler */ bl handle_sync_exception
/* restore registers */ ldp x29, x30, [sp], 16 ldp x2, x3, [sp], 16 ldp x0, x1, [sp], 16
eret |
Fig 3. Exception entry flow: processor saves state, jumps to vector table, handler runs, ERET returns.
6.
Conclusion
The ARMv8-A vector table is a 2KB-aligned block of memory containing 16 entries of 128 bytes each. The entries are organized by exception type: synchronous, IRQ, FIQ, and SError, crossed with the source of the exception: current EL with SP_EL0, current EL with SP_ELn, lower EL in AArch64, and lower EL in AArch32. The processor jumps directly to the relevant entry when an exception is taken, executing instructions there rather than reading a pointer.
Setting up the vector table is one of the first steps in any bare metal ARMv8-A firmware or kernel port. The alignment requirement is strict, the VBAR must be installed before exceptions are enabled, and each handler entry must save and restore context correctly before executing ERET.
