Initial aarch64/KVM support#1474
Conversation
There was a problem hiding this comment.
Pull request overview
This PR introduces initial aarch64 support backed by KVM, extending Hyperlight’s VM, guest runtime, and memory layout to run the existing sandbox model on arm64 (including MMIO-based “outb” equivalents, paging, and exception handling).
Changes:
- Add an aarch64 KVM
VirtualMachineimplementation, including vCPU reset support and MMIO-exit handling via an IO page. - Implement aarch64 guest runtime pieces (paging ops, exception vectors/handler, stack init, guest exits) and adjust test guests for aarch64.
- Refactor layout constants (
MAX_GPA/MAX_GVA→SCRATCH_TOP_*) and map the IO page into snapshots where applicable; update build tooling (Justfile/Nix/libc build lists) for aarch64 targets.
Reviewed changes
Copilot reviewed 49 out of 49 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tests/rust_guests/simpleguest/src/main.rs | Make the Rust test guest work on aarch64 (undef instruction, executable buffers, IO-page “outb” substitute, icache sync). |
| src/tests/rust_guests/dummyguest/src/main.rs | Add aarch64 halt/MMIO paths and switch entrypoint ABI. |
| src/hyperlight_libc/build.rs | Add aarch64 include paths and file lists to the picolibc build. |
| src/hyperlight_libc/build_files.rs | Introduce aarch64 libc/libm file lists. |
| src/hyperlight_host/tests/integration_test.rs | Adjust assertions and gate x86_64-only tests for architecture differences. |
| src/hyperlight_host/src/sandbox/uninitialized_evolve.rs | Update VM initialization call signature (page size argument removed). |
| src/hyperlight_host/src/sandbox/snapshot/mod.rs | Map IO page into guest page tables and switch to SCRATCH_TOP_* bounds. |
| src/hyperlight_host/src/sandbox/initialized_multi_use.rs | Adapt tests/fixtures for aarch64 instruction encodings and exception strings. |
| src/hyperlight_host/src/mem/mgr.rs | Use SCRATCH_TOP_GVA for crashdump region sizing. |
| src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs | Add ResetVcpuError and vCPU reset capability hooks to the VM trait. |
| src/hyperlight_host/src/hypervisor/virtual_machine/kvm/x86_64.rs | Switch CPUID max-phys-address computation to SCRATCH_TOP_GPA. |
| src/hyperlight_host/src/hypervisor/virtual_machine/kvm/aarch64.rs | Implement aarch64 KVM backend: map/unmap, run loop, MMIO → VmExit translation, regs/fpu/sregs, vCPU reset. |
| src/hyperlight_host/src/hypervisor/regs/aarch64/special_regs.rs | Define aarch64 special register structure and default register programming values. |
| src/hyperlight_host/src/hypervisor/regs/aarch64/mod.rs | Wire up real aarch64 common regs/fpu/special-reg modules and KVM reg IDs. |
| src/hyperlight_host/src/hypervisor/regs/aarch64/kvm_reg.rs | Define KVM register ID constants and typed get/set helpers for aarch64. |
| src/hyperlight_host/src/hypervisor/regs/aarch64/fpu.rs | Adds an (apparently redundant) CommonFpu definition file. |
| src/hyperlight_host/src/hypervisor/regs/aarch64/common_regs.rs | Define CommonRegisters for aarch64 (x0-x30, sp, pc, pstate). |
| src/hyperlight_host/src/hypervisor/regs/aarch64/common_fpu.rs | Define CommonFpu for aarch64 (v0-v31, fpsr/fpcr). |
| src/hyperlight_host/src/hypervisor/mod.rs | Update internal test setup to match new initialization signature and layout constants. |
| src/hyperlight_host/src/hypervisor/hyperlight_vm/x86_64.rs | Remove page_size parameter from initialise path and store page size in the VM struct. |
| src/hyperlight_host/src/hypervisor/hyperlight_vm/mod.rs | Plumb ResetVcpuError and add vm_can_reset_vcpu tracking in HyperlightVm. |
| src/hyperlight_host/src/hypervisor/hyperlight_vm/aarch64.rs | Implement aarch64 HyperlightVm lifecycle: create VM, init regs, dispatch calls, reset vCPU, root PT access. |
| src/hyperlight_guest/src/layout.rs | Switch scratch metadata pointers from MAX_GVA to SCRATCH_TOP_GVA. |
| src/hyperlight_guest/src/arch/amd64/prim_alloc.rs | Switch allocator ceiling to SCRATCH_TOP_GPA. |
| src/hyperlight_guest/src/arch/aarch64/prim_alloc.rs | Implement aarch64 physical page allocator using atomic add and scratch bounds checks. |
| src/hyperlight_guest/src/arch/aarch64/layout.rs | Implement scratch size/base getters and move main stack to lower half for aarch64. |
| src/hyperlight_guest/src/arch/aarch64/exit.rs | Implement aarch64 “out32” via IO-page MMIO writes. |
| src/hyperlight_guest_bin/src/paging.rs | Split paging implementation into per-arch modules and re-export shared API. |
| src/hyperlight_guest_bin/src/lib.rs | Make paging available on all arches; add shared init module. |
| src/hyperlight_guest_bin/src/init.rs | Add shared stack init logic used by multiple architectures. |
| src/hyperlight_guest_bin/src/arch/amd64/paging.rs | Move existing amd64 paging implementation under arch-specific module. |
| src/hyperlight_guest_bin/src/arch/amd64/init.rs | Use shared stack init and update scratch-top constant usage. |
| src/hyperlight_guest_bin/src/arch/aarch64/paging.rs | Add aarch64 paging implementation (volatile PTE ops, barriers, TTBR0 root). |
| src/hyperlight_guest_bin/src/arch/aarch64/mod.rs | Implement aarch64 entrypoint, dispatch stub (with optional TLBI), VBAR init, and stack pivoting. |
| src/hyperlight_guest_bin/src/arch/aarch64/exception/types.rs | Define saved exception context and exception origin/type enums. |
| src/hyperlight_guest_bin/src/arch/aarch64/exception/mod.rs | Add aarch64 exception module plumbing. |
| src/hyperlight_guest_bin/src/arch/aarch64/exception/handle.rs | Implement aarch64 exception decoding + handling for stack growth and CoW faults; emit abort diagnostics. |
| src/hyperlight_guest_bin/src/arch/aarch64/exception/entry.rs | Provide AArch64 vector table and context save/restore in global asm. |
| src/hyperlight_common/src/vmem.rs | Refactor shared UpdateParent* infrastructure and export aarch64 MAIR attribute index. |
| src/hyperlight_common/src/layout.rs | Replace MAX_GPA/MAX_GVA exports with SCRATCH_TOP_* and add io_page. |
| src/hyperlight_common/src/arch/i686/layout.rs | Rename max layout constants to scratch-top equivalents. |
| src/hyperlight_common/src/arch/amd64/vmem.rs | Move UpdateParentTable/Root definitions into shared vmem module and tighten visibility. |
| src/hyperlight_common/src/arch/amd64/layout.rs | Rename scratch-top constants and add io_page() stub returning None. |
| src/hyperlight_common/src/arch/aarch64/vmem.rs | Implement aarch64 page table encode/decode and virt→phys walking logic. |
| src/hyperlight_common/src/arch/aarch64/layout.rs | Define aarch64 scratch and IO page locations and scratch sizing logic. |
| Justfile | Add arch-selectable guest build outputs via HYPERLIGHT_TARGET. |
| flake.nix | Expand supported platforms (aarch64-linux) and adjust cargo-hyperlight source pinning/build. |
| docs/paging-development-notes.md | Document aarch64 addressing assumptions (48-bit, lower-half/TTBR0). |
| c.just | Make C guest compilation/linking target-arch aware. |
| mem_mgr, | ||
| host_funcs, | ||
| #[cfg(gdb)] | ||
| mem_access_fn, |
| decl_core_reg!(V30, 0xcc, U128); | ||
| decl_core_reg!(V31, 0xd0, U128); | ||
| decl_core_reg!(FPSR, 0xd4, U32); | ||
| decl_core_reg!(FPCR, 0xd4, U32); | ||
|
|
| #[derive(Debug, Default, Copy, Clone, PartialEq1)] | ||
| pub(crate) struct CommonFpu { | ||
| pub(crate) v: [u128; 32], | ||
| pub(crate) fpsr: u32, | ||
| pub(crate) fpcr: u32, | ||
| } |
| fn mmio_read() { | ||
| unsafe { | ||
| #[cfg(target_arch = "x86_64")] | ||
| asm!("mov al, [0x8000]"); | ||
|
|
||
| let mut out: u8; | ||
| #[cfg(target_arch = "aarch64")] | ||
| asm!("ldr {0:x}, [{1}]", out(reg) out, in(reg) 0x8000); | ||
| } |
| pub(crate) unsafe fn out32(port: u16, val: u32) { | ||
| if port as usize > (hyperlight_common::vmem::PAGE_SIZE / core::mem::size_of::<u64>()) { | ||
| panic!("aarch64 mmio: unsupported hypercall number {}", port); | ||
| } | ||
| unsafe { | ||
| (IO_PAGE_GVA as *mut u64) | ||
| .wrapping_add(port as usize) | ||
| .write_volatile(val as u64); |
| if vm_fd.check_extension_raw(KVM_CAP_ARM_NISV_TO_USER as u64) != 0 { | ||
| // Available since Linux 5.5. Needed for the workaround | ||
| // described below for KVM mis-behaviour when a cache | ||
| // maintenance operation is applied to a VA that is paged | ||
| // out at Stage 2. | ||
| // | ||
| // When this cap is not available, there is a (small) | ||
| // chance that self-modifying code inside the VM will | ||
| // cause [`run_vcpu`] to fail, ultimately poisoning the | ||
| // sandbox. With this capability, the relevant code will | ||
| // instead be retried. | ||
| let cap: kvm_enable_cap = kvm_enable_cap { | ||
| cap: KVM_CAP_ARM_NISV_TO_USER, | ||
| ..Default::default() | ||
| }; | ||
| unsafe { | ||
| vmm_sys_util::ioctl_iow_nr!(KVM_ENABLE_CAP, KVMIO, 0xa3, kvm_enable_cap); | ||
| vmm_sys_util::ioctl::ioctl_with_ref(&vm_fd, KVM_ENABLE_CAP(), &cap); | ||
| } | ||
| } |
There was a problem hiding this comment.
"Run normally, accepting the small chance that the user does something that triggers this issue" is a reasonable thing to do when this feature isn't available, since hopefully it shouldn't affect most executions.
| Ok(VcpuExit::MmioWrite(addr, data)) => { | ||
| let io_page_gpa = const { hyperlight_common::layout::io_page().unwrap().0 }; | ||
| if addr > io_page_gpa | ||
| && let off = (addr - io_page_gpa) as usize | ||
| && off < hyperlight_common::vmem::PAGE_SIZE | ||
| { |
|
This is great and the commits are very clean. I'm not an expert on the aarch64 specific stuff, so I'll trust you on most of those. Some other stuff:
I also have a suggestion for style on Style patch (click to expand)diff --git i/src/hyperlight_host/src/hypervisor/regs/aarch64/kvm_reg.rs w/src/hyperlight_host/src/hypervisor/regs/aarch64/kvm_reg.rs
index c6d5a51e9..96968172c 100644
--- i/src/hyperlight_host/src/hypervisor/regs/aarch64/kvm_reg.rs
+++ w/src/hyperlight_host/src/hypervisor/regs/aarch64/kvm_reg.rs
@@ -7,19 +7,7 @@ use kvm_bindings::{
};
use kvm_ioctls::VcpuFd;
-enum Size {
- U32,
- U64,
- U128,
-}
-const fn size_kvm_bits(s: Size) -> u64 {
- match s {
- Size::U32 => KVM_REG_SIZE_U32,
- Size::U64 => KVM_REG_SIZE_U64,
- Size::U128 => KVM_REG_SIZE_U128,
- }
-}
-const fn kvm_sys_reg(op0: u8, op1: u8, crn: u8, crm: u8, op2: u8, s: Size) -> u64 {
+const fn kvm_sys_reg(op0: u8, op1: u8, crn: u8, crm: u8, op2: u8, size: u64) -> u64 {
KVM_REG_ARM64
| (KVM_REG_ARM64_SYSREG as u64)
| (((op0 as u64) << KVM_REG_ARM64_SYSREG_OP0_SHIFT) & KVM_REG_ARM64_SYSREG_OP0_MASK as u64)
@@ -27,98 +15,54 @@ const fn kvm_sys_reg(op0: u8, op1: u8, crn: u8, crm: u8, op2: u8, s: Size) -> u6
| (((crn as u64) << KVM_REG_ARM64_SYSREG_CRN_SHIFT) & KVM_REG_ARM64_SYSREG_CRN_MASK as u64)
| (((crm as u64) << KVM_REG_ARM64_SYSREG_CRM_SHIFT) & KVM_REG_ARM64_SYSREG_CRM_MASK as u64)
| (((op2 as u64) << KVM_REG_ARM64_SYSREG_OP2_SHIFT) & KVM_REG_ARM64_SYSREG_OP2_MASK as u64)
- | size_kvm_bits(s)
+ | size
}
macro_rules! decl_sys_reg {
($name:ident, $op0:expr, $op1:expr, $crn:expr, $crm:expr, $op2:expr, $size:ident) => {
- pub const $name: u64 = kvm_sys_reg($op0, $op1, $crn, $crm, $op2, Size::$size);
+ pub const $name: u64 = kvm_sys_reg($op0, $op1, $crn, $crm, $op2, $size);
};
}
-decl_sys_reg!(TTBR0_EL1, 0b11, 0b000, 0b0010, 0b0000, 0b000, U64);
-decl_sys_reg!(TCR_EL1, 0b11, 0b000, 0b0010, 0b0000, 0b010, U64);
-decl_sys_reg!(MAIR_EL1, 0b11, 0b000, 0b1010, 0b0010, 0b000, U64);
-decl_sys_reg!(SCTLR_EL1, 0b11, 0b000, 0b0001, 0b0000, 0b000, U64);
-decl_sys_reg!(CPACR_EL1, 0b11, 0b000, 0b0001, 0b0000, 0b010, U64);
-decl_sys_reg!(VBAR_EL1, 0b11, 0b000, 0b1100, 0b0000, 0b000, U64);
+decl_sys_reg!(TTBR0_EL1, 0b11, 0b000, 0b0010, 0b0000, 0b000, KVM_REG_SIZE_U64);
+decl_sys_reg!(TCR_EL1, 0b11, 0b000, 0b0010, 0b0000, 0b010, KVM_REG_SIZE_U64);
+decl_sys_reg!(MAIR_EL1, 0b11, 0b000, 0b1010, 0b0010, 0b000, KVM_REG_SIZE_U64);
+decl_sys_reg!(SCTLR_EL1, 0b11, 0b000, 0b0001, 0b0000, 0b000, KVM_REG_SIZE_U64);
+decl_sys_reg!(CPACR_EL1, 0b11, 0b000, 0b0001, 0b0000, 0b010, KVM_REG_SIZE_U64);
+decl_sys_reg!(VBAR_EL1, 0b11, 0b000, 0b1100, 0b0000, 0b000, KVM_REG_SIZE_U64);
-const fn kvm_core_reg(offset: u8, s: Size) -> u64 {
- KVM_REG_ARM64 | 0x10_0000u64 | offset as u64 | size_kvm_bits(s)
+const fn kvm_core_reg(offset: u8, size: u64) -> u64 {
+ KVM_REG_ARM64 | 0x10_0000u64 | offset as u64 | size
}
-macro_rules! decl_core_reg {
- ($name:ident, $offset:expr, $size:ident) => {
- pub const $name: u64 = kvm_core_reg($offset, Size::$size);
- };
-}
-decl_core_reg!(X0, 0x00, U64);
-decl_core_reg!(X1, 0x02, U64);
-decl_core_reg!(X2, 0x04, U64);
-decl_core_reg!(X3, 0x06, U64);
-decl_core_reg!(X4, 0x08, U64);
-decl_core_reg!(X5, 0x0A, U64);
-decl_core_reg!(X6, 0x0C, U64);
-decl_core_reg!(X7, 0x0E, U64);
-decl_core_reg!(X8, 0x10, U64);
-decl_core_reg!(X9, 0x12, U64);
-decl_core_reg!(X10, 0x14, U64);
-decl_core_reg!(X11, 0x16, U64);
-decl_core_reg!(X12, 0x18, U64);
-decl_core_reg!(X13, 0x1A, U64);
-decl_core_reg!(X14, 0x1C, U64);
-decl_core_reg!(X15, 0x1E, U64);
-decl_core_reg!(X16, 0x20, U64);
-decl_core_reg!(X17, 0x22, U64);
-decl_core_reg!(X18, 0x24, U64);
-decl_core_reg!(X19, 0x26, U64);
-decl_core_reg!(X20, 0x28, U64);
-decl_core_reg!(X21, 0x2A, U64);
-decl_core_reg!(X22, 0x2C, U64);
-decl_core_reg!(X23, 0x2E, U64);
-decl_core_reg!(X24, 0x30, U64);
-decl_core_reg!(X25, 0x32, U64);
-decl_core_reg!(X26, 0x34, U64);
-decl_core_reg!(X27, 0x36, U64);
-decl_core_reg!(X28, 0x38, U64);
-decl_core_reg!(X29, 0x3A, U64);
-decl_core_reg!(X30, 0x3C, U64);
-decl_core_reg!(SP, 0x3E, U64);
-decl_core_reg!(PC, 0x40, U64);
-decl_core_reg!(PSTATE, 0x42, U64);
-decl_core_reg!(SP_EL1, 0x44, U64);
-// ignore the other SPSRs that are just for AA32-compat
-decl_core_reg!(V0, 0x54, U128);
-decl_core_reg!(V1, 0x58, U128);
-decl_core_reg!(V2, 0x5c, U128);
-decl_core_reg!(V3, 0x60, U128);
-decl_core_reg!(V4, 0x64, U128);
-decl_core_reg!(V5, 0x68, U128);
-decl_core_reg!(V6, 0x6c, U128);
-decl_core_reg!(V7, 0x70, U128);
-decl_core_reg!(V8, 0x74, U128);
-decl_core_reg!(V9, 0x78, U128);
-decl_core_reg!(V10, 0x7c, U128);
-decl_core_reg!(V11, 0x80, U128);
-decl_core_reg!(V12, 0x84, U128);
-decl_core_reg!(V13, 0x88, U128);
-decl_core_reg!(V14, 0x8c, U128);
-decl_core_reg!(V15, 0x90, U128);
-decl_core_reg!(V16, 0x94, U128);
-decl_core_reg!(V17, 0x98, U128);
-decl_core_reg!(V18, 0x9c, U128);
-decl_core_reg!(V19, 0xa0, U128);
-decl_core_reg!(V20, 0xa4, U128);
-decl_core_reg!(V21, 0xa8, U128);
-decl_core_reg!(V22, 0xac, U128);
-decl_core_reg!(V23, 0xb0, U128);
-decl_core_reg!(V24, 0xb4, U128);
-decl_core_reg!(V25, 0xb8, U128);
-decl_core_reg!(V26, 0xbc, U128);
-decl_core_reg!(V27, 0xc0, U128);
-decl_core_reg!(V28, 0xc4, U128);
-decl_core_reg!(V29, 0xc8, U128);
-decl_core_reg!(V30, 0xcc, U128);
-decl_core_reg!(V31, 0xd0, U128);
-decl_core_reg!(FPSR, 0xd4, U32);
-decl_core_reg!(FPCR, 0xd4, U32);
+
+/// KVM register IDs for the 31 general-purpose registers X0..X30.
+/// Each consecutive u64 register is 2 u32-slots apart in `kvm_regs`.
+pub const X: [u64; 31] = {
+ let mut r = [0u64; 31];
+ let mut i = 0;
+ while i < 31 {
+ r[i] = kvm_core_reg((i * 2) as u8, KVM_REG_SIZE_U64);
+ i += 1;
+ }
+ r
+};
+pub const SP: u64 = kvm_core_reg(0x3E, KVM_REG_SIZE_U64);
+pub const PC: u64 = kvm_core_reg(0x40, KVM_REG_SIZE_U64);
+pub const PSTATE: u64 = kvm_core_reg(0x42, KVM_REG_SIZE_U64);
+pub const SP_EL1: u64 = kvm_core_reg(0x44, KVM_REG_SIZE_U64);
+// The other SPSRs are AA32-compat only.
+
+/// KVM register IDs for the 32 NEON/FP registers V0..V31.
+/// Each consecutive u128 register is 4 u32-slots apart in `kvm_regs`.
+pub const V: [u64; 32] = {
+ let mut r = [0u64; 32];
+ let mut i = 0;
+ while i < 32 {
+ r[i] = kvm_core_reg((0x54 + i * 4) as u8, KVM_REG_SIZE_U128);
+ i += 1;
+ }
+ r
+};
+pub const FPSR: u64 = kvm_core_reg(0xd4, KVM_REG_SIZE_U32);
+pub const FPCR: u64 = kvm_core_reg(0xd4, KVM_REG_SIZE_U32);
pub(crate) fn get_reg_bytes<const N: usize, E>(
fd: &VcpuFd,
diff --git i/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/aarch64.rs w/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/aarch64.rs
index d20b6fd8b..da28d3081 100644
--- i/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/aarch64.rs
+++ w/src/hyperlight_host/src/hypervisor/virtual_machine/kvm/aarch64.rs
@@ -53,7 +53,7 @@ pub(crate) struct KvmVm {
}
impl KvmVm {
- pub(self) fn vcpu_init(&mut self) -> Result<(), HypervisorError> {
+ fn vcpu_init(&mut self) -> Result<(), HypervisorError> {
let mut kvi = kvm_bindings::kvm_vcpu_init::default();
self.vm_fd.get_preferred_target(&mut kvi)?;
self.vcpu_fd.vcpu_init(&kvi)?;
@@ -295,178 +295,63 @@ impl VirtualMachine for KvmVm {
}
fn regs(&self) -> std::result::Result<CommonRegisters, RegisterError> {
- use crate::hypervisor::regs::kvm_reg::get_reg;
+ use crate::hypervisor::regs::kvm_reg::{PC, PSTATE, SP, X, get_reg_bytes};
fn err(e: kvm_ioctls::Error) -> RegisterError {
RegisterError::GetSregs(e.into())
}
+ let mut x = [0u64; 31];
+ for (i, &id) in X.iter().enumerate() {
+ x[i] = u64::from_ne_bytes(get_reg_bytes::<8, _>(&self.vcpu_fd, id, err)?);
+ }
Ok(CommonRegisters {
- x: [
- get_reg!(&self.vcpu_fd, err, X0, u64)?,
- get_reg!(&self.vcpu_fd, err, X1, u64)?,
- get_reg!(&self.vcpu_fd, err, X2, u64)?,
- get_reg!(&self.vcpu_fd, err, X3, u64)?,
- get_reg!(&self.vcpu_fd, err, X4, u64)?,
- get_reg!(&self.vcpu_fd, err, X5, u64)?,
- get_reg!(&self.vcpu_fd, err, X6, u64)?,
- get_reg!(&self.vcpu_fd, err, X7, u64)?,
- get_reg!(&self.vcpu_fd, err, X8, u64)?,
- get_reg!(&self.vcpu_fd, err, X9, u64)?,
- get_reg!(&self.vcpu_fd, err, X10, u64)?,
- get_reg!(&self.vcpu_fd, err, X11, u64)?,
- get_reg!(&self.vcpu_fd, err, X12, u64)?,
- get_reg!(&self.vcpu_fd, err, X13, u64)?,
- get_reg!(&self.vcpu_fd, err, X14, u64)?,
- get_reg!(&self.vcpu_fd, err, X15, u64)?,
- get_reg!(&self.vcpu_fd, err, X16, u64)?,
- get_reg!(&self.vcpu_fd, err, X17, u64)?,
- get_reg!(&self.vcpu_fd, err, X18, u64)?,
- get_reg!(&self.vcpu_fd, err, X19, u64)?,
- get_reg!(&self.vcpu_fd, err, X20, u64)?,
- get_reg!(&self.vcpu_fd, err, X21, u64)?,
- get_reg!(&self.vcpu_fd, err, X22, u64)?,
- get_reg!(&self.vcpu_fd, err, X23, u64)?,
- get_reg!(&self.vcpu_fd, err, X24, u64)?,
- get_reg!(&self.vcpu_fd, err, X25, u64)?,
- get_reg!(&self.vcpu_fd, err, X26, u64)?,
- get_reg!(&self.vcpu_fd, err, X27, u64)?,
- get_reg!(&self.vcpu_fd, err, X28, u64)?,
- get_reg!(&self.vcpu_fd, err, X29, u64)?,
- get_reg!(&self.vcpu_fd, err, X30, u64)?,
- ],
- sp: get_reg!(&self.vcpu_fd, err, SP, u64)?,
- pc: get_reg!(&self.vcpu_fd, err, PC, u64)?,
- pstate: get_reg!(&self.vcpu_fd, err, PSTATE, u64)?,
+ x,
+ sp: u64::from_ne_bytes(get_reg_bytes::<8, _>(&self.vcpu_fd, SP, err)?),
+ pc: u64::from_ne_bytes(get_reg_bytes::<8, _>(&self.vcpu_fd, PC, err)?),
+ pstate: u64::from_ne_bytes(get_reg_bytes::<8, _>(&self.vcpu_fd, PSTATE, err)?),
})
}
fn set_regs(&self, regs: &CommonRegisters) -> std::result::Result<(), RegisterError> {
- use crate::hypervisor::regs::kvm_reg::set_reg;
+ use crate::hypervisor::regs::kvm_reg::{PC, PSTATE, SP, X, set_reg_bytes};
fn err(e: kvm_ioctls::Error) -> RegisterError {
RegisterError::SetSregs(e.into())
}
- set_reg!(&self.vcpu_fd, err, X0, u64, regs.x[0])?;
- set_reg!(&self.vcpu_fd, err, X1, u64, regs.x[1])?;
- set_reg!(&self.vcpu_fd, err, X2, u64, regs.x[2])?;
- set_reg!(&self.vcpu_fd, err, X3, u64, regs.x[3])?;
- set_reg!(&self.vcpu_fd, err, X4, u64, regs.x[4])?;
- set_reg!(&self.vcpu_fd, err, X5, u64, regs.x[5])?;
- set_reg!(&self.vcpu_fd, err, X6, u64, regs.x[6])?;
- set_reg!(&self.vcpu_fd, err, X7, u64, regs.x[7])?;
- set_reg!(&self.vcpu_fd, err, X8, u64, regs.x[8])?;
- set_reg!(&self.vcpu_fd, err, X9, u64, regs.x[9])?;
- set_reg!(&self.vcpu_fd, err, X10, u64, regs.x[10])?;
- set_reg!(&self.vcpu_fd, err, X11, u64, regs.x[11])?;
- set_reg!(&self.vcpu_fd, err, X12, u64, regs.x[12])?;
- set_reg!(&self.vcpu_fd, err, X13, u64, regs.x[13])?;
- set_reg!(&self.vcpu_fd, err, X14, u64, regs.x[14])?;
- set_reg!(&self.vcpu_fd, err, X15, u64, regs.x[15])?;
- set_reg!(&self.vcpu_fd, err, X16, u64, regs.x[16])?;
- set_reg!(&self.vcpu_fd, err, X17, u64, regs.x[17])?;
- set_reg!(&self.vcpu_fd, err, X18, u64, regs.x[18])?;
- set_reg!(&self.vcpu_fd, err, X19, u64, regs.x[19])?;
- set_reg!(&self.vcpu_fd, err, X20, u64, regs.x[20])?;
- set_reg!(&self.vcpu_fd, err, X21, u64, regs.x[21])?;
- set_reg!(&self.vcpu_fd, err, X22, u64, regs.x[22])?;
- set_reg!(&self.vcpu_fd, err, X23, u64, regs.x[23])?;
- set_reg!(&self.vcpu_fd, err, X24, u64, regs.x[24])?;
- set_reg!(&self.vcpu_fd, err, X25, u64, regs.x[25])?;
- set_reg!(&self.vcpu_fd, err, X26, u64, regs.x[26])?;
- set_reg!(&self.vcpu_fd, err, X27, u64, regs.x[27])?;
- set_reg!(&self.vcpu_fd, err, X28, u64, regs.x[28])?;
- set_reg!(&self.vcpu_fd, err, X29, u64, regs.x[29])?;
- set_reg!(&self.vcpu_fd, err, X30, u64, regs.x[30])?;
- set_reg!(&self.vcpu_fd, err, SP, u64, regs.sp)?;
- set_reg!(&self.vcpu_fd, err, PC, u64, regs.pc)?;
- set_reg!(&self.vcpu_fd, err, PSTATE, u64, regs.pstate)?;
-
+ for (i, &id) in X.iter().enumerate() {
+ set_reg_bytes::<8, _>(&self.vcpu_fd, err, id, regs.x[i].to_ne_bytes())?;
+ }
+ set_reg_bytes::<8, _>(&self.vcpu_fd, err, SP, regs.sp.to_ne_bytes())?;
+ set_reg_bytes::<8, _>(&self.vcpu_fd, err, PC, regs.pc.to_ne_bytes())?;
+ set_reg_bytes::<8, _>(&self.vcpu_fd, err, PSTATE, regs.pstate.to_ne_bytes())?;
Ok(())
}
fn fpu(&self) -> Result<CommonFpu, RegisterError> {
use crate::hypervisor::regs::CommonFpu;
- use crate::hypervisor::regs::kvm_reg::get_reg;
+ use crate::hypervisor::regs::kvm_reg::{FPCR, FPSR, V, get_reg_bytes};
fn err(e: kvm_ioctls::Error) -> RegisterError {
RegisterError::GetFpu(e.into())
}
+ let mut v = [0u128; 32];
+ for (i, &id) in V.iter().enumerate() {
+ v[i] = u128::from_ne_bytes(get_reg_bytes::<16, _>(&self.vcpu_fd, id, err)?);
+ }
Ok(CommonFpu {
- v: [
- get_reg!(&self.vcpu_fd, err, V0, u128)?,
- get_reg!(&self.vcpu_fd, err, V1, u128)?,
- get_reg!(&self.vcpu_fd, err, V2, u128)?,
- get_reg!(&self.vcpu_fd, err, V3, u128)?,
- get_reg!(&self.vcpu_fd, err, V4, u128)?,
- get_reg!(&self.vcpu_fd, err, V5, u128)?,
- get_reg!(&self.vcpu_fd, err, V6, u128)?,
- get_reg!(&self.vcpu_fd, err, V7, u128)?,
- get_reg!(&self.vcpu_fd, err, V8, u128)?,
- get_reg!(&self.vcpu_fd, err, V9, u128)?,
- get_reg!(&self.vcpu_fd, err, V10, u128)?,
- get_reg!(&self.vcpu_fd, err, V11, u128)?,
- get_reg!(&self.vcpu_fd, err, V12, u128)?,
- get_reg!(&self.vcpu_fd, err, V13, u128)?,
- get_reg!(&self.vcpu_fd, err, V14, u128)?,
- get_reg!(&self.vcpu_fd, err, V15, u128)?,
- get_reg!(&self.vcpu_fd, err, V16, u128)?,
- get_reg!(&self.vcpu_fd, err, V17, u128)?,
- get_reg!(&self.vcpu_fd, err, V18, u128)?,
- get_reg!(&self.vcpu_fd, err, V19, u128)?,
- get_reg!(&self.vcpu_fd, err, V20, u128)?,
- get_reg!(&self.vcpu_fd, err, V21, u128)?,
- get_reg!(&self.vcpu_fd, err, V22, u128)?,
- get_reg!(&self.vcpu_fd, err, V23, u128)?,
- get_reg!(&self.vcpu_fd, err, V24, u128)?,
- get_reg!(&self.vcpu_fd, err, V25, u128)?,
- get_reg!(&self.vcpu_fd, err, V26, u128)?,
- get_reg!(&self.vcpu_fd, err, V27, u128)?,
- get_reg!(&self.vcpu_fd, err, V28, u128)?,
- get_reg!(&self.vcpu_fd, err, V29, u128)?,
- get_reg!(&self.vcpu_fd, err, V30, u128)?,
- get_reg!(&self.vcpu_fd, err, V31, u128)?,
- ],
- fpsr: get_reg!(&self.vcpu_fd, err, FPSR, u32)?,
- fpcr: get_reg!(&self.vcpu_fd, err, FPCR, u32)?,
+ v,
+ fpsr: u32::from_ne_bytes(get_reg_bytes::<4, _>(&self.vcpu_fd, FPSR, err)?),
+ fpcr: u32::from_ne_bytes(get_reg_bytes::<4, _>(&self.vcpu_fd, FPCR, err)?),
})
}
fn set_fpu(&self, fpu: &CommonFpu) -> Result<(), RegisterError> {
- use crate::hypervisor::regs::kvm_reg::set_reg;
+ use crate::hypervisor::regs::kvm_reg::{FPCR, FPSR, V, set_reg_bytes};
fn err(e: kvm_ioctls::Error) -> RegisterError {
RegisterError::SetFpu(e.into())
}
- set_reg!(&self.vcpu_fd, err, V0, u128, fpu.v[0])?;
- set_reg!(&self.vcpu_fd, err, V1, u128, fpu.v[1])?;
- set_reg!(&self.vcpu_fd, err, V2, u128, fpu.v[2])?;
- set_reg!(&self.vcpu_fd, err, V3, u128, fpu.v[3])?;
- set_reg!(&self.vcpu_fd, err, V4, u128, fpu.v[4])?;
- set_reg!(&self.vcpu_fd, err, V5, u128, fpu.v[5])?;
- set_reg!(&self.vcpu_fd, err, V6, u128, fpu.v[6])?;
- set_reg!(&self.vcpu_fd, err, V7, u128, fpu.v[7])?;
- set_reg!(&self.vcpu_fd, err, V8, u128, fpu.v[8])?;
- set_reg!(&self.vcpu_fd, err, V9, u128, fpu.v[9])?;
- set_reg!(&self.vcpu_fd, err, V10, u128, fpu.v[10])?;
- set_reg!(&self.vcpu_fd, err, V11, u128, fpu.v[11])?;
- set_reg!(&self.vcpu_fd, err, V12, u128, fpu.v[12])?;
- set_reg!(&self.vcpu_fd, err, V13, u128, fpu.v[13])?;
- set_reg!(&self.vcpu_fd, err, V14, u128, fpu.v[14])?;
- set_reg!(&self.vcpu_fd, err, V15, u128, fpu.v[15])?;
- set_reg!(&self.vcpu_fd, err, V16, u128, fpu.v[16])?;
- set_reg!(&self.vcpu_fd, err, V17, u128, fpu.v[17])?;
- set_reg!(&self.vcpu_fd, err, V18, u128, fpu.v[18])?;
- set_reg!(&self.vcpu_fd, err, V19, u128, fpu.v[19])?;
- set_reg!(&self.vcpu_fd, err, V20, u128, fpu.v[20])?;
- set_reg!(&self.vcpu_fd, err, V21, u128, fpu.v[21])?;
- set_reg!(&self.vcpu_fd, err, V22, u128, fpu.v[22])?;
- set_reg!(&self.vcpu_fd, err, V23, u128, fpu.v[23])?;
- set_reg!(&self.vcpu_fd, err, V24, u128, fpu.v[24])?;
- set_reg!(&self.vcpu_fd, err, V25, u128, fpu.v[25])?;
- set_reg!(&self.vcpu_fd, err, V26, u128, fpu.v[26])?;
- set_reg!(&self.vcpu_fd, err, V27, u128, fpu.v[27])?;
- set_reg!(&self.vcpu_fd, err, V28, u128, fpu.v[28])?;
- set_reg!(&self.vcpu_fd, err, V29, u128, fpu.v[29])?;
- set_reg!(&self.vcpu_fd, err, V30, u128, fpu.v[30])?;
- set_reg!(&self.vcpu_fd, err, V31, u128, fpu.v[31])?;
- set_reg!(&self.vcpu_fd, err, FPSR, u32, fpu.fpsr)?;
- set_reg!(&self.vcpu_fd, err, FPCR, u32, fpu.fpcr)?;
+ for (i, &id) in V.iter().enumerate() {
+ set_reg_bytes::<16, _>(&self.vcpu_fd, err, id, fpu.v[i].to_ne_bytes())?;
+ }
+ set_reg_bytes::<4, _>(&self.vcpu_fd, err, FPSR, fpu.fpsr.to_ne_bytes())?;
+ set_reg_bytes::<4, _>(&self.vcpu_fd, err, FPCR, fpu.fpcr.to_ne_bytes())?;
Ok(())
} |
| ) { | ||
| // in practice, we never construct page tables that would result | ||
| // in reaching this right now. todo: implement this properly | ||
| unreachable!() |
There was a problem hiding this comment.
Local copilot reviewer flagged this as potentially reachable from snapshot building if a guest aliases page tables. I'm not sure if this is a real concern though.
There was a problem hiding this comment.
The "in practice, we never construct ..." is noting that the guest very much doesn't do that at the moment. This would need to be changed if it did, but I think it's OK to leave it in this state for now.
There was a problem hiding this comment.
On further thought---that reasoning is correct for why it doesn't matter that this isn't implemented, but the actual unimplemented! might possibly give a misbehaving guest a way to cause DOS, so I've replaced it with a debug_assert!.
andreiltd
left a comment
There was a problem hiding this comment.
Similar to Ludvig, I don't have a ton of experience with the code that this PR touches but this is a huge improvement and I trust your expertise here.
|
Great work !! I too have no experience with aarch64, I did however run this through a review locally using a parallel review skill. Other than the things above this is what it came up with, I don't know if these are valid , apologies if this is noise:
I also separately had Claude implement an mshv implementation based on this PR and eventually it was able to pass all tests. |
Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
In 137e964, the signature of `HyperlightVm::new` changed to take `page_size`, which was previously passed in `HyperlightVm::initialise`, so that mapping operations (which are page-size-dependent) could take place before the call to initialise. However, the argument to `HyperlightVm::initialise` was retained, creating the possibility of passing in mis-matched page sizes. This commit removes the second argument, in order to remove that possibility. This commit also, while doing that, fixes compilation of the stubs in hyperlight_vm/aarch64.rs, which was previously broken. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
In the future, some of these may be reimplemented, but for now, just gate them. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
This name is a more accurate reflection of how these constants are used Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
This is not yet used anywhere, but will be used for aarch64 KVM (where there is no other reasonable method of resetting all MSRs), and will likely be used on x86_64 in the future as well. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
This was never actually architecture-dependent, since it used the virtual memory APIs from hyperlight_guest. Extract it, so that it can be used by aarch64 init in the near future. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
This will be useful on multiple architectures. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
This includes adding the concept of a fixed "IO page" to the memory layout, to be used on architectures where MMIO is convenient. The IO page is above the scratch region at the top of memory, and is mapped in the guest, but not in the host, so that accesses to it will trigger vmexits. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
…specific module This changes hyperlight_guest_bin::paging to be a reexport of the interface of an architecture-specific implementation, much like hyperlight_common::vmem. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
This is very similar to the amd64 one, but implemented with different assembly instructions. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
These just implement the bare minimum for stack lazy-allocation and CoW. There is no extension mechanism for other crates (analogous to the raw exception handler table that is presently exported on amd64) yet; we will shortly need to add architecture-independent interfaces to allow other crates (e.g. hyperlight-wasm) to register specially-behaving memory ranges and intercept other errors. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
…stubs Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
There are a number of features not yet supported (e.g. debugging, trace collection etc); however, this should implement a minimal coherent subset of Hyperlight functionality. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Either by disabling them, or by rewriting them to do a similar thing on aarch64. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Don't use formatted string syntax, since the version of just in CI is too old Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fix off-by-one in check for whether an access is in the I/O page Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fix off-by-one in check for whether an access is in the I/O page Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Remove useless variable declaration from dummyguest Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Even though the gdb feature doesn't work on aarch64 at all yet, be consistent about the name of the dbg_mem_access_fn variable. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Add missing license headers Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Enable some more caching, which was disabled when debugging KVM's cache maintenance instruction handling. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Remove the `unreachable!` from ~space_aware_map~ on aarch64. It doesn't matter that the snapshot process does the wrong thing by ignoring these aliased mappings, since we never construct such mappings in the guest at the moment; however, leaving an `unreachable!()` present in production builds is a DoS vector for a misbehaving guest. Replace the `unreachable!` with a `debug_assert!` so that it would still be easy to notice if anyone ever changed the guest to construct page tables that ran into this case. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Set ptw shareability/cacheability Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fixes for changes in the `main` branch since this patchset was written Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fix warning about `KvmRegisterType` being too private Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fix for a change in the `main` branch since this patchset was written Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fix for a change in the `main` branch since this patchset was written Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fix pre-existing warnings Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Remove some detritus left over from using ad-hoc single-step debugging. Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fix typo Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fix typos Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fix formatting Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Fix formatting Signed-off-by: Lucy Menon <168595099+syntactically@users.noreply.github.com>
Initial aarch64 support.
Tested:
There's still some unsupported features (debug/trace/crashdump, etc) and further API surface needed to allow architecture-independent clients like Hyperlight-Wasm to access all of the virtual memory features they need; this is largely elaborated on in the commit messages.