AArch64 Exceptions
Exceptions are events that tell the processor something has happened that it needs to process. They are also called interrupts, because they interrupt whatever the processor was doing.
Differences from x86
On x86, there are 128 interrupt vectors that can be called by the hardware. Some of these are used by the processor itself to report errors, such as invalid instructions or unpermitted memory accesses. The rest are used by hardware peripherals, usually through a bus like PCI. On AArch64, there are only 4 exception vectors. Two of these are used for interacting with hardware, and the other two are used for reporting errors. On x86, the vector table (called the IDT) contains addresses for the processor to jump to. However, AArch64 has instructions directly in the exception table itself, and each vector is 128 bytes long, giving room for 32 instructions.
Exception Vectors
Each vector must be 128-byte aligned, and the table itself must be 2048-byte aligned.
| Name | Offset | Description |
|---|---|---|
| Synchronous | 0x000 | CPU exception occurred due to instruction |
| IRQ | 0x080 | Interrupt Request |
| FIQ | 0x100 | Fast Interrupt Request |
| SError | 0x180 | CPU exception unrelated to instructions |
VBAR and DAIF
On ARMv7 and before, the exception table could be placed at one of two places; either at 0x00000000, or 0xffff0000. AArch64 allows for much more precise control by introducing the VBAR (Vector Base Address Register). This register holds the location of the vector table, and each exception level (except EL0) has its own base.
The VBAR is accessed just like any other special register, with the MSR or MRS instructions.
// address to set to in x0
set_vbar_el1:
msr vbar_el1,x0
ret
In ARM speak, you disable exceptions by masking them so they don't trigger. This is done with the DAIF register. DAIF holds four flags that determine the masking of exceptions. It is effectively analogous to the Interrupt Flag on x86.
| Reserved | D | A | I | F | Reserved |
| 10-63 | 9 | 8 | 7 | 6 | 0-5 |
|---|
- D - Debug (Synchronous)
- A - SError
- I - IRQ
- F - FIQ
DAIF is also accessed in the same way VBAR is via the MSR and MRS instructions.
// flags in x0
set_daif:
msr daif,x0
ret
Setting DAIF to 0x000 will disable exception masking, while setting it to 0x3c0 will mask all exceptions, meaning none will occur.
Vector Table
The vector table contains 4 sets of 4 vectors that determine the code that runs when an exception occurs. Each set of 4 vectors is for a different mode the processor may be operating in when the exception occurs.
| Mode | Offset | Description |
|---|---|---|
| EL1t | 0x000 | EL1 with EL0 stack |
| EL1h | 0x200 | EL1 with own stack |
| EL0 64 | 0x400 | EL0 64-bit |
| EL0 32 | 0x600 | EL0 32-bit |
The last entry is provided for compatibility with 32-bit user-mode programs, but the handlers themselves are still 64-bit.
Syndromes
Because AArch64 only has 4 exception vectors, it gives you additional information on why the exception occurred in the form of the Exception Syndrome Register. Upon an exception, this register is populated with information like the exact cause (Exception Class), extra information, and instruction type.
| Reserved | ISS2 | EC | IL | ISS |
| 37-63 | 32-36 | 26-31 | 25 | 0-24 |
|---|
- ISS - Instruction Specific Syndrome, format specific to exception class
- IL - Instruction Length, if the instruction was 16-bit (0) or 32-bit (1)
- EC - Reason for the exception
- ISS2 - Register used in failed atomic store (FEAT_LS64 only)
ELR and SPSR
Once an exception has been processed, the processor needs to return back to where it was, and reset the PSTATE to what it was before the exception. The processor automatically stores this information in the Exception Link Register and Saved Program Status Register.
The ELR works in a very similar way to the link register, but it might not point to the next instruction, but rather run it again if it caused an exception or did not finish execution. The SPSR contains various PSTATE values, such as DAIF and ALU flags.
Returning from an exception to the ELR while setting PSTATE is automatically done by the ERET instruction. However, knowing this information is valuable to the kernel so it can be provided to debuggers.
Example
_start:
// setup vbar
ldr x0,=vectors
msr vbar_el1,x0
// enable exceptions
msr daif,xzr
// undefined instruction that will trigger a Synchronous exception
udf #0xdead
// macro that requires alignment and jumps to a label
.macro vector label
.p2align 7
b \label
.endm
// dummy exception handler that just halts forever
vinvalid:
b .
// vector table must be 2048-byte aligned
.p2align 11
vectors:
// EL1 keep stack
vector vinvalid
vector vinvalid
vector vinvalid
vector vinvalid
// EL1 own stack (kernel mode)
vector vinvalid
vector vinvalid
vector vinvalid
vector vinvalid
// EL0->EL1 (user mode)
vector vinvalid
vector vinvalid
vector vinvalid
vector vinvalid
// 32-bit EL0->EL1
vector vinvalid
vector vinvalid
vector vinvalid
vector vinvalid