Custom Exception Handlers in UEFI Firmware
Implementing custom exception handlers in UEFI firmware allows developers to intercept processor-level faults—such as page faults, invalid opcodes, or division errors—prior to standard platform termination. This article outlines the architectural mechanics of handling processor exceptions in UEFI, detailing how developers locate the relevant architectural protocol, register interrupt callbacks using the processor context, parse low-level CPU state, and either recover execution flow or log critical crash telemetry for hardware debugging.
Core Protocol: EFI_CPU_ARCH_PROTOCOL
In the UEFI environment, low-level CPU control is abstracted through
the EFI_CPU_ARCH_PROTOCOL. This protocol exposes
architectural services, including interrupt and exception vector
management, cache control, and processor timer configuration.
To manage exceptions, developers rely on the
RegisterInterruptHandler service exposed by this
protocol:
typedef
EFI_STATUS
(EFIAPI *EFI_CPU_REGISTER_INTERRUPT_HANDLER)(
IN EFI_CPU_ARCH_PROTOCOL *This,
IN EFI_EXCEPTION_TYPE InterruptType,
IN EFI_CPU_INTERRUPT_HANDLER InterruptHandler
);Step-by-Step Implementation
1. Locate the CPU Architectural Protocol
The driver must first query the UEFI Boot Services table to locate an
instance of EFI_CPU_ARCH_PROTOCOL. This protocol is
typically produced by a CPU DXE driver early in the DXE phase.
EFI_STATUS Status;
EFI_CPU_ARCH_PROTOCOL *CpuArch = NULL;
Status = gBS->LocateProtocol(
&gEfiCpuArchProtocolGuid,
NULL,
(VOID **)&CpuArch
);
if (EFI_ERROR(Status)) {
// Protocol not found; handle error
return Status;
}2. Implement the Exception Handler Callback
The exception handler must match the
EFI_CPU_INTERRUPT_HANDLER function signature. The callback
receives the interrupt vector type and a pointer to
EFI_SYSTEM_CONTEXT.
The EFI_SYSTEM_CONTEXT union provides direct access to
the architectural registers at the precise moment the exception
triggered. For x86_64 systems, this resolves to
EFI_SYSTEM_CONTEXT_X64, containing register states such as
RIP, RSP, RFLAGS, and control
registers like CR2 (useful for page fault address
inspection).
VOID
EFIAPI
CustomExceptionHandler (
IN EFI_EXCEPTION_TYPE InterruptType,
IN EFI_SYSTEM_CONTEXT SystemContext
)
{
// Example for x86_64 architecture
EFI_SYSTEM_CONTEXT_X64 *Context = SystemContext.SystemContextX64;
// Read offending instruction pointer
UINT64 FaultingIp = Context->Rip;
// Handle or inspect state based on the exception vector
if (InterruptType == EXCEPT_IA32_PAGE_FAULT) {
UINT64 FaultingAddress = Context->Cr2;
// Process address or record diagnostic information
}
// Returning from this function attempts to execute at Context->Rip.
// Modifying Context->Rip advances execution past the faulting instruction.
}3. Register the Handler for Specific Vectors
Once the callback is defined, it must be bound to specific exception
types. Common exception definitions are provided in the
UefiCpuPkg header files (e.g.,
EXCEPT_IA32_DIVIDE_ERROR,
EXCEPT_IA32_PAGE_FAULT).
Status = CpuArch->RegisterInterruptHandler(
CpuArch,
EXCEPT_IA32_PAGE_FAULT,
CustomExceptionHandler
);To remove the custom handler and restore default firmware behavior,
call RegisterInterruptHandler with NULL as the
InterruptHandler argument.
Execution Control and Recovery
When an exception occurs, the underlying Interrupt Descriptor Table
(IDT) redirects execution to a common dispatcher, which saves the
register state and invokes the registered
EFI_CPU_INTERRUPT_HANDLER.
From the handler, developers have two primary courses of action:
- Recovery and Continuation: The developer can
resolve the fault condition, alter registers inside
SystemContext(such as incrementingContext->Ripto skip an offending instruction, or remapping a missing page), and return. The CPU dispatcher then restores the modified register state and issues anIRETinstruction to resume execution. - Diagnostic Termination: If the fault is
unrecoverable, the handler can serialize the complete register dump to
non-volatile storage, a serial port, or a hardware debug port before
halting the processor via a
CpuDeadLoop()or initiating a controlled reset using the Reset System runtime service.
Critical Constraints
- Boot Services Reentrancy: Exception handlers run in
interrupt context. Invoking complex UEFI Boot Services (such as dynamic
memory allocation via
AllocatePoolor file system access) inside the handler can cause deadlocks if the exception occurred while a boot service lock was held. - Stack Integrity: Stack overflow exceptions
(
EXCEPT_IA32_DOUBLE_FAULT) cannot be reliably processed using the normal stack. Advanced implementations must utilize Task State Segments (TSS) with an Interrupt Stack Table (IST) entry to provide a dedicated scratch stack for catastrophic failure handlers.