Trap Handler Implementation
Relevant source files
This document covers the low-level assembly language trap handlers that manage guest virtual machine entry and exit. These handlers perform the critical register context switching between hypervisor and guest execution contexts, enabling secure isolation and efficient virtualization on RISC-V systems with H-extension support.
For higher-level trap processing and VM exit handling, see VM Exit Processing. For register data structure definitions, see Register Management.
Overview
The trap handler implementation consists of two primary assembly language routines that manage the transition between hypervisor and guest execution contexts:
- Guest Entry (
_run_guest) - Saves hypervisor state, loads guest state, and transfers control to the guest VM - Guest Exit (
_guest_exit) - Captures guest state upon VM exit and restores hypervisor execution context
These handlers work in conjunction with carefully designed data structures in src/regs.rs to ensure complete CPU state preservation during virtualization transitions.
Architecture and Data Flow
The trap handler operates through a precisely choreographed sequence of register and CSR manipulations:
flowchart TD
subgraph subGraph0["VmCpuRegisters Data Structure"]
HypRegs["hyp_regs: HypervisorCpuState"]
GuestRegs["guest_regs: GuestCpuState"]
VSCSRs["vs_csrs: GuestVsCsrs"]
TrapCSRs["trap_csrs: VmCpuTrapState"]
end
HypCall["Hypervisor calls _run_guest()"]
SaveHyp["Save Hypervisor State"]
LoadGuest["Load Guest State"]
SetTrap["Set trap vector to _guest_exit"]
SRET["Execute sret to enter guest"]
GuestExec["Guest Execution"]
TrapOccurs["Guest Trap/Exit Occurs"]
SaveGuest["Save Guest State"]
RestoreHyp["Restore Hypervisor State"]
Return["Return to Hypervisor"]
GuestExec --> TrapOccurs
HypCall --> SaveHyp
LoadGuest --> GuestRegs
LoadGuest --> SetTrap
RestoreHyp --> HypRegs
RestoreHyp --> Return
SRET --> GuestExec
SaveGuest --> GuestRegs
SaveGuest --> RestoreHyp
SaveHyp --> HypRegs
SaveHyp --> LoadGuest
SetTrap --> SRET
TrapOccurs --> SaveGuest
Sources: src/trap.S(L1 - L183) src/regs.rs(L116 - L196)
Register Context Data Structures
The trap handlers operate on the VmCpuRegisters structure, which contains separate state containers for hypervisor and guest contexts:
| Structure | Purpose | Key Fields |
|---|---|---|
| HypervisorCpuState | Hypervisor execution state | GPRs, sstatus, hstatus, scounteren, stvec, sscratch |
| GuestCpuState | Guest execution state | GPRs, sstatus, hstatus, scounteren, sepc |
| GeneralPurposeRegisters | 32 RISC-V registers | x0-x31 (zero, ra, sp, gp, tp, t0-t6, s0-s11, a0-a7) |
The assembly code accesses these structures through computed offsets provided by the Rust compilation system:
flowchart TD
subgraph subGraph2["Offset Calculation Functions"]
VmCpuRegs["VmCpuRegisters"]
HypState["hyp_regs"]
GuestState["guest_regs"]
HypGPRs["HypervisorCpuState.gprs"]
GuestGPRs["GuestCpuState.gprs"]
HypGPROffset["hyp_gpr_offset()"]
GuestGPROffset["guest_gpr_offset()"]
HypCSROffset["hyp_csr_offset!()"]
GuestCSROffset["guest_csr_offset!()"]
end
subgraph subGraph1["Assembly Constants"]
HypRA["hyp_ra offset"]
HypSP["hyp_sp offset"]
GuestRA["guest_ra offset"]
GuestSP["guest_sp offset"]
HypSSTATUS["hyp_sstatus offset"]
GuestSSTATUS["guest_sstatus offset"]
end
subgraph subGraph0["Rust Data Structure"]
VmCpuRegs["VmCpuRegisters"]
HypState["hyp_regs"]
GuestState["guest_regs"]
HypGPRs["HypervisorCpuState.gprs"]
GuestGPRs["GuestCpuState.gprs"]
HypGPROffset["hyp_gpr_offset()"]
GuestGPROffset["guest_gpr_offset()"]
end
GuestCSROffset --> GuestSSTATUS
GuestGPROffset --> GuestRA
GuestGPROffset --> GuestSP
GuestState --> GuestGPRs
HypCSROffset --> HypSSTATUS
HypGPROffset --> HypRA
HypGPROffset --> HypSP
HypState --> HypGPRs
VmCpuRegs --> GuestState
VmCpuRegs --> HypState
Sources: src/trap.rs(L7 - L33) src/regs.rs(L1 - L114) src/regs.rs(L116 - L138)
Guest Entry Sequence (_run_guest)
The _run_guest function implements the critical transition from hypervisor to guest execution:
Hypervisor State Preservation
The handler first saves the hypervisor's execution context, excluding temporary registers and the a0 register which contains the VmCpuRegisters pointer:
- General-purpose registers (ra, gp, tp, s0-s11, a1-a7, sp)
- Critical CSRs (sstatus, hstatus, scounteren, stvec, sscratch)
Guest State Loading
The handler then configures the CPU for guest execution:
- Swaps hypervisor CSRs with guest CSRs using
csrrwinstructions - Loads guest program counter into
sepc - Sets trap vector (
stvec) to_guest_exitfor handling guest traps - Stores
VmCpuRegisterspointer insscratchfor quick access during exit - Restores all guest general-purpose registers
Context Switch Completion
The sequence concludes with an sret instruction that:
- Switches to guest privilege level
- Jumps to the guest program counter (
sepc) - Enables guest execution with appropriate privilege and virtualization settings
Sources: src/trap.S(L3 - L91) src/trap.rs(L35 - L102)
Guest Exit Sequence (_guest_exit)
When the guest encounters a trap, interrupt, or exception, control transfers to _guest_exit:
Guest State Capture
The exit handler immediately preserves the guest's execution state:
- Retrieves
VmCpuRegisterspointer fromsscratch - Saves all guest general-purpose registers to the guest state structure
- Captures guest's
a0register value fromsscratch - Records guest program counter from
sepc
Hypervisor State Restoration
The handler restores the hypervisor execution environment:
- Swaps guest CSRs back to hypervisor CSRs using
csrrw - Restores hypervisor trap vector (
stvec) - Restores hypervisor scratch register (
sscratch) - Reloads all hypervisor general-purpose registers
Return to Hypervisor
The handler concludes with a ret instruction, returning control to the hypervisor code that called _run_guest.
sequenceDiagram
participant Hypervisor as Hypervisor
participant Guest as Guest
participant _run_guest as _run_guest
participant _guest_exit as _guest_exit
Hypervisor ->> _run_guest: Call with VmCpuRegisters*
_run_guest ->> _run_guest: Save hypervisor GPRs
_run_guest ->> _run_guest: Save hypervisor CSRs
_run_guest ->> _run_guest: Load guest CSRs
_run_guest ->> _run_guest: Set stvec to _guest_exit
_run_guest ->> _run_guest: Load guest GPRs
_run_guest ->> Guest: sret (enter guest)
Guest ->> Guest: Execute guest code
Guest ->> _guest_exit: Trap/Exception occurs
_guest_exit ->> _guest_exit: Save guest GPRs
_guest_exit ->> _guest_exit: Save guest CSRs
_guest_exit ->> _guest_exit: Restore hypervisor CSRs
_guest_exit ->> _guest_exit: Restore hypervisor GPRs
_guest_exit ->> Hypervisor: ret (return to hypervisor)
Sources: src/trap.S(L92 - L183)
Assembly-Rust Interface
The integration between assembly and Rust code relies on computed memory offsets and the global_asm! macro:
Offset Calculation
The Rust code provides compile-time offset calculations for accessing structure fields:
hyp_gpr_offset()computes offsets into hypervisor register arraysguest_gpr_offset()computes offsets into guest register arrayshyp_csr_offset!()andguest_csr_offset!()macros compute CSR field offsets
Assembly Integration
The core::arch::global_asm! macro embeds the assembly code with substituted constants:
core::arch::global_asm!(
include_str!("trap.S"),
hyp_ra = const hyp_gpr_offset(GprIndex::RA),
guest_ra = const guest_gpr_offset(GprIndex::RA),
hyp_sstatus = const hyp_csr_offset!(sstatus),
// ... additional offset constants
);
This approach ensures type safety and automatic offset calculation while maintaining the performance benefits of hand-optimized assembly code.
Sources: src/trap.rs(L1 - L102) src/regs.rs(L5 - L86)
Special Register Handling
sscratch Register Usage
The sscratch CSR serves a dual purpose in the trap handling mechanism:
- During guest execution: Contains the
VmCpuRegisterspointer for fast access - During hypervisor execution: Contains the hypervisor's original
sscratchvalue
Register A0 Special Case
The a0 register receives special handling because it carries the VmCpuRegisters pointer:
- Not saved during initial hypervisor state preservation
- Swapped with guest
a0viasscratchduring guest entry - Guest's
a0value recovered fromsscratchduring exit
Zero Register Enforcement
The GeneralPurposeRegisters::set_reg() method enforces RISC-V architectural requirements by preventing writes to the zero register (x0), ensuring compliance with the RISC-V specification.
Sources: src/trap.S(L53 - L55) src/trap.S(L94 - L95) src/trap.S(L129 - L131) src/regs.rs(L96 - L102)