A standalone hypervisor application running on ArceOS, demonstrating hardware-assisted virtualization by booting a minimal guest OS (skernel) in guest mode. Supports RISC-V H-extension, ARM AArch64 EL2, and AMD SVM (x86_64) virtualization. All kernel dependencies are sourced from crates.io. The guest performs a single shutdown call (SBI ecall on RISC-V / PSCI HVC on AArch64 / VMMCALL on x86_64), which the hypervisor intercepts and handles, demonstrating the complete VM lifecycle: guest context setup, second-stage page table configuration, guest entry, VM exit handling, and shutdown call parsing.
This application builds a minimal Type-1 hypervisor on ArceOS that:
- Creates a guest address space (
main.rs): Allocates a new address space for the virtual machine using ArceOS's memory management (axmm). - Loads guest image (
loader.rs): Reads the guest binary (/sbin/skernel) from a FAT32 virtual disk and maps it at the guest's entry point. - Prepares guest context (
main.rs): Configures architecture-specific hypervisor registers and the guest's initial CPU state (CSRs on RISC-V, system registers on AArch64, VMCB on x86_64). - Configures second-stage page table (
main.rs): Sets up stage-2 / nested address translation so the guest's physical addresses map to real host memory. - Enters guest mode: Saves hypervisor state, loads guest registers, and transitions to the guest (
sreton RISC-V,ereton AArch64,vmrunon x86_64). - Handles VM exit (
main.rs): On guest trap/exception, parses the shutdown request and terminates the VM gracefully.
| RISC-V 64 (H-extension) | AArch64 (EL2) | x86_64 (AMD SVM) | |
|---|---|---|---|
| Guest entry | sret with hstatus.SPV=Guest |
eret from EL2 to EL1 |
vmrun with guest VMCB |
| Stage-2 PT register | hgatp (Sv39x4) |
VTTBR_EL2 |
nCR3 (NPT root in VMCB) |
| VM exit mechanism | scause = VirtualSupervisorEnvCall (code 10) |
ESR_EL2 EC=0x16 (HVC64) |
VMCB EXITCODE = 0x81 (VMMCALL) |
| Guest shutdown call | SBI legacy shutdown (a7=8, ecall) |
PSCI SYSTEM_OFF (x0=0x84000008, hvc #0) |
VMMCALL (eax=0x84000008) |
| Guest entry point | 0x8020_0000 |
0x4020_0000 |
0x10000 (real mode) |
| Guest CPU mode | S-mode (64-bit) | EL1 (AArch64) | 16-bit real mode |
skernel is now a binary target of the main package, not a standalone crate.
The guest (skernel) is a minimal bare-metal program that immediately performs a shutdown:
RISC-V:
li a7, 8 // SBI legacy shutdown
ecallAArch64:
movz x0, #0x0008
movk x0, #0x8400, lsl #16 // PSCI SYSTEM_OFF = 0x84000008
svc #0x86_64 (16-bit real mode):
.code16
mov eax, 0x84000008 // function ID = PSCI-style SYSTEM_OFF
vmmcall // AMD SVM hypercall → VMEXIT to hypervisorNote (x86_64): The guest payload is assembled as 16-bit real-mode code using
.code16. It usesglobal_asm!instead of a Rust function to avoid the compiler inserting a function prologue (push rax), which would cause a stack access fault in a real-mode guest with no mapped stack.
| Architecture | Rust Target | QEMU Machine | Platform | CPU |
|---|---|---|---|---|
| riscv64 | riscv64gc-unknown-none-elf |
qemu-system-riscv64 -machine virt |
riscv64-qemu-virt | default |
| aarch64 | aarch64-unknown-none-softfloat |
qemu-system-aarch64 -machine virt,virtualization=on |
aarch64-qemu-virt | max |
| x86_64 | x86_64-unknown-none |
qemu-system-x86_64 -machine q35 |
x86-pc | EPYC |
Note (AArch64): QEMU must be started with
-machine virt,virtualization=onand-cpu maxto enable EL2 support. ArceOS must be configured to stay at EL2 (with VHE — Virtualization Host Extensions) rather than dropping to EL1 during boot.
Note (x86_64): QEMU must use
-cpu EPYC(or another AMD CPU model) to enable AMD SVM extensions. Intel VMX is not supported by this implementation.
rustup install nightly
rustup default nightlyrustup target add riscv64gc-unknown-none-elf
rustup target add aarch64-unknown-none-softfloat
rustup target add x86_64-unknown-nonecargo install cargo-binutils
rustup component add llvm-tools# Ubuntu 24.04
sudo apt update
sudo apt install qemu-system-misc qemu-system-arm qemu-system-x86
# macOS (Homebrew)
brew install qemuNote: On Ubuntu,
qemu-system-aarch64is provided byqemu-system-arm.qemu-system-riscv64is provided byqemu-system-misc.qemu-system-x86_64is provided byqemu-system-x86.
# All-in-one install
sudo apt update
sudo apt install -y build-essential qemu-system-misc qemu-system-arm qemu-system-x86
# Rust toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup install nightly && rustup default nightly
rustup target add riscv64gc-unknown-none-elf aarch64-unknown-none-softfloat x86_64-unknown-none
cargo install cargo-binutils
rustup component add llvm-tools# Install cargo-clone sub-command
cargo install cargo-clone
# Get source code of arceos-guestmode crate from crates.io
cargo clone arceos-guestmode
# Enter crate directory
cd arceos-guestmode
# Build and run on RISC-V 64 QEMU (default)
cargo xtask run
# Build and run on AArch64 QEMU
cargo xtask run --arch aarch64
# Build and run on x86_64 QEMU (AMD SVM)
cargo xtask run --arch x86_64
# Build only (no QEMU)
cargo xtask build --arch riscv64
cargo xtask build --arch aarch64
cargo xtask build --arch x86_64The xtask command automates the full workflow:
- Install config — copies
configs/<arch>.toml→.axconfig.toml - Build payload — compiles
payload/skernel(a minimalno_stdRust guest) for the target architecture, converts to flat binary viarust-objcopy - Create disk image — builds a 64 MB FAT32 image containing
/sbin/skernel - Build kernel —
AX_CONFIG_PATH=.axconfig.toml cargo build --release --target <target> --features axstd - Objcopy — converts hypervisor ELF to raw binary (riscv64 and aarch64 only; x86_64 boots ELF directly)
- Run QEMU — launches QEMU with VirtIO block device attached
Hypervisor ...
app: /sbin/skernel
paddr: PA:0x80633000
VmExit Reason: VSuperEcall: Some(Reset(Reset { reset_type: Shutdown, reason: NoReason }))
Shutdown vm normally!
Hypervisor ...
app: /sbin/skernel
paddr: PA:0x404f4000
VmExit Reason: GuestSVC: Some(PsciSystemOff)
Shutdown vm normally!
Hypervisor ok!
Hypervisor ...
app: /sbin/skernel
paddr: PA:0x44e000
paddr: PA:0x415000
VmExit Reason: VMMCALL
Shutdown vm normally!
Hypervisor ok!
QEMU will automatically exit after the hypervisor prints the final message.
app-guestmode/
├── xtask/
│ └── src/
│ └── main.rs # Build/run tool: payload, disk image, QEMU
├── configs/
│ ├── riscv64.toml # RISC-V platform config
│ ├── aarch64.toml # AArch64 platform config
│ └── x86_64.toml # x86_64 platform config
├── payload/
│ └── skernel/ # Minimal guest OS (no_std Rust)
│ └── src/
│ └── main.rs # SBI/PSCI/VMMCALL shutdown (multi-arch)
├── src/
│ ├── main.rs # Entry: arch dispatch, load guest, run VM
│ ├── loader.rs # Guest binary loader (FAT32 → address space)
│ │
│ │ ── RISC-V 64 specific ──
│ ├── vcpu.rs # vCPU state & guest.S inclusion
│ ├── guest.S # Save/restore hypervisor↔guest, sret
│ ├── regs.rs # GPR file abstraction
│ ├── csrs.rs # H-extension CSR definitions
│ └── sbi/ # SBI message parsing
│ ├── mod.rs
│ ├── base.rs, srst.rs, rfnc.rs, pmu.rs, dbcn.rs
│ │
│ │ ── AArch64 specific ──
│ ├── aarch64/
│ │ ├── mod.rs # Module declarations
│ │ ├── vcpu.rs # vCPU state & guest.S inclusion
│ │ ├── guest.S # Exception vector table, eret/exit
│ │ ├── regs.rs # GPR file (x0-x30)
│ │ └── hvc.rs # HVC/PSCI message parsing
│ │
│ │ ── x86_64 (AMD SVM) specific ──
│ └── x86_64/
│ ├── mod.rs # Module declarations
│ ├── vmcb.rs # VMCB structure, offsets, accessors
│ └── svm.rs # CPUID/MSR helpers, VMRUN wrapper (global_asm)
│
├── build.rs # Linker script path (auto-detects arch)
├── Cargo.toml # Dependencies from crates.io
├── rust-toolchain.toml # Nightly toolchain & targets
└── README.md
| Component | Role |
|---|---|
axstd |
ArceOS standard library (replaces Rust's std in no_std environment) |
axhal |
Hardware Abstraction Layer — paging, trap handling, uspace support |
axmm |
Memory management — guest address spaces, page mapping with SharedPages backend |
axtask |
Task scheduler — CFS scheduling, context switching |
axfs / axfeat |
Filesystem — FAT32 virtual disk access for loading the guest binary |
axio |
I/O traits for file operations |
axsync |
Synchronization primitives |
axlog |
Kernel logging (ax_println!) |
riscv |
RISC-V CSR access — sstatus, scause (riscv64 only) |
tock-registers |
Type-safe register bitfield definitions for H-extension CSRs (riscv64 only) |
sbi-spec |
SBI extension ID constants (riscv64 only) |
memoffset |
Struct field offset calculation for assembly register save/restore |
The RISC-V Hypervisor extension provides:
- VS-mode (Virtual Supervisor): A virtualized supervisor mode for the guest OS
- Two-stage address translation:
hgatpcontrols guest-physical → host-physical translation - Trap delegation: Guest exceptions/interrupts can be delegated or handled by the hypervisor
- CSRs:
hstatus,hgatp,hedeleg,hideleg,hvip, etc.
Guest Entry/Exit Flow (RISC-V):
- Entry (
guest.S): Save hypervisor GPRs → swap CSRs → setstvecto exit handler → load guest GPRs →sret - Exit (
guest.S): Save guest GPRs → swap back CSRs → restore hypervisor GPRs →ret - Handler (
main.rs): Readscause→ if VirtualSupervisorEnvCall (code 10), parse SBI message → handle shutdown
The AArch64 hypervisor uses ARM's EL2 (Exception Level 2):
- EL2: The hypervisor exception level, runs the ArceOS hypervisor
- EL1: The guest exception level, runs the minimal skernel guest
- Stage-2 translation:
VTTBR_EL2+VTCR_EL2control IPA → PA mapping - HCR_EL2: Hypervisor Configuration Register — enables VM mode (
VM=1) and AArch64 EL1 (RW=1) - Exception vectors: Custom
VBAR_EL2vector table catches guest HVC traps
Guest Entry/Exit Flow (AArch64):
- Entry (
guest.S): Save host x19-x30/SP → setVBAR_EL2to exit vectors → load guest x0-x30/ELR/SPSR →eretto EL1 - Exit (
guest.S): Save guest x0-x30/ELR/SPSR → readESR_EL2/FAR_EL2/HPFAR_EL2→ restoreVBAR_EL2→ restore host state →ret - Handler (
main.rs): CheckESR_EL2EC field → if HVC64 (EC=0x16), parse PSCI function ID from x0 → handle SYSTEM_OFF
AMD SVM (Secure Virtual Machine) is AMD's hardware-assisted virtualization extension for x86 processors:
- VMCB (Virtual Machine Control Block): A 4 KB page-aligned structure containing the guest state (Save Area) and hypervisor intercept configuration (Control Area). The CPU reads/writes the VMCB on
VMRUN/VMEXIT. - VMRUN: Instruction that enters the guest context described by the VMCB. Takes the VMCB physical address in
RAX. - VMEXIT: Occurs when the guest executes an intercepted instruction (e.g.,
VMMCALL). Exit reason and info are stored in the VMCB Control Area. - VMMCALL: Guest hypercall instruction — triggers a
VMEXITwith exit code0x81, allowing the guest to request services from the hypervisor. - NPT (Nested Page Tables): Second-stage address translation in SVM. Enabled by setting
NP_ENABLE=1in the VMCB and the nested page table root (nCR3). - VMSAVE/VMLOAD: Instructions to save/restore host FS, GS, TR, LDTR, and related MSRs. Required because
VMRUN/VMEXITdo not automatically save/restore these registers. - IOPM/MSRPM: I/O Permission Map (12 KB) and MSR Permission Map (8 KB) — bitmaps that control which I/O port and MSR accesses by the guest trigger a VMEXIT.
Guest Entry/Exit Flow (x86_64 SVM):
- Setup: Check SVM support via
CPUID→ enableEFER.SVME→ allocate host save area (MSR_VM_HSAVE_PA) → allocate IOPM, MSRPM, and host VMCB pages → configure guest VMCB (intercepts, NPT, real-mode segments) - Entry (
svm.rs): Push callee-saved GPRs →cli→vmsavehost FS/GS/TR/LDTR →vmrunwith guest VMCB PA in RAX - Exit (
svm.rs): On VMEXIT, hardware restores host RSP/RIP/RAX →vmloadhost FS/GS/TR/LDTR →sti→ pop callee-saved GPRs →ret - Handler (
main.rs): ReadEXITCODEfrom VMCB → if VMMCALL (0x81), read guest RAX → match0x84000008for shutdown → ACPI power-off QEMU
Key VMCB Layout:
| Region | Offset Range | Contents |
|---|---|---|
| Control Area | 0x000–0x3FF |
Intercept masks, IOPM/MSRPM base, ASID, NPT config, exit code/info |
| Save Area | 0x400–0xFFF |
Guest segment registers, CR0-4, EFER, DR6/7, RFLAGS, RIP, RSP, RAX |
Real-Mode Guest:
The x86_64 guest runs in 16-bit real mode to simplify setup — no GDT, IDT, or paging configuration is needed in the guest. The VMCB's CS segment base is set to the guest entry point (0x10000), so RIP=0 begins execution at guest physical address 0x10000. NPT handles address translation from guest physical to host physical addresses.
This crate is derived from the ArceOS hypervisor tutorial in the ArceOS ecosystem, adapted to work as a standalone crate with all dependencies from crates.io and extended to support multiple architectures.
This crate is part of a series of tutorial crates for learning OS development with ArceOS. The crates are organized by functionality and complexity progression:
| # | Crate Name | Description |
|---|---|---|
| 1 | arceos-helloworld | Minimal ArceOS unikernel application that prints Hello World, demonstrating the basic boot flow |
| 2 | arceos-collections | Dynamic memory allocation on a unikernel, demonstrating the use of String, Vec, and other collection types |
| 3 | arceos-readpflash | MMIO device access via page table remapping, reading data from QEMU's PFlash device |
| 4 | arceos-childtask | Multi-tasking basics: spawning a child task (thread) that accesses a PFlash MMIO device |
| 5 | arceos-msgqueue | Cooperative multi-task scheduling with a producer-consumer message queue, demonstrating inter-task communication |
| 6 | arceos-fairsched | Preemptive CFS scheduling with timer-interrupt-driven task switching, demonstrating automatic task preemption |
| 7 | arceos-readblk | VirtIO block device driver discovery and disk I/O, demonstrating device probing and block read operations |
| 8 | arceos-loadapp | FAT filesystem initialization and file I/O, demonstrating the full I/O stack from VirtIO block device to filesystem |
| 9 | arceos-userprivilege | User-privilege mode switching: loading a user-space program, switching to unprivileged mode, and handling syscalls |
| 10 | arceos-lazymapping | Lazy page mapping (demand paging): user-space program triggers page faults, and the kernel maps physical pages on demand |
| 11 | arceos-runlinuxapp | Loading and running real Linux ELF applications (musl libc) on ArceOS, with ELF parsing and Linux syscall handling |
| 12 | arceos-guestmode (this crate) | Minimal hypervisor: creating a guest address space, entering guest mode, and handling a single VM exit (shutdown) |
| 13 | arceos-guestaspace | Hypervisor address space management: loop-based VM exit handling with nested page fault (NPF) on-demand mapping |
| 14 | arceos-guestvdev | Hypervisor virtual device support: timer virtualization, console I/O forwarding, and NPF passthrough; guest runs preemptive multi-tasking |
| 15 | arceos-guestmonolithickernel | Full hypervisor + guest monolithic kernel: the guest kernel supports user-space process management, syscall handling, and preemptive scheduling |
Progression Logic:
- #1–#8 (Unikernel Stage): Starting from the simplest output, these crates progressively introduce memory allocation, device access (MMIO / VirtIO), multi-task scheduling (both cooperative and preemptive), and filesystem support, building up the core capabilities of a unikernel.
- #8–#10 (Monolithic Kernel Stage): Building on the unikernel foundation, these crates add user/kernel privilege separation, page fault handling, and ELF loading, progressively evolving toward a monolithic kernel.
- #11–#14 (Hypervisor Stage): Starting from minimal VM lifecycle management, these crates progressively add address space management, virtual devices, timer injection, and ultimately run a full monolithic kernel inside a virtual machine.
GPL-3.0-or-later OR Apache-2.0 OR MulanPSL-2.0