From adcbc02ea343f96644b47d1317b75061ab4ab6b9 Mon Sep 17 00:00:00 2001 From: Luo ruihao <1906353110@qq.com> Date: Fri, 26 Dec 2025 17:12:58 +0800 Subject: [PATCH 1/5] add svm to axvisor --- src/frame.rs | 137 ++++++++ src/lib.rs | 29 +- src/msr.rs | 126 +------ src/regs.rs | 196 +++++++++++ src/svm/definitions.rs | 207 ++++++++++++ src/svm/flags.rs | 142 ++++++++ src/svm/instructions.rs | 71 ++++ src/svm/mod.rs | 25 ++ src/svm/percpu.rs | 88 +++++ src/svm/structs.rs | 167 ++++++++++ src/svm/vcpu.rs | 707 ++++++++++++++++++++++++++++++++++++++++ src/svm/vmcb.rs | 391 ++++++++++++++++++++++ src/vmx/mod.rs | 1 + src/vmx/percpu.rs | 86 +---- src/vmx/structs.rs | 218 +------------ src/vmx/vcpu.rs | 603 ++++------------------------------ src/vmx/vmcs.rs | 61 ---- 17 files changed, 2252 insertions(+), 1003 deletions(-) create mode 100644 src/frame.rs create mode 100644 src/regs.rs create mode 100644 src/svm/definitions.rs create mode 100644 src/svm/flags.rs create mode 100644 src/svm/instructions.rs create mode 100644 src/svm/mod.rs create mode 100644 src/svm/percpu.rs create mode 100644 src/svm/structs.rs create mode 100644 src/svm/vcpu.rs create mode 100644 src/svm/vmcb.rs diff --git a/src/frame.rs b/src/frame.rs new file mode 100644 index 0000000..c7a2169 --- /dev/null +++ b/src/frame.rs @@ -0,0 +1,137 @@ +use core::marker::PhantomData; + +use axaddrspace::HostPhysAddr; +use axerrno::{AxResult, ax_err_type}; + +use axvcpu::AxVCpuHal; + +pub(crate) use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; + +/// A 4K-sized contiguous physical memory page, it will deallocate the page +/// automatically on drop. +#[derive(Debug)] +pub struct PhysFrame { + start_paddr: Option, + _marker: PhantomData, +} + +impl PhysFrame { + pub fn alloc() -> AxResult { + let start_paddr = H::alloc_frame() + .ok_or_else(|| ax_err_type!(NoMemory, "allocate physical frame failed"))?; + assert_ne!(start_paddr.as_usize(), 0); + Ok(Self { + start_paddr: Some(start_paddr), + _marker: PhantomData, + }) + } + + pub fn alloc_zero() -> AxResult { + let mut f = Self::alloc()?; + f.fill(0); + Ok(f) + } + + pub const unsafe fn uninit() -> Self { + Self { + start_paddr: None, + _marker: PhantomData, + } + } + + pub fn start_paddr(&self) -> HostPhysAddr { + self.start_paddr.expect("uninitialized PhysFrame") + } + + pub fn as_mut_ptr(&self) -> *mut u8 { + H::phys_to_virt(self.start_paddr()).as_mut_ptr() + } + + pub fn fill(&mut self, byte: u8) { + unsafe { core::ptr::write_bytes(self.as_mut_ptr(), byte, PAGE_SIZE) } + } +} + +impl Drop for PhysFrame { + fn drop(&mut self) { + if let Some(start_paddr) = self.start_paddr { + H::dealloc_frame(start_paddr); + debug!("[AxVM] deallocated PhysFrame({:#x})", start_paddr); + } + } +} + +/// A contiguous block of physical memory frames that will be automatically +/// deallocated when dropped. Used for hardware structures requiring contiguous +/// physical memory (e.g., IOPM, MSRPM). +#[derive(Debug)] +pub struct ContiguousPhysFrames { + start_paddr: Option, + frame_count: usize, + _marker: PhantomData, +} + +impl ContiguousPhysFrames { + pub fn alloc(frame_count: usize) -> AxResult { + let start_paddr = H::alloc_contiguous_frames(frame_count) + .ok_or_else(|| ax_err_type!(NoMemory, "allocate contiguous frames failed"))?; + + assert_ne!(start_paddr.as_usize(), 0); + Ok(Self { + start_paddr: Some(start_paddr), + frame_count, + _marker: PhantomData, + }) + } + + pub fn alloc_zero(frame_count: usize) -> AxResult { + let mut frames = Self::alloc(frame_count)?; + frames.fill(0); + Ok(frames) + } + + pub const unsafe fn uninit() -> Self { + Self { + start_paddr: None, + frame_count: 0, + _marker: PhantomData, + } + } + + + pub fn start_paddr(&self) -> HostPhysAddr { + self.start_paddr.expect("uninitialized ContiguousPhysFrames") + } + + pub fn frame_count(&self) -> usize { + self.frame_count + } + + pub fn size(&self) -> usize { + PAGE_SIZE * self.frame_count + } + + + pub fn as_mut_ptr(&self) -> *mut u8 { + H::phys_to_virt(self.start_paddr()).as_mut_ptr() + } + + + pub fn fill(&mut self, byte: u8) { + unsafe { + core::ptr::write_bytes(self.as_mut_ptr(), byte, self.size()); + } + } +} + +impl Drop for ContiguousPhysFrames { + fn drop(&mut self) { + if let Some(start_paddr) = self.start_paddr { + H::dealloc_contiguous_frames(start_paddr, self.frame_count); + debug!( + "[AxVM] deallocated ContiguousPhysFrames({:#x}, {} frames)", + start_paddr, self.frame_count + ); + } + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 79d3e6c..6d37743 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ #![no_std] #![feature(doc_cfg)] +#![feature(concat_idents)] +#![feature(naked_functions)] #![doc = include_str!("../README.md")] #[macro_use] @@ -7,25 +9,44 @@ extern crate log; extern crate alloc; -#[cfg(test)] -mod test_utils; - pub(crate) mod msr; #[macro_use] pub(crate) mod regs; mod ept; +mod frame; cfg_if::cfg_if! { if #[cfg(feature = "vmx")] { mod vmx; use vmx as vender; pub use vmx::{VmxExitInfo, VmxExitReason, VmxInterruptInfo, VmxIoExitInfo}; - pub use vender::VmxArchVCpu; pub use vender::VmxArchPerCpuState; + }else if #[cfg(feature = "svm")] { + mod svm; + use svm as vender; + pub use vender::{ + SvmArchVCpu,SvmArchPerCpuState, + }; } } +// +// mod vmx; +// use vmx as vender; +// pub use vmx::{VmxExitInfo, VmxExitReason, VmxInterruptInfo, VmxIoExitInfo}; +// +// pub use vender::VmxArchVCpu; +// pub use vender::VmxArchPerCpuState; +// +// +// mod svm; +// use svm as vendor; +// pub use vendor::{ +// SvmArchVCpu, +// SvmArchPerCpuState, +// }; + pub use ept::GuestPageWalkInfo; pub use regs::GeneralRegisters; pub use vender::has_hardware_support; diff --git a/src/msr.rs b/src/msr.rs index a5a77db..0b5933b 100644 --- a/src/msr.rs +++ b/src/msr.rs @@ -37,6 +37,18 @@ pub enum Msr { IA32_FS_BASE = 0xc000_0100, IA32_GS_BASE = 0xc000_0101, IA32_KERNEL_GSBASE = 0xc000_0102, + + // SVM Related MSRs: + VM_CR = 0xc001_0114, + IGNNE = 0xc001_0115, + VM_HSAVE_PA = 0xc001_0117, + + PERF_EVT_SEL0 = 0xc001_0200, + PERF_EVT_SEL1 = 0xc001_0202, + PERF_EVT_SEL2 = 0xc001_0204, + PERF_EVT_SEL3 = 0xc001_0206, + PERF_EVT_SEL4 = 0xc001_0208, + PERF_EVT_SEL5 = 0xc001_020a, } impl Msr { @@ -71,117 +83,3 @@ pub(super) trait MsrReadWrite { } } } - -#[cfg(test)] -mod tests { - use super::*; - use alloc::format; - - #[test] - fn test_msr_enum_values() { - // Test that MSR enum values match the expected constants - assert_eq!(Msr::IA32_FEATURE_CONTROL as u32, 0x3a); - assert_eq!(Msr::IA32_PAT as u32, 0x277); - assert_eq!(Msr::IA32_VMX_BASIC as u32, 0x480); - assert_eq!(Msr::IA32_EFER as u32, 0xc000_0080); - assert_eq!(Msr::IA32_LSTAR as u32, 0xc000_0082); - } - - #[test] - fn test_msr_debug() { - // Test that MSR implements Debug properly - let msr = Msr::IA32_VMX_BASIC; - let debug_str = format!("{:?}", msr); - assert!(!debug_str.is_empty()); - assert!(debug_str.contains("IA32_VMX_BASIC")); - } - - #[test] - fn test_msr_copy_clone() { - // Test that MSR implements Copy and Clone - let msr1 = Msr::IA32_EFER; - let msr2 = msr1; // Copy - let msr3 = msr1.clone(); // Clone - - assert_eq!(msr1 as u32, msr2 as u32); - assert_eq!(msr1 as u32, msr3 as u32); - } - - #[test] - fn test_vmx_msr_ranges() { - // Test VMX MSR values are in the correct range - assert!(Msr::IA32_VMX_BASIC as u32 >= 0x480); - assert!(Msr::IA32_VMX_TRUE_ENTRY_CTLS as u32 <= 0x490); - - // Test that VMX MSRs are consecutive where expected - assert_eq!( - Msr::IA32_VMX_BASIC as u32 + 1, - Msr::IA32_VMX_PINBASED_CTLS as u32 - ); - assert_eq!( - Msr::IA32_VMX_PINBASED_CTLS as u32 + 1, - Msr::IA32_VMX_PROCBASED_CTLS as u32 - ); - } - - #[test] - fn test_fs_gs_base_msr_values() { - // Test FS/GS base MSRs - assert_eq!(Msr::IA32_FS_BASE as u32, 0xc000_0100); - assert_eq!(Msr::IA32_GS_BASE as u32, 0xc000_0101); - assert_eq!(Msr::IA32_KERNEL_GSBASE as u32, 0xc000_0102); - - // These should be consecutive - assert_eq!(Msr::IA32_FS_BASE as u32 + 1, Msr::IA32_GS_BASE as u32); - assert_eq!(Msr::IA32_GS_BASE as u32 + 1, Msr::IA32_KERNEL_GSBASE as u32); - } - - #[test] - fn test_system_call_msr_values() { - // Test system call related MSRs - assert_eq!(Msr::IA32_STAR as u32, 0xc000_0081); - assert_eq!(Msr::IA32_LSTAR as u32, 0xc000_0082); - assert_eq!(Msr::IA32_CSTAR as u32, 0xc000_0083); - assert_eq!(Msr::IA32_FMASK as u32, 0xc000_0084); - - // These should be consecutive - assert_eq!(Msr::IA32_STAR as u32 + 1, Msr::IA32_LSTAR as u32); - assert_eq!(Msr::IA32_LSTAR as u32 + 1, Msr::IA32_CSTAR as u32); - assert_eq!(Msr::IA32_CSTAR as u32 + 1, Msr::IA32_FMASK as u32); - } - - // Note: We can't test the actual read/write methods without running on real hardware - // and having the appropriate privileges. Those would be integration tests. - - // Mock implementation for testing the MsrReadWrite trait - struct TestMsr; - - impl MsrReadWrite for TestMsr { - const MSR: Msr = Msr::IA32_PAT; - } - - #[test] - fn test_msr_read_write_trait() { - // Test that the trait compiles and has the expected methods - // We can't actually call read_raw() without MSR access - assert_eq!(TestMsr::MSR as u32, 0x277); - } - - #[test] - fn test_msr_as_u32_conversion() { - // Test that we can convert MSR enum to u32 properly - let msrs = [ - Msr::IA32_FEATURE_CONTROL, - Msr::IA32_VMX_BASIC, - Msr::IA32_EFER, - Msr::IA32_LSTAR, - ]; - - for msr in msrs.iter() { - let value = *msr as u32; - assert!(value > 0); - // Values should be reasonable MSR numbers - assert!(value < 0xffff_ffff); - } - } -} diff --git a/src/regs.rs b/src/regs.rs new file mode 100644 index 0000000..2039c77 --- /dev/null +++ b/src/regs.rs @@ -0,0 +1,196 @@ +/// General-purpose registers for the 64-bit x86 architecture. +/// +/// This structure holds the values of the general-purpose registers +/// used in 64-bit x86 systems, allowing for easy manipulation and storage of register states. +#[repr(C)] +#[derive(Debug, Default, Clone)] +pub struct GeneralRegisters { + /// The RAX register, typically used for return values in functions. + pub rax: u64, + /// The RCX register, often used as a counter in loops. + pub rcx: u64, + /// The RDX register, commonly used for I/O operations. + pub rdx: u64, + /// The RBX register, usually used as a base pointer or to store values across function calls. + pub rbx: u64, + /// Unused space for the RSP register, preserved for padding or future use. + _unused_rsp: u64, + /// The RBP register, often used as a frame pointer in function calls. + pub rbp: u64, + /// The RSI register, often used as a source index in string operations. + pub rsi: u64, + /// The RDI register, often used as a destination index in string operations. + pub rdi: u64, + /// The R8 register, an additional general-purpose register available in 64-bit mode. + pub r8: u64, + /// The R9 register, an additional general-purpose register available in 64-bit mode. + pub r9: u64, + /// The R10 register, an additional general-purpose register available in 64-bit mode. + pub r10: u64, + /// The R11 register, an additional general-purpose register available in 64-bit mode. + pub r11: u64, + /// The R12 register, an additional general-purpose register available in 64-bit mode. + pub r12: u64, + /// The R13 register, an additional general-purpose register available in 64-bit mode. + pub r13: u64, + /// The R14 register, an additional general-purpose register available in 64-bit mode. + pub r14: u64, + /// The R15 register, an additional general-purpose register available in 64-bit mode. + pub r15: u64, +} + +impl GeneralRegisters { + /// Returns the value of the general-purpose register corresponding to the given index. + /// + /// The mapping of indices to registers is as follows: + /// - 0: `rax` + /// - 1: `rcx` + /// - 2: `rdx` + /// - 3: `rbx` + /// - 5: `rbp` + /// - 6: `rsi` + /// - 7: `rdi` + /// - 8: `r8` + /// - 9: `r9` + /// - 10: `r10` + /// - 11: `r11` + /// - 12: `r12` + /// - 13: `r13` + /// - 14: `r14` + /// - 15: `r15` + /// + /// # Panics + /// + /// This function will panic if the provided index is out of the range [0, 15] or if the index + /// corresponds to an unused register (`rsp` at index 4). + /// + /// # Arguments + /// + /// * `index` - A `u8` value representing the index of the register. + /// + /// # Returns + /// + /// * `u64` - The value of the corresponding general-purpose register. + pub fn get_reg_of_index(&self, index: u8) -> u64 { + match index { + 0 => self.rax, + 1 => self.rcx, + 2 => self.rdx, + 3 => self.rbx, + // 4 => self._unused_rsp, + 5 => self.rbp, + 6 => self.rsi, + 7 => self.rdi, + 8 => self.r8, + 9 => self.r9, + 10 => self.r10, + 11 => self.r11, + 12 => self.r12, + 13 => self.r13, + 14 => self.r14, + 15 => self.r15, + _ => { + panic!("Illegal index of GeneralRegisters {}", index); + } + } + } + + /// Sets the value of the general-purpose register corresponding to the given index. + /// + /// The mapping of indices to registers is as follows: + /// - 0: `rax` + /// - 1: `rcx` + /// - 2: `rdx` + /// - 3: `rbx` + /// - 5: `rbp` + /// - 6: `rsi` + /// - 7: `rdi` + /// - 8: `r8` + /// - 9: `r9` + /// - 10: `r10` + /// - 11: `r11` + /// - 12: `r12` + /// - 13: `r13` + /// - 14: `r14` + /// - 15: `r15` + /// + /// # Panics + /// + /// This function will panic if the provided index is out of the range [0, 15] or if the index + /// corresponds to an unused register (`rsp` at index 4). + /// + /// # Arguments + /// + /// * `index` - A `u8` value representing the index of the register. + /// + /// # Returns + /// + /// * `u64` - The value of the corresponding general-purpose register. + pub fn set_reg_of_index(&mut self, index: u8, value: u64) { + match index { + 0 => self.rax = value, + 1 => self.rcx = value, + 2 => self.rdx = value, + 3 => self.rbx = value, + // 4 => self._unused_rsp, + 5 => self.rbp = value, + 6 => self.rsi = value, + 7 => self.rdi = value, + 8 => self.r8 = value, + 9 => self.r9 = value, + 10 => self.r10 = value, + 11 => self.r11 = value, + 12 => self.r12 = value, + 13 => self.r13 = value, + 14 => self.r14 = value, + 15 => self.r15 = value, + _ => { + panic!("Illegal index of GeneralRegisters {}", index); + } + } + } +} + +macro_rules! save_regs_to_stack { + () => { + " + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rbp + sub rsp, 8 + push rbx + push rdx + push rcx + push rax" + }; +} + +macro_rules! restore_regs_from_stack { + () => { + " + pop rax + pop rcx + pop rdx + pop rbx + add rsp, 8 + pop rbp + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15" + }; +} diff --git a/src/svm/definitions.rs b/src/svm/definitions.rs new file mode 100644 index 0000000..9c452c7 --- /dev/null +++ b/src/svm/definitions.rs @@ -0,0 +1,207 @@ +use core::convert::TryFrom; + +#[derive(Debug, Clone, Copy)] +#[allow(non_camel_case_types)] +pub enum SvmExitCode { + CR_READ(u8), + CR_WRITE(u8), + DR_READ(u8), + DR_WRITE(u8), + EXCP(u8), + INTR, + NMI, + SMI, + INIT, + VINTR, + CR0_SEL_WRITE, + IDTR_READ, + GDTR_READ, + LDTR_READ, + TR_READ, + IDTR_WRITE, + GDTR_WRITE, + LDTR_WRITE, + TR_WRITE, + RDTSC, + RDPMC, + PUSHF, + POPF, + CPUID, + RSM, + IRET, + SWINT, + INVD, + PAUSE, + HLT, + INVLPG, + INVLPGA, + IOIO, + MSR, + TASK_SWITCH, + FERR_FREEZE, + SHUTDOWN, + VMRUN, + VMMCALL, + VMLOAD, + VMSAVE, + STGI, + CLGI, + SKINIT, + RDTSCP, + ICEBP, + WBINVD, + MONITOR, + MWAIT, + MWAIT_CONDITIONAL, + XSETBV, + RDPRU, + EFER_WRITE_TRAP, + CR_WRITE_TRAP(u8), + INVLPGB, + INVLPGB_ILLEGAL, + INVPCID, + MCOMMIT, + TLBSYNC, + NPF, + AVIC_INCOMPLETE_IPI, + AVIC_NOACCEL, + VMGEXIT, + INVALID, + BUSY, +} + +impl TryFrom for SvmExitCode { + type Error = u64; + fn try_from(val: u64) -> Result { + match val as i64 { + 0x00..=0x0F => Ok(Self::CR_READ(val as u8)), + 0x10..=0x1F => Ok(Self::CR_WRITE(val as u8 - 0x10)), + 0x20..=0x2F => Ok(Self::DR_READ(val as u8 - 0x20)), + 0x30..=0x3F => Ok(Self::DR_WRITE(val as u8 - 0x30)), + 0x40..=0x5F => Ok(Self::EXCP(val as u8 - 0x40)), + 0x60 => Ok(Self::INTR), + 0x61 => Ok(Self::NMI), + 0x62 => Ok(Self::SMI), + 0x63 => Ok(Self::INIT), + 0x64 => Ok(Self::VINTR), + 0x65 => Ok(Self::CR0_SEL_WRITE), + 0x66 => Ok(Self::IDTR_READ), + 0x67 => Ok(Self::GDTR_READ), + 0x68 => Ok(Self::LDTR_READ), + 0x69 => Ok(Self::TR_READ), + 0x6A => Ok(Self::IDTR_WRITE), + 0x6B => Ok(Self::GDTR_WRITE), + 0x6C => Ok(Self::LDTR_WRITE), + 0x6D => Ok(Self::TR_WRITE), + 0x6E => Ok(Self::RDTSC), + 0x6F => Ok(Self::RDPMC), + 0x70 => Ok(Self::PUSHF), + 0x71 => Ok(Self::POPF), + 0x72 => Ok(Self::CPUID), + 0x73 => Ok(Self::RSM), + 0x74 => Ok(Self::IRET), + 0x75 => Ok(Self::SWINT), + 0x76 => Ok(Self::INVD), + 0x77 => Ok(Self::PAUSE), + 0x78 => Ok(Self::HLT), + 0x79 => Ok(Self::INVLPG), + 0x7A => Ok(Self::INVLPGA), + 0x7B => Ok(Self::IOIO), + 0x7C => Ok(Self::MSR), + 0x7D => Ok(Self::TASK_SWITCH), + 0x7E => Ok(Self::FERR_FREEZE), + 0x7F => Ok(Self::SHUTDOWN), + 0x80 => Ok(Self::VMRUN), + 0x81 => Ok(Self::VMMCALL), + 0x82 => Ok(Self::VMLOAD), + 0x83 => Ok(Self::VMSAVE), + 0x84 => Ok(Self::STGI), + 0x85 => Ok(Self::CLGI), + 0x86 => Ok(Self::SKINIT), + 0x87 => Ok(Self::RDTSCP), + 0x88 => Ok(Self::ICEBP), + 0x89 => Ok(Self::WBINVD), + 0x8A => Ok(Self::MONITOR), + 0x8B => Ok(Self::MWAIT), + 0x8C => Ok(Self::MWAIT_CONDITIONAL), + 0x8D => Ok(Self::XSETBV), + 0x8E => Ok(Self::RDPRU), + 0x8F => Ok(Self::EFER_WRITE_TRAP), + 0x90..=0x9F => Ok(Self::CR_WRITE_TRAP(val as u8 - 0x90)), + 0xA0 => Ok(Self::INVLPGB), + 0xA1 => Ok(Self::INVLPGB_ILLEGAL), + 0xA2 => Ok(Self::INVPCID), + 0xA3 => Ok(Self::MCOMMIT), + 0xA4 => Ok(Self::TLBSYNC), + 0x400 => Ok(Self::NPF), + 0x401 => Ok(Self::AVIC_INCOMPLETE_IPI), + 0x402 => Ok(Self::AVIC_NOACCEL), + 0x403 => Ok(Self::VMGEXIT), + -1 => Ok(Self::INVALID), + -2 => Ok(Self::BUSY), + _ => Err(val), + } + } +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy)] +#[allow(non_camel_case_types)] +pub enum SvmIntercept { + // 0x0C (vector 3) + INTR, + NMI, + SMI, + INIT, + VINTR, + CR0_SEL_WRITE, + IDTR_READ, + GDTR_READ, + LDTR_READ, + TR_READ, + IDTR_WRITE, + GDTR_WRITE, + LDTR_WRITE, + TR_WRITE, + RDTSC, + RDPMC, + PUSHF, + POPF, + CPUID, + RSM, + IRET, + SWINT, + INVD, + PAUSE, + HLT, + INVLPG, + INVLPGA, + IOIO_PROT, + MSR_PROT, + TASK_SWITCH, + FERR_FREEZE, + SHUTDOWN, + // 0x10 (vector 4) + VMRUN, + VMMCALL, + VMLOAD, + VMSAVE, + STGI, + CLGI, + SKINIT, + RDTSCP, + ICEBP, + WBINVD, + MONITOR, + MWAIT, + MWAIT_CONDITIONAL, + XSETBV, + RDPRU, + EFER_WRITE_TRAP, + // 0x14 (vector 5) + INVLPGB, + INVLPGB_ILLEGAL, + INVPCID, + MCOMMIT, + TLBSYNC, +} diff --git a/src/svm/flags.rs b/src/svm/flags.rs new file mode 100644 index 0000000..d57af8a --- /dev/null +++ b/src/svm/flags.rs @@ -0,0 +1,142 @@ +use bit_field::BitField; +use bitflags::bitflags; + +use crate::msr::{Msr, MsrReadWrite}; + +bitflags! { + /// VM_CR MSR flags. + pub struct VmCrFlags: u64 { + /// If set, disables HDT and certain internal debug features. + const DPD = 1 << 0; + /// If set, non-intercepted INIT signals are converted into an #SX + /// exception. + const R_INIT = 1 << 1; + /// If set, disables A20 masking. + const DIS_A20M = 1 << 2; + /// When this bit is set, writes to LOCK and SVMDIS are silently ignored. + /// When this bit is clear, VM_CR bits 3 and 4 can be written. Once set, + /// LOCK can only be cleared using the SVM_KEY MSR (See Section 15.31.) + /// This bit is not affected by INIT or SKINIT. + const LOCK = 1 << 3; + /// When this bit is set, writes to EFER treat the SVME bit as MBZ. When + /// this bit is clear, EFER.SVME can be written normally. This bit does + /// not prevent CPUID from reporting that SVM is available. Setting + /// SVMDIS while EFER.SVME is 1 generates a #GP fault, regardless of the + /// current state of VM_CR.LOCK. This bit is not affected by SKINIT. It + /// is cleared by INIT when LOCK is cleared to 0; otherwise, it is not + /// affected. + const SVMDIS = 1 << 4; + } +} + +/// The VM_CR MSR controls certain global aspects of SVM. +pub struct VmCr; + +impl MsrReadWrite for VmCr { + const MSR: Msr = Msr::VM_CR; +} + +impl VmCr { + pub fn read() -> VmCrFlags { + VmCrFlags::from_bits_truncate(Self::read_raw()) + } +} + +bitflags! { + /// The VMCB Clean field (VMCB offset 0C0h, bits 31:0) controls which guest + /// register values are loaded from the VMCB state cache on VMRUN. + pub struct VmcbCleanBits: u32 { + /// Intercepts: all the intercept vectors, TSC offset, Pause Filter Count + const I = 1 << 0; + /// IOMSRPM: IOPM_BASE, MSRPM_BASE + const IOPM = 1 << 1; + /// ASID + const ASID = 1 << 2; + /// V_TPR, V_IRQ, V_INTR_PRIO, V_IGN_TPR, V_INTR_MASKING, V_INTR_VECTOR + /// (Offset 60h–67h) + const TPR = 1 << 3; + /// Nested Paging: NCR3, G_PAT + const NP = 1 << 4; + /// CR0, CR3, CR4, EFER + const CR_X = 1 << 5; + /// DR6, DR7 + const DR_X = 1 << 6; + /// GDT/IDT Limit and Base + const DT = 1 << 7; + /// CS/DS/SS/ES Sel/Base/Limit/Attr, CPL + const SEG = 1 << 8; + /// CR2 + const CR2 = 1 << 9; + /// DbgCtlMsr, br_from/to, lastint_from/to + const LBR = 1 << 10; + /// AVIC APIC_BAR; AVIC APIC_BACKING_PAGE, AVIC PHYSICAL_TABLE and AVIC + /// LOGICAL_TABLE Pointers + const AVIC = 1 << 11; + /// S_CET, SSP, ISST_ADDR + const CET = 1 << 12; + /// The hypervisor has not modified the VMCB. + const UNMODIFIED = 0xffff_ffff; + } +} + +bitflags! { + /// EXITINTINFO/EVENTINJ field in the VMCB. + pub struct VmcbIntInfo: u32 { + /// Error Code Valid + const ERROR_CODE = 1 << 11; + /// Valid + const VALID = 1 << 31; + } +} + +#[repr(u32)] +#[derive(Debug)] +pub enum InterruptType { + /// External or virtual interrupt (INTR) + External = 0, + /// Non-maskable interrupt (NMI) + NMI = 2, + /// Exception (fault or trap) + Exception = 3, + /// Software interrupt (caused by INTn instruction) + SoftIntr = 4, +} + +impl VmcbIntInfo { + fn has_error_code(vector: u8) -> bool { + use x86::irq::*; + matches!( + vector, + DOUBLE_FAULT_VECTOR + | INVALID_TSS_VECTOR + | SEGMENT_NOT_PRESENT_VECTOR + | STACK_SEGEMENT_FAULT_VECTOR + | GENERAL_PROTECTION_FAULT_VECTOR + | PAGE_FAULT_VECTOR + | ALIGNMENT_CHECK_VECTOR + ) + } + + pub fn from(int_type: InterruptType, vector: u8) -> Self { + let mut bits = vector as u32; + bits.set_bits(8..11, int_type as u32); + let mut info = Self::from_bits_retain(bits) | Self::VALID; + if Self::has_error_code(vector) { + info |= Self::ERROR_CODE; + } + info + } +} + +#[repr(u8)] +#[derive(Debug)] +pub enum VmcbTlbControl { + /// Do not flush + DoNotFlush = 0, + /// Flush entire TLB (Should be used only on legacy hardware.) + FlushAll = 0x01, + /// Flush this guest's TLB entries + FlushAsid = 0x03, + /// Flush this guest's non-global TLB entries + FlushAsidNonGlobal = 0x07, +} diff --git a/src/svm/instructions.rs b/src/svm/instructions.rs new file mode 100644 index 0000000..aee2daf --- /dev/null +++ b/src/svm/instructions.rs @@ -0,0 +1,71 @@ +//! AMD-SVM 指令封装 +//! +//! 资料来源:AMD64 APM Vol-2 “Secure Virtual Machine” §15 & §16 +//! - VMRUN / VMLOAD / VMSAVE / STGI / CLGI / INVLPGA +//! - 这些指令 **不会像 VMX 指令那样用 RFLAGS.CF / ZF 返回错误**; +//! 如果控制区不合法,会直接 #GP(0) 或 #UD,所以简单包一层即可。 + +use core::arch::asm; + +/// SVM 指令基本都不会返回状态;如果失败直接 #UD/#GP, +/// 我们姑且用最简单的 `Result<(), ()>` 占位,后续如需 +/// 细分错误再扩展枚举即可。 +pub type Result = core::result::Result; + +/// 进入来宾:`vmrun rax` +#[inline(always)] +pub unsafe fn vmrun(vmcb_pa: u64) -> ! { + asm!( + "vmrun {0}", + in(reg) vmcb_pa, + options(noreturn, nostack), + ) +} + +/// 保存 Host-state:`vmsave rax` +#[inline(always)] +pub unsafe fn vmsave(vmcb_pa: u64) -> Result { + asm!( + "vmsave {0}", + in(reg) vmcb_pa, + options(nostack, preserves_flags), + ); + Ok(()) +} + +/// 恢复 Host-state:`vmload rax` +#[inline(always)] +pub unsafe fn vmload(vmcb_pa: u64) -> Result { + asm!( + "vmload {0}", + in(reg) vmcb_pa, + options(nostack, preserves_flags), + ); + Ok(()) +} + +/// 允许全局中断 (`STGI`) +#[inline(always)] +pub unsafe fn stgi() { + asm!("stgi", options(nostack, preserves_flags)); +} + +/// 禁止全局中断 (`CLGI`) +#[inline(always)] +pub unsafe fn clgi() { + asm!("clgi", options(nostack, preserves_flags)); +} + +/// `INVLpga` —— 按 (guest-virt addr, ASID) 刷 TLB +/// +/// * `addr` : Guest 虚拟地址 (通常传 0 代表“全页表”) +/// * `asid` : Address-Space ID,0 触发全局 flush +#[inline(always)] +pub unsafe fn invlpga(addr: u64, asid: u32) { + asm!( + "invlpga {0}, {1:e}", + in(reg) addr, + in(reg) asid, + options(nostack, preserves_flags), + ); +} diff --git a/src/svm/mod.rs b/src/svm/mod.rs new file mode 100644 index 0000000..2bba4b3 --- /dev/null +++ b/src/svm/mod.rs @@ -0,0 +1,25 @@ +mod definitions; // SvmExitCode / Intercept 位 +mod instructions; // vmrun / vmload / vmsave / stgi / clgi / invlpga +mod percpu; // SvmPerCpuState(EFER.SVME & HSAVE) +mod structs; // IOPm / MSRPm / Vmcb 封装 +mod vcpu; // SvmVcpu(核心逻辑) +mod vmcb; +mod flags; +// VMCB 读写 & VMEXIT 解码 + +pub use self::definitions::{SvmExitCode,SvmIntercept}; +pub use self::percpu::SvmPerCpuState as SvmArchPerCpuState; +pub use self::vcpu::SvmVcpu as SvmArchVCpu; + + +pub fn has_hardware_support() -> bool { + if let Some(ext) = raw_cpuid::CpuId::new().get_extended_processor_and_feature_identifiers() + { + ext.has_svm() + } else { + false + } +} + +/* 额外:SVM 没有 VMX-instruction-error / VMCS revision id 概念, + * 如需调试可在 vmcb::exit_code() 中直接查看 SvmExitCode。*/ diff --git a/src/svm/percpu.rs b/src/svm/percpu.rs new file mode 100644 index 0000000..f309b72 --- /dev/null +++ b/src/svm/percpu.rs @@ -0,0 +1,88 @@ +//! AMD-SVM per-CPU enable/disable logic +//! +//! References (AMD APM v2 *System Programming*): +//! https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24593.pdf +//! • § 15.4 Enabling SVM +//! +//! Summary of procedure (compared to Intel VMX): +//! 1. check if the CPU supports SVM +//! 2. Allocate Host-Save Area (HSAVE) and write its physical address to `MSR_VM_HSAVE_PA` +//! 3. Set `EFER.SVME` (bit 12) to enable SVM mode +//! 4. Clearing `EFER.SVME` disables SVM (no need for VMXON/VMXOFF equivalents) + +use axerrno::{ax_err, ax_err_type, AxResult}; +use axvcpu::{AxArchPerCpu, AxVCpuHal}; +use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; +use raw_cpuid::CpuId; +use x86_64::registers::control::EferFlags; + +use crate::frame::PhysFrame; +use crate::msr::Msr; +use crate::svm::has_hardware_support; + + +/// Per-core state for AMD-SVM + +// (AMD64 APM Vol.2, Section 15.30.4) +//The 64-bit read/write VM_HSAVE_PA MSR holds the physical address of a 4KB block of memory where VMRUN saves host state +pub struct SvmPerCpuState { + hsave_page: PhysFrame, +} + +impl AxArchPerCpu for SvmPerCpuState { + fn new(_cpu_id: usize) -> AxResult { + Ok(Self { + hsave_page: unsafe { PhysFrame::uninit() }, + }) + } + + /// Returns true if SVM is enabled on this core (EFER.SVME == 1) + fn is_enabled(&self) -> bool { + let efer = Msr::IA32_EFER.read(); + EferFlags::from_bits_truncate(efer).contains(EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE) + } + + fn hardware_enable(&mut self) -> AxResult { + if !has_hardware_support() { + return ax_err!(Unsupported, "CPU does not support AMD-SVM"); + } + if self.is_enabled() { + return ax_err!(ResourceBusy, "SVM already enabled"); + } + + // Enable XSAVE/XRSTOR. + super::vcpu::XState::enable_xsave(); + + // Allocate & register Host-Save Area + self.hsave_page = PhysFrame::alloc_zero()?; + let hsave_pa = self.hsave_page.start_paddr().as_usize() as u64; + unsafe { Msr::VM_HSAVE_PA.write(hsave_pa); } + + + //Set EFER.SVME to enable SVM + let mut efer = EferFlags::from_bits_truncate(Msr::IA32_EFER.read()); + efer.insert(EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE); // bit 12 + unsafe { Msr::IA32_EFER.write(efer.bits()); } + + info!("[AxVM] SVM enabled (HSAVE @ {:#x}).", hsave_pa); + Ok(()) + } + + + fn hardware_disable(&mut self) -> AxResult { + if !self.is_enabled() { + return ax_err!(BadState, "SVM is not enabled"); + } + unsafe { + // 1) Clear SVME bit + let mut efer = EferFlags::from_bits_truncate(Msr::IA32_EFER.read()); + efer.remove(EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE); + Msr::IA32_EFER.write(efer.bits()); + + // 2) Clear HSAVE pointer + Msr::VM_HSAVE_PA.write(0); + } + info!("[AxVM] SVM disabled."); + Ok(()) + } +} diff --git a/src/svm/structs.rs b/src/svm/structs.rs new file mode 100644 index 0000000..d52922c --- /dev/null +++ b/src/svm/structs.rs @@ -0,0 +1,167 @@ +//! AMD-SVM helper structs +//! https://www.amd.com/content/dam/amd/en/documents/processor-tech-docs/programmer-references/24593.pdf + +#![allow(dead_code)] + +use axaddrspace::HostPhysAddr; +use axerrno::{AxResult}; +use axvcpu::AxVCpuHal; +use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; +use crate::frame::{ContiguousPhysFrames, PhysFrame}; + + +/// Virtual-Machine Control Block (VMCB) +/// One 4 KiB page per vCPU: [control-area | save-area]. +#[derive(Debug)] +pub struct VmcbFrame { + page: PhysFrame, +} + +impl VmcbFrame { + pub const unsafe fn uninit() -> Self { + Self { page: unsafe { PhysFrame::uninit() } } + } + + pub fn new() -> AxResult { + Ok(Self { page: PhysFrame::alloc_zero()? }) + } + + pub fn phys_addr(&self) -> HostPhysAddr { + self.page.start_paddr() + } + + pub fn as_mut_ptr(&self) -> *mut u8 { + self.page.as_mut_ptr() + } +} + + +// (AMD64 APM Vol.2, Section 15.10) +// The I/O Permissions Map (IOPM) occupies 12 Kbytes of contiguous physical memory. +// The map is structured as a linear array of 64K+3 bits (two 4-Kbyte pages, and the first three bits of a third 4-Kbyte page) and must be aligned on a 4-Kbyte boundary; +#[derive(Debug)] +pub struct IOPm { + frames: ContiguousPhysFrames, // 3 contiguous frames (12KB) +} + +impl IOPm { + pub fn passthrough_all() -> AxResult { + let mut frames = ContiguousPhysFrames::::alloc_zero(3)?; + + // Set first 3 bits of third frame to intercept (ports > 0xFFFF) + let third_frame_start = frames.as_mut_ptr() as usize + 2 * PAGE_SIZE; + unsafe { + let third_byte = third_frame_start as *mut u8; + *third_byte |= 0x07; // Set bits 0-2 (0b00000111) + } + + Ok(Self { frames }) + } + + #[allow(unused)] + pub fn intercept_all() -> AxResult { + let mut frames = ContiguousPhysFrames::::alloc(3)?; + frames.fill(0xFF); // Set all bits to 1 (intercept) + Ok(Self { frames }) + } + + pub fn phys_addr(&self) -> HostPhysAddr { + self.frames.start_paddr() + } + pub fn as_mut_ptr(&self) -> *mut u8 { + self.frames.as_mut_ptr() + } + + pub fn set_intercept(&mut self, port: u32, intercept: bool) { + let byte_index = port as usize / 8; + let bit_offset = (port % 8) as u8; + let iopm_ptr = self.frames.as_mut_ptr(); + + unsafe { + let byte_ptr = iopm_ptr.add(byte_index); + if intercept { + *byte_ptr |= 1 << bit_offset; + } else { + *byte_ptr &= !(1 << bit_offset); + } + } + } + + pub fn set_intercept_of_range(&mut self, port_base: u32, count: u32, intercept: bool) { + for port in port_base..port_base + count { + self.set_intercept(port, intercept) + } + } + +} +// (AMD64 APM Vol.2, Section 15.10) +// The VMM can intercept RDMSR and WRMSR instructions by means of the SVM MSR permissions map (MSRPM) on a per-MSR basis +// The four separate bit vectors must be packed together and located in two contiguous physical pages of memory. +#[derive(Debug)] +pub struct MSRPm { + frames: ContiguousPhysFrames, +} + +impl MSRPm { + pub fn passthrough_all() -> AxResult { + Ok(Self { + frames: ContiguousPhysFrames::alloc_zero(2)?, + }) + } + + #[allow(unused)] + pub fn intercept_all() -> AxResult { + let mut frames = ContiguousPhysFrames::alloc(2)?; + frames.fill(0xFF); + Ok(Self { frames }) + } + + pub fn phys_addr(&self) -> HostPhysAddr { + self.frames.start_paddr() + } + pub fn as_mut_ptr(&self) -> *mut u8 { + self.frames.as_mut_ptr() + } + + pub fn set_intercept(&mut self, msr: u32, is_write: bool, intercept: bool) { + let (segment, msr_low) = if msr <= 0x1fff { + (0u32, msr) + } else if (0xc000_0000..=0xc000_1fff).contains(&msr) { + (1u32, msr & 0x1fff) + } else if (0xc001_0000..=0xc001_1fff).contains(&msr) { + (2u32, msr & 0x1fff) + } else { + unreachable!("MSR {:#x} Not supported by MSRPM", msr); + }; + + let base_offset = (segment * 2048) as usize; + + let byte_in_segment = (msr_low as usize) / 4; + let bit_pair_offset = ((msr_low & 0b11) * 2) as u8; // 0,2,4,6 + let bit_offset = bit_pair_offset + is_write as u8; // +0=读, +1=写 + + unsafe { + let byte_ptr = self + .frames + .as_mut_ptr() + .add(base_offset + byte_in_segment); + + let old = core::ptr::read_volatile(byte_ptr); + let new = if intercept { + old | (1u8 << bit_offset) + } else { + old & !(1u8 << bit_offset) + }; + core::ptr::write_volatile(byte_ptr, new); + } + } + + pub fn set_read_intercept(&mut self, msr: u32, intercept: bool) { + self.set_intercept(msr, false, intercept); + } + + pub fn set_write_intercept(&mut self, msr: u32, intercept: bool) { + self.set_intercept(msr, true, intercept); + } + +} \ No newline at end of file diff --git a/src/svm/vcpu.rs b/src/svm/vcpu.rs new file mode 100644 index 0000000..6ff435c --- /dev/null +++ b/src/svm/vcpu.rs @@ -0,0 +1,707 @@ +use alloc::collections::VecDeque; +use bit_field::BitField; +use core::fmt::{Debug, Formatter, Result}; +use core::{arch::naked_asm, mem::size_of}; +use core::arch::asm; +use raw_cpuid::CpuId; +use x86::controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}; +use x86::dtables::{self, DescriptorTablePointer}; +use x86::segmentation::SegmentSelector; +use x86_64::registers::control::{Cr0, Cr0Flags, Cr3, Cr4, Cr4Flags, EferFlags}; + +use axaddrspace::{GuestPhysAddr, GuestVirtAddr, HostPhysAddr, NestedPageFaultInfo}; +use axerrno::{AxResult, ax_err, ax_err_type}; +use axvcpu::{AccessWidth, AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; +use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; + +use super::definitions::SvmExitCode; +use super::structs::{VmcbFrame,IOPm, MSRPm}; +use super::vmcb::{NestedCtl,VmcbTlbControl,SvmExitInfo, VmcbCleanBits,set_vmcb_segment}; +use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters}; + +const QEMU_EXIT_PORT: u16 = 0x604; +const QEMU_EXIT_MAGIC: u64 = 0x2000; +pub struct XState { + host_xcr0: u64, + guest_xcr0: u64, + host_xss: u64, + guest_xss: u64, + + xsave_available: bool, + xsaves_available: bool, +} + +#[derive(PartialEq, Eq, Debug)] +pub enum VmCpuMode { + Real, + Protected, + Compatibility, // IA-32E mode (CS.L = 0) + Mode64, // IA-32E mode (CS.L = 1) +} + +impl XState { + /// Create a new [`XState`] instance with current host state + fn new() -> Self { + // Check if XSAVE is available + let xsave_available = Self::xsave_available(); + // Check if XSAVES and XRSTORS (as well as IA32_XSS) are available + let xsaves_available = if xsave_available { + Self::xsaves_available() + } else { + false + }; + + // Read XCR0 iff XSAVE is available + let xcr0 = if xsave_available { + unsafe { xcr0_read().bits() } + } else { + 0 + }; + // Read IA32_XSS iff XSAVES is available + let xss = if xsaves_available { + Msr::IA32_XSS.read() + } else { + 0 + }; + + Self { + host_xcr0: xcr0, + guest_xcr0: xcr0, + host_xss: xss, + guest_xss: xss, + xsave_available, + xsaves_available, + } + } + + /// Enable extended processor state management instructions, including XGETBV and XSAVE. + pub fn enable_xsave() { + if Self::xsave_available() { + unsafe { Cr4::write(Cr4::read() | Cr4Flags::OSXSAVE) }; + } + } + + /// Check if XSAVE is available on the current CPU. + pub fn xsave_available() -> bool { + let cpuid = CpuId::new(); + cpuid + .get_feature_info() + .map(|f| f.has_xsave()) + .unwrap_or(false) + } + + /// Check if XSAVES and XRSTORS (as well as IA32_XSS) are available on the current CPU. + pub fn xsaves_available() -> bool { + let cpuid = CpuId::new(); + cpuid + .get_extended_state_info() + .map(|f| f.has_xsaves_xrstors()) + .unwrap_or(false) + } + + /// Save the current host XCR0 and IA32_XSS values and load the guest values. + pub fn switch_to_guest(&mut self) { + unsafe { + if self.xsave_available { + self.host_xcr0 = xcr0_read().bits(); + xcr0_write(Xcr0::from_bits_unchecked(self.guest_xcr0)); + + if self.xsaves_available { + self.host_xss = Msr::IA32_XSS.read(); + Msr::IA32_XSS.write(self.guest_xss); + } + } + } + } + + /// Save the current guest XCR0 and IA32_XSS values and load the host values. + pub fn switch_to_host(&mut self) { + unsafe { + if self.xsave_available { + self.guest_xcr0 = xcr0_read().bits(); + xcr0_write(Xcr0::from_bits_unchecked(self.host_xcr0)); + + if self.xsaves_available { + self.guest_xss = Msr::IA32_XSS.read(); + Msr::IA32_XSS.write(self.host_xss); + } + } + } + } +} + +pub struct SvmVcpu { + // DO NOT modify `guest_regs` and `host_stack_top` and their order unless you do know what you are doing! + // DO NOT add anything before or between them unless you do know what you are doing! + guest_regs: GeneralRegisters, + host_stack_top: u64, + launched: bool, + vmcb: VmcbFrame, + iopm: IOPm, + msrpm: MSRPm, + pending_events: VecDeque<(u8, Option)>, + xstate: XState, + entry: Option, + npt_root: Option, + // is_host: bool, temporary removed because we don't care about type 1.5 now +} + +impl SvmVcpu { + /// Create a new [`SvmVcpu`]. + pub fn new() -> AxResult { + let vcpu = Self { + guest_regs: GeneralRegisters::default(), + host_stack_top: 0, + launched: false, + vmcb:VmcbFrame::new()?, + iopm: IOPm::passthrough_all()?, + msrpm: MSRPm::passthrough_all()?, + pending_events: VecDeque::with_capacity(8), + xstate: XState::new(), + entry: None, + npt_root: None, + // is_host: false, + }; + info!("[HV] created SvmVcpu(vmcb: {:#x})", vcpu.vmcb.phys_addr()); + Ok(vcpu) + } + + /// Set the new [`SvmVcpu`] context from guest OS. + pub fn setup(&mut self, npt_root: HostPhysAddr, entry: GuestPhysAddr) -> AxResult { + self.setup_vmcb(entry, npt_root)?; + Ok(()) + } + + /// No operation is needed for SVM binding. + /// + /// Unlike VMX which requires VMCS to be loaded via VMPTRLD, + /// SVM uses the `VMRUN` instruction and takes the VMCB physical address + /// from the `RAX` register at the moment of execution. + /// + /// Since `RAX` is a volatile register and may be clobbered during normal execution, + /// it is unsafe to set `RAX` earlier and rely on it later. + /// Therefore, the correct place to set `RAX` is right before `VMRUN`, + /// inside the actual launch/resume assembly code. + /// + /// This function is kept for interface consistency but performs no action. + pub fn bind_to_current_processor(&self) -> AxResult { + Ok(()) + } + + /// No operation is needed for SVM unbinding. + /// + /// SVM does not maintain a per-CPU binding state like VMX (e.g., via VMPTRLD). + /// Once `VMEXIT` occurs, the VCPU state is saved to the VMCB, and no + /// unbinding step is required. + /// + /// This function is kept for interface compatibility. + pub fn unbind_from_current_processor(&self) -> AxResult { + Ok(()) + } + + pub fn get_cpu_mode(&self) -> VmCpuMode { + let vmcb = unsafe { self.vmcb.as_vmcb() }.state; + + let ia32_efer = vmcb.efer.get(); + let cs_attr = vmcb.cs.attr.get(); + let cr0 = vmcb.cr0.get(); + + if (ia32_efer & (1 << 10)) != 0 { + if (cs_attr & (1 << 13)) != 0 { + // CS.L = 1 + VmCpuMode::Mode64 + } else { + VmCpuMode::Compatibility + } + } else if (cr0 & (1 << 0)) != 0 { + // CR0.PE = 1 + VmCpuMode::Protected + } else { + VmCpuMode::Real + } + } + + pub fn inner_run(&mut self) -> Option { + // Inject pending events + if self.launched { + self.inject_pending_events().unwrap(); + } + + // Run guest + self.load_guest_xstate(); + + unsafe { + self.svm_run(); + + } + + self.load_host_xstate(); + + // Handle vm-exits + let exit_info = self.exit_info().unwrap(); + // debug!("VM exit: {:#x?}", exit_info); + + match self.builtin_vmexit_handler(&exit_info) { + Some(result) => { + if result.is_err() { + panic!( + "VmxVcpu failed to handle a VM-exit that should be handled by itself: {:?}, error {:?}, vcpu: {:#x?}", + exit_info.exit_info_1, + result.unwrap_err(), + self + ); + } + None + } + None => Some(exit_info), + } + } + + pub fn exit_info(&self) -> AxResult { + unsafe { self.vmcb.as_vmcb().exit_info() } + } + + pub fn raw_interrupt_exit_info(&self) -> AxResult { + todo!() + } + + // pub fn interrupt_exit_info(&self) -> AxResult { + // todo!() + // } + + // pub fn io_exit_info(&self) -> AxResult { + // todo!() + // } + + pub fn nested_page_fault_info(&self) -> AxResult { + todo!() + } + + pub fn regs(&self) -> &GeneralRegisters { + &self.guest_regs + } + + pub fn regs_mut(&mut self) -> &mut GeneralRegisters { + &mut self.guest_regs + } + + pub fn stack_pointer(&self) -> usize { + todo!() + } + + pub fn set_stack_pointer(&mut self, rsp: usize) { + todo!() + } + + pub fn gla2gva(&self, guest_rip: GuestVirtAddr) -> GuestVirtAddr { + let vmcb = unsafe { self.vmcb.as_vmcb() }; + let cpu_mode = self.get_cpu_mode(); + let seg_base = if cpu_mode == VmCpuMode::Mode64 { + 0 + } else { + vmcb.state.cs.base.get() + }; + guest_rip + seg_base as usize + } + + pub fn get_ptw_info(&self) -> GuestPageWalkInfo { + todo!() + } + + pub fn rip(&self) -> usize { + todo!() + } + + pub fn cs(&self) -> u16 { + todo!() + } + + pub fn advance_rip(&mut self, instr_len: u8) -> AxResult { + todo!() + } + + pub fn queue_event(&mut self, vector: u8, err_code: Option) { + todo!() + } + + pub fn set_interrupt_window(&mut self, enable: bool) -> AxResult { + todo!() + } + + pub fn set_io_intercept_of_range(&mut self, port_base: u32, count: u32, intercept: bool) { + todo!() + } + + pub fn set_msr_intercept_of_range(&mut self, msr: u32, intercept: bool) { + todo!() + } +} + +// Implementation of private methods +impl SvmVcpu { + fn setup_io_bitmap(&mut self) -> AxResult { + todo!() + } + + #[allow(dead_code)] + fn setup_msr_bitmap(&mut self) -> AxResult { + todo!() + } + + fn setup_vmcb(&mut self, entry: GuestPhysAddr, npt_root: HostPhysAddr) -> AxResult { + self.bind_to_current_processor()?; + self.setup_vmcb_guest(entry)?; + self.setup_vmcb_control(npt_root, true)?; + self.unbind_from_current_processor()?; + Ok(()) + } + + + fn setup_vmcb_guest(&mut self, entry: GuestPhysAddr) -> AxResult { + info!("[AxVM] Setting up VMCB for guest at {:#x}", entry); + let cr0_val: Cr0Flags = + Cr0Flags::NOT_WRITE_THROUGH | Cr0Flags::CACHE_DISABLE | Cr0Flags::EXTENSION_TYPE; + info!("here??????????????????"); + self.set_cr(0, cr0_val.bits()); + self.set_cr(4, 0); + + let st = unsafe { self.vmcb.as_vmcb() }.state; + + macro_rules! seg { + ($seg:ident, $attr:expr) => { + set_vmcb_segment(&mut st.$seg, 0, $attr); + }; + } + seg!(es, 0x93); seg!(cs, 0x9b); seg!(ss, 0x93); seg!(ds, 0x93); + seg!(fs, 0x93); seg!(gs, 0x93); seg!(tr, 0x8b); seg!(ldtr, 0x82); + + // GDTR / IDTR + st.gdtr.base.set(0); st.gdtr.limit.set(0xffff); + st.idtr.base.set(0); st.idtr.limit.set(0xffff); + + // 关键寄存器与指针 + st.cr3.set(0); + st.dr7.set(0x400); + st.rsp.set(0); + st.rip.set(entry.as_usize() as u64); + st.rflags.set(0x2); // bit1 必须为 1 + st.dr6.set(0); // Pending-DBG-Exceptions 对应 0 + + // SYSENTER MSRs + st.sysenter_cs.set(0); + st.sysenter_esp.set(0); + st.sysenter_eip.set(0); + + // MSR / PAT / EFER + st.efer.set(0 | EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE.bits()); // 必须置 SVME 位 + st.g_pat.set(Msr::IA32_PAT.read()); + + st.cpl.set(0); + st.star.set(0); + st.lstar.set(0); + st.cstar.set(0); + st.sfmask.set(0); + st.kernel_gs_base.set(Msr::IA32_KERNEL_GSBASE.read()); + st.rax.set(0); // hypervisor 返回值 + + Ok(()) + } + + fn setup_vmcb_control(&mut self, npt_root: HostPhysAddr, is_guest: bool) -> AxResult { + let ct = unsafe { self.vmcb.as_vmcb() }.control; // control-area 速记别名 + // ──────────────────────────────────────────────────────── + // 1) 基本运行环境:Nested Paging / ASID / Clean Bits / TLB + // ──────────────────────────────────────────────────────── + + // ① 开启 Nested Paging(AMD 对应 Intel 的 EPT) + // → set bit 0 of NESTED_CTL + ct.nested_ctl.modify(NestedCtl::NP_ENABLE::SET); + + // ② guest ASID:NPT 使用的 TLB 标签 + ct.guest_asid.set(1); + + // ③ 嵌套 CR3(NPT root PA) + ct.nested_cr3.set(npt_root.as_usize() as u64); + + // ④ Clean-Bits:0 = “全部脏” ⇒ 第一次 VMRUN 必定重新加载 save-area + ct.clean_bits.set(0); + + // ⑤ TLB Control:0 = NONE, 1 = FLUSH-ASID, 3 = FLUSH-ALL + ct.tlb_control.modify(VmcbTlbControl::FlushGuestTlb::SET); + + // ──────────────────────────────────────────────────────── + // 2) 选择要拦截的指令 / 事件 + // (相当于 VMX 的 Pin-based / Primary / Secondary CTLS) + // ──────────────────────────────────────────────────────── + + use super::definitions::SvmIntercept; // 你自己定义的枚举 + + for intc in &[ + SvmIntercept::NMI, // 非屏蔽中断 + SvmIntercept::CPUID, // CPUID 指令 + SvmIntercept::SHUTDOWN, // HLT 时 Triple-Fault + SvmIntercept::VMRUN, // 来宾企图再次 VMRUN + SvmIntercept::VMMCALL, // Hypercall + SvmIntercept::VMLOAD, + SvmIntercept::VMSAVE, + SvmIntercept::STGI, // 设置全局中断 + SvmIntercept::CLGI, // 清除全局中断 + SvmIntercept::SKINIT, // 安全启动 + ] { + ct.set_intercept(*intc); + } + Ok(()) + } + // 如果你用 bitfield 方式,也可以: + // ct.intercept_vector3.modify(InterceptVec3::NMI::SET + InterceptVec3::VINTR::SET); + + fn get_paging_level(&self) -> usize { + todo!() + } + +} +// Implementaton for type1.5 hypervisor +// #[cfg(feature = "type1_5")] +impl SvmVcpu { + + pub fn set_cr(&mut self, cr_idx: usize, val: u64) -> AxResult { + let mut vmcb = unsafe { self.vmcb.as_vmcb() }; + info!("here??????????????????"); + + match cr_idx { + 0 => vmcb.state.cr0.set(val), + 3 => vmcb.state.cr3.set(val), + 4 => vmcb.state.cr4.set(val), + _ => return ax_err!(InvalidInput, format_args!("Unsupported CR{}", cr_idx)), + } + + Ok(()) + } + #[allow(dead_code)] + fn cr(&self, cr_idx: usize) -> usize { + let mut vmcb = unsafe { self.vmcb.as_vmcb() }; + (|| -> AxResult { + Ok(match cr_idx { + 0 => vmcb.state.cr0.get() as usize, + 3 => vmcb.state.cr3.get() as usize, + 4 => vmcb.state.cr4.get() as usize, + _ => unreachable!(), + }) + })() + .expect("Failed to read guest control register") + } +} + +impl SvmVcpu { + + // unsafe extern "C" fn svm_run(&mut self) -> usize { + // let vmcb_phy = self.vmcb.phys_addr().as_usize() as u64; + // + // unsafe { + // naked_asm!( + // save_regs_to_stack!(), + // // "clgi", // 清除中断,确保 SVM 运行不中断 + // "mov [rdi + {host_stack_size}], rsp", // save current RSP to Vcpu::host_stack_top + // "mov rsp, rdi", // set RSP to guest regs area + // restore_regs_from_stack!(), // restore guest status + // "mov rax,{vmcb}", + // "vmload rax", + // "vmrun rax", + // "jmp {failed}", + // host_stack_size = const size_of::(), + // failed = sym Self::svm_entry_failed, + // vmcb = in(reg) vmcb_phy, // 正确绑定 vmcb 变量 + // // options(noreturn), + // ); + // } + // 0 + // } + + pub unsafe fn svm_run(&mut self) -> usize { + let vmcb = self.vmcb.phys_addr().as_usize() as u64; + let guest_regs = self.regs_mut(); + // panic!("{:x}",vmcb); + // panic!("SVM run not implemented yet"); + loop{}; + asm!( + // "clgi", + "mov rax, {0}", + "vmload rax", + "vmrun rax", + // "call {entry}", + // in(reg) guest_regs, + in(reg) vmcb, + // entry = sym Self::svm_entry, + options(noreturn), + ); + } + + #[naked] + unsafe extern "C" fn svm_entry() -> ! { + naked_asm!( + "ud2", + // "mov [rdi + {host_stack_size}], rsp", + // "mov rsp, rdi", + // // restore_regs_from_stack!(), + // "vmload rax", + // "vmrun rax", + "jmp {failed}", + // host_stack_size = const size_of::(), + failed = sym Self::svm_entry_failed, + ) + } + + + + #[naked] + /// Return after vm-exit. + /// + /// The return value is a dummy value. + unsafe extern "C" fn svm_exit(&mut self) -> usize { + unsafe { + naked_asm!( + save_regs_to_stack!(), // save guest status + "mov rsp, [rsp + {host_stack_top}]", // set RSP to Vcpu::host_stack_top + restore_regs_from_stack!(), // restore host status + "ret", + host_stack_top = const size_of::(), + ); + } + + } + + + fn svm_entry_failed() -> ! { + panic!("svm_entry_failed"); + } + + fn allow_interrupt(&self) -> bool { + todo!() + } + + fn inject_pending_events(&mut self) -> AxResult { + todo!() + } + + fn builtin_vmexit_handler(&mut self, exit_info: &SvmExitInfo) -> Option { + let exit_code = match exit_info.exit_code { + Ok(code) => code, + Err(code) => { + error!("Unknown #VMEXIT exit code: {:#x}", code); + panic!("wrong code"); + } + }; + + match exit_code { + SvmExitCode::CPUID => Some(self.handle_cpuid()), + _ => None, + } + + // + // let res = match exit_code { + // SvmExitCode::EXCP(vec) => self.handle_exception(vec, &exit_info), + // SvmExitCode::NMI => self.handle_nmi(), + // SvmExitCode::CPUID => self.handle_cpuid(), + // SvmExitCode::VMMCALL => self.handle_hypercall(), + // SvmExitCode::NPF => self.handle_nested_page_fault(&exit_info), + // SvmExitCode::MSR => match exit_info.exit_info_1 { + // 0 => self.handle_msr_read(), + // 1 => self.handle_msr_write(), + // _ => panic!("MSR can't handle"), + // }, + // SvmExitCode::SHUTDOWN => { + // error!("#VMEXIT(SHUTDOWN): {:#x?}", exit_info); + // self.cpu_data.vcpu.inject_fault()?; + // Ok(()) + // } + // _ => panic!("code can't handle"), + // }; + } + + fn handle_svm_preemption_timer(&mut self) -> AxResult { + todo!() + } + + fn handle_cr(&mut self) -> AxResult { + todo!() + } + + fn handle_cpuid(&mut self) -> AxResult { + todo!() + } + + fn handle_xsetbv(&mut self) -> AxResult { + todo!() + } + + fn load_guest_xstate(&mut self) { + self.xstate.switch_to_guest(); + } + + fn load_host_xstate(&mut self) { + self.xstate.switch_to_host(); + } +} + +impl Drop for SvmVcpu { + fn drop(&mut self) { + todo!() + } +} + +impl core::fmt::Debug for SvmVcpu { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + todo!() + } +} + +impl AxArchVCpu for SvmVcpu { + type CreateConfig = (); + type SetupConfig = (); + + fn new(_config: Self::CreateConfig) -> AxResult { + Self::new() + } + + + fn set_entry(&mut self, entry: GuestPhysAddr) -> AxResult { + self.entry = Some(entry); + Ok(()) + } + + fn set_ept_root(&mut self, ept_root: HostPhysAddr) -> AxResult { + self.npt_root = Some(ept_root); + Ok(()) + } + + fn setup(&mut self, _config: Self::SetupConfig) -> AxResult { + self.setup_vmcb(self.entry.unwrap(), self.npt_root.unwrap()) + } + + fn run(&mut self) -> AxResult { + match self.inner_run() { + Some(exit_info) => { + warn!("VMX unsupported VM-Exit: {:#x?}",exit_info.exit_info_1); + warn!("VCpu {:#x?}", self); + Ok(AxVCpuExitReason::Halt) + } + _ => Ok(AxVCpuExitReason::Halt) + } + } + + + fn bind(&mut self) -> AxResult { + self.bind_to_current_processor() + } + + fn unbind(&mut self) -> AxResult { + self.launched = false; + self.unbind_from_current_processor() + } + + fn set_gpr(&mut self, reg: usize, val: usize) { + self.regs_mut().set_reg_of_index(reg as u8, val as u64); + } +} + + diff --git a/src/svm/vmcb.rs b/src/svm/vmcb.rs new file mode 100644 index 0000000..5e11272 --- /dev/null +++ b/src/svm/vmcb.rs @@ -0,0 +1,391 @@ +// vmcb.rs — AMD‑SVM Virtual‑Machine Control Block helpers +// +// This is the SVM counterpart of `vmcs.rs`. A VMCB is a single 4‑KiB page +// split into a 1024‑byte Control Area (offset 0x0) and a 3‑KiB +// State‑Save Area (offset 0x400). Unlike Intel VMCS, each field is +// located at a fixed offset inside the page. That means the hypervisor can +// touch the fields with normal memory operations – no special I/O encoding or +// `VMWRITE/VMREAD` instructions are required. +// +// We use tock‑registers to generate strongly‑typed register proxies so you +// get: +// • type‑safe read/write helpers (no accidental mix‑ups) +// • handy bitfield accessors for intercept vectors, event injection, … +// • zero‑cost abstractions that compile to plain loads/stores. +// +// Reference: AMD 64 APM v2,Appendix B VMCB Layout + +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(dead_code)] + +use tock_registers::registers::ReadWrite; +use tock_registers::{register_bitfields, register_structs}; + +use axaddrspace::HostPhysAddr; +use axerrno::AxResult; + +use axvcpu::AxVCpuHal; +use memory_addr::MemoryAddr; +use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use super::structs::VmcbFrame; // the user‑supplied wrapper that owns the backing page +use super::definitions::{SvmIntercept,SvmExitCode}; +// ───────────────────────────────────────────────────────────────────────────── +// Control‑area bitfields +// ───────────────────────────────────────────────────────────────────────────── + + + + +register_bitfields![u32, + // vector 0 + pub InterceptCrRw [ + READ_CR0 0, READ_CR3 3, READ_CR4 4, READ_CR8 8, + WRITE_CR0 16, WRITE_CR3 19, WRITE_CR4 20, WRITE_CR8 24, + ], + + // vector 1 + pub InterceptDrRw [ + READ_DR0 0, READ_DR7 7, + WRITE_DR0 16, WRITE_DR7 23, + ], + + // vector 2 + pub InterceptExceptions [ + DE 0, DB 1, BP 3, OF 4, DF 8, GP 13, PF 14, MC 18, + ], + + /// Vector 3 (offset 0x000C) + pub InterceptVec3 [ + INTR 0, NMI 1, SMI 2, INIT 3, + VINTR 4, CR0_SEL_WRITE 5, IDTR_READ 6, GDTR_READ 7, + LDTR_READ 8, TR_READ 9, IDTR_WRITE 10, GDTR_WRITE 11, + LDTR_WRITE 12, TR_WRITE 13, RDTSC 14, RDPMC 15, + PUSHF 16, POPF 17, CPUID 18, RSM 19, + IRET 20, SWINT 21, INVD 22, PAUSE 23, + HLT 24, INVLPG 25, INVLPGA 26, IOIO_PROT 27, + MSR_PROT 28, TASK_SWITCH 29, FERR_FREEZE 30, SHUTDOWN 31, + ], + + /// Vector 4 (offset 0x0010) + pub InterceptVec4 [ + VMRUN 0, VMMCALL 1, VMLOAD 2, VMSAVE 3, + STGI 4, CLGI 5, SKINIT 6, RDTSCP 7, + ICEBP 8, WBINVD 9, MONITOR 10, MWAIT 11, + MWAIT_CONDITIONAL 12, XSETBV 13, RDPRU 14, EFER_WRITE_TRAP 15, + ], + + /// Vector 5 (offset 0x0014) + pub InterceptVec5 [ + INVLPGB 0, INVLPGB_ILLEGAL 1, INVPCID 2, + MCOMMIT 3, TLBSYNC 4, + ], + // VMCB Clean-Bits 15.15.3 + pub VmcbCleanBits [ + INTERCEPTS 0, + IOPM 1, + ASID 2, + TPR 3, + NP 4, + CRx 5, + DRx 6, + DT 7, + SEG 8, + CR2 9, + LBR 10, + AVIC 11, + CET 12, + ], +]; + +register_bitfields![u64, + pub NestedCtl [ + NP_ENABLE 0, + SEV_ENABLE 1, + SEV_ES_ENABLE 2, + GMET_ENABLE 3, // Guest-Mode-Exec-Trap + SSCheckEn 4, + VTE_ENABLE 5, // Virtual Transparent Encryption + RO_GPT_EN 6, // Read-Only Guest Page Tables + INVLPGB_TLBSYNC 7, + ], +]; + +register_bitfields![u8, + pub VmcbTlbControl [ + DoNothing 0, + FlushAllOnVmrun 1, + FlushGuestTlb 3, + FlushGuestNonGlobalTlb 7, + ] +]; + +register_structs![ + pub VmcbControlArea { + (0x0000 => pub intercept_cr: ReadWrite), + (0x0004 => pub intercept_dr: ReadWrite), + + (0x0008 => pub intercept_exceptions: ReadWrite), + (0x000C => pub intercept_vector3: ReadWrite), + (0x0010 => pub intercept_vector4: ReadWrite), + (0x0014 => pub intercept_vector5: ReadWrite), + (0x0018 => _reserved_0018), + (0x003C => pub pause_filter_thresh: ReadWrite), + (0x003E => pub pause_filter_count: ReadWrite), + + (0x0040 => pub iopm_base_pa: ReadWrite), + (0x0048 => pub msrpm_base_pa: ReadWrite), + (0x0050 => pub tsc_offset: ReadWrite), + + (0x0058 => pub guest_asid: ReadWrite), + (0x005C => pub tlb_control: ReadWrite), + (0x005D => _reserved_005D), + + (0x0060 => pub int_control: ReadWrite), + (0x0064 => pub int_vector: ReadWrite), + (0x0068 => pub int_state: ReadWrite), + (0x006C => _reserved_006C), + + // ───── VMEXIT --------------------------------------------------- + (0x0070 => pub exit_code: ReadWrite), + (0x0078 => pub exit_info_1: ReadWrite), + (0x0080 => pub exit_info_2: ReadWrite), + // 15.7.2 + (0x0088 => pub exit_int_info: ReadWrite), + (0x008C => pub exit_int_info_err: ReadWrite), + + // ───── Nested Paging / AVIC ----------------------------------------- + (0x0090 => pub nested_ctl: ReadWrite), + + // 0x0098 — AVIC_VAPIC_BAR(APIC-access BAR,only 40 bit are vaild) + (0x0098 => pub avic_vapic_bar: ReadWrite), + + // 0x00A0 — GHCB guest-physical address + (0x00A0 => pub ghcb_gpa: ReadWrite), + + // ── Event-injection / Nested CR3 / LBR -------------------------------- + (0x00A8 => pub event_inj: ReadWrite), + (0x00AC => pub event_inj_err: ReadWrite), + (0x00B0 => pub nested_cr3: ReadWrite), + (0x00B8 => pub virt_ext: ReadWrite), // LBR-control & V-VMLOAD/VMSAVE + + // ── Clean-bits & Next-RIP -------------------------------------------- + (0x00C0 => pub clean_bits: ReadWrite), + (0x00C4 => pub _rsvd5: ReadWrite), + (0x00C8 => pub next_rip: ReadWrite), + + // ── Decoded-instruction cache ---------------------------------------- + (0x00D0 => pub insn_len: ReadWrite), + (0x00D1 => pub insn_bytes: [ReadWrite; 15]), + + // ── AVIC extra -------------------------------------------------------- + (0x00E0 => pub avic_backing_page: ReadWrite), + (0x00E8 => _reserved_00E8), + + (0x00F0 => pub avic_logical_id: ReadWrite), + (0x00F8 => pub avic_physical_id: ReadWrite), + (0x0100 => _reserved_0100), + + (0x0108 => pub vmsa_pa: ReadWrite), // SEV-ES guest only + (0x0110 => _reserved_0110), + + (0x0120 => pub bus_lock_counter: ReadWrite), + (0x0122 => _reserved_0122), + + (0x0138 => pub allowed_sev_features: ReadWrite), + (0x0140 => pub guest_sev_features: ReadWrite), + (0x0148 => _reserved_0148), + + (0x0400 => @END), + } +]; + +register_structs![ + pub VmcbSegment { + (0x0 => pub selector: ReadWrite), + (0x2 => pub attr: ReadWrite), + (0x4 => pub limit: ReadWrite), + (0x8 => pub base: ReadWrite), + (0x10 => @END), + } +]; + +register_structs![ + pub VmcbStateSaveArea { + (0x0000 => pub es: VmcbSegment), + (0x0010 => pub cs: VmcbSegment), + (0x0020 => pub ss: VmcbSegment), + (0x0030 => pub ds: VmcbSegment), + (0x0040 => pub fs: VmcbSegment), + (0x0050 => pub gs: VmcbSegment), + (0x0060 => pub gdtr: VmcbSegment), + (0x0070 => pub ldtr: VmcbSegment), + (0x0080 => pub idtr: VmcbSegment), + (0x0090 => pub tr: VmcbSegment), + (0x00A0 => _reserved_00A0), + + (0x00CB => pub cpl: ReadWrite), + (0x00CC => _reserved_00CC), + + (0x00D0 => pub efer: ReadWrite), + (0x00D8 => _reserved_00D8), + + (0x0148 => pub cr4: ReadWrite), + (0x0150 => pub cr3: ReadWrite), + (0x0158 => pub cr0: ReadWrite), + (0x0160 => pub dr7: ReadWrite), + (0x0168 => pub dr6: ReadWrite), + (0x0170 => pub rflags:ReadWrite), + (0x0178 => pub rip: ReadWrite), + (0x0180 => _reserved_0180), + + (0x01D8 => pub rsp: ReadWrite), + (0x01E0 => pub s_cet: ReadWrite), + (0x01E8 => pub ssp: ReadWrite), + (0x01F0 => pub isst_addr: ReadWrite), + (0x01F8 => pub rax: ReadWrite), + + (0x0200 => pub star: ReadWrite), + (0x0208 => pub lstar: ReadWrite), + (0x0210 => pub cstar: ReadWrite), + (0x0218 => pub sfmask: ReadWrite), + (0x0220 => pub kernel_gs_base:ReadWrite), + (0x0228 => pub sysenter_cs: ReadWrite), + (0x0230 => pub sysenter_esp: ReadWrite), + (0x0238 => pub sysenter_eip: ReadWrite), + (0x0240 => pub cr2: ReadWrite), + (0x0248 => _reserved_0248), + + (0x0268 => pub g_pat: ReadWrite), + (0x0270 => pub dbgctl: ReadWrite), + (0x0278 => pub br_from: ReadWrite), + (0x0280 => pub br_to: ReadWrite), + (0x0288 => pub last_excp_from:ReadWrite), + (0x0290 => pub last_excp_to: ReadWrite), + (0x0298 => _reserved_0298), + + (0x0FFF => @END), + } +]; + +/// Unified façade returning typed accessors to both halves of the VMCB. +pub struct Vmcb<'a> { + pub control: &'a mut VmcbControlArea, + pub state: &'a mut VmcbStateSaveArea, +} + +impl VmcbFrame { + /// # Safety + /// caller must guarantee the page is mapped + pub unsafe fn as_vmcb<'a>(&'a self) -> Vmcb<'a> { + let base = self.as_mut_ptr(); + + Vmcb { + control: &mut *(base as *mut VmcbControlArea), + state: &mut *(base.add(0x400) as *mut VmcbStateSaveArea), + } + } +} + +impl Vmcb <'_>{ + /// Zero‑initialise the control area + pub fn clear_control(&mut self) { + unsafe { core::ptr::write_bytes(self.control as *mut _ as *mut u8, 0, 0x400) }; + } + pub fn clean_bits(&mut self)-> &mut ReadWrite { + &mut self.control.clean_bits + } +} + +pub fn set_vmcb_segment(seg: &mut VmcbSegment, selector: u16, attr: u16) { + seg.selector.set(selector); // 一般初始化阶段都传 0 + seg.base.set(0); // 实模式/平坦段:基址 0 + seg.limit.set(0xFFFF); // 64 KiB 段界限 + seg.attr.set(attr); // AR 字节(0x93, 0x9B, 0x8B, 0x82 …) +} + +impl VmcbControlArea { + pub fn set_intercept(&mut self, intc: SvmIntercept) { + use super::definitions::SvmIntercept::*; + match intc { + // ── Vector 3 ─────────────────────────────── + INTR => self.intercept_vector3.modify(InterceptVec3::INTR::SET), + NMI => self.intercept_vector3.modify(InterceptVec3::NMI::SET), + SMI => self.intercept_vector3.modify(InterceptVec3::SMI::SET), + INIT => self.intercept_vector3.modify(InterceptVec3::INIT::SET), + VINTR => self.intercept_vector3.modify(InterceptVec3::VINTR::SET), + CR0_SEL_WRITE => self.intercept_vector3.modify(InterceptVec3::CR0_SEL_WRITE::SET), + IDTR_READ => self.intercept_vector3.modify(InterceptVec3::IDTR_READ::SET), + GDTR_READ => self.intercept_vector3.modify(InterceptVec3::GDTR_READ::SET), + LDTR_READ => self.intercept_vector3.modify(InterceptVec3::LDTR_READ::SET), + TR_READ => self.intercept_vector3.modify(InterceptVec3::TR_READ::SET), + IDTR_WRITE => self.intercept_vector3.modify(InterceptVec3::IDTR_WRITE::SET), + GDTR_WRITE => self.intercept_vector3.modify(InterceptVec3::GDTR_WRITE::SET), + LDTR_WRITE => self.intercept_vector3.modify(InterceptVec3::LDTR_WRITE::SET), + TR_WRITE => self.intercept_vector3.modify(InterceptVec3::TR_WRITE::SET), + RDTSC => self.intercept_vector3.modify(InterceptVec3::RDTSC::SET), + RDPMC => self.intercept_vector3.modify(InterceptVec3::RDPMC::SET), + PUSHF => self.intercept_vector3.modify(InterceptVec3::PUSHF::SET), + POPF => self.intercept_vector3.modify(InterceptVec3::POPF::SET), + CPUID => self.intercept_vector3.modify(InterceptVec3::CPUID::SET), + RSM => self.intercept_vector3.modify(InterceptVec3::RSM::SET), + IRET => self.intercept_vector3.modify(InterceptVec3::IRET::SET), + SWINT => self.intercept_vector3.modify(InterceptVec3::SWINT::SET), + INVD => self.intercept_vector3.modify(InterceptVec3::INVD::SET), + PAUSE => self.intercept_vector3.modify(InterceptVec3::PAUSE::SET), + HLT => self.intercept_vector3.modify(InterceptVec3::HLT::SET), + INVLPG => self.intercept_vector3.modify(InterceptVec3::INVLPG::SET), + INVLPGA => self.intercept_vector3.modify(InterceptVec3::INVLPGA::SET), + IOIO_PROT => self.intercept_vector3.modify(InterceptVec3::IOIO_PROT::SET), + MSR_PROT => self.intercept_vector3.modify(InterceptVec3::MSR_PROT::SET), + TASK_SWITCH => self.intercept_vector3.modify(InterceptVec3::TASK_SWITCH::SET), + FERR_FREEZE => self.intercept_vector3.modify(InterceptVec3::FERR_FREEZE::SET), + SHUTDOWN => self.intercept_vector3.modify(InterceptVec3::SHUTDOWN::SET), + + // ── Vector 4 ─────────────────────────────── + VMRUN => self.intercept_vector4.modify(InterceptVec4::VMRUN::SET), + VMMCALL => self.intercept_vector4.modify(InterceptVec4::VMMCALL::SET), + VMLOAD => self.intercept_vector4.modify(InterceptVec4::VMLOAD::SET), + VMSAVE => self.intercept_vector4.modify(InterceptVec4::VMSAVE::SET), + STGI => self.intercept_vector4.modify(InterceptVec4::STGI::SET), + CLGI => self.intercept_vector4.modify(InterceptVec4::CLGI::SET), + SKINIT => self.intercept_vector4.modify(InterceptVec4::SKINIT::SET), + RDTSCP => self.intercept_vector4.modify(InterceptVec4::RDTSCP::SET), + ICEBP => self.intercept_vector4.modify(InterceptVec4::ICEBP::SET), + WBINVD => self.intercept_vector4.modify(InterceptVec4::WBINVD::SET), + MONITOR => self.intercept_vector4.modify(InterceptVec4::MONITOR::SET), + MWAIT => self.intercept_vector4.modify(InterceptVec4::MWAIT::SET), + MWAIT_CONDITIONAL => self.intercept_vector4.modify(InterceptVec4::MWAIT_CONDITIONAL::SET), + XSETBV => self.intercept_vector4.modify(InterceptVec4::XSETBV::SET), + RDPRU => self.intercept_vector4.modify(InterceptVec4::RDPRU::SET), + EFER_WRITE_TRAP => self.intercept_vector4.modify(InterceptVec4::EFER_WRITE_TRAP::SET), + + // ── Vector 5 ─────────────────────────────── + INVLPGB => self.intercept_vector5.modify(InterceptVec5::INVLPGB::SET), + INVLPGB_ILLEGAL => self.intercept_vector5.modify(InterceptVec5::INVLPGB_ILLEGAL::SET), + INVPCID => self.intercept_vector5.modify(InterceptVec5::INVPCID::SET), + MCOMMIT => self.intercept_vector5.modify(InterceptVec5::MCOMMIT::SET), + TLBSYNC => self.intercept_vector5.modify(InterceptVec5::TLBSYNC::SET), + } + } +} + +pub struct SvmExitInfo { + pub exit_code: core::result::Result, + pub exit_info_1: u64, + pub exit_info_2: u64, + pub guest_rip: u64, + pub guest_next_rip: u64, +} + +impl Vmcb <'_> { + pub fn exit_info(mut self) -> AxResult { + Ok(SvmExitInfo { + exit_code: self.control.exit_code.get().try_into(), + exit_info_1: self.control.exit_info_1.get(), + exit_info_2: self.control.exit_info_2.get(), + guest_rip: self.state.rip.get(), + guest_next_rip: self.control.next_rip.get(), + }) + } +} diff --git a/src/vmx/mod.rs b/src/vmx/mod.rs index 9fb40b7..e80688e 100644 --- a/src/vmx/mod.rs +++ b/src/vmx/mod.rs @@ -22,6 +22,7 @@ pub fn has_hardware_support() -> bool { } } + pub fn read_vmcs_revision_id() -> u32 { VmxBasic::read().revision_id } diff --git a/src/vmx/percpu.rs b/src/vmx/percpu.rs index 484a723..6e1e743 100644 --- a/src/vmx/percpu.rs +++ b/src/vmx/percpu.rs @@ -14,7 +14,6 @@ use crate::vmx::structs::{FeatureControl, FeatureControlFlags, VmxBasic, VmxRegi /// This structure holds the state information specific to a CPU core /// when operating in VMX mode, including the VMCS revision identifier and /// the VMX region. -#[derive(Debug)] pub struct VmxPerCpuState { /// The VMCS (Virtual Machine Control Structure) revision identifier. /// @@ -26,7 +25,7 @@ pub struct VmxPerCpuState { /// /// This region typically contains the VMCS and other state information /// required for managing virtual machines on this particular CPU. - vmx_region: VmxRegion, + vmx_region: VmxRegion, } impl AxArchPerCpu for VmxPerCpuState { @@ -69,10 +68,8 @@ impl AxArchPerCpu for VmxPerCpuState { ($value: expr, $crx: ident) => {{ use Msr::*; let value = $value; - paste::paste! { - let fixed0 = [].read(); - let fixed1 = [].read(); - } + let fixed0 = concat_idents!(IA32_VMX_, $crx, _FIXED0).read(); + let fixed1 = concat_idents!(IA32_VMX_, $crx, _FIXED1).read(); (!fixed0 | value != 0) && (fixed1 | !value != 0) }}; } @@ -141,80 +138,3 @@ impl AxArchPerCpu for VmxPerCpuState { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::mock::{MockMmHal, MockVCpuHal}; - use alloc::format; - use alloc::vec::Vec; - - #[test] - fn test_vmx_per_cpu_state_new() { - MockMmHal::reset(); // Reset before test - let result = VmxPerCpuState::::new(0); - assert!(result.is_ok()); - - let state = result.unwrap(); - assert_eq!(state.vmcs_revision_id, 0); - } - - #[test] - fn test_vmx_per_cpu_state_default_values() { - MockMmHal::reset(); // Reset before test - let state = VmxPerCpuState::::new(0).unwrap(); - - // Test that vmcs_revision_id is initialized to 0 - assert_eq!(state.vmcs_revision_id, 0); - - // The VMX region should be in an uninitialized state - // We can't test this directly as the field is private, - // but we can ensure the struct is created successfully - } - - #[test] - fn test_multiple_cpu_states_independence() { - MockMmHal::reset(); // Reset before test - let mut states = Vec::new(); - - // Create states for multiple CPUs - for cpu_id in 0..4 { - let state = VmxPerCpuState::::new(cpu_id).unwrap(); - states.push(state); - } - - // Test independence by modifying one state and verifying others are unaffected - states[0].vmcs_revision_id = 0x12345678; - states[1].vmcs_revision_id = 0x87654321; - - // Verify each state maintains its own value - assert_eq!(states[0].vmcs_revision_id, 0x12345678); - assert_eq!(states[1].vmcs_revision_id, 0x87654321); - assert_eq!(states[2].vmcs_revision_id, 0); - assert_eq!(states[3].vmcs_revision_id, 0); - } - - #[test] - fn test_vmx_per_cpu_state_debug() { - MockMmHal::reset(); // Reset before test - let state = VmxPerCpuState::::new(0).unwrap(); - - // Test that Debug trait is implemented and doesn't panic - let debug_str = format!("{:?}", state); - assert!(!debug_str.is_empty()); - } - - #[test] - fn test_vmx_per_cpu_state_size() { - use core::mem; - - // Test that the struct has a reasonable size - let size = mem::size_of::>(); - - // Should be larger than just the u32 field due to the VmxRegion - assert!(size > 4); - - // But shouldn't be excessively large (this is a sanity check) - assert!(size < 1024); - } -} diff --git a/src/vmx/structs.rs b/src/vmx/structs.rs index 8e00a7a..fa3f698 100644 --- a/src/vmx/structs.rs +++ b/src/vmx/structs.rs @@ -3,18 +3,20 @@ use bitflags::bitflags; use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; -use axaddrspace::{AxMmHal, HostPhysAddr, PhysFrame}; +use axaddrspace::HostPhysAddr; use axerrno::AxResult; +use axvcpu::AxVCpuHal; +use crate::frame::PhysFrame; use crate::msr::{Msr, MsrReadWrite}; /// VMCS/VMXON region in 4K size. (SDM Vol. 3C, Section 24.2) #[derive(Debug)] -pub struct VmxRegion { +pub struct VmxRegion { frame: PhysFrame, } -impl VmxRegion { +impl VmxRegion { pub const unsafe fn uninit() -> Self { Self { frame: unsafe { PhysFrame::uninit() }, @@ -41,12 +43,12 @@ impl VmxRegion { // I/O bitmap A contains one bit for each I/O port in the range 0000H through 7FFFH; // I/O bitmap B contains bits for ports in the range 8000H through FFFFH. #[derive(Debug)] -pub struct IOBitmap { +pub struct IOBitmap { io_bitmap_a_frame: PhysFrame, io_bitmap_b_frame: PhysFrame, } -impl IOBitmap { +impl IOBitmap { pub fn passthrough_all() -> AxResult { Ok(Self { io_bitmap_a_frame: PhysFrame::alloc_zero()?, @@ -101,11 +103,11 @@ impl IOBitmap { } #[derive(Debug)] -pub struct MsrBitmap { +pub struct MsrBitmap { frame: PhysFrame, } -impl MsrBitmap { +impl MsrBitmap { pub fn passthrough_all() -> AxResult { Ok(Self { frame: PhysFrame::alloc_zero()?, @@ -204,7 +206,6 @@ impl VmxBasic { bitflags! { /// IA32_FEATURE_CONTROL flags. - #[derive(Debug)] pub struct FeatureControlFlags: u64 { /// Lock bit: when set, locks this MSR from being written. when clear, /// VMXON causes a #GP. @@ -240,7 +241,6 @@ impl FeatureControl { bitflags! { /// Extended-Page-Table Pointer. (SDM Vol. 3C, Section 24.6.11) - #[derive(Debug)] pub struct EPTPointer: u64 { /// EPT paging-structure memory type: Uncacheable (UC). #[allow(clippy::identity_op)] @@ -268,203 +268,3 @@ impl EPTPointer { flags | Self::MEM_TYPE_WB | Self::WALK_LENGTH_4 | Self::ENABLE_ACCESSED_DIRTY } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::test_utils::mock::MockMmHal; - use alloc::format; - - #[test] - fn test_vmx_region_uninit() { - let region = unsafe { VmxRegion::::uninit() }; - - // Test that we can create an uninitialized region - // Can't test much more without allocating memory - let debug_str = format!("{:?}", region); - assert!(!debug_str.is_empty()); - } - - #[test] - fn test_vmx_region_new() { - // Reset allocator for consistent testing - MockMmHal::reset(); - - // Test VmxRegion::new with valid parameters - let region = VmxRegion::::new(0x12345, false); - assert!(region.is_ok()); - - let region = region.unwrap(); - let addr = region.phys_addr(); - assert_ne!(addr.as_usize(), 0); - // Should be page-aligned - assert_eq!(addr.as_usize() % 0x1000, 0); - } - - #[test] - fn test_vmx_region_new_with_shadow() { - // Reset allocator for consistent testing - MockMmHal::reset(); - - // Test VmxRegion::new with different shadow indicator values - let region_no_shadow = VmxRegion::::new(0x12345, false); - assert!(region_no_shadow.is_ok()); - - let region_with_shadow = VmxRegion::::new(0x12345, true); - assert!(region_with_shadow.is_ok()); - - // Test that both regions have valid physical addresses - let region1 = region_no_shadow.unwrap(); - let region2 = region_with_shadow.unwrap(); - - let addr1 = region1.phys_addr(); - let addr2 = region2.phys_addr(); - - assert_ne!(addr1.as_usize(), 0); - assert_ne!(addr2.as_usize(), 0); - assert_ne!(addr1.as_usize(), addr2.as_usize()); - assert_eq!(addr1.as_usize() % 0x1000, 0); - assert_eq!(addr2.as_usize() % 0x1000, 0); - } - - #[test] - fn test_io_bitmap_creation() { - // Test IOBitmap creation methods - MockMmHal::reset(); - - // Test passthrough_all creation - let passthrough_bitmap = IOBitmap::::passthrough_all(); - assert!(passthrough_bitmap.is_ok()); - - // Test intercept_all creation - let intercept_bitmap = IOBitmap::::intercept_all(); - assert!(intercept_bitmap.is_ok()); - - // Test that phys_addr returns valid addresses - let bitmap = passthrough_bitmap.unwrap(); - let (addr_a, addr_b) = bitmap.phys_addr(); - assert_ne!(addr_a.as_usize(), 0); - assert_ne!(addr_b.as_usize(), 0); - assert_ne!(addr_a.as_usize(), addr_b.as_usize()); - } - - #[test] - fn test_msr_bitmap_creation() { - // Test MsrBitmap creation methods - MockMmHal::reset(); - - // Test passthrough_all creation - let passthrough_bitmap = MsrBitmap::::passthrough_all(); - assert!(passthrough_bitmap.is_ok()); - - // Test intercept_all creation - let intercept_bitmap = MsrBitmap::::intercept_all(); - assert!(intercept_bitmap.is_ok()); - - // Test that phys_addr returns valid addresses - let bitmap = passthrough_bitmap.unwrap(); - let addr = bitmap.phys_addr(); - assert_ne!(addr.as_usize(), 0); - assert_eq!(addr.as_usize() % 0x1000, 0); - } - - #[test] - fn test_ept_pointer_creation() { - // Test EPTPointer creation with from_table_phys method - let ept_ptr1 = EPTPointer::from_table_phys(memory_addr::PhysAddr::from(0x1000)); - let ept_ptr2 = EPTPointer::from_table_phys(memory_addr::PhysAddr::from(0x2000)); - - // Verify the EPT pointers were created successfully - assert_ne!(ept_ptr1.0, ept_ptr2.0); - } - - #[test] - fn test_ept_pointer_getters() { - let phys_addr = memory_addr::PhysAddr::from(0x3000); - let ept_ptr = EPTPointer::from_table_phys(phys_addr); - - // Test that we can create EPT pointer and it has expected flags - let bits = ept_ptr.bits(); - assert_ne!(bits, 0); - - // Should have the expected flags set - let expected_flags = - EPTPointer::MEM_TYPE_WB | EPTPointer::WALK_LENGTH_4 | EPTPointer::ENABLE_ACCESSED_DIRTY; - assert_eq!(bits & expected_flags.bits(), expected_flags.bits()); - } - - #[test] - fn test_vmx_basic_constants() { - assert_eq!(VmxBasic::VMX_MEMORY_TYPE_WRITE_BACK, 6); - } - - #[test] - fn test_feature_control_flags() { - let flags = FeatureControlFlags::LOCKED | FeatureControlFlags::VMXON_ENABLED_OUTSIDE_SMX; - - assert!(flags.contains(FeatureControlFlags::LOCKED)); - assert!(flags.contains(FeatureControlFlags::VMXON_ENABLED_OUTSIDE_SMX)); - assert!(!flags.contains(FeatureControlFlags::VMXON_ENABLED_INSIDE_SMX)); - } - - #[test] - fn test_ept_pointer_flags() { - use EPTPointer as EPT; - - // Test individual flags - assert_eq!(EPT::MEM_TYPE_UC.bits(), 0); - assert_eq!(EPT::MEM_TYPE_WB.bits(), 6); - assert_eq!(EPT::WALK_LENGTH_4.bits(), 3 << 3); - - // Test flag combination - let combined = EPT::MEM_TYPE_WB | EPT::WALK_LENGTH_4 | EPT::ENABLE_ACCESSED_DIRTY; - assert!(combined.contains(EPT::MEM_TYPE_WB)); - assert!(combined.contains(EPT::WALK_LENGTH_4)); - assert!(combined.contains(EPT::ENABLE_ACCESSED_DIRTY)); - } - - #[test] - fn test_ept_pointer_from_table_phys() { - let pml4_addr = HostPhysAddr::from(0x12345000_usize); // Page-aligned address - let ept_ptr = EPTPointer::from_table_phys(pml4_addr); - - // Should have the correct flags set - assert!(ept_ptr.contains(EPTPointer::MEM_TYPE_WB)); - assert!(ept_ptr.contains(EPTPointer::WALK_LENGTH_4)); - assert!(ept_ptr.contains(EPTPointer::ENABLE_ACCESSED_DIRTY)); - - // Address should be preserved (and aligned) - let addr_part = ept_ptr.bits() & !0xfff; - assert_eq!(addr_part, 0x12345000); - } - - #[test] - fn test_ept_pointer_from_unaligned_addr() { - let unaligned_addr = HostPhysAddr::from(0x12345678_usize); // Not page-aligned - let ept_ptr = EPTPointer::from_table_phys(unaligned_addr); - - // Address should be aligned down - let addr_part = ept_ptr.bits() & !0xfff; - // Should be aligned to 4K boundary - assert_eq!(addr_part, 0x12345000); - } - - #[test] - fn test_debug_implementations() { - // Test that all our structs implement Debug properly - let vmx_region = unsafe { VmxRegion::::uninit() }; - let _debug_str = format!("{:?}", vmx_region); - - let io_bitmap = IOBitmap::::passthrough_all().unwrap(); - let _debug_str = format!("{:?}", io_bitmap); - - let msr_bitmap = MsrBitmap::::passthrough_all().unwrap(); - let _debug_str = format!("{:?}", msr_bitmap); - - let flags = FeatureControlFlags::LOCKED; - let _debug_str = format!("{:?}", flags); - - let ept_flags = EPTPointer::MEM_TYPE_WB; - let _debug_str = format!("{:?}", ept_flags); - } -} diff --git a/src/vmx/vcpu.rs b/src/vmx/vcpu.rs index e142fdb..b8e43b8 100644 --- a/src/vmx/vcpu.rs +++ b/src/vmx/vcpu.rs @@ -1,36 +1,27 @@ use alloc::collections::VecDeque; use bit_field::BitField; -use core::{ - arch::naked_asm, - fmt::{Debug, Formatter, Result}, - mem::size_of, -}; +use core::fmt::{Debug, Formatter, Result}; +use core::{arch::naked_asm, mem::size_of}; use raw_cpuid::CpuId; -use x86::{ - bits64::vmx, - controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}, - dtables::{self, DescriptorTablePointer}, - segmentation::SegmentSelector, -}; +use x86::bits64::vmx; +use x86::controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}; +use x86::dtables::{self, DescriptorTablePointer}; +use x86::segmentation::SegmentSelector; use x86_64::registers::control::{Cr0, Cr0Flags, Cr3, Cr4, Cr4Flags, EferFlags}; -use x86_vlapic::EmulatedLocalApic; -use axaddrspace::{ - GuestPhysAddr, GuestVirtAddr, HostPhysAddr, NestedPageFaultInfo, - device::{AccessWidth, Port, SysRegAddr, SysRegAddrRange}, -}; -use axdevice_base::BaseDeviceOps; +use axaddrspace::{GuestPhysAddr, GuestVirtAddr, HostPhysAddr, NestedPageFaultInfo}; use axerrno::{AxResult, ax_err, ax_err_type}; -use axvcpu::{AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; -use axvisor_api::vmm::{VCpuId, VMId}; +use axvcpu::{AccessWidth, AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; + + use super::VmxExitInfo; use super::as_axerr; use super::definitions::VmxExitReason; use super::structs::{IOBitmap, MsrBitmap, VmxRegion}; use super::vmcs::{ - self, ApicAccessExitType, VmcsControl32, VmcsControl64, VmcsControlNW, VmcsGuest16, - VmcsGuest32, VmcsGuest64, VmcsGuestNW, VmcsHost16, VmcsHost32, VmcsHost64, VmcsHostNW, + self, VmcsControl32, VmcsControl64, VmcsControlNW, VmcsGuest16, VmcsGuest32, VmcsGuest64, + VmcsGuestNW, VmcsHost16, VmcsHost32, VmcsHost64, VmcsHostNW, }; use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters}; @@ -154,68 +145,37 @@ const CR0_PE: usize = 1 << 0; /// A virtual CPU within a guest. #[repr(C)] pub struct VmxVcpu { - // The order of `guest_regs` and `host_stack_top` is mandatory. They must be the first two fields. If you want to - // change the order or the type of these fields, you must also change the assembly in this file. - /// Guest general-purpose registers. + // DO NOT modify `guest_regs` and `host_stack_top` and their order unless you do know what you are doing! + // DO NOT add anything before or between them unless you do know what you are doing! guest_regs: GeneralRegisters, - /// The top of the host stack. host_stack_top: u64, - - // The order of the following fields is not mandatory. - - // VCpu states and configurations - /// Whether the VMCS has been launched. Used to determine whether to `vmx_launch` or `vmx_resume`. launched: bool, - /// The guest entry point. + vmcs: VmxRegion, + io_bitmap: IOBitmap, + msr_bitmap: MsrBitmap, + pending_events: VecDeque<(u8, Option)>, + xstate: XState, entry: Option, - /// The EPT root address. ept_root: Option, - // /// Whether this VCPU is a host VCpu. Used in type 1.5 hypervisor. // is_host: bool, temporary removed because we don't care about type 1.5 now - - // VMCS-related fields - /// The VMCS region. - vmcs: VmxRegion, - /// The I/O bitmap for the VMCS. - io_bitmap: IOBitmap, - /// The MSR bitmap for the VMCS. - msr_bitmap: MsrBitmap, - - // Interrupt-related fields - /// Pending events to be injected to the guest. - pending_events: VecDeque<(u8, Option)>, - /// Emulated Local APIC. - vlapic: EmulatedLocalApic, - - // Extra states - /// The XState of the VCpu. Both host and guest. - xstate: XState, - - // Tracing-related fields - #[cfg(feature = "tracing")] - /// The guest registers when the VM-exit happens. - guest_regs_exiting: GeneralRegisters, } impl VmxVcpu { /// Create a new [`VmxVcpu`]. - pub fn new(vm_id: VMId, vcpu_id: VCpuId) -> AxResult { + pub fn new() -> AxResult { let vmcs_revision_id = super::read_vmcs_revision_id(); let vcpu = Self { guest_regs: GeneralRegisters::default(), host_stack_top: 0, launched: false, - entry: None, - ept_root: None, - // is_host: false, vmcs: VmxRegion::new(vmcs_revision_id, false)?, io_bitmap: IOBitmap::passthrough_all()?, msr_bitmap: MsrBitmap::passthrough_all()?, pending_events: VecDeque::with_capacity(8), - vlapic: EmulatedLocalApic::new(vm_id, vcpu_id), xstate: XState::new(), - #[cfg(feature = "tracing")] - guest_regs_exiting: GeneralRegisters::default(), + entry: None, + ept_root: None, + // is_host: false, }; info!("[HV] created VmxVcpu(vmcs: {:#x})", vcpu.vmcs.phys_addr()); Ok(vcpu) @@ -279,26 +239,13 @@ impl VmxVcpu { /// Run the guest. It returns when a vm-exit happens and returns the vm-exit if it cannot be handled by this [`VmxVcpu`] itself. pub fn inner_run(&mut self) -> Option { - self.inject_pending_events().unwrap(); + // Inject pending events + if self.launched { + self.inject_pending_events().unwrap(); + } // Run guest self.load_guest_xstate(); - - #[cfg(feature = "tracing")] - { - use crate::regs::GeneralRegistersDiff; - // Tracing, do a diff of the guest registers before entering the guest - let diff = GeneralRegistersDiff::new(self.guest_regs_exiting, self.guest_regs); - if !diff.is_same() { - debug!( - "VCpu registers changed during handling VM-exit: {:#x?}", - diff - ); - } else { - debug!("VCpu registers unchanged during handling VM-exit"); - } - } - unsafe { if self.launched { self.vmx_resume(); @@ -313,11 +260,6 @@ impl VmxVcpu { } self.load_host_xstate(); - #[cfg(feature = "tracing")] - { - self.guest_regs_exiting = self.guest_regs; - } - // Handle vm-exits let exit_info = self.exit_info().unwrap(); // debug!("VM exit: {:#x?}", exit_info); @@ -358,17 +300,12 @@ impl VmxVcpu { pub fn io_exit_info(&self) -> AxResult { vmcs::io_exit_info() } - +/////////////////////////////////////////////////111111111 /// Information for VM exits due to nested page table faults (EPT violation). pub fn nested_page_fault_info(&self) -> AxResult { vmcs::ept_violation_info() } - /// Information for VM exits due to APIC access. - pub fn apic_access_exit_info(&self) -> AxResult { - vmcs::apic_access_exit_info() - } - /// Guest general-purpose registers. pub fn regs(&self) -> &GeneralRegisters { &self.guest_regs @@ -558,10 +495,10 @@ impl VmxVcpu { .set_read_intercept(IA32_UMWAIT_CONTROL, true); // Intercept all x2APIC MSR accesses - for msr in 0x800..=0x83f { - self.msr_bitmap.set_read_intercept(msr, true); - self.msr_bitmap.set_write_intercept(msr, true); - } + // for msr in 0x800..=0x83f { + // self.msr_bitmap.set_read_intercept(msr, true); + // self.msr_bitmap.set_write_intercept(msr, true); + // } Ok(()) } @@ -571,7 +508,6 @@ impl VmxVcpu { vmx::vmclear(paddr).map_err(as_axerr)?; } self.bind_to_current_processor()?; - self.setup_msr_bitmap()?; self.setup_vmcs_guest(entry)?; self.setup_vmcs_control(ept_root, true)?; self.unbind_from_current_processor()?; @@ -626,12 +562,10 @@ impl VmxVcpu { use VmcsGuest16::*; use VmcsGuest32::*; use VmcsGuestNW::*; - paste::paste! { - [<$seg _SELECTOR>].write(0)?; - [<$seg _BASE>].write(0)?; - [<$seg _LIMIT>].write(0xffff)?; - [<$seg _ACCESS_RIGHTS>].write($access_rights)?; - } + concat_idents!($seg, _SELECTOR).write(0)?; + concat_idents!($seg, _BASE).write(0)?; + concat_idents!($seg, _LIMIT).write(0xffff)?; + concat_idents!($seg, _ACCESS_RIGHTS).write($access_rights)?; }}; } @@ -681,9 +615,9 @@ impl VmxVcpu { VmcsControl32::PINBASED_EXEC_CONTROLS, Msr::IA32_VMX_TRUE_PINBASED_CTLS, Msr::IA32_VMX_PINBASED_CTLS.read() as u32, - (PinCtrl::NMI_EXITING | PinCtrl::EXTERNAL_INTERRUPT_EXITING).bits(), + // (PinCtrl::NMI_EXITING | PinCtrl::EXTERNAL_INTERRUPT_EXITING).bits(), // (PinCtrl::NMI_EXITING | PinCtrl::VMX_PREEMPTION_TIMER).bits(), - // PinCtrl::NMI_EXITING.bits(), + PinCtrl::NMI_EXITING.bits(), 0, )?; @@ -705,9 +639,7 @@ impl VmxVcpu { // Enable EPT, RDTSCP, INVPCID, and unrestricted guest. use SecondaryControls as CpuCtrl2; - let mut val = - // CpuCtrl2::VIRTUALIZE_APIC | - CpuCtrl2::ENABLE_EPT | CpuCtrl2::UNRESTRICTED_GUEST; + let mut val = CpuCtrl2::ENABLE_EPT | CpuCtrl2::UNRESTRICTED_GUEST; if let Some(features) = raw_cpuid.get_extended_processor_and_feature_identifiers() { if features.has_rdtscp() { val |= CpuCtrl2::ENABLE_RDTSCP; @@ -785,10 +717,6 @@ impl VmxVcpu { VmcsControl64::IO_BITMAP_A_ADDR.write(self.io_bitmap.phys_addr().0.as_usize() as _)?; VmcsControl64::IO_BITMAP_B_ADDR.write(self.io_bitmap.phys_addr().1.as_usize() as _)?; VmcsControl64::MSR_BITMAPS_ADDR.write(self.msr_bitmap.phys_addr().as_usize() as _)?; - - // VmcsControl64::APIC_ACCESS_ADDR.write( - // EmulatedLocalApic::::virtual_apic_access_addr().as_usize() as _, - // )?; Ok(()) } @@ -876,22 +804,24 @@ impl VmxVcpu { /// Get ready then vmlaunch or vmresume. macro_rules! vmx_entry_with { ($instr:literal) => { - naked_asm!( - save_regs_to_stack!(), // save host status - "mov [rdi + {host_stack_size}], rsp", // save current RSP to Vcpu::host_stack_top - "mov rsp, rdi", // set RSP to guest regs area - restore_regs_from_stack!(), // restore guest status - $instr, // let's go! - "jmp {failed}", - host_stack_size = const size_of::(), - failed = sym Self::vmx_entry_failed, - // options(noreturn), - ) + unsafe { + naked_asm!( + save_regs_to_stack!(), // save host status + "mov [rdi + {host_stack_size}], rsp", // save current RSP to Vcpu::host_stack_top + "mov rsp, rdi", // set RSP to guest regs area + restore_regs_from_stack!(), // restore guest status + $instr, // let's go! + "jmp {failed}", + host_stack_size = const size_of::(), + failed = sym Self::vmx_entry_failed, + // options(noreturn), + ) + } } } impl VmxVcpu { - #[unsafe(naked)] + #[naked] /// Enter guest with vmlaunch. /// /// `#[naked]` is essential here, without it the rust compiler will think `&mut self` is not used and won't give us correct %rdi. @@ -903,7 +833,7 @@ impl VmxVcpu { vmx_entry_with!("vmlaunch") } - #[unsafe(naked)] + #[naked] /// Enter guest with vmresume. /// /// See [`Self::vmx_launch`] for detail. @@ -911,21 +841,20 @@ impl VmxVcpu { vmx_entry_with!("vmresume") } - #[unsafe(naked)] - /// Return after vm-exit. This function is used only for returning from [`Self::vmx_launch`] or [`Self::vmx_resume`]. - /// - /// NEVER call this function directly. + #[naked] + /// Return after vm-exit. /// /// The return value is a dummy value. unsafe extern "C" fn vmx_exit(&mut self) -> usize { - // it's not necessary to use another `unsafe` here, as Rust now do not require it in naked functions. - naked_asm!( - save_regs_to_stack!(), // save guest status, after this, rsp points to the `VmxVcpu` - "mov rsp, [rsp + {host_stack_top}]", // set RSP to Vcpu::host_stack_top - restore_regs_from_stack!(), // restore host status - "ret", - host_stack_top = const size_of::(), - ); + unsafe { + naked_asm!( + save_regs_to_stack!(), // save guest status + "mov rsp, [rsp + {host_stack_top}]", // set RSP to Vcpu::host_stack_top + restore_regs_from_stack!(), // restore host status + "ret", + host_stack_top = const size_of::(), + ); + } } fn vmx_entry_failed() -> ! { @@ -943,8 +872,8 @@ impl VmxVcpu { /// Try to inject a pending event before next VM entry. fn inject_pending_events(&mut self) -> AxResult { if let Some(event) = self.pending_events.front() { - // trace!( - // "pending event vector {:#x} allow_int {}", + // debug!( + // "inject_pending_events vector {:#x} allow_int {}", // event.0, // self.allow_interrupt() // ); @@ -964,8 +893,6 @@ impl VmxVcpu { /// /// Return the result or None if the vm-exit was not handled. fn builtin_vmexit_handler(&mut self, exit_info: &VmxExitInfo) -> Option { - const X2APIC_MSR_BASE: u32 = 0x800; - const X2APIC_MSR_END: u32 = 0x8ff; // SDM says 0x8ff, but actually 0x83f, we respect the SDM here. // Following vm-exits are handled here: // - interrupt window: turn off interrupt window; // - xsetbv: set guest xcr; @@ -976,91 +903,10 @@ impl VmxVcpu { VmxExitReason::XSETBV => Some(self.handle_xsetbv()), VmxExitReason::CR_ACCESS => Some(self.handle_cr()), VmxExitReason::CPUID => Some(self.handle_cpuid()), - msr_rw @ (VmxExitReason::MSR_READ | VmxExitReason::MSR_WRITE) - if { - let msr = self.regs().rcx as u32; - msr >= X2APIC_MSR_BASE && msr <= X2APIC_MSR_END - } => - { - Some(self.handle_apic_msr_access( - msr_rw == VmxExitReason::MSR_WRITE, - self.regs().rcx as u32, - )) - } - VmxExitReason::APIC_ACCESS => Some(self.handle_apic_access(exit_info)), _ => None, } } - /// Read a 64-bit value from EDX:EAX. - fn read_edx_eax(&self) -> u64 { - ((self.regs().rdx & 0xffff_ffff) << 32) | (self.regs().rax & 0xffff_ffff) - } - - /// Write a 64-bit value to EDX:EAX. - fn write_edx_eax(&mut self, val: u64) { - self.regs_mut().rax = val & 0xffff_ffff; - self.regs_mut().rdx = val >> 32; - } - - fn handle_apic_msr_access(&mut self, write: bool, msr: u32) -> AxResult { - const VMEXIT_INSTR_LEN_RDMSR_WRMSR: u8 = 2; - - self.advance_rip(VMEXIT_INSTR_LEN_RDMSR_WRMSR)?; - - let msr = msr as _; - if write { - let value = self.read_edx_eax() as usize; - - trace!( - "handle_vlapic_msr_write: msr={:#x}, value={:#x}", - msr, value - ); - - >::handle_write( - &self.vlapic, - SysRegAddr::new(msr), - AccessWidth::Qword, - value, - ) - } else { - let value = >::handle_read( - &self.vlapic, - SysRegAddr::new(msr), - AccessWidth::Qword, - )? as u64; - - trace!("handle_vlapic_msr_read: msr={:#x}, value={:#x}", msr, value); - - self.write_edx_eax(value); - Ok(()) - } - } - - fn handle_apic_access(&mut self, exit_info: &VmxExitInfo) -> AxResult { - let apic_access_exit_info = self.apic_access_exit_info()?; - - let write = match apic_access_exit_info.access_type { - ApicAccessExitType::LinearDataWrite => true, - ApicAccessExitType::LinearDataRead => false, - _ => { - warn!( - "Unsupported APIC access type: {:?}", - apic_access_exit_info.access_type - ); - return ax_err!(BadState, "Unsupported APIC access type"); - } - }; - - // TODO: handle APIC access. - let _ = write; - - self.advance_rip(exit_info.exit_instruction_length as _)?; - - unimplemented!("apic access"); - // TODO - } - fn handle_vmx_preemption_timer(&mut self) -> AxResult { /* The VMX-preemption timer counts down at rate proportional to that of the timestamp counter (TSC). @@ -1302,8 +1148,8 @@ impl AxArchVCpu for VmxVcpu { type SetupConfig = (); - fn new(vm_id: VMId, vcpu_id: VCpuId, _config: Self::CreateConfig) -> AxResult { - Self::new(vm_id, vcpu_id) + fn new(_config: Self::CreateConfig) -> AxResult { + Self::new() } fn set_entry(&mut self, entry: GuestPhysAddr) -> AxResult { @@ -1370,10 +1216,7 @@ impl AxArchVCpu for VmxVcpu { }; if io_info.is_in { - AxVCpuExitReason::IoRead { - port: Port(port), - width, - } + AxVCpuExitReason::IoRead { port, width } } else if port == QEMU_EXIT_PORT && width == AccessWidth::Word && self.regs().rax == QEMU_EXIT_MAGIC @@ -1381,35 +1224,13 @@ impl AxArchVCpu for VmxVcpu { AxVCpuExitReason::SystemDown } else { AxVCpuExitReason::IoWrite { - port: Port(port), + port, width, data: self.regs().rax.get_bits(width.bits_range()), } } } } - VmxExitReason::EXTERNAL_INTERRUPT => { - let int_info = self.interrupt_exit_info()?; - assert!(int_info.valid); - AxVCpuExitReason::ExternalInterrupt { - vector: int_info.vector as _, - } - } - VmxExitReason::MSR_READ => { - // `reg` is unused here. - AxVCpuExitReason::SysRegRead { - addr: SysRegAddr::new(self.regs().rcx as _), - reg: 0, - } - } - VmxExitReason::MSR_WRITE => { - let value = (self.regs().rax & 0xffff_ffff) - | ((self.regs().rdx & 0xffff_ffff) << 32); - AxVCpuExitReason::SysRegWrite { - addr: SysRegAddr::new(self.regs().rcx as _), - value, - } - } _ => { warn!("VMX unsupported VM-Exit: {:#x?}", exit_info); warn!("VCpu {:#x?}", self); @@ -1433,286 +1254,4 @@ impl AxArchVCpu for VmxVcpu { fn set_gpr(&mut self, reg: usize, val: usize) { self.regs_mut().set_reg_of_index(reg as u8, val as u64); } - - fn inject_interrupt(&mut self, vector: usize) -> AxResult { - if vector != 0 { - // warn!("interrupt queued in inject_interrupt: vector {:#x}", vector); - } else { - warn!("interrupt queued in inject_interrupt: vector 0"); - panic!() - } - Ok(self.queue_event(vector as u8, None)) - } - - fn set_return_value(&mut self, val: usize) { - self.regs_mut().rax = val as u64; - } -} - -#[cfg(test)] -mod tests { - use super::*; - use alloc::format; - - #[test] - fn test_vm_cpu_mode_enum() { - // Test VmCpuMode enum values - assert_ne!(VmCpuMode::Real, VmCpuMode::Protected); - assert_ne!(VmCpuMode::Protected, VmCpuMode::Compatibility); - assert_ne!(VmCpuMode::Compatibility, VmCpuMode::Mode64); - - // Test Debug formatting - let debug_str = format!("{:?}", VmCpuMode::Mode64); - assert!(debug_str.contains("Mode64")); - } - - #[test] - fn test_general_registers_operations() { - let mut regs = GeneralRegisters::default(); - - // Test initial state - assert_eq!(regs.rax, 0); - assert_eq!(regs.rbx, 0); - - // Test setting and getting values - regs.rax = 0x1234567890abcdef; - regs.rbx = 0xfedcba0987654321; - - assert_eq!(regs.rax, 0x1234567890abcdef); - assert_eq!(regs.rbx, 0xfedcba0987654321); - - // Test register access by index - regs.set_reg_of_index(0, 0x1111111111111111); // RAX - assert_eq!(regs.get_reg_of_index(0), 0x1111111111111111); - - regs.set_reg_of_index(1, 0x2222222222222222); // RCX - assert_eq!(regs.get_reg_of_index(1), 0x2222222222222222); - } - - #[test] - fn test_constants() { - // Test that constants have expected values - assert_eq!(VMX_PREEMPTION_TIMER_SET_VALUE, 1_000_000); - assert_eq!(QEMU_EXIT_PORT, 0x604); - assert_eq!(QEMU_EXIT_MAGIC, 0x2000); - assert_eq!(MSR_IA32_EFER_LMA_BIT, 1 << 10); - assert_eq!(CR0_PE, 1 << 0); - } - - #[test] - fn test_bit_operations() { - use bit_field::BitField; - - let mut value = 0u64; - value.set_bits(0..32, 0x12345678); - value.set_bits(32..64, 0xabcdef00); - - assert_eq!(value.get_bits(0..32), 0x12345678); - assert_eq!(value.get_bits(32..64), 0xabcdef00); - } - - // Mock tests for VmxVcpu (limited to safe operations) - mod vmx_vcpu_tests { - use super::*; - - // Helper function to create a test VmxVcpu (this would normally require VMX hardware) - fn create_test_vcpu_regs() -> GeneralRegisters { - let mut regs = GeneralRegisters::default(); - regs.rax = 0x1000; - regs.rbx = 0x2000; - regs.rcx = 0x3000; - regs.rdx = 0x4000; - regs - } - - #[test] - fn test_general_registers_clone() { - let regs = create_test_vcpu_regs(); - let cloned_regs = regs.clone(); - - assert_eq!(regs.rax, cloned_regs.rax); - assert_eq!(regs.rbx, cloned_regs.rbx); - assert_eq!(regs.rcx, cloned_regs.rcx); - assert_eq!(regs.rdx, cloned_regs.rdx); - } - - #[test] - fn test_edx_eax_operations() { - // Test the logic for combining EDX:EAX - let rax = 0x12345678u64; - let rdx = 0xabcdef00u64; - - // Simulate read_edx_eax logic - let combined = ((rdx & 0xffff_ffff) << 32) | (rax & 0xffff_ffff); - assert_eq!(combined, 0xabcdef0012345678); - - // Simulate write_edx_eax logic - let val = 0xfedcba0987654321u64; - let new_rax = val & 0xffff_ffff; - let new_rdx = val >> 32; - - assert_eq!(new_rax, 0x87654321); - assert_eq!(new_rdx, 0xfedcba09); - } - - #[test] - fn test_register_bit_operations() { - let mut regs = GeneralRegisters::default(); - - // Test setting specific bits in registers - regs.rcx = 0; - regs.rcx.set_bits(0..32, 0x12345678); - assert_eq!(regs.rcx.get_bits(0..32), 0x12345678); - - regs.rdx = 0xffffffffffffffff; - regs.rdx.set_bits(32..64, 0); - assert_eq!(regs.rdx.get_bits(32..64), 0); - assert_eq!(regs.rdx.get_bits(0..32), 0xffffffff); - } - - #[test] - fn test_gla2gva_logic() { - // Test the address translation logic (without actual VMX hardware) - let guest_rip = 0x1000usize; - let seg_base_64bit = 0; // In 64-bit mode, segment base is 0 - let seg_base_other = 0x10000; // In other modes, segment base matters - - // 64-bit mode calculation - let gva_64bit = guest_rip + seg_base_64bit; - assert_eq!(gva_64bit, 0x1000); - - // Other mode calculation - let gva_other = guest_rip + seg_base_other; - assert_eq!(gva_other, 0x11000); - } - - #[test] - fn test_interrupt_vector_validation() { - // Test interrupt vector validation logic - let valid_exception = 6; // #UD exception - let valid_interrupt = 0x20; - let invalid_vector = 0; - - assert!(valid_exception < 32); // Exceptions are < 32 - assert!(valid_interrupt >= 32); // Interrupts are >= 32 - assert_eq!(invalid_vector, 0); // Vector 0 should be handled specially - } - - #[test] - fn test_page_walk_info_struct() { - let ptw_info = GuestPageWalkInfo { - top_entry: 0x1000, - level: 4, - width: 9, - is_user_mode_access: false, - is_write_access: false, - is_inst_fetch: false, - pse: true, - wp: true, - nxe: true, - is_smap_on: false, - is_smep_on: false, - }; - - assert_eq!(ptw_info.level, 4); - assert_eq!(ptw_info.width, 9); - assert_eq!(ptw_info.top_entry, 0x1000); - } - - #[test] - fn test_cpuid_constants() { - // Test CPUID-related constants used in handle_cpuid - const LEAF_FEATURE_INFO: u32 = 0x1; - const LEAF_HYPERVISOR_INFO: u32 = 0x4000_0000; - const FEATURE_VMX: u32 = 1 << 5; - const FEATURE_HYPERVISOR: u32 = 1 << 31; - - assert_eq!(LEAF_FEATURE_INFO, 1); - assert_eq!(LEAF_HYPERVISOR_INFO, 0x40000000); - assert_eq!(FEATURE_VMX, 32); - assert_eq!(FEATURE_HYPERVISOR, 0x80000000); - } - - #[test] - fn test_cr_flags_operations() { - use x86_64::registers::control::{Cr0Flags, Cr4Flags}; - - // Test CR0 flags - let cr0_flags = Cr0Flags::PAGING | Cr0Flags::PROTECTED_MODE_ENABLE; - assert!(cr0_flags.contains(Cr0Flags::PAGING)); - assert!(cr0_flags.contains(Cr0Flags::PROTECTED_MODE_ENABLE)); - assert!(!cr0_flags.contains(Cr0Flags::CACHE_DISABLE)); - - // Test CR4 flags - let cr4_flags = Cr4Flags::VIRTUAL_MACHINE_EXTENSIONS | Cr4Flags::PAGE_SIZE_EXTENSION; - assert!(cr4_flags.contains(Cr4Flags::VIRTUAL_MACHINE_EXTENSIONS)); - assert!(cr4_flags.contains(Cr4Flags::PAGE_SIZE_EXTENSION)); - } - - #[test] - fn test_access_width_operations() { - // Test access width enumeration - use axaddrspace::device::AccessWidth; - - assert_eq!(AccessWidth::Byte as usize, 0); - assert_eq!(AccessWidth::Word as usize, 1); - assert_eq!(AccessWidth::Dword as usize, 2); - assert_eq!(AccessWidth::Qword as usize, 3); - - // Test conversion - assert_eq!(AccessWidth::try_from(1), Ok(AccessWidth::Byte)); - assert_eq!(AccessWidth::try_from(2), Ok(AccessWidth::Word)); - assert_eq!(AccessWidth::try_from(4), Ok(AccessWidth::Dword)); - assert_eq!(AccessWidth::try_from(8), Ok(AccessWidth::Qword)); - } - } - - // Tests for utility functions that don't require hardware - #[test] - fn test_get_tr_base_logic() { - let mut test_entry = 0u64; - test_entry |= 1u64 << 47; // Present bit - test_entry |= (0x1000u64 & 0xFFFFFF) << 16; // Base address bits 16-39 - - // Present bit check - let present = test_entry & (1 << 47) != 0; - assert!(present); - - // Base address extraction - let base_low = (test_entry >> 16) & 0xFFFFFF; - let base_high = (test_entry >> 56) & 0xFF; - let base_addr = base_low | (base_high << 24); - - assert_eq!(base_addr, 0x1000); - } - - #[test] - fn test_vmx_exit_reason_enum() { - // Test that VmxExitReason enum can be used in match statements - let test_reason = VmxExitReason::VMCALL; - match test_reason { - VmxExitReason::VMCALL => assert!(true), - _ => assert!(false), - } - } - - #[test] - fn test_debug_implementations() { - // Test Debug implementations for various types - let cpu_mode = VmCpuMode::Mode64; - let debug_str = format!("{:?}", cpu_mode); - assert!(!debug_str.is_empty()); - - let regs = GeneralRegisters::default(); - let debug_str = format!("{:?}", regs); - assert!(!debug_str.is_empty()); - } - - // Note: Most VmxVcpu methods require actual VMX hardware support and cannot be unit tested - // without either: - // 1. Running on VMX-capable hardware with appropriate privileges - // 2. Extensive mocking of the entire VMX infrastructure - // - // For comprehensive testing of VmxVcpu, integration tests on actual hardware - // or hardware simulators would be more appropriate. } diff --git a/src/vmx/vmcs.rs b/src/vmx/vmcs.rs index 43d3822..2b8ba99 100644 --- a/src/vmx/vmcs.rs +++ b/src/vmx/vmcs.rs @@ -581,56 +581,6 @@ pub struct CrAccessInfo { pub lmsw_source_data: u8, } -/// Type of APIC-access, used in Exit Qualification for APIC Accesses. (SDM Vol. 3C, Section 28.2.2, Table 28-6) -#[derive(Debug)] -pub enum ApicAccessExitType { - /// Linear access for data read. - LinearDataRead = 0, - /// Linear access for data write. - LinearDataWrite = 1, - /// Linear access for instruction fetch. - LinearInstructionFetch = 2, - /// Linear access for event delivery. - LinearEventDelivery = 3, - /// Linear access for monitoring. - LinearMonitoring = 4, - /// Guest-physical access for event delivery. - GuestPhysicalEventDelivery = 10, - /// Guest-physical access for monitoring. - GuestPhysicalMonitoring = 11, - /// Guest-physical access for instruction fetch, data read, or data write. - GuestPhysicalInstructionFetchReadWrite = 15, -} - -impl TryFrom for ApicAccessExitType { - type Error = (); - - fn try_from(value: u8) -> Result { - match value { - 0 => Ok(Self::LinearDataRead), - 1 => Ok(Self::LinearDataWrite), - 2 => Ok(Self::LinearInstructionFetch), - 3 => Ok(Self::LinearEventDelivery), - 4 => Ok(Self::LinearMonitoring), - 10 => Ok(Self::GuestPhysicalEventDelivery), - 11 => Ok(Self::GuestPhysicalMonitoring), - 15 => Ok(Self::GuestPhysicalInstructionFetchReadWrite), - _ => Err(()), - } - } -} - -/// Exit Qualification for APIC Accesses. (SDM Vol. 3C, Section 28.2.2, Table 28-6) -#[derive(Debug)] -pub struct ApicAccessExitInfo { - /// Offset within the APIC-access page. Not defined if `access_type` is 10, 11, or 15. - pub offset: u16, - /// Access type. - pub access_type: ApicAccessExitType, - /// Actually not used by us, see SDM for details. - pub non_event_delivery_asynchronous: bool, -} - pub mod controls { pub use x86::vmx::vmcs::control::{EntryControls, ExitControls}; pub use x86::vmx::vmcs::control::{PinbasedControls, PrimaryControls, SecondaryControls}; @@ -822,14 +772,3 @@ pub fn cr_access_info() -> AxResult { lmsw_source_data: qualification.get_bits(16..32) as u8, }) } - -pub fn apic_access_exit_info() -> AxResult { - let qualification = VmcsReadOnlyNW::EXIT_QUALIFICATION.read()?; - // debug!("apic_access_info qualification {:#x}", qualification); - - Ok(ApicAccessExitInfo { - offset: qualification.get_bits(0..12) as u16, - access_type: ApicAccessExitType::try_from(qualification.get_bits(12..16) as u8).unwrap(), - non_event_delivery_asynchronous: qualification.get_bit(16), - }) -} From 2650f7d9508e34b245cca8329995dd1515aaa003 Mon Sep 17 00:00:00 2001 From: aarkegz Date: Mon, 5 Jan 2026 13:24:19 +0000 Subject: [PATCH 2/5] make code compilable --- Cargo.toml | 4 +- src/frame.rs | 137 ---------- src/lib.rs | 42 ++-- src/regs.rs | 196 --------------- src/svm/frame.rs | 81 ++++++ src/svm/mod.rs | 1 + src/svm/percpu.rs | 4 +- src/svm/structs.rs | 13 +- src/svm/vcpu.rs | 87 ++++--- src/vmx/mod.rs | 1 - src/vmx/percpu.rs | 86 ++++++- src/vmx/structs.rs | 218 +++++++++++++++- src/vmx/vcpu.rs | 603 +++++++++++++++++++++++++++++++++++++++------ src/vmx/vmcs.rs | 61 +++++ 14 files changed, 1042 insertions(+), 492 deletions(-) delete mode 100644 src/frame.rs delete mode 100644 src/regs.rs create mode 100644 src/svm/frame.rs diff --git a/Cargo.toml b/Cargo.toml index c78d47c..f3f0149 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,11 +33,13 @@ axvcpu = "0.1.0" x86_vlapic = "0.1.0" axdevice_base = "0.1.0" axvisor_api = "0.1.0" +tock-registers = "0.10" spin = { version = "0.9", default-features = false } [features] -default = ["vmx"] +default = ["svm"] +# default = ["vmx"] tracing = [] vmx = [] svm = [] diff --git a/src/frame.rs b/src/frame.rs deleted file mode 100644 index c7a2169..0000000 --- a/src/frame.rs +++ /dev/null @@ -1,137 +0,0 @@ -use core::marker::PhantomData; - -use axaddrspace::HostPhysAddr; -use axerrno::{AxResult, ax_err_type}; - -use axvcpu::AxVCpuHal; - -pub(crate) use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; - -/// A 4K-sized contiguous physical memory page, it will deallocate the page -/// automatically on drop. -#[derive(Debug)] -pub struct PhysFrame { - start_paddr: Option, - _marker: PhantomData, -} - -impl PhysFrame { - pub fn alloc() -> AxResult { - let start_paddr = H::alloc_frame() - .ok_or_else(|| ax_err_type!(NoMemory, "allocate physical frame failed"))?; - assert_ne!(start_paddr.as_usize(), 0); - Ok(Self { - start_paddr: Some(start_paddr), - _marker: PhantomData, - }) - } - - pub fn alloc_zero() -> AxResult { - let mut f = Self::alloc()?; - f.fill(0); - Ok(f) - } - - pub const unsafe fn uninit() -> Self { - Self { - start_paddr: None, - _marker: PhantomData, - } - } - - pub fn start_paddr(&self) -> HostPhysAddr { - self.start_paddr.expect("uninitialized PhysFrame") - } - - pub fn as_mut_ptr(&self) -> *mut u8 { - H::phys_to_virt(self.start_paddr()).as_mut_ptr() - } - - pub fn fill(&mut self, byte: u8) { - unsafe { core::ptr::write_bytes(self.as_mut_ptr(), byte, PAGE_SIZE) } - } -} - -impl Drop for PhysFrame { - fn drop(&mut self) { - if let Some(start_paddr) = self.start_paddr { - H::dealloc_frame(start_paddr); - debug!("[AxVM] deallocated PhysFrame({:#x})", start_paddr); - } - } -} - -/// A contiguous block of physical memory frames that will be automatically -/// deallocated when dropped. Used for hardware structures requiring contiguous -/// physical memory (e.g., IOPM, MSRPM). -#[derive(Debug)] -pub struct ContiguousPhysFrames { - start_paddr: Option, - frame_count: usize, - _marker: PhantomData, -} - -impl ContiguousPhysFrames { - pub fn alloc(frame_count: usize) -> AxResult { - let start_paddr = H::alloc_contiguous_frames(frame_count) - .ok_or_else(|| ax_err_type!(NoMemory, "allocate contiguous frames failed"))?; - - assert_ne!(start_paddr.as_usize(), 0); - Ok(Self { - start_paddr: Some(start_paddr), - frame_count, - _marker: PhantomData, - }) - } - - pub fn alloc_zero(frame_count: usize) -> AxResult { - let mut frames = Self::alloc(frame_count)?; - frames.fill(0); - Ok(frames) - } - - pub const unsafe fn uninit() -> Self { - Self { - start_paddr: None, - frame_count: 0, - _marker: PhantomData, - } - } - - - pub fn start_paddr(&self) -> HostPhysAddr { - self.start_paddr.expect("uninitialized ContiguousPhysFrames") - } - - pub fn frame_count(&self) -> usize { - self.frame_count - } - - pub fn size(&self) -> usize { - PAGE_SIZE * self.frame_count - } - - - pub fn as_mut_ptr(&self) -> *mut u8 { - H::phys_to_virt(self.start_paddr()).as_mut_ptr() - } - - - pub fn fill(&mut self, byte: u8) { - unsafe { - core::ptr::write_bytes(self.as_mut_ptr(), byte, self.size()); - } - } -} - -impl Drop for ContiguousPhysFrames { - fn drop(&mut self) { - if let Some(start_paddr) = self.start_paddr { - H::dealloc_contiguous_frames(start_paddr, self.frame_count); - debug!( - "[AxVM] deallocated ContiguousPhysFrames({:#x}, {} frames)", - start_paddr, self.frame_count - ); - } - } -} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 6d37743..0515cdb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,6 @@ #![no_std] #![feature(doc_cfg)] #![feature(concat_idents)] -#![feature(naked_functions)] #![doc = include_str!("../README.md")] #[macro_use] @@ -9,44 +8,33 @@ extern crate log; extern crate alloc; +#[cfg(test)] +mod test_utils; + pub(crate) mod msr; #[macro_use] pub(crate) mod regs; mod ept; -mod frame; + +#[cfg(all(feature = "vmx", feature = "svm"))] +compile_error!("Features 'vmx' and 'svm' are mutually exclusive. Please enable only one of them."); cfg_if::cfg_if! { if #[cfg(feature = "vmx")] { mod vmx; - use vmx as vender; - pub use vmx::{VmxExitInfo, VmxExitReason, VmxInterruptInfo, VmxIoExitInfo}; - pub use vender::VmxArchVCpu; - pub use vender::VmxArchPerCpuState; - }else if #[cfg(feature = "svm")] { + use vmx as vendor; + // pub use vmx::{VmxExitInfo, VmxExitReason, VmxInterruptInfo, VmxIoExitInfo}; + pub use vendor::VmxArchVCpu as X86ArchVCpu; + pub use vendor::VmxArchPerCpuState as X86ArchPerCpuState; + } else if #[cfg(feature = "svm")] { mod svm; - use svm as vender; - pub use vender::{ - SvmArchVCpu,SvmArchPerCpuState, + use svm as vendor; + pub use vendor::{ + SvmArchVCpu as X86ArchVCpu, SvmArchPerCpuState as X86ArchPerCpuState, }; } } -// -// mod vmx; -// use vmx as vender; -// pub use vmx::{VmxExitInfo, VmxExitReason, VmxInterruptInfo, VmxIoExitInfo}; -// -// pub use vender::VmxArchVCpu; -// pub use vender::VmxArchPerCpuState; -// -// -// mod svm; -// use svm as vendor; -// pub use vendor::{ -// SvmArchVCpu, -// SvmArchPerCpuState, -// }; - pub use ept::GuestPageWalkInfo; pub use regs::GeneralRegisters; -pub use vender::has_hardware_support; +pub use vendor::has_hardware_support; diff --git a/src/regs.rs b/src/regs.rs deleted file mode 100644 index 2039c77..0000000 --- a/src/regs.rs +++ /dev/null @@ -1,196 +0,0 @@ -/// General-purpose registers for the 64-bit x86 architecture. -/// -/// This structure holds the values of the general-purpose registers -/// used in 64-bit x86 systems, allowing for easy manipulation and storage of register states. -#[repr(C)] -#[derive(Debug, Default, Clone)] -pub struct GeneralRegisters { - /// The RAX register, typically used for return values in functions. - pub rax: u64, - /// The RCX register, often used as a counter in loops. - pub rcx: u64, - /// The RDX register, commonly used for I/O operations. - pub rdx: u64, - /// The RBX register, usually used as a base pointer or to store values across function calls. - pub rbx: u64, - /// Unused space for the RSP register, preserved for padding or future use. - _unused_rsp: u64, - /// The RBP register, often used as a frame pointer in function calls. - pub rbp: u64, - /// The RSI register, often used as a source index in string operations. - pub rsi: u64, - /// The RDI register, often used as a destination index in string operations. - pub rdi: u64, - /// The R8 register, an additional general-purpose register available in 64-bit mode. - pub r8: u64, - /// The R9 register, an additional general-purpose register available in 64-bit mode. - pub r9: u64, - /// The R10 register, an additional general-purpose register available in 64-bit mode. - pub r10: u64, - /// The R11 register, an additional general-purpose register available in 64-bit mode. - pub r11: u64, - /// The R12 register, an additional general-purpose register available in 64-bit mode. - pub r12: u64, - /// The R13 register, an additional general-purpose register available in 64-bit mode. - pub r13: u64, - /// The R14 register, an additional general-purpose register available in 64-bit mode. - pub r14: u64, - /// The R15 register, an additional general-purpose register available in 64-bit mode. - pub r15: u64, -} - -impl GeneralRegisters { - /// Returns the value of the general-purpose register corresponding to the given index. - /// - /// The mapping of indices to registers is as follows: - /// - 0: `rax` - /// - 1: `rcx` - /// - 2: `rdx` - /// - 3: `rbx` - /// - 5: `rbp` - /// - 6: `rsi` - /// - 7: `rdi` - /// - 8: `r8` - /// - 9: `r9` - /// - 10: `r10` - /// - 11: `r11` - /// - 12: `r12` - /// - 13: `r13` - /// - 14: `r14` - /// - 15: `r15` - /// - /// # Panics - /// - /// This function will panic if the provided index is out of the range [0, 15] or if the index - /// corresponds to an unused register (`rsp` at index 4). - /// - /// # Arguments - /// - /// * `index` - A `u8` value representing the index of the register. - /// - /// # Returns - /// - /// * `u64` - The value of the corresponding general-purpose register. - pub fn get_reg_of_index(&self, index: u8) -> u64 { - match index { - 0 => self.rax, - 1 => self.rcx, - 2 => self.rdx, - 3 => self.rbx, - // 4 => self._unused_rsp, - 5 => self.rbp, - 6 => self.rsi, - 7 => self.rdi, - 8 => self.r8, - 9 => self.r9, - 10 => self.r10, - 11 => self.r11, - 12 => self.r12, - 13 => self.r13, - 14 => self.r14, - 15 => self.r15, - _ => { - panic!("Illegal index of GeneralRegisters {}", index); - } - } - } - - /// Sets the value of the general-purpose register corresponding to the given index. - /// - /// The mapping of indices to registers is as follows: - /// - 0: `rax` - /// - 1: `rcx` - /// - 2: `rdx` - /// - 3: `rbx` - /// - 5: `rbp` - /// - 6: `rsi` - /// - 7: `rdi` - /// - 8: `r8` - /// - 9: `r9` - /// - 10: `r10` - /// - 11: `r11` - /// - 12: `r12` - /// - 13: `r13` - /// - 14: `r14` - /// - 15: `r15` - /// - /// # Panics - /// - /// This function will panic if the provided index is out of the range [0, 15] or if the index - /// corresponds to an unused register (`rsp` at index 4). - /// - /// # Arguments - /// - /// * `index` - A `u8` value representing the index of the register. - /// - /// # Returns - /// - /// * `u64` - The value of the corresponding general-purpose register. - pub fn set_reg_of_index(&mut self, index: u8, value: u64) { - match index { - 0 => self.rax = value, - 1 => self.rcx = value, - 2 => self.rdx = value, - 3 => self.rbx = value, - // 4 => self._unused_rsp, - 5 => self.rbp = value, - 6 => self.rsi = value, - 7 => self.rdi = value, - 8 => self.r8 = value, - 9 => self.r9 = value, - 10 => self.r10 = value, - 11 => self.r11 = value, - 12 => self.r12 = value, - 13 => self.r13 = value, - 14 => self.r14 = value, - 15 => self.r15 = value, - _ => { - panic!("Illegal index of GeneralRegisters {}", index); - } - } - } -} - -macro_rules! save_regs_to_stack { - () => { - " - push r15 - push r14 - push r13 - push r12 - push r11 - push r10 - push r9 - push r8 - push rdi - push rsi - push rbp - sub rsp, 8 - push rbx - push rdx - push rcx - push rax" - }; -} - -macro_rules! restore_regs_from_stack { - () => { - " - pop rax - pop rcx - pop rdx - pop rbx - add rsp, 8 - pop rbp - pop rsi - pop rdi - pop r8 - pop r9 - pop r10 - pop r11 - pop r12 - pop r13 - pop r14 - pop r15" - }; -} diff --git a/src/svm/frame.rs b/src/svm/frame.rs new file mode 100644 index 0000000..e36bb36 --- /dev/null +++ b/src/svm/frame.rs @@ -0,0 +1,81 @@ +use core::marker::PhantomData; + +use axaddrspace::{AxMmHal, HostPhysAddr}; +use axerrno::{AxResult, ax_err_type}; +use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; + + +/// A contiguous block of physical memory frames that will be automatically +/// deallocated when dropped. Used for hardware structures requiring contiguous +/// physical memory (e.g., IOPM, MSRPM). +#[derive(Debug)] +pub struct ContiguousPhysFrames { + start_paddr: Option, + frame_count: usize, + _marker: PhantomData, +} + +impl ContiguousPhysFrames { + pub fn alloc(frame_count: usize) -> AxResult { + let start_paddr = axvisor_api::memory::alloc_contiguous_frames(frame_count, 1) + .ok_or_else(|| ax_err_type!(NoMemory, "allocate contiguous frames failed"))?; + + assert_ne!(start_paddr.as_usize(), 0); + Ok(Self { + start_paddr: Some(start_paddr), + frame_count, + _marker: PhantomData, + }) + } + + pub fn alloc_zero(frame_count: usize) -> AxResult { + let mut frames = Self::alloc(frame_count)?; + frames.fill(0); + Ok(frames) + } + + pub const unsafe fn uninit() -> Self { + Self { + start_paddr: None, + frame_count: 0, + _marker: PhantomData, + } + } + + + pub fn start_paddr(&self) -> HostPhysAddr { + self.start_paddr.expect("uninitialized ContiguousPhysFrames") + } + + pub fn frame_count(&self) -> usize { + self.frame_count + } + + pub fn size(&self) -> usize { + PAGE_SIZE * self.frame_count + } + + + pub fn as_mut_ptr(&self) -> *mut u8 { + H::phys_to_virt(self.start_paddr()).as_mut_ptr() + } + + + pub fn fill(&mut self, byte: u8) { + unsafe { + core::ptr::write_bytes(self.as_mut_ptr(), byte, self.size()); + } + } +} + +impl Drop for ContiguousPhysFrames { + fn drop(&mut self) { + if let Some(start_paddr) = self.start_paddr { + axvisor_api::memory::dealloc_contiguous_frames(start_paddr, self.frame_count); + debug!( + "[AxVM] deallocated ContiguousPhysFrames({:#x}, {} frames)", + start_paddr, self.frame_count + ); + } + } +} \ No newline at end of file diff --git a/src/svm/mod.rs b/src/svm/mod.rs index 2bba4b3..f0acf24 100644 --- a/src/svm/mod.rs +++ b/src/svm/mod.rs @@ -5,6 +5,7 @@ mod structs; // IOPm / MSRPm / Vmcb 封装 mod vcpu; // SvmVcpu(核心逻辑) mod vmcb; mod flags; +mod frame; // VMCB 读写 & VMEXIT 解码 pub use self::definitions::{SvmExitCode,SvmIntercept}; diff --git a/src/svm/percpu.rs b/src/svm/percpu.rs index f309b72..fd2fe68 100644 --- a/src/svm/percpu.rs +++ b/src/svm/percpu.rs @@ -16,7 +16,7 @@ use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; use raw_cpuid::CpuId; use x86_64::registers::control::EferFlags; -use crate::frame::PhysFrame; +use axaddrspace::PhysFrame; use crate::msr::Msr; use crate::svm::has_hardware_support; @@ -26,7 +26,7 @@ use crate::svm::has_hardware_support; // (AMD64 APM Vol.2, Section 15.30.4) //The 64-bit read/write VM_HSAVE_PA MSR holds the physical address of a 4KB block of memory where VMRUN saves host state pub struct SvmPerCpuState { - hsave_page: PhysFrame, + hsave_page: PhysFrame, } impl AxArchPerCpu for SvmPerCpuState { diff --git a/src/svm/structs.rs b/src/svm/structs.rs index d52922c..72a573e 100644 --- a/src/svm/structs.rs +++ b/src/svm/structs.rs @@ -7,14 +7,15 @@ use axaddrspace::HostPhysAddr; use axerrno::{AxResult}; use axvcpu::AxVCpuHal; use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; -use crate::frame::{ContiguousPhysFrames, PhysFrame}; +use axaddrspace::PhysFrame; +use super::frame::{ContiguousPhysFrames}; /// Virtual-Machine Control Block (VMCB) /// One 4 KiB page per vCPU: [control-area | save-area]. #[derive(Debug)] pub struct VmcbFrame { - page: PhysFrame, + page: PhysFrame, } impl VmcbFrame { @@ -41,12 +42,12 @@ impl VmcbFrame { // The map is structured as a linear array of 64K+3 bits (two 4-Kbyte pages, and the first three bits of a third 4-Kbyte page) and must be aligned on a 4-Kbyte boundary; #[derive(Debug)] pub struct IOPm { - frames: ContiguousPhysFrames, // 3 contiguous frames (12KB) + frames: ContiguousPhysFrames, // 3 contiguous frames (12KB) } impl IOPm { pub fn passthrough_all() -> AxResult { - let mut frames = ContiguousPhysFrames::::alloc_zero(3)?; + let mut frames = ContiguousPhysFrames::::alloc_zero(3)?; // Set first 3 bits of third frame to intercept (ports > 0xFFFF) let third_frame_start = frames.as_mut_ptr() as usize + 2 * PAGE_SIZE; @@ -60,7 +61,7 @@ impl IOPm { #[allow(unused)] pub fn intercept_all() -> AxResult { - let mut frames = ContiguousPhysFrames::::alloc(3)?; + let mut frames = ContiguousPhysFrames::::alloc(3)?; frames.fill(0xFF); // Set all bits to 1 (intercept) Ok(Self { frames }) } @@ -99,7 +100,7 @@ impl IOPm { // The four separate bit vectors must be packed together and located in two contiguous physical pages of memory. #[derive(Debug)] pub struct MSRPm { - frames: ContiguousPhysFrames, + frames: ContiguousPhysFrames, } impl MSRPm { diff --git a/src/svm/vcpu.rs b/src/svm/vcpu.rs index 6ff435c..e277476 100644 --- a/src/svm/vcpu.rs +++ b/src/svm/vcpu.rs @@ -1,4 +1,5 @@ use alloc::collections::VecDeque; +use axvisor_api::vmm::{VCpuId, VMId}; use bit_field::BitField; use core::fmt::{Debug, Formatter, Result}; use core::{arch::naked_asm, mem::size_of}; @@ -11,7 +12,8 @@ use x86_64::registers::control::{Cr0, Cr0Flags, Cr3, Cr4, Cr4Flags, EferFlags}; use axaddrspace::{GuestPhysAddr, GuestVirtAddr, HostPhysAddr, NestedPageFaultInfo}; use axerrno::{AxResult, ax_err, ax_err_type}; -use axvcpu::{AccessWidth, AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; +use axvcpu::{AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; +use axaddrspace::device::AccessWidth; use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; use super::definitions::SvmExitCode; @@ -522,52 +524,51 @@ impl SvmVcpu { let guest_regs = self.regs_mut(); // panic!("{:x}",vmcb); // panic!("SVM run not implemented yet"); - loop{}; - asm!( - // "clgi", - "mov rax, {0}", - "vmload rax", - "vmrun rax", - // "call {entry}", - // in(reg) guest_regs, - in(reg) vmcb, - // entry = sym Self::svm_entry, - options(noreturn), - ); + // loop{}; + unsafe { + asm!( + // "clgi", + "mov rax, {0}", + "vmload rax", + "vmrun rax", + // "call {entry}", + // in(reg) guest_regs, + in(reg) vmcb, + // entry = sym Self::svm_entry, + options(noreturn), + ); + } } - #[naked] - unsafe extern "C" fn svm_entry() -> ! { - naked_asm!( - "ud2", - // "mov [rdi + {host_stack_size}], rsp", - // "mov rsp, rdi", - // // restore_regs_from_stack!(), - // "vmload rax", - // "vmrun rax", - "jmp {failed}", - // host_stack_size = const size_of::(), - failed = sym Self::svm_entry_failed, - ) - } + // #[unsafe(naked)] + // unsafe extern "C" fn svm_entry() -> ! { + // naked_asm!( + // "ud2", + // // "mov [rdi + {host_stack_size}], rsp", + // // "mov rsp, rdi", + // // // restore_regs_from_stack!(), + // // "vmload rax", + // // "vmrun rax", + // "jmp {failed}", + // // host_stack_size = const size_of::(), + // failed = sym Self::svm_entry_failed, + // ) + // } - #[naked] + #[unsafe(naked)] /// Return after vm-exit. /// /// The return value is a dummy value. unsafe extern "C" fn svm_exit(&mut self) -> usize { - unsafe { - naked_asm!( - save_regs_to_stack!(), // save guest status - "mov rsp, [rsp + {host_stack_top}]", // set RSP to Vcpu::host_stack_top - restore_regs_from_stack!(), // restore host status - "ret", - host_stack_top = const size_of::(), - ); - } - + naked_asm!( + save_regs_to_stack!(), // save guest status + "mov rsp, [rsp + {host_stack_top}]", // set RSP to Vcpu::host_stack_top + restore_regs_from_stack!(), // restore host status + "ret", + host_stack_top = const size_of::(), + ); } @@ -659,7 +660,7 @@ impl AxArchVCpu for SvmVcpu { type CreateConfig = (); type SetupConfig = (); - fn new(_config: Self::CreateConfig) -> AxResult { + fn new(vm_id: VMId, vcpu_id: VCpuId, config: Self::CreateConfig) -> AxResult { Self::new() } @@ -702,6 +703,14 @@ impl AxArchVCpu for SvmVcpu { fn set_gpr(&mut self, reg: usize, val: usize) { self.regs_mut().set_reg_of_index(reg as u8, val as u64); } + + fn inject_interrupt(&mut self, vector: usize) -> AxResult { + todo!() + } + + fn set_return_value(&mut self, val: usize) { + self.regs_mut().rax = val as u64; + } } diff --git a/src/vmx/mod.rs b/src/vmx/mod.rs index e80688e..9fb40b7 100644 --- a/src/vmx/mod.rs +++ b/src/vmx/mod.rs @@ -22,7 +22,6 @@ pub fn has_hardware_support() -> bool { } } - pub fn read_vmcs_revision_id() -> u32 { VmxBasic::read().revision_id } diff --git a/src/vmx/percpu.rs b/src/vmx/percpu.rs index 6e1e743..484a723 100644 --- a/src/vmx/percpu.rs +++ b/src/vmx/percpu.rs @@ -14,6 +14,7 @@ use crate::vmx::structs::{FeatureControl, FeatureControlFlags, VmxBasic, VmxRegi /// This structure holds the state information specific to a CPU core /// when operating in VMX mode, including the VMCS revision identifier and /// the VMX region. +#[derive(Debug)] pub struct VmxPerCpuState { /// The VMCS (Virtual Machine Control Structure) revision identifier. /// @@ -25,7 +26,7 @@ pub struct VmxPerCpuState { /// /// This region typically contains the VMCS and other state information /// required for managing virtual machines on this particular CPU. - vmx_region: VmxRegion, + vmx_region: VmxRegion, } impl AxArchPerCpu for VmxPerCpuState { @@ -68,8 +69,10 @@ impl AxArchPerCpu for VmxPerCpuState { ($value: expr, $crx: ident) => {{ use Msr::*; let value = $value; - let fixed0 = concat_idents!(IA32_VMX_, $crx, _FIXED0).read(); - let fixed1 = concat_idents!(IA32_VMX_, $crx, _FIXED1).read(); + paste::paste! { + let fixed0 = [].read(); + let fixed1 = [].read(); + } (!fixed0 | value != 0) && (fixed1 | !value != 0) }}; } @@ -138,3 +141,80 @@ impl AxArchPerCpu for VmxPerCpuState { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::mock::{MockMmHal, MockVCpuHal}; + use alloc::format; + use alloc::vec::Vec; + + #[test] + fn test_vmx_per_cpu_state_new() { + MockMmHal::reset(); // Reset before test + let result = VmxPerCpuState::::new(0); + assert!(result.is_ok()); + + let state = result.unwrap(); + assert_eq!(state.vmcs_revision_id, 0); + } + + #[test] + fn test_vmx_per_cpu_state_default_values() { + MockMmHal::reset(); // Reset before test + let state = VmxPerCpuState::::new(0).unwrap(); + + // Test that vmcs_revision_id is initialized to 0 + assert_eq!(state.vmcs_revision_id, 0); + + // The VMX region should be in an uninitialized state + // We can't test this directly as the field is private, + // but we can ensure the struct is created successfully + } + + #[test] + fn test_multiple_cpu_states_independence() { + MockMmHal::reset(); // Reset before test + let mut states = Vec::new(); + + // Create states for multiple CPUs + for cpu_id in 0..4 { + let state = VmxPerCpuState::::new(cpu_id).unwrap(); + states.push(state); + } + + // Test independence by modifying one state and verifying others are unaffected + states[0].vmcs_revision_id = 0x12345678; + states[1].vmcs_revision_id = 0x87654321; + + // Verify each state maintains its own value + assert_eq!(states[0].vmcs_revision_id, 0x12345678); + assert_eq!(states[1].vmcs_revision_id, 0x87654321); + assert_eq!(states[2].vmcs_revision_id, 0); + assert_eq!(states[3].vmcs_revision_id, 0); + } + + #[test] + fn test_vmx_per_cpu_state_debug() { + MockMmHal::reset(); // Reset before test + let state = VmxPerCpuState::::new(0).unwrap(); + + // Test that Debug trait is implemented and doesn't panic + let debug_str = format!("{:?}", state); + assert!(!debug_str.is_empty()); + } + + #[test] + fn test_vmx_per_cpu_state_size() { + use core::mem; + + // Test that the struct has a reasonable size + let size = mem::size_of::>(); + + // Should be larger than just the u32 field due to the VmxRegion + assert!(size > 4); + + // But shouldn't be excessively large (this is a sanity check) + assert!(size < 1024); + } +} diff --git a/src/vmx/structs.rs b/src/vmx/structs.rs index fa3f698..8e00a7a 100644 --- a/src/vmx/structs.rs +++ b/src/vmx/structs.rs @@ -3,20 +3,18 @@ use bitflags::bitflags; use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; -use axaddrspace::HostPhysAddr; +use axaddrspace::{AxMmHal, HostPhysAddr, PhysFrame}; use axerrno::AxResult; -use axvcpu::AxVCpuHal; -use crate::frame::PhysFrame; use crate::msr::{Msr, MsrReadWrite}; /// VMCS/VMXON region in 4K size. (SDM Vol. 3C, Section 24.2) #[derive(Debug)] -pub struct VmxRegion { +pub struct VmxRegion { frame: PhysFrame, } -impl VmxRegion { +impl VmxRegion { pub const unsafe fn uninit() -> Self { Self { frame: unsafe { PhysFrame::uninit() }, @@ -43,12 +41,12 @@ impl VmxRegion { // I/O bitmap A contains one bit for each I/O port in the range 0000H through 7FFFH; // I/O bitmap B contains bits for ports in the range 8000H through FFFFH. #[derive(Debug)] -pub struct IOBitmap { +pub struct IOBitmap { io_bitmap_a_frame: PhysFrame, io_bitmap_b_frame: PhysFrame, } -impl IOBitmap { +impl IOBitmap { pub fn passthrough_all() -> AxResult { Ok(Self { io_bitmap_a_frame: PhysFrame::alloc_zero()?, @@ -103,11 +101,11 @@ impl IOBitmap { } #[derive(Debug)] -pub struct MsrBitmap { +pub struct MsrBitmap { frame: PhysFrame, } -impl MsrBitmap { +impl MsrBitmap { pub fn passthrough_all() -> AxResult { Ok(Self { frame: PhysFrame::alloc_zero()?, @@ -206,6 +204,7 @@ impl VmxBasic { bitflags! { /// IA32_FEATURE_CONTROL flags. + #[derive(Debug)] pub struct FeatureControlFlags: u64 { /// Lock bit: when set, locks this MSR from being written. when clear, /// VMXON causes a #GP. @@ -241,6 +240,7 @@ impl FeatureControl { bitflags! { /// Extended-Page-Table Pointer. (SDM Vol. 3C, Section 24.6.11) + #[derive(Debug)] pub struct EPTPointer: u64 { /// EPT paging-structure memory type: Uncacheable (UC). #[allow(clippy::identity_op)] @@ -268,3 +268,203 @@ impl EPTPointer { flags | Self::MEM_TYPE_WB | Self::WALK_LENGTH_4 | Self::ENABLE_ACCESSED_DIRTY } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::mock::MockMmHal; + use alloc::format; + + #[test] + fn test_vmx_region_uninit() { + let region = unsafe { VmxRegion::::uninit() }; + + // Test that we can create an uninitialized region + // Can't test much more without allocating memory + let debug_str = format!("{:?}", region); + assert!(!debug_str.is_empty()); + } + + #[test] + fn test_vmx_region_new() { + // Reset allocator for consistent testing + MockMmHal::reset(); + + // Test VmxRegion::new with valid parameters + let region = VmxRegion::::new(0x12345, false); + assert!(region.is_ok()); + + let region = region.unwrap(); + let addr = region.phys_addr(); + assert_ne!(addr.as_usize(), 0); + // Should be page-aligned + assert_eq!(addr.as_usize() % 0x1000, 0); + } + + #[test] + fn test_vmx_region_new_with_shadow() { + // Reset allocator for consistent testing + MockMmHal::reset(); + + // Test VmxRegion::new with different shadow indicator values + let region_no_shadow = VmxRegion::::new(0x12345, false); + assert!(region_no_shadow.is_ok()); + + let region_with_shadow = VmxRegion::::new(0x12345, true); + assert!(region_with_shadow.is_ok()); + + // Test that both regions have valid physical addresses + let region1 = region_no_shadow.unwrap(); + let region2 = region_with_shadow.unwrap(); + + let addr1 = region1.phys_addr(); + let addr2 = region2.phys_addr(); + + assert_ne!(addr1.as_usize(), 0); + assert_ne!(addr2.as_usize(), 0); + assert_ne!(addr1.as_usize(), addr2.as_usize()); + assert_eq!(addr1.as_usize() % 0x1000, 0); + assert_eq!(addr2.as_usize() % 0x1000, 0); + } + + #[test] + fn test_io_bitmap_creation() { + // Test IOBitmap creation methods + MockMmHal::reset(); + + // Test passthrough_all creation + let passthrough_bitmap = IOBitmap::::passthrough_all(); + assert!(passthrough_bitmap.is_ok()); + + // Test intercept_all creation + let intercept_bitmap = IOBitmap::::intercept_all(); + assert!(intercept_bitmap.is_ok()); + + // Test that phys_addr returns valid addresses + let bitmap = passthrough_bitmap.unwrap(); + let (addr_a, addr_b) = bitmap.phys_addr(); + assert_ne!(addr_a.as_usize(), 0); + assert_ne!(addr_b.as_usize(), 0); + assert_ne!(addr_a.as_usize(), addr_b.as_usize()); + } + + #[test] + fn test_msr_bitmap_creation() { + // Test MsrBitmap creation methods + MockMmHal::reset(); + + // Test passthrough_all creation + let passthrough_bitmap = MsrBitmap::::passthrough_all(); + assert!(passthrough_bitmap.is_ok()); + + // Test intercept_all creation + let intercept_bitmap = MsrBitmap::::intercept_all(); + assert!(intercept_bitmap.is_ok()); + + // Test that phys_addr returns valid addresses + let bitmap = passthrough_bitmap.unwrap(); + let addr = bitmap.phys_addr(); + assert_ne!(addr.as_usize(), 0); + assert_eq!(addr.as_usize() % 0x1000, 0); + } + + #[test] + fn test_ept_pointer_creation() { + // Test EPTPointer creation with from_table_phys method + let ept_ptr1 = EPTPointer::from_table_phys(memory_addr::PhysAddr::from(0x1000)); + let ept_ptr2 = EPTPointer::from_table_phys(memory_addr::PhysAddr::from(0x2000)); + + // Verify the EPT pointers were created successfully + assert_ne!(ept_ptr1.0, ept_ptr2.0); + } + + #[test] + fn test_ept_pointer_getters() { + let phys_addr = memory_addr::PhysAddr::from(0x3000); + let ept_ptr = EPTPointer::from_table_phys(phys_addr); + + // Test that we can create EPT pointer and it has expected flags + let bits = ept_ptr.bits(); + assert_ne!(bits, 0); + + // Should have the expected flags set + let expected_flags = + EPTPointer::MEM_TYPE_WB | EPTPointer::WALK_LENGTH_4 | EPTPointer::ENABLE_ACCESSED_DIRTY; + assert_eq!(bits & expected_flags.bits(), expected_flags.bits()); + } + + #[test] + fn test_vmx_basic_constants() { + assert_eq!(VmxBasic::VMX_MEMORY_TYPE_WRITE_BACK, 6); + } + + #[test] + fn test_feature_control_flags() { + let flags = FeatureControlFlags::LOCKED | FeatureControlFlags::VMXON_ENABLED_OUTSIDE_SMX; + + assert!(flags.contains(FeatureControlFlags::LOCKED)); + assert!(flags.contains(FeatureControlFlags::VMXON_ENABLED_OUTSIDE_SMX)); + assert!(!flags.contains(FeatureControlFlags::VMXON_ENABLED_INSIDE_SMX)); + } + + #[test] + fn test_ept_pointer_flags() { + use EPTPointer as EPT; + + // Test individual flags + assert_eq!(EPT::MEM_TYPE_UC.bits(), 0); + assert_eq!(EPT::MEM_TYPE_WB.bits(), 6); + assert_eq!(EPT::WALK_LENGTH_4.bits(), 3 << 3); + + // Test flag combination + let combined = EPT::MEM_TYPE_WB | EPT::WALK_LENGTH_4 | EPT::ENABLE_ACCESSED_DIRTY; + assert!(combined.contains(EPT::MEM_TYPE_WB)); + assert!(combined.contains(EPT::WALK_LENGTH_4)); + assert!(combined.contains(EPT::ENABLE_ACCESSED_DIRTY)); + } + + #[test] + fn test_ept_pointer_from_table_phys() { + let pml4_addr = HostPhysAddr::from(0x12345000_usize); // Page-aligned address + let ept_ptr = EPTPointer::from_table_phys(pml4_addr); + + // Should have the correct flags set + assert!(ept_ptr.contains(EPTPointer::MEM_TYPE_WB)); + assert!(ept_ptr.contains(EPTPointer::WALK_LENGTH_4)); + assert!(ept_ptr.contains(EPTPointer::ENABLE_ACCESSED_DIRTY)); + + // Address should be preserved (and aligned) + let addr_part = ept_ptr.bits() & !0xfff; + assert_eq!(addr_part, 0x12345000); + } + + #[test] + fn test_ept_pointer_from_unaligned_addr() { + let unaligned_addr = HostPhysAddr::from(0x12345678_usize); // Not page-aligned + let ept_ptr = EPTPointer::from_table_phys(unaligned_addr); + + // Address should be aligned down + let addr_part = ept_ptr.bits() & !0xfff; + // Should be aligned to 4K boundary + assert_eq!(addr_part, 0x12345000); + } + + #[test] + fn test_debug_implementations() { + // Test that all our structs implement Debug properly + let vmx_region = unsafe { VmxRegion::::uninit() }; + let _debug_str = format!("{:?}", vmx_region); + + let io_bitmap = IOBitmap::::passthrough_all().unwrap(); + let _debug_str = format!("{:?}", io_bitmap); + + let msr_bitmap = MsrBitmap::::passthrough_all().unwrap(); + let _debug_str = format!("{:?}", msr_bitmap); + + let flags = FeatureControlFlags::LOCKED; + let _debug_str = format!("{:?}", flags); + + let ept_flags = EPTPointer::MEM_TYPE_WB; + let _debug_str = format!("{:?}", ept_flags); + } +} diff --git a/src/vmx/vcpu.rs b/src/vmx/vcpu.rs index b8e43b8..e142fdb 100644 --- a/src/vmx/vcpu.rs +++ b/src/vmx/vcpu.rs @@ -1,27 +1,36 @@ use alloc::collections::VecDeque; use bit_field::BitField; -use core::fmt::{Debug, Formatter, Result}; -use core::{arch::naked_asm, mem::size_of}; +use core::{ + arch::naked_asm, + fmt::{Debug, Formatter, Result}, + mem::size_of, +}; use raw_cpuid::CpuId; -use x86::bits64::vmx; -use x86::controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}; -use x86::dtables::{self, DescriptorTablePointer}; -use x86::segmentation::SegmentSelector; +use x86::{ + bits64::vmx, + controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}, + dtables::{self, DescriptorTablePointer}, + segmentation::SegmentSelector, +}; use x86_64::registers::control::{Cr0, Cr0Flags, Cr3, Cr4, Cr4Flags, EferFlags}; +use x86_vlapic::EmulatedLocalApic; -use axaddrspace::{GuestPhysAddr, GuestVirtAddr, HostPhysAddr, NestedPageFaultInfo}; +use axaddrspace::{ + GuestPhysAddr, GuestVirtAddr, HostPhysAddr, NestedPageFaultInfo, + device::{AccessWidth, Port, SysRegAddr, SysRegAddrRange}, +}; +use axdevice_base::BaseDeviceOps; use axerrno::{AxResult, ax_err, ax_err_type}; -use axvcpu::{AccessWidth, AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; - - +use axvcpu::{AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; +use axvisor_api::vmm::{VCpuId, VMId}; use super::VmxExitInfo; use super::as_axerr; use super::definitions::VmxExitReason; use super::structs::{IOBitmap, MsrBitmap, VmxRegion}; use super::vmcs::{ - self, VmcsControl32, VmcsControl64, VmcsControlNW, VmcsGuest16, VmcsGuest32, VmcsGuest64, - VmcsGuestNW, VmcsHost16, VmcsHost32, VmcsHost64, VmcsHostNW, + self, ApicAccessExitType, VmcsControl32, VmcsControl64, VmcsControlNW, VmcsGuest16, + VmcsGuest32, VmcsGuest64, VmcsGuestNW, VmcsHost16, VmcsHost32, VmcsHost64, VmcsHostNW, }; use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters}; @@ -145,37 +154,68 @@ const CR0_PE: usize = 1 << 0; /// A virtual CPU within a guest. #[repr(C)] pub struct VmxVcpu { - // DO NOT modify `guest_regs` and `host_stack_top` and their order unless you do know what you are doing! - // DO NOT add anything before or between them unless you do know what you are doing! + // The order of `guest_regs` and `host_stack_top` is mandatory. They must be the first two fields. If you want to + // change the order or the type of these fields, you must also change the assembly in this file. + /// Guest general-purpose registers. guest_regs: GeneralRegisters, + /// The top of the host stack. host_stack_top: u64, + + // The order of the following fields is not mandatory. + + // VCpu states and configurations + /// Whether the VMCS has been launched. Used to determine whether to `vmx_launch` or `vmx_resume`. launched: bool, - vmcs: VmxRegion, - io_bitmap: IOBitmap, - msr_bitmap: MsrBitmap, - pending_events: VecDeque<(u8, Option)>, - xstate: XState, + /// The guest entry point. entry: Option, + /// The EPT root address. ept_root: Option, + // /// Whether this VCPU is a host VCpu. Used in type 1.5 hypervisor. // is_host: bool, temporary removed because we don't care about type 1.5 now + + // VMCS-related fields + /// The VMCS region. + vmcs: VmxRegion, + /// The I/O bitmap for the VMCS. + io_bitmap: IOBitmap, + /// The MSR bitmap for the VMCS. + msr_bitmap: MsrBitmap, + + // Interrupt-related fields + /// Pending events to be injected to the guest. + pending_events: VecDeque<(u8, Option)>, + /// Emulated Local APIC. + vlapic: EmulatedLocalApic, + + // Extra states + /// The XState of the VCpu. Both host and guest. + xstate: XState, + + // Tracing-related fields + #[cfg(feature = "tracing")] + /// The guest registers when the VM-exit happens. + guest_regs_exiting: GeneralRegisters, } impl VmxVcpu { /// Create a new [`VmxVcpu`]. - pub fn new() -> AxResult { + pub fn new(vm_id: VMId, vcpu_id: VCpuId) -> AxResult { let vmcs_revision_id = super::read_vmcs_revision_id(); let vcpu = Self { guest_regs: GeneralRegisters::default(), host_stack_top: 0, launched: false, + entry: None, + ept_root: None, + // is_host: false, vmcs: VmxRegion::new(vmcs_revision_id, false)?, io_bitmap: IOBitmap::passthrough_all()?, msr_bitmap: MsrBitmap::passthrough_all()?, pending_events: VecDeque::with_capacity(8), + vlapic: EmulatedLocalApic::new(vm_id, vcpu_id), xstate: XState::new(), - entry: None, - ept_root: None, - // is_host: false, + #[cfg(feature = "tracing")] + guest_regs_exiting: GeneralRegisters::default(), }; info!("[HV] created VmxVcpu(vmcs: {:#x})", vcpu.vmcs.phys_addr()); Ok(vcpu) @@ -239,13 +279,26 @@ impl VmxVcpu { /// Run the guest. It returns when a vm-exit happens and returns the vm-exit if it cannot be handled by this [`VmxVcpu`] itself. pub fn inner_run(&mut self) -> Option { - // Inject pending events - if self.launched { - self.inject_pending_events().unwrap(); - } + self.inject_pending_events().unwrap(); // Run guest self.load_guest_xstate(); + + #[cfg(feature = "tracing")] + { + use crate::regs::GeneralRegistersDiff; + // Tracing, do a diff of the guest registers before entering the guest + let diff = GeneralRegistersDiff::new(self.guest_regs_exiting, self.guest_regs); + if !diff.is_same() { + debug!( + "VCpu registers changed during handling VM-exit: {:#x?}", + diff + ); + } else { + debug!("VCpu registers unchanged during handling VM-exit"); + } + } + unsafe { if self.launched { self.vmx_resume(); @@ -260,6 +313,11 @@ impl VmxVcpu { } self.load_host_xstate(); + #[cfg(feature = "tracing")] + { + self.guest_regs_exiting = self.guest_regs; + } + // Handle vm-exits let exit_info = self.exit_info().unwrap(); // debug!("VM exit: {:#x?}", exit_info); @@ -300,12 +358,17 @@ impl VmxVcpu { pub fn io_exit_info(&self) -> AxResult { vmcs::io_exit_info() } -/////////////////////////////////////////////////111111111 + /// Information for VM exits due to nested page table faults (EPT violation). pub fn nested_page_fault_info(&self) -> AxResult { vmcs::ept_violation_info() } + /// Information for VM exits due to APIC access. + pub fn apic_access_exit_info(&self) -> AxResult { + vmcs::apic_access_exit_info() + } + /// Guest general-purpose registers. pub fn regs(&self) -> &GeneralRegisters { &self.guest_regs @@ -495,10 +558,10 @@ impl VmxVcpu { .set_read_intercept(IA32_UMWAIT_CONTROL, true); // Intercept all x2APIC MSR accesses - // for msr in 0x800..=0x83f { - // self.msr_bitmap.set_read_intercept(msr, true); - // self.msr_bitmap.set_write_intercept(msr, true); - // } + for msr in 0x800..=0x83f { + self.msr_bitmap.set_read_intercept(msr, true); + self.msr_bitmap.set_write_intercept(msr, true); + } Ok(()) } @@ -508,6 +571,7 @@ impl VmxVcpu { vmx::vmclear(paddr).map_err(as_axerr)?; } self.bind_to_current_processor()?; + self.setup_msr_bitmap()?; self.setup_vmcs_guest(entry)?; self.setup_vmcs_control(ept_root, true)?; self.unbind_from_current_processor()?; @@ -562,10 +626,12 @@ impl VmxVcpu { use VmcsGuest16::*; use VmcsGuest32::*; use VmcsGuestNW::*; - concat_idents!($seg, _SELECTOR).write(0)?; - concat_idents!($seg, _BASE).write(0)?; - concat_idents!($seg, _LIMIT).write(0xffff)?; - concat_idents!($seg, _ACCESS_RIGHTS).write($access_rights)?; + paste::paste! { + [<$seg _SELECTOR>].write(0)?; + [<$seg _BASE>].write(0)?; + [<$seg _LIMIT>].write(0xffff)?; + [<$seg _ACCESS_RIGHTS>].write($access_rights)?; + } }}; } @@ -615,9 +681,9 @@ impl VmxVcpu { VmcsControl32::PINBASED_EXEC_CONTROLS, Msr::IA32_VMX_TRUE_PINBASED_CTLS, Msr::IA32_VMX_PINBASED_CTLS.read() as u32, - // (PinCtrl::NMI_EXITING | PinCtrl::EXTERNAL_INTERRUPT_EXITING).bits(), + (PinCtrl::NMI_EXITING | PinCtrl::EXTERNAL_INTERRUPT_EXITING).bits(), // (PinCtrl::NMI_EXITING | PinCtrl::VMX_PREEMPTION_TIMER).bits(), - PinCtrl::NMI_EXITING.bits(), + // PinCtrl::NMI_EXITING.bits(), 0, )?; @@ -639,7 +705,9 @@ impl VmxVcpu { // Enable EPT, RDTSCP, INVPCID, and unrestricted guest. use SecondaryControls as CpuCtrl2; - let mut val = CpuCtrl2::ENABLE_EPT | CpuCtrl2::UNRESTRICTED_GUEST; + let mut val = + // CpuCtrl2::VIRTUALIZE_APIC | + CpuCtrl2::ENABLE_EPT | CpuCtrl2::UNRESTRICTED_GUEST; if let Some(features) = raw_cpuid.get_extended_processor_and_feature_identifiers() { if features.has_rdtscp() { val |= CpuCtrl2::ENABLE_RDTSCP; @@ -717,6 +785,10 @@ impl VmxVcpu { VmcsControl64::IO_BITMAP_A_ADDR.write(self.io_bitmap.phys_addr().0.as_usize() as _)?; VmcsControl64::IO_BITMAP_B_ADDR.write(self.io_bitmap.phys_addr().1.as_usize() as _)?; VmcsControl64::MSR_BITMAPS_ADDR.write(self.msr_bitmap.phys_addr().as_usize() as _)?; + + // VmcsControl64::APIC_ACCESS_ADDR.write( + // EmulatedLocalApic::::virtual_apic_access_addr().as_usize() as _, + // )?; Ok(()) } @@ -804,24 +876,22 @@ impl VmxVcpu { /// Get ready then vmlaunch or vmresume. macro_rules! vmx_entry_with { ($instr:literal) => { - unsafe { - naked_asm!( - save_regs_to_stack!(), // save host status - "mov [rdi + {host_stack_size}], rsp", // save current RSP to Vcpu::host_stack_top - "mov rsp, rdi", // set RSP to guest regs area - restore_regs_from_stack!(), // restore guest status - $instr, // let's go! - "jmp {failed}", - host_stack_size = const size_of::(), - failed = sym Self::vmx_entry_failed, - // options(noreturn), - ) - } + naked_asm!( + save_regs_to_stack!(), // save host status + "mov [rdi + {host_stack_size}], rsp", // save current RSP to Vcpu::host_stack_top + "mov rsp, rdi", // set RSP to guest regs area + restore_regs_from_stack!(), // restore guest status + $instr, // let's go! + "jmp {failed}", + host_stack_size = const size_of::(), + failed = sym Self::vmx_entry_failed, + // options(noreturn), + ) } } impl VmxVcpu { - #[naked] + #[unsafe(naked)] /// Enter guest with vmlaunch. /// /// `#[naked]` is essential here, without it the rust compiler will think `&mut self` is not used and won't give us correct %rdi. @@ -833,7 +903,7 @@ impl VmxVcpu { vmx_entry_with!("vmlaunch") } - #[naked] + #[unsafe(naked)] /// Enter guest with vmresume. /// /// See [`Self::vmx_launch`] for detail. @@ -841,20 +911,21 @@ impl VmxVcpu { vmx_entry_with!("vmresume") } - #[naked] - /// Return after vm-exit. + #[unsafe(naked)] + /// Return after vm-exit. This function is used only for returning from [`Self::vmx_launch`] or [`Self::vmx_resume`]. + /// + /// NEVER call this function directly. /// /// The return value is a dummy value. unsafe extern "C" fn vmx_exit(&mut self) -> usize { - unsafe { - naked_asm!( - save_regs_to_stack!(), // save guest status - "mov rsp, [rsp + {host_stack_top}]", // set RSP to Vcpu::host_stack_top - restore_regs_from_stack!(), // restore host status - "ret", - host_stack_top = const size_of::(), - ); - } + // it's not necessary to use another `unsafe` here, as Rust now do not require it in naked functions. + naked_asm!( + save_regs_to_stack!(), // save guest status, after this, rsp points to the `VmxVcpu` + "mov rsp, [rsp + {host_stack_top}]", // set RSP to Vcpu::host_stack_top + restore_regs_from_stack!(), // restore host status + "ret", + host_stack_top = const size_of::(), + ); } fn vmx_entry_failed() -> ! { @@ -872,8 +943,8 @@ impl VmxVcpu { /// Try to inject a pending event before next VM entry. fn inject_pending_events(&mut self) -> AxResult { if let Some(event) = self.pending_events.front() { - // debug!( - // "inject_pending_events vector {:#x} allow_int {}", + // trace!( + // "pending event vector {:#x} allow_int {}", // event.0, // self.allow_interrupt() // ); @@ -893,6 +964,8 @@ impl VmxVcpu { /// /// Return the result or None if the vm-exit was not handled. fn builtin_vmexit_handler(&mut self, exit_info: &VmxExitInfo) -> Option { + const X2APIC_MSR_BASE: u32 = 0x800; + const X2APIC_MSR_END: u32 = 0x8ff; // SDM says 0x8ff, but actually 0x83f, we respect the SDM here. // Following vm-exits are handled here: // - interrupt window: turn off interrupt window; // - xsetbv: set guest xcr; @@ -903,10 +976,91 @@ impl VmxVcpu { VmxExitReason::XSETBV => Some(self.handle_xsetbv()), VmxExitReason::CR_ACCESS => Some(self.handle_cr()), VmxExitReason::CPUID => Some(self.handle_cpuid()), + msr_rw @ (VmxExitReason::MSR_READ | VmxExitReason::MSR_WRITE) + if { + let msr = self.regs().rcx as u32; + msr >= X2APIC_MSR_BASE && msr <= X2APIC_MSR_END + } => + { + Some(self.handle_apic_msr_access( + msr_rw == VmxExitReason::MSR_WRITE, + self.regs().rcx as u32, + )) + } + VmxExitReason::APIC_ACCESS => Some(self.handle_apic_access(exit_info)), _ => None, } } + /// Read a 64-bit value from EDX:EAX. + fn read_edx_eax(&self) -> u64 { + ((self.regs().rdx & 0xffff_ffff) << 32) | (self.regs().rax & 0xffff_ffff) + } + + /// Write a 64-bit value to EDX:EAX. + fn write_edx_eax(&mut self, val: u64) { + self.regs_mut().rax = val & 0xffff_ffff; + self.regs_mut().rdx = val >> 32; + } + + fn handle_apic_msr_access(&mut self, write: bool, msr: u32) -> AxResult { + const VMEXIT_INSTR_LEN_RDMSR_WRMSR: u8 = 2; + + self.advance_rip(VMEXIT_INSTR_LEN_RDMSR_WRMSR)?; + + let msr = msr as _; + if write { + let value = self.read_edx_eax() as usize; + + trace!( + "handle_vlapic_msr_write: msr={:#x}, value={:#x}", + msr, value + ); + + >::handle_write( + &self.vlapic, + SysRegAddr::new(msr), + AccessWidth::Qword, + value, + ) + } else { + let value = >::handle_read( + &self.vlapic, + SysRegAddr::new(msr), + AccessWidth::Qword, + )? as u64; + + trace!("handle_vlapic_msr_read: msr={:#x}, value={:#x}", msr, value); + + self.write_edx_eax(value); + Ok(()) + } + } + + fn handle_apic_access(&mut self, exit_info: &VmxExitInfo) -> AxResult { + let apic_access_exit_info = self.apic_access_exit_info()?; + + let write = match apic_access_exit_info.access_type { + ApicAccessExitType::LinearDataWrite => true, + ApicAccessExitType::LinearDataRead => false, + _ => { + warn!( + "Unsupported APIC access type: {:?}", + apic_access_exit_info.access_type + ); + return ax_err!(BadState, "Unsupported APIC access type"); + } + }; + + // TODO: handle APIC access. + let _ = write; + + self.advance_rip(exit_info.exit_instruction_length as _)?; + + unimplemented!("apic access"); + // TODO + } + fn handle_vmx_preemption_timer(&mut self) -> AxResult { /* The VMX-preemption timer counts down at rate proportional to that of the timestamp counter (TSC). @@ -1148,8 +1302,8 @@ impl AxArchVCpu for VmxVcpu { type SetupConfig = (); - fn new(_config: Self::CreateConfig) -> AxResult { - Self::new() + fn new(vm_id: VMId, vcpu_id: VCpuId, _config: Self::CreateConfig) -> AxResult { + Self::new(vm_id, vcpu_id) } fn set_entry(&mut self, entry: GuestPhysAddr) -> AxResult { @@ -1216,7 +1370,10 @@ impl AxArchVCpu for VmxVcpu { }; if io_info.is_in { - AxVCpuExitReason::IoRead { port, width } + AxVCpuExitReason::IoRead { + port: Port(port), + width, + } } else if port == QEMU_EXIT_PORT && width == AccessWidth::Word && self.regs().rax == QEMU_EXIT_MAGIC @@ -1224,13 +1381,35 @@ impl AxArchVCpu for VmxVcpu { AxVCpuExitReason::SystemDown } else { AxVCpuExitReason::IoWrite { - port, + port: Port(port), width, data: self.regs().rax.get_bits(width.bits_range()), } } } } + VmxExitReason::EXTERNAL_INTERRUPT => { + let int_info = self.interrupt_exit_info()?; + assert!(int_info.valid); + AxVCpuExitReason::ExternalInterrupt { + vector: int_info.vector as _, + } + } + VmxExitReason::MSR_READ => { + // `reg` is unused here. + AxVCpuExitReason::SysRegRead { + addr: SysRegAddr::new(self.regs().rcx as _), + reg: 0, + } + } + VmxExitReason::MSR_WRITE => { + let value = (self.regs().rax & 0xffff_ffff) + | ((self.regs().rdx & 0xffff_ffff) << 32); + AxVCpuExitReason::SysRegWrite { + addr: SysRegAddr::new(self.regs().rcx as _), + value, + } + } _ => { warn!("VMX unsupported VM-Exit: {:#x?}", exit_info); warn!("VCpu {:#x?}", self); @@ -1254,4 +1433,286 @@ impl AxArchVCpu for VmxVcpu { fn set_gpr(&mut self, reg: usize, val: usize) { self.regs_mut().set_reg_of_index(reg as u8, val as u64); } + + fn inject_interrupt(&mut self, vector: usize) -> AxResult { + if vector != 0 { + // warn!("interrupt queued in inject_interrupt: vector {:#x}", vector); + } else { + warn!("interrupt queued in inject_interrupt: vector 0"); + panic!() + } + Ok(self.queue_event(vector as u8, None)) + } + + fn set_return_value(&mut self, val: usize) { + self.regs_mut().rax = val as u64; + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::format; + + #[test] + fn test_vm_cpu_mode_enum() { + // Test VmCpuMode enum values + assert_ne!(VmCpuMode::Real, VmCpuMode::Protected); + assert_ne!(VmCpuMode::Protected, VmCpuMode::Compatibility); + assert_ne!(VmCpuMode::Compatibility, VmCpuMode::Mode64); + + // Test Debug formatting + let debug_str = format!("{:?}", VmCpuMode::Mode64); + assert!(debug_str.contains("Mode64")); + } + + #[test] + fn test_general_registers_operations() { + let mut regs = GeneralRegisters::default(); + + // Test initial state + assert_eq!(regs.rax, 0); + assert_eq!(regs.rbx, 0); + + // Test setting and getting values + regs.rax = 0x1234567890abcdef; + regs.rbx = 0xfedcba0987654321; + + assert_eq!(regs.rax, 0x1234567890abcdef); + assert_eq!(regs.rbx, 0xfedcba0987654321); + + // Test register access by index + regs.set_reg_of_index(0, 0x1111111111111111); // RAX + assert_eq!(regs.get_reg_of_index(0), 0x1111111111111111); + + regs.set_reg_of_index(1, 0x2222222222222222); // RCX + assert_eq!(regs.get_reg_of_index(1), 0x2222222222222222); + } + + #[test] + fn test_constants() { + // Test that constants have expected values + assert_eq!(VMX_PREEMPTION_TIMER_SET_VALUE, 1_000_000); + assert_eq!(QEMU_EXIT_PORT, 0x604); + assert_eq!(QEMU_EXIT_MAGIC, 0x2000); + assert_eq!(MSR_IA32_EFER_LMA_BIT, 1 << 10); + assert_eq!(CR0_PE, 1 << 0); + } + + #[test] + fn test_bit_operations() { + use bit_field::BitField; + + let mut value = 0u64; + value.set_bits(0..32, 0x12345678); + value.set_bits(32..64, 0xabcdef00); + + assert_eq!(value.get_bits(0..32), 0x12345678); + assert_eq!(value.get_bits(32..64), 0xabcdef00); + } + + // Mock tests for VmxVcpu (limited to safe operations) + mod vmx_vcpu_tests { + use super::*; + + // Helper function to create a test VmxVcpu (this would normally require VMX hardware) + fn create_test_vcpu_regs() -> GeneralRegisters { + let mut regs = GeneralRegisters::default(); + regs.rax = 0x1000; + regs.rbx = 0x2000; + regs.rcx = 0x3000; + regs.rdx = 0x4000; + regs + } + + #[test] + fn test_general_registers_clone() { + let regs = create_test_vcpu_regs(); + let cloned_regs = regs.clone(); + + assert_eq!(regs.rax, cloned_regs.rax); + assert_eq!(regs.rbx, cloned_regs.rbx); + assert_eq!(regs.rcx, cloned_regs.rcx); + assert_eq!(regs.rdx, cloned_regs.rdx); + } + + #[test] + fn test_edx_eax_operations() { + // Test the logic for combining EDX:EAX + let rax = 0x12345678u64; + let rdx = 0xabcdef00u64; + + // Simulate read_edx_eax logic + let combined = ((rdx & 0xffff_ffff) << 32) | (rax & 0xffff_ffff); + assert_eq!(combined, 0xabcdef0012345678); + + // Simulate write_edx_eax logic + let val = 0xfedcba0987654321u64; + let new_rax = val & 0xffff_ffff; + let new_rdx = val >> 32; + + assert_eq!(new_rax, 0x87654321); + assert_eq!(new_rdx, 0xfedcba09); + } + + #[test] + fn test_register_bit_operations() { + let mut regs = GeneralRegisters::default(); + + // Test setting specific bits in registers + regs.rcx = 0; + regs.rcx.set_bits(0..32, 0x12345678); + assert_eq!(regs.rcx.get_bits(0..32), 0x12345678); + + regs.rdx = 0xffffffffffffffff; + regs.rdx.set_bits(32..64, 0); + assert_eq!(regs.rdx.get_bits(32..64), 0); + assert_eq!(regs.rdx.get_bits(0..32), 0xffffffff); + } + + #[test] + fn test_gla2gva_logic() { + // Test the address translation logic (without actual VMX hardware) + let guest_rip = 0x1000usize; + let seg_base_64bit = 0; // In 64-bit mode, segment base is 0 + let seg_base_other = 0x10000; // In other modes, segment base matters + + // 64-bit mode calculation + let gva_64bit = guest_rip + seg_base_64bit; + assert_eq!(gva_64bit, 0x1000); + + // Other mode calculation + let gva_other = guest_rip + seg_base_other; + assert_eq!(gva_other, 0x11000); + } + + #[test] + fn test_interrupt_vector_validation() { + // Test interrupt vector validation logic + let valid_exception = 6; // #UD exception + let valid_interrupt = 0x20; + let invalid_vector = 0; + + assert!(valid_exception < 32); // Exceptions are < 32 + assert!(valid_interrupt >= 32); // Interrupts are >= 32 + assert_eq!(invalid_vector, 0); // Vector 0 should be handled specially + } + + #[test] + fn test_page_walk_info_struct() { + let ptw_info = GuestPageWalkInfo { + top_entry: 0x1000, + level: 4, + width: 9, + is_user_mode_access: false, + is_write_access: false, + is_inst_fetch: false, + pse: true, + wp: true, + nxe: true, + is_smap_on: false, + is_smep_on: false, + }; + + assert_eq!(ptw_info.level, 4); + assert_eq!(ptw_info.width, 9); + assert_eq!(ptw_info.top_entry, 0x1000); + } + + #[test] + fn test_cpuid_constants() { + // Test CPUID-related constants used in handle_cpuid + const LEAF_FEATURE_INFO: u32 = 0x1; + const LEAF_HYPERVISOR_INFO: u32 = 0x4000_0000; + const FEATURE_VMX: u32 = 1 << 5; + const FEATURE_HYPERVISOR: u32 = 1 << 31; + + assert_eq!(LEAF_FEATURE_INFO, 1); + assert_eq!(LEAF_HYPERVISOR_INFO, 0x40000000); + assert_eq!(FEATURE_VMX, 32); + assert_eq!(FEATURE_HYPERVISOR, 0x80000000); + } + + #[test] + fn test_cr_flags_operations() { + use x86_64::registers::control::{Cr0Flags, Cr4Flags}; + + // Test CR0 flags + let cr0_flags = Cr0Flags::PAGING | Cr0Flags::PROTECTED_MODE_ENABLE; + assert!(cr0_flags.contains(Cr0Flags::PAGING)); + assert!(cr0_flags.contains(Cr0Flags::PROTECTED_MODE_ENABLE)); + assert!(!cr0_flags.contains(Cr0Flags::CACHE_DISABLE)); + + // Test CR4 flags + let cr4_flags = Cr4Flags::VIRTUAL_MACHINE_EXTENSIONS | Cr4Flags::PAGE_SIZE_EXTENSION; + assert!(cr4_flags.contains(Cr4Flags::VIRTUAL_MACHINE_EXTENSIONS)); + assert!(cr4_flags.contains(Cr4Flags::PAGE_SIZE_EXTENSION)); + } + + #[test] + fn test_access_width_operations() { + // Test access width enumeration + use axaddrspace::device::AccessWidth; + + assert_eq!(AccessWidth::Byte as usize, 0); + assert_eq!(AccessWidth::Word as usize, 1); + assert_eq!(AccessWidth::Dword as usize, 2); + assert_eq!(AccessWidth::Qword as usize, 3); + + // Test conversion + assert_eq!(AccessWidth::try_from(1), Ok(AccessWidth::Byte)); + assert_eq!(AccessWidth::try_from(2), Ok(AccessWidth::Word)); + assert_eq!(AccessWidth::try_from(4), Ok(AccessWidth::Dword)); + assert_eq!(AccessWidth::try_from(8), Ok(AccessWidth::Qword)); + } + } + + // Tests for utility functions that don't require hardware + #[test] + fn test_get_tr_base_logic() { + let mut test_entry = 0u64; + test_entry |= 1u64 << 47; // Present bit + test_entry |= (0x1000u64 & 0xFFFFFF) << 16; // Base address bits 16-39 + + // Present bit check + let present = test_entry & (1 << 47) != 0; + assert!(present); + + // Base address extraction + let base_low = (test_entry >> 16) & 0xFFFFFF; + let base_high = (test_entry >> 56) & 0xFF; + let base_addr = base_low | (base_high << 24); + + assert_eq!(base_addr, 0x1000); + } + + #[test] + fn test_vmx_exit_reason_enum() { + // Test that VmxExitReason enum can be used in match statements + let test_reason = VmxExitReason::VMCALL; + match test_reason { + VmxExitReason::VMCALL => assert!(true), + _ => assert!(false), + } + } + + #[test] + fn test_debug_implementations() { + // Test Debug implementations for various types + let cpu_mode = VmCpuMode::Mode64; + let debug_str = format!("{:?}", cpu_mode); + assert!(!debug_str.is_empty()); + + let regs = GeneralRegisters::default(); + let debug_str = format!("{:?}", regs); + assert!(!debug_str.is_empty()); + } + + // Note: Most VmxVcpu methods require actual VMX hardware support and cannot be unit tested + // without either: + // 1. Running on VMX-capable hardware with appropriate privileges + // 2. Extensive mocking of the entire VMX infrastructure + // + // For comprehensive testing of VmxVcpu, integration tests on actual hardware + // or hardware simulators would be more appropriate. } diff --git a/src/vmx/vmcs.rs b/src/vmx/vmcs.rs index 2b8ba99..43d3822 100644 --- a/src/vmx/vmcs.rs +++ b/src/vmx/vmcs.rs @@ -581,6 +581,56 @@ pub struct CrAccessInfo { pub lmsw_source_data: u8, } +/// Type of APIC-access, used in Exit Qualification for APIC Accesses. (SDM Vol. 3C, Section 28.2.2, Table 28-6) +#[derive(Debug)] +pub enum ApicAccessExitType { + /// Linear access for data read. + LinearDataRead = 0, + /// Linear access for data write. + LinearDataWrite = 1, + /// Linear access for instruction fetch. + LinearInstructionFetch = 2, + /// Linear access for event delivery. + LinearEventDelivery = 3, + /// Linear access for monitoring. + LinearMonitoring = 4, + /// Guest-physical access for event delivery. + GuestPhysicalEventDelivery = 10, + /// Guest-physical access for monitoring. + GuestPhysicalMonitoring = 11, + /// Guest-physical access for instruction fetch, data read, or data write. + GuestPhysicalInstructionFetchReadWrite = 15, +} + +impl TryFrom for ApicAccessExitType { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::LinearDataRead), + 1 => Ok(Self::LinearDataWrite), + 2 => Ok(Self::LinearInstructionFetch), + 3 => Ok(Self::LinearEventDelivery), + 4 => Ok(Self::LinearMonitoring), + 10 => Ok(Self::GuestPhysicalEventDelivery), + 11 => Ok(Self::GuestPhysicalMonitoring), + 15 => Ok(Self::GuestPhysicalInstructionFetchReadWrite), + _ => Err(()), + } + } +} + +/// Exit Qualification for APIC Accesses. (SDM Vol. 3C, Section 28.2.2, Table 28-6) +#[derive(Debug)] +pub struct ApicAccessExitInfo { + /// Offset within the APIC-access page. Not defined if `access_type` is 10, 11, or 15. + pub offset: u16, + /// Access type. + pub access_type: ApicAccessExitType, + /// Actually not used by us, see SDM for details. + pub non_event_delivery_asynchronous: bool, +} + pub mod controls { pub use x86::vmx::vmcs::control::{EntryControls, ExitControls}; pub use x86::vmx::vmcs::control::{PinbasedControls, PrimaryControls, SecondaryControls}; @@ -772,3 +822,14 @@ pub fn cr_access_info() -> AxResult { lmsw_source_data: qualification.get_bits(16..32) as u8, }) } + +pub fn apic_access_exit_info() -> AxResult { + let qualification = VmcsReadOnlyNW::EXIT_QUALIFICATION.read()?; + // debug!("apic_access_info qualification {:#x}", qualification); + + Ok(ApicAccessExitInfo { + offset: qualification.get_bits(0..12) as u16, + access_type: ApicAccessExitType::try_from(qualification.get_bits(12..16) as u8).unwrap(), + non_event_delivery_asynchronous: qualification.get_bit(16), + }) +} From 3466e5241007854ee81b583b13663db3081150af Mon Sep 17 00:00:00 2001 From: aarkegz Date: Thu, 8 Jan 2026 11:25:38 +0000 Subject: [PATCH 3/5] temp - Thu Jan 8 11:25:38 AM UTC 2026 --- .gitignore | 2 + Cargo.toml | 3 + src/svm/frame.rs | 9 +- src/svm/mod.rs | 20 ++- src/svm/percpu.rs | 45 ++++--- src/svm/structs.rs | 68 +++++++--- src/svm/vcpu.rs | 272 ++++++++++++++++++++++------------------ src/svm/vmcb.rs | 300 +++++++++++++++++++++++++++++++++------------ src/vmx/vcpu.rs | 6 +- 9 files changed, 474 insertions(+), 251 deletions(-) diff --git a/.gitignore b/.gitignore index 55c0d45..f7a0b62 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ rusty-tags.vi # We ignore Cargo.lock because `axvcpu` is just a library Cargo.lock + +tmp/ diff --git a/Cargo.toml b/Cargo.toml index f3f0149..54d5534 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,9 @@ tock-registers = "0.10" spin = { version = "0.9", default-features = false } +[dev-dependencies] +memoffset = "0.9" + [features] default = ["svm"] # default = ["vmx"] diff --git a/src/svm/frame.rs b/src/svm/frame.rs index e36bb36..7edeb10 100644 --- a/src/svm/frame.rs +++ b/src/svm/frame.rs @@ -4,7 +4,6 @@ use axaddrspace::{AxMmHal, HostPhysAddr}; use axerrno::{AxResult, ax_err_type}; use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; - /// A contiguous block of physical memory frames that will be automatically /// deallocated when dropped. Used for hardware structures requiring contiguous /// physical memory (e.g., IOPM, MSRPM). @@ -42,9 +41,9 @@ impl ContiguousPhysFrames { } } - pub fn start_paddr(&self) -> HostPhysAddr { - self.start_paddr.expect("uninitialized ContiguousPhysFrames") + self.start_paddr + .expect("uninitialized ContiguousPhysFrames") } pub fn frame_count(&self) -> usize { @@ -55,12 +54,10 @@ impl ContiguousPhysFrames { PAGE_SIZE * self.frame_count } - pub fn as_mut_ptr(&self) -> *mut u8 { H::phys_to_virt(self.start_paddr()).as_mut_ptr() } - pub fn fill(&mut self, byte: u8) { unsafe { core::ptr::write_bytes(self.as_mut_ptr(), byte, self.size()); @@ -78,4 +75,4 @@ impl Drop for ContiguousPhysFrames { ); } } -} \ No newline at end of file +} diff --git a/src/svm/mod.rs b/src/svm/mod.rs index f0acf24..4e31162 100644 --- a/src/svm/mod.rs +++ b/src/svm/mod.rs @@ -1,21 +1,19 @@ -mod definitions; // SvmExitCode / Intercept 位 -mod instructions; // vmrun / vmload / vmsave / stgi / clgi / invlpga -mod percpu; // SvmPerCpuState(EFER.SVME & HSAVE) -mod structs; // IOPm / MSRPm / Vmcb 封装 -mod vcpu; // SvmVcpu(核心逻辑) -mod vmcb; +mod definitions; // SvmExitCode / Intercept 位 mod flags; mod frame; +mod instructions; // vmrun / vmload / vmsave / stgi / clgi / invlpga +mod percpu; // SvmPerCpuState(EFER.SVME & HSAVE) +mod structs; // IOPm / MSRPm / Vmcb 封装 +mod vcpu; // SvmVcpu(核心逻辑) +mod vmcb; // VMCB 读写 & VMEXIT 解码 -pub use self::definitions::{SvmExitCode,SvmIntercept}; +pub use self::definitions::{SvmExitCode, SvmIntercept}; pub use self::percpu::SvmPerCpuState as SvmArchPerCpuState; -pub use self::vcpu::SvmVcpu as SvmArchVCpu; - +pub use self::vcpu::SvmVcpu as SvmArchVCpu; pub fn has_hardware_support() -> bool { - if let Some(ext) = raw_cpuid::CpuId::new().get_extended_processor_and_feature_identifiers() - { + if let Some(ext) = raw_cpuid::CpuId::new().get_extended_processor_and_feature_identifiers() { ext.has_svm() } else { false diff --git a/src/svm/percpu.rs b/src/svm/percpu.rs index fd2fe68..0c6d59a 100644 --- a/src/svm/percpu.rs +++ b/src/svm/percpu.rs @@ -10,16 +10,15 @@ //! 3. Set `EFER.SVME` (bit 12) to enable SVM mode //! 4. Clearing `EFER.SVME` disables SVM (no need for VMXON/VMXOFF equivalents) -use axerrno::{ax_err, ax_err_type, AxResult}; +use axerrno::{AxResult, ax_err, ax_err_type}; use axvcpu::{AxArchPerCpu, AxVCpuHal}; use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; use raw_cpuid::CpuId; -use x86_64::registers::control::EferFlags; +use x86_64::registers::control::{Efer, EferFlags}; -use axaddrspace::PhysFrame; use crate::msr::Msr; use crate::svm::has_hardware_support; - +use axaddrspace::PhysFrame; /// Per-core state for AMD-SVM @@ -50,38 +49,52 @@ impl AxArchPerCpu for SvmPerCpuState { return ax_err!(ResourceBusy, "SVM already enabled"); } + // make sure all perf counters are off + unsafe { + /// Core Performance Event-Select Register (PerfEvtSeln), Counter Enable (bit 22) + const PERF_EVT_SEL_EN: u64 = 1 << 22; + Msr::PERF_EVT_SEL0.write(Msr::PERF_EVT_SEL0.read() & !PERF_EVT_SEL_EN); + Msr::PERF_EVT_SEL1.write(Msr::PERF_EVT_SEL1.read() & !PERF_EVT_SEL_EN); + Msr::PERF_EVT_SEL2.write(Msr::PERF_EVT_SEL2.read() & !PERF_EVT_SEL_EN); + Msr::PERF_EVT_SEL3.write(Msr::PERF_EVT_SEL3.read() & !PERF_EVT_SEL_EN); + Msr::PERF_EVT_SEL4.write(Msr::PERF_EVT_SEL4.read() & !PERF_EVT_SEL_EN); + Msr::PERF_EVT_SEL5.write(Msr::PERF_EVT_SEL5.read() & !PERF_EVT_SEL_EN); + } + // Enable XSAVE/XRSTOR. super::vcpu::XState::enable_xsave(); // Allocate & register Host-Save Area self.hsave_page = PhysFrame::alloc_zero()?; let hsave_pa = self.hsave_page.start_paddr().as_usize() as u64; - unsafe { Msr::VM_HSAVE_PA.write(hsave_pa); } - + unsafe { + Msr::VM_HSAVE_PA.write(hsave_pa); + } - //Set EFER.SVME to enable SVM + //S et EFER.SVME to enable SVM let mut efer = EferFlags::from_bits_truncate(Msr::IA32_EFER.read()); efer.insert(EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE); // bit 12 - unsafe { Msr::IA32_EFER.write(efer.bits()); } + unsafe { + Msr::IA32_EFER.write(efer.bits()); + } info!("[AxVM] SVM enabled (HSAVE @ {:#x}).", hsave_pa); Ok(()) } - fn hardware_disable(&mut self) -> AxResult { if !self.is_enabled() { return ax_err!(BadState, "SVM is not enabled"); } unsafe { - // 1) Clear SVME bit - let mut efer = EferFlags::from_bits_truncate(Msr::IA32_EFER.read()); - efer.remove(EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE); - Msr::IA32_EFER.write(efer.bits()); + // 1) Clear SVME bit + let mut efer = EferFlags::from_bits_truncate(Msr::IA32_EFER.read()); + efer.remove(EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE); + Msr::IA32_EFER.write(efer.bits()); - // 2) Clear HSAVE pointer - Msr::VM_HSAVE_PA.write(0); - } + // 2) Clear HSAVE pointer + Msr::VM_HSAVE_PA.write(0); + } info!("[AxVM] SVM disabled."); Ok(()) } diff --git a/src/svm/structs.rs b/src/svm/structs.rs index 72a573e..416346f 100644 --- a/src/svm/structs.rs +++ b/src/svm/structs.rs @@ -3,13 +3,14 @@ #![allow(dead_code)] +use crate::svm::vmcb::VmcbStruct; + +use super::frame::ContiguousPhysFrames; use axaddrspace::HostPhysAddr; -use axerrno::{AxResult}; +use axaddrspace::PhysFrame; +use axerrno::AxResult; use axvcpu::AxVCpuHal; use memory_addr::PAGE_SIZE_4K as PAGE_SIZE; -use axaddrspace::PhysFrame; -use super::frame::{ContiguousPhysFrames}; - /// Virtual-Machine Control Block (VMCB) /// One 4 KiB page per vCPU: [control-area | save-area]. @@ -20,11 +21,15 @@ pub struct VmcbFrame { impl VmcbFrame { pub const unsafe fn uninit() -> Self { - Self { page: unsafe { PhysFrame::uninit() } } + Self { + page: unsafe { PhysFrame::uninit() }, + } } pub fn new() -> AxResult { - Ok(Self { page: PhysFrame::alloc_zero()? }) + Ok(Self { + page: PhysFrame::alloc_zero()?, + }) } pub fn phys_addr(&self) -> HostPhysAddr { @@ -34,21 +39,51 @@ impl VmcbFrame { pub fn as_mut_ptr(&self) -> *mut u8 { self.page.as_mut_ptr() } -} + pub fn as_mut_ptr_vmcb(&self) -> *mut VmcbStruct { + self.page.as_mut_ptr() as *mut VmcbStruct + } + + pub fn hex_dump(&self) -> alloc::string::String { + use alloc::string::ToString; + + // dump 16 bytes per line, 256 lines + let mut dump = alloc::string::String::new(); + for i in 0..256 { + let offset = i * 16; + let line = unsafe { + let ptr = self.as_mut_ptr().add(offset); + let bytes = core::slice::from_raw_parts(ptr, 16); + let hex_bytes: alloc::string::String = bytes + .iter() + .map(|b| if *b == 0 { " --".to_string() } else { alloc::format!(" {:02x}", b) }) + .collect(); + alloc::format!("{:04x}:{}\n", offset, hex_bytes) + }; + dump.push_str(&line); + } + + dump + } +} // (AMD64 APM Vol.2, Section 15.10) // The I/O Permissions Map (IOPM) occupies 12 Kbytes of contiguous physical memory. // The map is structured as a linear array of 64K+3 bits (two 4-Kbyte pages, and the first three bits of a third 4-Kbyte page) and must be aligned on a 4-Kbyte boundary; #[derive(Debug)] pub struct IOPm { - frames: ContiguousPhysFrames, // 3 contiguous frames (12KB) + frames: ContiguousPhysFrames, // 3 contiguous frames (12KB) } impl IOPm { pub fn passthrough_all() -> AxResult { let mut frames = ContiguousPhysFrames::::alloc_zero(3)?; + info!( + "IOPM allocated at phys addr: {:#x}", + frames.start_paddr().as_usize() + ); + // Set first 3 bits of third frame to intercept (ports > 0xFFFF) let third_frame_start = frames.as_mut_ptr() as usize + 2 * PAGE_SIZE; unsafe { @@ -93,7 +128,6 @@ impl IOPm { self.set_intercept(port, intercept) } } - } // (AMD64 APM Vol.2, Section 15.10) // The VMM can intercept RDMSR and WRMSR instructions by means of the SVM MSR permissions map (MSRPM) on a per-MSR basis @@ -135,17 +169,14 @@ impl MSRPm { unreachable!("MSR {:#x} Not supported by MSRPM", msr); }; - let base_offset = (segment * 2048) as usize; + let base_offset = (segment * 2048) as usize; - let byte_in_segment = (msr_low as usize) / 4; - let bit_pair_offset = ((msr_low & 0b11) * 2) as u8; // 0,2,4,6 - let bit_offset = bit_pair_offset + is_write as u8; // +0=读, +1=写 + let byte_in_segment = (msr_low as usize) / 4; + let bit_pair_offset = ((msr_low & 0b11) * 2) as u8; // 0,2,4,6 + let bit_offset = bit_pair_offset + is_write as u8; // +0=读, +1=写 unsafe { - let byte_ptr = self - .frames - .as_mut_ptr() - .add(base_offset + byte_in_segment); + let byte_ptr = self.frames.as_mut_ptr().add(base_offset + byte_in_segment); let old = core::ptr::read_volatile(byte_ptr); let new = if intercept { @@ -164,5 +195,4 @@ impl MSRPm { pub fn set_write_intercept(&mut self, msr: u32, intercept: bool) { self.set_intercept(msr, true, intercept); } - -} \ No newline at end of file +} diff --git a/src/svm/vcpu.rs b/src/svm/vcpu.rs index e277476..3824727 100644 --- a/src/svm/vcpu.rs +++ b/src/svm/vcpu.rs @@ -1,24 +1,24 @@ use alloc::collections::VecDeque; use axvisor_api::vmm::{VCpuId, VMId}; use bit_field::BitField; +use core::arch::asm; use core::fmt::{Debug, Formatter, Result}; use core::{arch::naked_asm, mem::size_of}; -use core::arch::asm; use raw_cpuid::CpuId; use x86::controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}; use x86::dtables::{self, DescriptorTablePointer}; use x86::segmentation::SegmentSelector; use x86_64::registers::control::{Cr0, Cr0Flags, Cr3, Cr4, Cr4Flags, EferFlags}; +use axaddrspace::device::AccessWidth; use axaddrspace::{GuestPhysAddr, GuestVirtAddr, HostPhysAddr, NestedPageFaultInfo}; use axerrno::{AxResult, ax_err, ax_err_type}; use axvcpu::{AxArchVCpu, AxVCpuExitReason, AxVCpuHal}; -use axaddrspace::device::AccessWidth; -use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; +use tock_registers::interfaces::{Debuggable, ReadWriteable, Readable, Writeable}; use super::definitions::SvmExitCode; -use super::structs::{VmcbFrame,IOPm, MSRPm}; -use super::vmcb::{NestedCtl,VmcbTlbControl,SvmExitInfo, VmcbCleanBits,set_vmcb_segment}; +use super::structs::{IOPm, MSRPm, VmcbFrame}; +use super::vmcb::{NestedCtl, SvmExitInfo, VmcbCleanBits, VmcbTlbControl, set_vmcb_segment}; use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters}; const QEMU_EXIT_PORT: u16 = 0x604; @@ -155,7 +155,7 @@ impl SvmVcpu { guest_regs: GeneralRegisters::default(), host_stack_top: 0, launched: false, - vmcb:VmcbFrame::new()?, + vmcb: VmcbFrame::new()?, iopm: IOPm::passthrough_all()?, msrpm: MSRPm::passthrough_all()?, pending_events: VecDeque::with_capacity(8), @@ -169,10 +169,10 @@ impl SvmVcpu { } /// Set the new [`SvmVcpu`] context from guest OS. - pub fn setup(&mut self, npt_root: HostPhysAddr, entry: GuestPhysAddr) -> AxResult { - self.setup_vmcb(entry, npt_root)?; - Ok(()) - } + // pub fn setup(&mut self, npt_root: HostPhysAddr, entry: GuestPhysAddr) -> AxResult { + // self.setup_vmcb(entry, npt_root)?; + // Ok(()) + // } /// No operation is needed for SVM binding. /// @@ -202,7 +202,7 @@ impl SvmVcpu { } pub fn get_cpu_mode(&self) -> VmCpuMode { - let vmcb = unsafe { self.vmcb.as_vmcb() }.state; + let vmcb = &mut unsafe { self.vmcb.as_vmcb() }.state; let ia32_efer = vmcb.efer.get(); let cs_attr = vmcb.cs.attr.get(); @@ -230,11 +230,10 @@ impl SvmVcpu { } // Run guest - self.load_guest_xstate(); + // self.load_guest_xstate(); unsafe { self.svm_run(); - } self.load_host_xstate(); @@ -341,6 +340,7 @@ impl SvmVcpu { // Implementation of private methods impl SvmVcpu { + #[allow(dead_code)] fn setup_io_bitmap(&mut self) -> AxResult { todo!() } @@ -351,124 +351,173 @@ impl SvmVcpu { } fn setup_vmcb(&mut self, entry: GuestPhysAddr, npt_root: HostPhysAddr) -> AxResult { - self.bind_to_current_processor()?; self.setup_vmcb_guest(entry)?; self.setup_vmcb_control(npt_root, true)?; - self.unbind_from_current_processor()?; + + info!("VMCB:\n{}", self.vmcb.hex_dump()); + + let vm_hsave_pa = Msr::VM_HSAVE_PA.read(); + let cr4 = Cr4::read(); + let efer = EferFlags::from_bits_truncate(Msr::IA32_EFER.read()); + info!( + "[AxVM] VMCB setup complete (HSAVE @ {:#x}, CR4: {:#x}, EFER: {:#x})", + vm_hsave_pa, + cr4.bits(), + efer.bits() + ); + + unsafe { + asm!( + "mov rax, {0}", + "vmload rax", + in(reg) self.vmcb.phys_addr().as_usize() as u64, + ) + } + + panic!( + "VMLOAD OK" + ); + Ok(()) } - fn setup_vmcb_guest(&mut self, entry: GuestPhysAddr) -> AxResult { info!("[AxVM] Setting up VMCB for guest at {:#x}", entry); let cr0_val: Cr0Flags = Cr0Flags::NOT_WRITE_THROUGH | Cr0Flags::CACHE_DISABLE | Cr0Flags::EXTENSION_TYPE; - info!("here??????????????????"); - self.set_cr(0, cr0_val.bits()); - self.set_cr(4, 0); + self.set_cr(0, cr0_val.bits())?; + self.set_cr(4, 0)?; - let st = unsafe { self.vmcb.as_vmcb() }.state; + let st = &mut unsafe { self.vmcb.as_vmcb() }.state; macro_rules! seg { - ($seg:ident, $attr:expr) => { - set_vmcb_segment(&mut st.$seg, 0, $attr); - }; - } - seg!(es, 0x93); seg!(cs, 0x9b); seg!(ss, 0x93); seg!(ds, 0x93); - seg!(fs, 0x93); seg!(gs, 0x93); seg!(tr, 0x8b); seg!(ldtr, 0x82); + ($seg:ident, $attr:expr) => { + set_vmcb_segment(&mut st.$seg, 0, $attr); + }; + } + + // CS: P S CODE READ (bit 7, 4, 3, 1) = 0x9a + // seg!(cs, 0x9b); + st.cs.selector.set(0xf000); + st.cs.base.set(0xffff0000); + st.cs.limit.set(0xffff); + st.cs.attr.set(0x9b); + + // DS ~ SS: P S WRITE (bit 7, 4, 1) = 0x92 + seg!(ds, 0x93); + seg!(es, 0x93); + seg!(fs, 0x93); + seg!(gs, 0x93); + seg!(ss, 0x93); + seg!(ldtr, 0x82); + seg!(tr, 0x83); + + // st.ldtr.selector.set(0); + // st.ldtr.base.set(0); + // st.ldtr.limit.set(0); + // st.ldtr.attr.set(0x1000); // GDTR / IDTR - st.gdtr.base.set(0); st.gdtr.limit.set(0xffff); - st.idtr.base.set(0); st.idtr.limit.set(0xffff); + st.gdtr.base.set(0); + st.gdtr.limit.set(0xffff); + st.idtr.base.set(0); + st.idtr.limit.set(0xffff); // 关键寄存器与指针 st.cr3.set(0); st.dr7.set(0x400); st.rsp.set(0); st.rip.set(entry.as_usize() as u64); - st.rflags.set(0x2); // bit1 必须为 1 - st.dr6.set(0); // Pending-DBG-Exceptions 对应 0 + st.rflags.set(0x2); // bit1 必须为 1 + st.dr6.set(0); + // st.dr6.set(0xffff0ff0); // Pending-DBG-Exceptions 对应 0 // SYSENTER MSRs - st.sysenter_cs.set(0); - st.sysenter_esp.set(0); - st.sysenter_eip.set(0); + // st.sysenter_cs.set(0); + // st.sysenter_esp.set(0); + // st.sysenter_eip.set(0); // MSR / PAT / EFER - st.efer.set(0 | EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE.bits()); // 必须置 SVME 位 + st.efer + .set(0 | EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE.bits()); // 必须置 SVME 位 st.g_pat.set(Msr::IA32_PAT.read()); - st.cpl.set(0); - st.star.set(0); - st.lstar.set(0); - st.cstar.set(0); - st.sfmask.set(0); - st.kernel_gs_base.set(Msr::IA32_KERNEL_GSBASE.read()); - st.rax.set(0); // hypervisor 返回值 + // st.cpl.set(0); + // st.star.set(0); + // st.lstar.set(0); + // st.cstar.set(0); + // st.sfmask.set(0); + // st.kernel_gs_base.set(Msr::IA32_KERNEL_GSBASE.read()); + // st.rax.set(0); // hypervisor 返回值 Ok(()) } fn setup_vmcb_control(&mut self, npt_root: HostPhysAddr, is_guest: bool) -> AxResult { - let ct = unsafe { self.vmcb.as_vmcb() }.control; // control-area 速记别名 - // ──────────────────────────────────────────────────────── - // 1) 基本运行环境:Nested Paging / ASID / Clean Bits / TLB - // ──────────────────────────────────────────────────────── - - // ① 开启 Nested Paging(AMD 对应 Intel 的 EPT) - // → set bit 0 of NESTED_CTL - ct.nested_ctl.modify(NestedCtl::NP_ENABLE::SET); - - // ② guest ASID:NPT 使用的 TLB 标签 - ct.guest_asid.set(1); - - // ③ 嵌套 CR3(NPT root PA) - ct.nested_cr3.set(npt_root.as_usize() as u64); - - // ④ Clean-Bits:0 = “全部脏” ⇒ 第一次 VMRUN 必定重新加载 save-area - ct.clean_bits.set(0); - - // ⑤ TLB Control:0 = NONE, 1 = FLUSH-ASID, 3 = FLUSH-ALL - ct.tlb_control.modify(VmcbTlbControl::FlushGuestTlb::SET); - - // ──────────────────────────────────────────────────────── - // 2) 选择要拦截的指令 / 事件 - // (相当于 VMX 的 Pin-based / Primary / Secondary CTLS) - // ──────────────────────────────────────────────────────── - - use super::definitions::SvmIntercept; // 你自己定义的枚举 - - for intc in &[ - SvmIntercept::NMI, // 非屏蔽中断 - SvmIntercept::CPUID, // CPUID 指令 - SvmIntercept::SHUTDOWN, // HLT 时 Triple-Fault - SvmIntercept::VMRUN, // 来宾企图再次 VMRUN - SvmIntercept::VMMCALL, // Hypercall - SvmIntercept::VMLOAD, - SvmIntercept::VMSAVE, - SvmIntercept::STGI, // 设置全局中断 - SvmIntercept::CLGI, // 清除全局中断 - SvmIntercept::SKINIT, // 安全启动 - ] { - ct.set_intercept(*intc); - } + let ct = &mut unsafe { self.vmcb.as_vmcb() }.control; // control-area 速记别名 + // ──────────────────────────────────────────────────────── + // 1) 基本运行环境:Nested Paging / ASID / Clean Bits / TLB + // ──────────────────────────────────────────────────────── + + // ① 开启 Nested Paging(AMD 对应 Intel 的 EPT) + // → set bit 0 of NESTED_CTL + ct.nested_ctl.modify(NestedCtl::NP_ENABLE::SET); + + // ② guest ASID:NPT 使用的 TLB 标签 + ct.guest_asid.set(1); + + // ③ 嵌套 CR3(NPT root PA) + ct.nested_cr3.set(npt_root.as_usize() as u64); + + // ④ Clean-Bits:0 = “全部脏” ⇒ 第一次 VMRUN 必定重新加载 save-area + ct.clean_bits.set(0); + + // ⑤ TLB Control:0 = NONE, 1 = FLUSH-ASID, 3 = FLUSH-ALL + ct.tlb_control.modify(VmcbTlbControl::CONTROL::FlushGuestTlb); + + ct.int_control.set(1 << 24); // V_INTR_MASKING_MASK + + // ──────────────────────────────────────────────────────── + // 2) 选择要拦截的指令 / 事件 + // (相当于 VMX 的 Pin-based / Primary / Secondary CTLS) + // ──────────────────────────────────────────────────────── + + use super::definitions::SvmIntercept; // 你自己定义的枚举 + + for intc in &[ + SvmIntercept::NMI, // 非屏蔽中断 + SvmIntercept::CPUID, // CPUID 指令 + SvmIntercept::SHUTDOWN, // HLT 时 Triple-Fault + SvmIntercept::VMRUN, // 来宾企图再次 VMRUN + SvmIntercept::VMMCALL, // Hypercall + SvmIntercept::VMLOAD, + SvmIntercept::VMSAVE, + SvmIntercept::STGI, // 设置全局中断 + SvmIntercept::CLGI, // 清除全局中断 + SvmIntercept::SKINIT, // 安全启动 + ] { + ct.set_intercept(*intc); + } + + ct.iopm_base_pa.set(self.iopm.phys_addr().as_usize() as u64); + ct.msrpm_base_pa + .set(self.msrpm.phys_addr().as_usize() as u64); + Ok(()) } - // 如果你用 bitfield 方式,也可以: - // ct.intercept_vector3.modify(InterceptVec3::NMI::SET + InterceptVec3::VINTR::SET); + // 如果你用 bitfield 方式,也可以: + // ct.intercept_vector3.modify(InterceptVec3::NMI::SET + InterceptVec3::VINTR::SET); fn get_paging_level(&self) -> usize { todo!() } - } // Implementaton for type1.5 hypervisor // #[cfg(feature = "type1_5")] impl SvmVcpu { - pub fn set_cr(&mut self, cr_idx: usize, val: u64) -> AxResult { - let mut vmcb = unsafe { self.vmcb.as_vmcb() }; - info!("here??????????????????"); + let vmcb = unsafe { self.vmcb.as_vmcb() }; + info!("Setting CR{} to {:#x}", cr_idx, val); match cr_idx { 0 => vmcb.state.cr0.set(val), @@ -490,12 +539,11 @@ impl SvmVcpu { _ => unreachable!(), }) })() - .expect("Failed to read guest control register") + .expect("Failed to read guest control register") } } impl SvmVcpu { - // unsafe extern "C" fn svm_run(&mut self) -> usize { // let vmcb_phy = self.vmcb.phys_addr().as_usize() as u64; // @@ -521,23 +569,20 @@ impl SvmVcpu { pub unsafe fn svm_run(&mut self) -> usize { let vmcb = self.vmcb.phys_addr().as_usize() as u64; - let guest_regs = self.regs_mut(); - // panic!("{:x}",vmcb); - // panic!("SVM run not implemented yet"); - // loop{}; + unsafe { asm!( - // "clgi", + "clgi", "mov rax, {0}", "vmload rax", - "vmrun rax", - // "call {entry}", - // in(reg) guest_regs, in(reg) vmcb, - // entry = sym Self::svm_entry, - options(noreturn), + // options(noreturn), ); } + panic!("fall through after vmrun"); + + // let guest_regs = self.regs_mut(); + } // #[unsafe(naked)] @@ -555,13 +600,11 @@ impl SvmVcpu { // ) // } - - - #[unsafe(naked)] /// Return after vm-exit. /// /// The return value is a dummy value. - unsafe extern "C" fn svm_exit(&mut self) -> usize { + #[unsafe(naked)] + unsafe extern fn svm_exit(&mut self) -> usize { naked_asm!( save_regs_to_stack!(), // save guest status "mov rsp, [rsp + {host_stack_top}]", // set RSP to Vcpu::host_stack_top @@ -571,7 +614,6 @@ impl SvmVcpu { ); } - fn svm_entry_failed() -> ! { panic!("svm_entry_failed"); } @@ -636,11 +678,11 @@ impl SvmVcpu { } fn load_guest_xstate(&mut self) { - self.xstate.switch_to_guest(); + self.xstate.switch_to_guest(); } fn load_host_xstate(&mut self) { - self.xstate.switch_to_host(); + self.xstate.switch_to_host(); } } @@ -664,7 +706,6 @@ impl AxArchVCpu for SvmVcpu { Self::new() } - fn set_entry(&mut self, entry: GuestPhysAddr) -> AxResult { self.entry = Some(entry); Ok(()) @@ -681,16 +722,15 @@ impl AxArchVCpu for SvmVcpu { fn run(&mut self) -> AxResult { match self.inner_run() { - Some(exit_info) => { - warn!("VMX unsupported VM-Exit: {:#x?}",exit_info.exit_info_1); + Some(exit_info) => { + warn!("VMX unsupported VM-Exit: {:#x?}", exit_info.exit_info_1); warn!("VCpu {:#x?}", self); Ok(AxVCpuExitReason::Halt) } - _ => Ok(AxVCpuExitReason::Halt) + _ => Ok(AxVCpuExitReason::Halt), } } - fn bind(&mut self) -> AxResult { self.bind_to_current_processor() } @@ -703,14 +743,12 @@ impl AxArchVCpu for SvmVcpu { fn set_gpr(&mut self, reg: usize, val: usize) { self.regs_mut().set_reg_of_index(reg as u8, val as u64); } - + fn inject_interrupt(&mut self, vector: usize) -> AxResult { todo!() } - + fn set_return_value(&mut self, val: usize) { self.regs_mut().rax = val as u64; } } - - diff --git a/src/svm/vmcb.rs b/src/svm/vmcb.rs index 5e11272..405a134 100644 --- a/src/svm/vmcb.rs +++ b/src/svm/vmcb.rs @@ -25,18 +25,15 @@ use tock_registers::{register_bitfields, register_structs}; use axaddrspace::HostPhysAddr; use axerrno::AxResult; +use super::definitions::{SvmExitCode, SvmIntercept}; +use super::structs::VmcbFrame; // the user‑supplied wrapper that owns the backing page use axvcpu::AxVCpuHal; use memory_addr::MemoryAddr; use tock_registers::interfaces::{ReadWriteable, Readable, Writeable}; -use super::structs::VmcbFrame; // the user‑supplied wrapper that owns the backing page -use super::definitions::{SvmIntercept,SvmExitCode}; // ───────────────────────────────────────────────────────────────────────────── // Control‑area bitfields // ───────────────────────────────────────────────────────────────────────────── - - - register_bitfields![u32, // vector 0 pub InterceptCrRw [ @@ -113,13 +110,29 @@ register_bitfields![u64, register_bitfields![u8, pub VmcbTlbControl [ - DoNothing 0, - FlushAllOnVmrun 1, - FlushGuestTlb 3, - FlushGuestNonGlobalTlb 7, + CONTROL OFFSET(0) NUMBITS(3) [ + DoNothing = 0, + FlushAllOnVmrun = 1, + FlushGuestTlb = 3, + FlushGuestNonGlobalTlb = 7, + ] ] ]; +// register_bitfields![u16, +// pub VmcbSegmentAttr [ +// // ACCESSED OFFSET(0) NUMBITS(1), // not used in VMCB +// READABLE OFFSET(1) NUMBITS(1), + +// /// Code/Data bit, available for User segments (`S` = 1) only +// CODE OFFSET(3) NUMBITS(1), +// /// User/System bit `S` +// USER OFFSET(4) NUMBITS(1), +// /// DPL +// DPL OFFSET(5) NUMBITS(2), +// ] +// ]; + register_structs![ pub VmcbControlArea { (0x0000 => pub intercept_cr: ReadWrite), @@ -264,44 +277,47 @@ register_structs![ (0x0290 => pub last_excp_to: ReadWrite), (0x0298 => _reserved_0298), - (0x0FFF => @END), + (0x0C00 => @END), + } +]; + +register_structs![ + pub VmcbStruct { + (0x0000 => pub control: VmcbControlArea), + (0x0400 => pub state: VmcbStateSaveArea), + (0x1000 => @END), } ]; /// Unified façade returning typed accessors to both halves of the VMCB. pub struct Vmcb<'a> { pub control: &'a mut VmcbControlArea, - pub state: &'a mut VmcbStateSaveArea, + pub state: &'a mut VmcbStateSaveArea, } impl VmcbFrame { /// # Safety /// caller must guarantee the page is mapped - pub unsafe fn as_vmcb<'a>(&'a self) -> Vmcb<'a> { - let base = self.as_mut_ptr(); - - Vmcb { - control: &mut *(base as *mut VmcbControlArea), - state: &mut *(base.add(0x400) as *mut VmcbStateSaveArea), - } + pub unsafe fn as_vmcb(&self) -> &mut VmcbStruct { + unsafe { self.as_mut_ptr_vmcb().as_mut().unwrap() } } } -impl Vmcb <'_>{ +impl VmcbStruct { /// Zero‑initialise the control area pub fn clear_control(&mut self) { - unsafe { core::ptr::write_bytes(self.control as *mut _ as *mut u8, 0, 0x400) }; + unsafe { core::ptr::write_bytes(&mut self.control as *mut _ as *mut u8, 0, 0x400) }; } - pub fn clean_bits(&mut self)-> &mut ReadWrite { + pub fn clean_bits(&mut self) -> &mut ReadWrite { &mut self.control.clean_bits } } pub fn set_vmcb_segment(seg: &mut VmcbSegment, selector: u16, attr: u16) { seg.selector.set(selector); // 一般初始化阶段都传 0 - seg.base.set(0); // 实模式/平坦段:基址 0 - seg.limit.set(0xFFFF); // 64 KiB 段界限 - seg.attr.set(attr); // AR 字节(0x93, 0x9B, 0x8B, 0x82 …) + seg.base.set(0); // 实模式/平坦段:基址 0 + seg.limit.set(0xFFFF); // 64 KiB 段界限 + seg.attr.set(attr); // AR 字节(0x93, 0x9B, 0x8B, 0x82 …) } impl VmcbControlArea { @@ -309,63 +325,81 @@ impl VmcbControlArea { use super::definitions::SvmIntercept::*; match intc { // ── Vector 3 ─────────────────────────────── - INTR => self.intercept_vector3.modify(InterceptVec3::INTR::SET), - NMI => self.intercept_vector3.modify(InterceptVec3::NMI::SET), - SMI => self.intercept_vector3.modify(InterceptVec3::SMI::SET), - INIT => self.intercept_vector3.modify(InterceptVec3::INIT::SET), - VINTR => self.intercept_vector3.modify(InterceptVec3::VINTR::SET), - CR0_SEL_WRITE => self.intercept_vector3.modify(InterceptVec3::CR0_SEL_WRITE::SET), - IDTR_READ => self.intercept_vector3.modify(InterceptVec3::IDTR_READ::SET), - GDTR_READ => self.intercept_vector3.modify(InterceptVec3::GDTR_READ::SET), - LDTR_READ => self.intercept_vector3.modify(InterceptVec3::LDTR_READ::SET), - TR_READ => self.intercept_vector3.modify(InterceptVec3::TR_READ::SET), - IDTR_WRITE => self.intercept_vector3.modify(InterceptVec3::IDTR_WRITE::SET), - GDTR_WRITE => self.intercept_vector3.modify(InterceptVec3::GDTR_WRITE::SET), - LDTR_WRITE => self.intercept_vector3.modify(InterceptVec3::LDTR_WRITE::SET), - TR_WRITE => self.intercept_vector3.modify(InterceptVec3::TR_WRITE::SET), - RDTSC => self.intercept_vector3.modify(InterceptVec3::RDTSC::SET), - RDPMC => self.intercept_vector3.modify(InterceptVec3::RDPMC::SET), - PUSHF => self.intercept_vector3.modify(InterceptVec3::PUSHF::SET), - POPF => self.intercept_vector3.modify(InterceptVec3::POPF::SET), - CPUID => self.intercept_vector3.modify(InterceptVec3::CPUID::SET), - RSM => self.intercept_vector3.modify(InterceptVec3::RSM::SET), - IRET => self.intercept_vector3.modify(InterceptVec3::IRET::SET), - SWINT => self.intercept_vector3.modify(InterceptVec3::SWINT::SET), - INVD => self.intercept_vector3.modify(InterceptVec3::INVD::SET), - PAUSE => self.intercept_vector3.modify(InterceptVec3::PAUSE::SET), - HLT => self.intercept_vector3.modify(InterceptVec3::HLT::SET), - INVLPG => self.intercept_vector3.modify(InterceptVec3::INVLPG::SET), - INVLPGA => self.intercept_vector3.modify(InterceptVec3::INVLPGA::SET), - IOIO_PROT => self.intercept_vector3.modify(InterceptVec3::IOIO_PROT::SET), - MSR_PROT => self.intercept_vector3.modify(InterceptVec3::MSR_PROT::SET), - TASK_SWITCH => self.intercept_vector3.modify(InterceptVec3::TASK_SWITCH::SET), - FERR_FREEZE => self.intercept_vector3.modify(InterceptVec3::FERR_FREEZE::SET), - SHUTDOWN => self.intercept_vector3.modify(InterceptVec3::SHUTDOWN::SET), + INTR => self.intercept_vector3.modify(InterceptVec3::INTR::SET), + NMI => self.intercept_vector3.modify(InterceptVec3::NMI::SET), + SMI => self.intercept_vector3.modify(InterceptVec3::SMI::SET), + INIT => self.intercept_vector3.modify(InterceptVec3::INIT::SET), + VINTR => self.intercept_vector3.modify(InterceptVec3::VINTR::SET), + CR0_SEL_WRITE => self + .intercept_vector3 + .modify(InterceptVec3::CR0_SEL_WRITE::SET), + IDTR_READ => self.intercept_vector3.modify(InterceptVec3::IDTR_READ::SET), + GDTR_READ => self.intercept_vector3.modify(InterceptVec3::GDTR_READ::SET), + LDTR_READ => self.intercept_vector3.modify(InterceptVec3::LDTR_READ::SET), + TR_READ => self.intercept_vector3.modify(InterceptVec3::TR_READ::SET), + IDTR_WRITE => self + .intercept_vector3 + .modify(InterceptVec3::IDTR_WRITE::SET), + GDTR_WRITE => self + .intercept_vector3 + .modify(InterceptVec3::GDTR_WRITE::SET), + LDTR_WRITE => self + .intercept_vector3 + .modify(InterceptVec3::LDTR_WRITE::SET), + TR_WRITE => self.intercept_vector3.modify(InterceptVec3::TR_WRITE::SET), + RDTSC => self.intercept_vector3.modify(InterceptVec3::RDTSC::SET), + RDPMC => self.intercept_vector3.modify(InterceptVec3::RDPMC::SET), + PUSHF => self.intercept_vector3.modify(InterceptVec3::PUSHF::SET), + POPF => self.intercept_vector3.modify(InterceptVec3::POPF::SET), + CPUID => self.intercept_vector3.modify(InterceptVec3::CPUID::SET), + RSM => self.intercept_vector3.modify(InterceptVec3::RSM::SET), + IRET => self.intercept_vector3.modify(InterceptVec3::IRET::SET), + SWINT => self.intercept_vector3.modify(InterceptVec3::SWINT::SET), + INVD => self.intercept_vector3.modify(InterceptVec3::INVD::SET), + PAUSE => self.intercept_vector3.modify(InterceptVec3::PAUSE::SET), + HLT => self.intercept_vector3.modify(InterceptVec3::HLT::SET), + INVLPG => self.intercept_vector3.modify(InterceptVec3::INVLPG::SET), + INVLPGA => self.intercept_vector3.modify(InterceptVec3::INVLPGA::SET), + IOIO_PROT => self.intercept_vector3.modify(InterceptVec3::IOIO_PROT::SET), + MSR_PROT => self.intercept_vector3.modify(InterceptVec3::MSR_PROT::SET), + TASK_SWITCH => self + .intercept_vector3 + .modify(InterceptVec3::TASK_SWITCH::SET), + FERR_FREEZE => self + .intercept_vector3 + .modify(InterceptVec3::FERR_FREEZE::SET), + SHUTDOWN => self.intercept_vector3.modify(InterceptVec3::SHUTDOWN::SET), // ── Vector 4 ─────────────────────────────── - VMRUN => self.intercept_vector4.modify(InterceptVec4::VMRUN::SET), - VMMCALL => self.intercept_vector4.modify(InterceptVec4::VMMCALL::SET), - VMLOAD => self.intercept_vector4.modify(InterceptVec4::VMLOAD::SET), - VMSAVE => self.intercept_vector4.modify(InterceptVec4::VMSAVE::SET), - STGI => self.intercept_vector4.modify(InterceptVec4::STGI::SET), - CLGI => self.intercept_vector4.modify(InterceptVec4::CLGI::SET), - SKINIT => self.intercept_vector4.modify(InterceptVec4::SKINIT::SET), - RDTSCP => self.intercept_vector4.modify(InterceptVec4::RDTSCP::SET), - ICEBP => self.intercept_vector4.modify(InterceptVec4::ICEBP::SET), - WBINVD => self.intercept_vector4.modify(InterceptVec4::WBINVD::SET), - MONITOR => self.intercept_vector4.modify(InterceptVec4::MONITOR::SET), - MWAIT => self.intercept_vector4.modify(InterceptVec4::MWAIT::SET), - MWAIT_CONDITIONAL => self.intercept_vector4.modify(InterceptVec4::MWAIT_CONDITIONAL::SET), - XSETBV => self.intercept_vector4.modify(InterceptVec4::XSETBV::SET), - RDPRU => self.intercept_vector4.modify(InterceptVec4::RDPRU::SET), - EFER_WRITE_TRAP => self.intercept_vector4.modify(InterceptVec4::EFER_WRITE_TRAP::SET), + VMRUN => self.intercept_vector4.modify(InterceptVec4::VMRUN::SET), + VMMCALL => self.intercept_vector4.modify(InterceptVec4::VMMCALL::SET), + VMLOAD => self.intercept_vector4.modify(InterceptVec4::VMLOAD::SET), + VMSAVE => self.intercept_vector4.modify(InterceptVec4::VMSAVE::SET), + STGI => self.intercept_vector4.modify(InterceptVec4::STGI::SET), + CLGI => self.intercept_vector4.modify(InterceptVec4::CLGI::SET), + SKINIT => self.intercept_vector4.modify(InterceptVec4::SKINIT::SET), + RDTSCP => self.intercept_vector4.modify(InterceptVec4::RDTSCP::SET), + ICEBP => self.intercept_vector4.modify(InterceptVec4::ICEBP::SET), + WBINVD => self.intercept_vector4.modify(InterceptVec4::WBINVD::SET), + MONITOR => self.intercept_vector4.modify(InterceptVec4::MONITOR::SET), + MWAIT => self.intercept_vector4.modify(InterceptVec4::MWAIT::SET), + MWAIT_CONDITIONAL => self + .intercept_vector4 + .modify(InterceptVec4::MWAIT_CONDITIONAL::SET), + XSETBV => self.intercept_vector4.modify(InterceptVec4::XSETBV::SET), + RDPRU => self.intercept_vector4.modify(InterceptVec4::RDPRU::SET), + EFER_WRITE_TRAP => self + .intercept_vector4 + .modify(InterceptVec4::EFER_WRITE_TRAP::SET), // ── Vector 5 ─────────────────────────────── - INVLPGB => self.intercept_vector5.modify(InterceptVec5::INVLPGB::SET), - INVLPGB_ILLEGAL => self.intercept_vector5.modify(InterceptVec5::INVLPGB_ILLEGAL::SET), - INVPCID => self.intercept_vector5.modify(InterceptVec5::INVPCID::SET), - MCOMMIT => self.intercept_vector5.modify(InterceptVec5::MCOMMIT::SET), - TLBSYNC => self.intercept_vector5.modify(InterceptVec5::TLBSYNC::SET), + INVLPGB => self.intercept_vector5.modify(InterceptVec5::INVLPGB::SET), + INVLPGB_ILLEGAL => self + .intercept_vector5 + .modify(InterceptVec5::INVLPGB_ILLEGAL::SET), + INVPCID => self.intercept_vector5.modify(InterceptVec5::INVPCID::SET), + MCOMMIT => self.intercept_vector5.modify(InterceptVec5::MCOMMIT::SET), + TLBSYNC => self.intercept_vector5.modify(InterceptVec5::TLBSYNC::SET), } } } @@ -378,8 +412,8 @@ pub struct SvmExitInfo { pub guest_next_rip: u64, } -impl Vmcb <'_> { - pub fn exit_info(mut self) -> AxResult { +impl VmcbStruct { + pub fn exit_info(&self) -> AxResult { Ok(SvmExitInfo { exit_code: self.control.exit_code.get().try_into(), exit_info_1: self.control.exit_info_1.get(), @@ -389,3 +423,111 @@ impl Vmcb <'_> { }) } } + +#[cfg(test)] +mod test { + use super::*; + + fn vmcb_size_check() { + use core::mem::size_of; + + assert_eq!(size_of::(), 0x400); + assert_eq!(size_of::(), 0xC00); + assert_eq!(size_of::(), 0x1000); + } + + #[test] + fn vmcb_offset_check() { + use memoffset::offset_of; + + assert_eq!(offset_of!(VmcbStruct, control), 0x0000); + assert_eq!(offset_of!(VmcbStruct, state), 0x0400); + + macro_rules! assert_vmcb_ctrl_offset { + ($field:ident, $offset:expr) => { + assert_eq!(offset_of!(VmcbControlArea, $field), $offset); + }; + } + + macro_rules! assert_vmcb_save_offset { + ($field:ident, $offset:expr) => { + assert_eq!(offset_of!(VmcbStateSaveArea, $field), $offset); + }; + } + + assert_vmcb_ctrl_offset!(intercept_cr, 0x00); + assert_vmcb_ctrl_offset!(intercept_dr, 0x04); + assert_vmcb_ctrl_offset!(intercept_exceptions, 0x08); + assert_vmcb_ctrl_offset!(intercept_vector3, 0x0C); + assert_vmcb_ctrl_offset!(intercept_vector4, 0x10); + assert_vmcb_ctrl_offset!(intercept_vector5, 0x14); + assert_vmcb_ctrl_offset!(pause_filter_thresh, 0x3C); + assert_vmcb_ctrl_offset!(pause_filter_count, 0x3E); + assert_vmcb_ctrl_offset!(iopm_base_pa, 0x40); + assert_vmcb_ctrl_offset!(msrpm_base_pa, 0x48); + assert_vmcb_ctrl_offset!(tsc_offset, 0x50); + assert_vmcb_ctrl_offset!(guest_asid, 0x58); + assert_vmcb_ctrl_offset!(tlb_control, 0x5C); + assert_vmcb_ctrl_offset!(int_control, 0x60); + assert_vmcb_ctrl_offset!(int_vector, 0x64); + assert_vmcb_ctrl_offset!(int_state, 0x68); + assert_vmcb_ctrl_offset!(exit_code, 0x70); + assert_vmcb_ctrl_offset!(exit_info_1, 0x78); + assert_vmcb_ctrl_offset!(exit_info_2, 0x80); + assert_vmcb_ctrl_offset!(exit_int_info, 0x88); + assert_vmcb_ctrl_offset!(exit_int_info_err, 0x8C); + assert_vmcb_ctrl_offset!(nested_ctl, 0x90); + assert_vmcb_ctrl_offset!(avic_vapic_bar, 0x98); + assert_vmcb_ctrl_offset!(event_inj, 0xA8); + assert_vmcb_ctrl_offset!(event_inj_err, 0xAC); + assert_vmcb_ctrl_offset!(nested_cr3, 0xB0); + assert_vmcb_ctrl_offset!(virt_ext, 0xB8); + assert_vmcb_ctrl_offset!(clean_bits, 0xC0); + assert_vmcb_ctrl_offset!(next_rip, 0xC8); + assert_vmcb_ctrl_offset!(insn_len, 0xD0); + assert_vmcb_ctrl_offset!(insn_bytes, 0xD1); + assert_vmcb_ctrl_offset!(avic_backing_page, 0xE0); + assert_vmcb_ctrl_offset!(avic_logical_id, 0xF0); + assert_vmcb_ctrl_offset!(avic_physical_id, 0xF8); + + assert_vmcb_save_offset!(es, 0x00); + assert_vmcb_save_offset!(cs, 0x10); + assert_vmcb_save_offset!(ss, 0x20); + assert_vmcb_save_offset!(ds, 0x30); + assert_vmcb_save_offset!(fs, 0x40); + assert_vmcb_save_offset!(gs, 0x50); + assert_vmcb_save_offset!(gdtr, 0x60); + assert_vmcb_save_offset!(ldtr, 0x70); + assert_vmcb_save_offset!(idtr, 0x80); + assert_vmcb_save_offset!(tr, 0x90); + assert_vmcb_save_offset!(cpl, 0xCB); + assert_vmcb_save_offset!(efer, 0xD0); + assert_vmcb_save_offset!(cr4, 0x148); + assert_vmcb_save_offset!(cr3, 0x150); + assert_vmcb_save_offset!(cr0, 0x158); + assert_vmcb_save_offset!(dr7, 0x160); + assert_vmcb_save_offset!(dr6, 0x168); + assert_vmcb_save_offset!(rflags, 0x170); + assert_vmcb_save_offset!(rip, 0x178); + assert_vmcb_save_offset!(rsp, 0x1D8); + assert_vmcb_save_offset!(s_cet, 0x1E0); + assert_vmcb_save_offset!(ssp, 0x1E8); + assert_vmcb_save_offset!(isst_addr, 0x1F0); + assert_vmcb_save_offset!(rax, 0x1F8); + assert_vmcb_save_offset!(star, 0x200); + assert_vmcb_save_offset!(lstar, 0x208); + assert_vmcb_save_offset!(cstar, 0x210); + assert_vmcb_save_offset!(sfmask, 0x218); + assert_vmcb_save_offset!(kernel_gs_base, 0x220); + assert_vmcb_save_offset!(sysenter_cs, 0x228); + assert_vmcb_save_offset!(sysenter_esp, 0x230); + assert_vmcb_save_offset!(sysenter_eip, 0x238); + assert_vmcb_save_offset!(cr2, 0x240); + assert_vmcb_save_offset!(g_pat, 0x268); + assert_vmcb_save_offset!(dbgctl, 0x270); + assert_vmcb_save_offset!(br_from, 0x278); + assert_vmcb_save_offset!(br_to, 0x280); + assert_vmcb_save_offset!(last_excp_from, 0x288); + assert_vmcb_save_offset!(last_excp_to, 0x290); + } +} diff --git a/src/vmx/vcpu.rs b/src/vmx/vcpu.rs index e142fdb..a4e2e53 100644 --- a/src/vmx/vcpu.rs +++ b/src/vmx/vcpu.rs @@ -899,7 +899,7 @@ impl VmxVcpu { /// This function itself never returns, but [`Self::vmx_exit`] will do the return for this. /// /// The return value is a dummy value. - unsafe extern "C" fn vmx_launch(&mut self) -> usize { + unsafe extern fn vmx_launch(&mut self) -> usize { vmx_entry_with!("vmlaunch") } @@ -907,7 +907,7 @@ impl VmxVcpu { /// Enter guest with vmresume. /// /// See [`Self::vmx_launch`] for detail. - unsafe extern "C" fn vmx_resume(&mut self) -> usize { + unsafe extern fn vmx_resume(&mut self) -> usize { vmx_entry_with!("vmresume") } @@ -917,7 +917,7 @@ impl VmxVcpu { /// NEVER call this function directly. /// /// The return value is a dummy value. - unsafe extern "C" fn vmx_exit(&mut self) -> usize { + unsafe extern fn vmx_exit(&mut self) -> usize { // it's not necessary to use another `unsafe` here, as Rust now do not require it in naked functions. naked_asm!( save_regs_to_stack!(), // save guest status, after this, rsp points to the `VmxVcpu` From c8782ca9407747397fe052b48429cc87f7ee243b Mon Sep 17 00:00:00 2001 From: aarkegz Date: Thu, 8 Jan 2026 17:16:08 +0000 Subject: [PATCH 4/5] extract xstate to new module and enhance SVM support with new MSRs and state management --- src/lib.rs | 1 + src/msr.rs | 4 + src/regs/mod.rs | 39 +++++ src/svm/percpu.rs | 4 +- src/svm/structs.rs | 8 +- src/svm/vcpu.rs | 368 ++++++++++++++++++++++++++------------------- src/svm/vmcb.rs | 1 + src/vmx/percpu.rs | 2 +- src/vmx/vcpu.rs | 107 +------------ src/xstate.rs | 137 +++++++++++++++++ 10 files changed, 405 insertions(+), 266 deletions(-) create mode 100644 src/xstate.rs diff --git a/src/lib.rs b/src/lib.rs index 0515cdb..71164bd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,7 @@ pub(crate) mod msr; #[macro_use] pub(crate) mod regs; mod ept; +pub(crate) mod xstate; #[cfg(all(feature = "vmx", feature = "svm"))] compile_error!("Features 'vmx' and 'svm' are mutually exclusive. Please enable only one of them."); diff --git a/src/msr.rs b/src/msr.rs index 0b5933b..813f0a2 100644 --- a/src/msr.rs +++ b/src/msr.rs @@ -7,6 +7,10 @@ use x86::msr::{rdmsr, wrmsr}; pub enum Msr { IA32_FEATURE_CONTROL = 0x3a, + IA32_SYSENTER_CS = 0x174, + IA32_SYSENTER_ESP = 0x175, + IA32_SYSENTER_EIP = 0x176, + IA32_PAT = 0x277, IA32_VMX_BASIC = 0x480, diff --git a/src/regs/mod.rs b/src/regs/mod.rs index 4f20052..d049f8c 100644 --- a/src/regs/mod.rs +++ b/src/regs/mod.rs @@ -83,6 +83,26 @@ macro_rules! save_regs_to_stack { push rcx push rax" }; + (norax) => { + " + push r15 + push r14 + push r13 + push r12 + push r11 + push r10 + push r9 + push r8 + push rdi + push rsi + push rbp + sub rsp, 8 + push rbx + push rdx + push rcx + sub rsp, 8 + " + }; } macro_rules! restore_regs_from_stack { @@ -105,4 +125,23 @@ macro_rules! restore_regs_from_stack { pop r14 pop r15" }; + (norax) => { + " + add rsp, 8 + pop rcx + pop rdx + pop rbx + add rsp, 8 + pop rbp + pop rsi + pop rdi + pop r8 + pop r9 + pop r10 + pop r11 + pop r12 + pop r13 + pop r14 + pop r15" + }; } diff --git a/src/svm/percpu.rs b/src/svm/percpu.rs index 0c6d59a..8cc4504 100644 --- a/src/svm/percpu.rs +++ b/src/svm/percpu.rs @@ -62,7 +62,7 @@ impl AxArchPerCpu for SvmPerCpuState { } // Enable XSAVE/XRSTOR. - super::vcpu::XState::enable_xsave(); + crate::xstate::enable_xsave(); // Allocate & register Host-Save Area self.hsave_page = PhysFrame::alloc_zero()?; @@ -71,7 +71,7 @@ impl AxArchPerCpu for SvmPerCpuState { Msr::VM_HSAVE_PA.write(hsave_pa); } - //S et EFER.SVME to enable SVM + // Set EFER.SVME to enable SVM let mut efer = EferFlags::from_bits_truncate(Msr::IA32_EFER.read()); efer.insert(EferFlags::SECURE_VIRTUAL_MACHINE_ENABLE); // bit 12 unsafe { diff --git a/src/svm/structs.rs b/src/svm/structs.rs index 416346f..d21d497 100644 --- a/src/svm/structs.rs +++ b/src/svm/structs.rs @@ -56,7 +56,13 @@ impl VmcbFrame { let bytes = core::slice::from_raw_parts(ptr, 16); let hex_bytes: alloc::string::String = bytes .iter() - .map(|b| if *b == 0 { " --".to_string() } else { alloc::format!(" {:02x}", b) }) + .map(|b| { + if *b == 0 { + " --".to_string() + } else { + alloc::format!(" {:02x}", b) + } + }) .collect(); alloc::format!("{:04x}:{}\n", offset, hex_bytes) }; diff --git a/src/svm/vcpu.rs b/src/svm/vcpu.rs index 3824727..0030244 100644 --- a/src/svm/vcpu.rs +++ b/src/svm/vcpu.rs @@ -4,9 +4,9 @@ use bit_field::BitField; use core::arch::asm; use core::fmt::{Debug, Formatter, Result}; use core::{arch::naked_asm, mem::size_of}; -use raw_cpuid::CpuId; use x86::controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}; use x86::dtables::{self, DescriptorTablePointer}; +use x86::msr::IA32_GS_BASE; use x86::segmentation::SegmentSelector; use x86_64::registers::control::{Cr0, Cr0Flags, Cr3, Cr4, Cr4Flags, EferFlags}; @@ -19,19 +19,10 @@ use tock_registers::interfaces::{Debuggable, ReadWriteable, Readable, Writeable} use super::definitions::SvmExitCode; use super::structs::{IOPm, MSRPm, VmcbFrame}; use super::vmcb::{NestedCtl, SvmExitInfo, VmcbCleanBits, VmcbTlbControl, set_vmcb_segment}; -use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters}; +use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters, xstate::XState}; const QEMU_EXIT_PORT: u16 = 0x604; const QEMU_EXIT_MAGIC: u64 = 0x2000; -pub struct XState { - host_xcr0: u64, - guest_xcr0: u64, - host_xss: u64, - guest_xss: u64, - - xsave_available: bool, - xsaves_available: bool, -} #[derive(PartialEq, Eq, Debug)] pub enum VmCpuMode { @@ -41,104 +32,167 @@ pub enum VmCpuMode { Mode64, // IA-32E mode (CS.L = 1) } -impl XState { - /// Create a new [`XState`] instance with current host state - fn new() -> Self { - // Check if XSAVE is available - let xsave_available = Self::xsave_available(); - // Check if XSAVES and XRSTORS (as well as IA32_XSS) are available - let xsaves_available = if xsave_available { - Self::xsaves_available() - } else { - false - }; +/// States loaded/stored during VMLOAD/VMSAVE instructions. +/// +/// VMLOAD/VMSAVE only load/store the guest version of these states from/to the +/// VMCB, but not the host version. Therefore, we need to keep track of the host +/// versions separately. +#[derive(Default)] +pub struct VmLoadSaveStates { + /// The base address of the FS segment. + pub fs_base: u64, + /// The base address of the GS segment. + pub gs_base: u64, + /// The value of the KERNEL_GS_BASE MSR. + pub kernel_gs_base: u64, + /// The value of the SYSENTER_CS MSR. + pub sysenter_cs: u64, + /// The value of the SYSENTER_ESP MSR. + pub sysenter_esp: u64, + /// The value of the SYSENTER_EIP MSR. + pub sysenter_eip: u64, + /// The value of the STAR MSR. + pub star: u64, + /// The value of the LSTAR MSR. + pub lstar: u64, + /// The value of the CSTAR MSR. + pub cstar: u64, + /// The value of the SF_MASK MSR. + pub sfmask: u64, + /// The local descriptor table register. + pub ldtr: u16, + /// The task register. + pub tr: u16, +} - // Read XCR0 iff XSAVE is available - let xcr0 = if xsave_available { - unsafe { xcr0_read().bits() } - } else { - 0 - }; - // Read IA32_XSS iff XSAVES is available - let xss = if xsaves_available { - Msr::IA32_XSS.read() - } else { - 0 - }; +// Some fields are not loaded/saved because they are not used in ArceOS and +// AxVisor, we keep their fields and loader/savers for future use. +#[allow(dead_code)] +impl VmLoadSaveStates { + /// Save the current FS and GS (including KERNEL_GS_BASE) bases. + #[inline(always)] + pub fn save_fs_gs(&mut self) { + self.fs_base = Msr::IA32_FS_BASE.read(); + self.gs_base = Msr::IA32_GS_BASE.read(); + self.kernel_gs_base = Msr::IA32_KERNEL_GSBASE.read(); + } + + /// Save the current SYSENTER MSRs. + #[inline(always)] + pub fn save_sysenter(&mut self) { + self.sysenter_cs = Msr::IA32_SYSENTER_CS.read(); + self.sysenter_esp = Msr::IA32_SYSENTER_ESP.read(); + self.sysenter_eip = Msr::IA32_SYSENTER_EIP.read(); + } + + /// Save the current SYSCALL MSRs. + #[inline(always)] + pub fn save_syscall(&mut self) { + self.star = Msr::IA32_STAR.read(); + self.lstar = Msr::IA32_LSTAR.read(); + self.cstar = Msr::IA32_CSTAR.read(); + self.sfmask = Msr::IA32_FMASK.read(); + } + + /// Save the current LDTR and TR registers. + #[inline(always)] + pub fn save_segs(&mut self) { + let ldtr: u16; + let tr: u16; - Self { - host_xcr0: xcr0, - guest_xcr0: xcr0, - host_xss: xss, - guest_xss: xss, - xsave_available, - xsaves_available, + unsafe { + asm!( + "sldt {0:x}", + "str {1:x}", + out(reg) ldtr, + out(reg) tr, + ); } - } - /// Enable extended processor state management instructions, including XGETBV and XSAVE. - pub fn enable_xsave() { - if Self::xsave_available() { - unsafe { Cr4::write(Cr4::read() | Cr4Flags::OSXSAVE) }; - } + self.ldtr = ldtr; + self.tr = tr; } - /// Check if XSAVE is available on the current CPU. - pub fn xsave_available() -> bool { - let cpuid = CpuId::new(); - cpuid - .get_feature_info() - .map(|f| f.has_xsave()) - .unwrap_or(false) + /// Save all VMLOAD/VMSAVE related states. + pub fn save_all(&mut self) { + self.save_fs_gs(); + self.save_sysenter(); + self.save_syscall(); + self.save_segs(); } - /// Check if XSAVES and XRSTORS (as well as IA32_XSS) are available on the current CPU. - pub fn xsaves_available() -> bool { - let cpuid = CpuId::new(); - cpuid - .get_extended_state_info() - .map(|f| f.has_xsaves_xrstors()) - .unwrap_or(false) + /// Load the saved FS and GS (including KERNEL_GS_BASE) bases. + #[inline(always)] + pub fn load_fs_gs(&self) { + unsafe { + Msr::IA32_FS_BASE.write(self.fs_base); + Msr::IA32_GS_BASE.write(self.gs_base); + Msr::IA32_KERNEL_GSBASE.write(self.kernel_gs_base); + } } - /// Save the current host XCR0 and IA32_XSS values and load the guest values. - pub fn switch_to_guest(&mut self) { + /// Load the saved SYSENTER MSRs. + #[inline(always)] + pub fn load_sysenter(&self) { unsafe { - if self.xsave_available { - self.host_xcr0 = xcr0_read().bits(); - xcr0_write(Xcr0::from_bits_unchecked(self.guest_xcr0)); - - if self.xsaves_available { - self.host_xss = Msr::IA32_XSS.read(); - Msr::IA32_XSS.write(self.guest_xss); - } - } + Msr::IA32_SYSENTER_CS.write(self.sysenter_cs); + Msr::IA32_SYSENTER_ESP.write(self.sysenter_esp); + Msr::IA32_SYSENTER_EIP.write(self.sysenter_eip); } } - /// Save the current guest XCR0 and IA32_XSS values and load the host values. - pub fn switch_to_host(&mut self) { + /// Load the saved SYSCALL MSRs. + #[inline(always)] + pub fn load_syscall(&self) { unsafe { - if self.xsave_available { - self.guest_xcr0 = xcr0_read().bits(); - xcr0_write(Xcr0::from_bits_unchecked(self.host_xcr0)); + Msr::IA32_STAR.write(self.star); + Msr::IA32_LSTAR.write(self.lstar); + Msr::IA32_CSTAR.write(self.cstar); + Msr::IA32_FMASK.write(self.sfmask); + } + } - if self.xsaves_available { - self.guest_xss = Msr::IA32_XSS.read(); - Msr::IA32_XSS.write(self.host_xss); - } - } + /// Load all VMLOAD/VMSAVE related states. + #[inline(always)] + pub fn load_segs(&self) { + let ldtr = self.ldtr; + let tr = self.tr; + + unsafe { + asm!( + "lldt {0:x}", + "ltr {1:x}", + in(reg) ldtr, + in(reg) tr, + ); } } + + /// Load all VMLOAD/VMSAVE related states. + pub fn load_all(&self) { + self.load_fs_gs(); + self.load_sysenter(); + self.load_syscall(); + self.load_segs(); + } + + /// Create a new [`VmLoadSaveStates`] instance from current hardware states. + pub fn new_from_hardware() -> Self { + let mut states = Self::default(); + states.save_all(); + states + } } pub struct SvmVcpu { // DO NOT modify `guest_regs` and `host_stack_top` and their order unless you do know what you are doing! // DO NOT add anything before or between them unless you do know what you are doing! guest_regs: GeneralRegisters, + #[allow(dead_code)] // actually used in asm! host_stack_top: u64, launched: bool, vmcb: VmcbFrame, + load_save_states: VmLoadSaveStates, iopm: IOPm, msrpm: MSRPm, pending_events: VecDeque<(u8, Option)>, @@ -156,6 +210,7 @@ impl SvmVcpu { host_stack_top: 0, launched: false, vmcb: VmcbFrame::new()?, + load_save_states: VmLoadSaveStates::default(), iopm: IOPm::passthrough_all()?, msrpm: MSRPm::passthrough_all()?, pending_events: VecDeque::with_capacity(8), @@ -230,7 +285,7 @@ impl SvmVcpu { } // Run guest - // self.load_guest_xstate(); + self.load_guest_xstate(); unsafe { self.svm_run(); @@ -238,9 +293,11 @@ impl SvmVcpu { self.load_host_xstate(); + panic!("fall through after vmrun"); + // Handle vm-exits let exit_info = self.exit_info().unwrap(); - // debug!("VM exit: {:#x?}", exit_info); + panic!("VM exit: {:#x?}", exit_info); match self.builtin_vmexit_handler(&exit_info) { Some(result) => { @@ -351,34 +408,11 @@ impl SvmVcpu { } fn setup_vmcb(&mut self, entry: GuestPhysAddr, npt_root: HostPhysAddr) -> AxResult { + // Commented out because not implemented yet + // self.setup_io_bitmap()?; + // self.setup_msr_bitmap()?; self.setup_vmcb_guest(entry)?; - self.setup_vmcb_control(npt_root, true)?; - - info!("VMCB:\n{}", self.vmcb.hex_dump()); - - let vm_hsave_pa = Msr::VM_HSAVE_PA.read(); - let cr4 = Cr4::read(); - let efer = EferFlags::from_bits_truncate(Msr::IA32_EFER.read()); - info!( - "[AxVM] VMCB setup complete (HSAVE @ {:#x}, CR4: {:#x}, EFER: {:#x})", - vm_hsave_pa, - cr4.bits(), - efer.bits() - ); - - unsafe { - asm!( - "mov rax, {0}", - "vmload rax", - in(reg) self.vmcb.phys_addr().as_usize() as u64, - ) - } - - panic!( - "VMLOAD OK" - ); - - Ok(()) + self.setup_vmcb_control(npt_root, true) } fn setup_vmcb_guest(&mut self, entry: GuestPhysAddr) -> AxResult { @@ -410,12 +444,7 @@ impl SvmVcpu { seg!(gs, 0x93); seg!(ss, 0x93); seg!(ldtr, 0x82); - seg!(tr, 0x83); - - // st.ldtr.selector.set(0); - // st.ldtr.base.set(0); - // st.ldtr.limit.set(0); - // st.ldtr.attr.set(0x1000); + seg!(tr, 0x8b); // GDTR / IDTR st.gdtr.base.set(0); @@ -429,8 +458,7 @@ impl SvmVcpu { st.rsp.set(0); st.rip.set(entry.as_usize() as u64); st.rflags.set(0x2); // bit1 必须为 1 - st.dr6.set(0); - // st.dr6.set(0xffff0ff0); // Pending-DBG-Exceptions 对应 0 + st.dr6.set(0xffff0ff0); // SYSENTER MSRs // st.sysenter_cs.set(0); @@ -473,7 +501,8 @@ impl SvmVcpu { ct.clean_bits.set(0); // ⑤ TLB Control:0 = NONE, 1 = FLUSH-ASID, 3 = FLUSH-ALL - ct.tlb_control.modify(VmcbTlbControl::CONTROL::FlushGuestTlb); + ct.tlb_control + .modify(VmcbTlbControl::CONTROL::FlushGuestTlb); ct.int_control.set(1 << 24); // V_INTR_MASKING_MASK @@ -567,55 +596,78 @@ impl SvmVcpu { // 0 // } - pub unsafe fn svm_run(&mut self) -> usize { - let vmcb = self.vmcb.phys_addr().as_usize() as u64; + /// Operations immediately before VMRUN instruction. This includes: + /// + /// 1. Disabling interrupts (CLGI) + /// 2. Syncing RAX from guest_regs to VMCB + /// 3. Saving host FS/GS related states + /// 4. `VMLOAD`ing the VMCB + #[inline(always)] + fn before_vmrun(&mut self) { + unsafe { + asm!("clgi"); + } + + unsafe { self.vmcb.as_vmcb().state.rax.set(self.regs().rax) }; + + self.load_save_states.save_fs_gs(); unsafe { asm!( - "clgi", - "mov rax, {0}", "vmload rax", - in(reg) vmcb, - // options(noreturn), + in("rax") self.vmcb.phys_addr().as_usize() as u64, ); } - panic!("fall through after vmrun"); + } + + /// Operations immediately after VMRUN instruction. This includes: + /// + /// 1. `VMSAVE`ing the VMCB + /// 2. Restoring host FS/GS related states + /// 3. Syncing RAX from VMCB to guest_regs + /// 4. Enabling interrupts (STGI) + #[inline(always)] + fn after_vmrun(&mut self) { + let vmcb = self.vmcb.phys_addr().as_usize() as u64; - // let guest_regs = self.regs_mut(); + unsafe { + asm!( + "vmsave rax", + in("rax") vmcb, + ); + } + self.load_save_states.load_fs_gs(); + + self.regs_mut().rax = unsafe { self.vmcb.as_vmcb().state.rax.get() }; + + unsafe { + asm!("stgi"); + } } - // #[unsafe(naked)] - // unsafe extern "C" fn svm_entry() -> ! { - // naked_asm!( - // "ud2", - // // "mov [rdi + {host_stack_size}], rsp", - // // "mov rsp, rdi", - // // // restore_regs_from_stack!(), - // // "vmload rax", - // // "vmrun rax", - // "jmp {failed}", - // // host_stack_size = const size_of::(), - // failed = sym Self::svm_entry_failed, - // ) - // } + pub unsafe fn svm_run(&mut self) { + let vmcb = self.vmcb.phys_addr().as_usize() as u64; - /// Return after vm-exit. - /// - /// The return value is a dummy value. - #[unsafe(naked)] - unsafe extern fn svm_exit(&mut self) -> usize { - naked_asm!( - save_regs_to_stack!(), // save guest status - "mov rsp, [rsp + {host_stack_top}]", // set RSP to Vcpu::host_stack_top - restore_regs_from_stack!(), // restore host status - "ret", - host_stack_top = const size_of::(), - ); - } - - fn svm_entry_failed() -> ! { - panic!("svm_entry_failed"); + self.before_vmrun(); + + unsafe { + asm!( + save_regs_to_stack!(norax), // Save host gpr except RAX, which holds vmcb pa + "mov [rdi + {host_stack_top}], rsp", // Save current RSP to Vcpu::host_stack_top + "mov rsp, rdi", // Set RSP to guest_regs area + restore_regs_from_stack!(norax), // Restore guest status except RAX + "vmrun rax", // Let's go! + save_regs_to_stack!(norax), // Save guest gpr except RAX + "mov rdi, rsp", // Regain the pointer to VCpu struct + "mov rsp, [rdi + {host_stack_top}]", // Restore host RSP from Vcpu::host_stack_top + restore_regs_from_stack!(norax), // Restore host gpr except RAX + host_stack_top = const size_of::(), + in("rax") vmcb, + ) + } + + self.after_vmrun(); } fn allow_interrupt(&self) -> bool { diff --git a/src/svm/vmcb.rs b/src/svm/vmcb.rs index 405a134..b87126c 100644 --- a/src/svm/vmcb.rs +++ b/src/svm/vmcb.rs @@ -404,6 +404,7 @@ impl VmcbControlArea { } } +#[derive(Debug)] pub struct SvmExitInfo { pub exit_code: core::result::Result, pub exit_info_1: u64, diff --git a/src/vmx/percpu.rs b/src/vmx/percpu.rs index 484a723..c5d979a 100644 --- a/src/vmx/percpu.rs +++ b/src/vmx/percpu.rs @@ -50,7 +50,7 @@ impl AxArchPerCpu for VmxPerCpuState { } // Enable XSAVE/XRSTOR. - super::vcpu::XState::enable_xsave(); + crate::xstate::enable_xsave(); // Enable VMXON, if required. let ctrl = FeatureControl::read(); diff --git a/src/vmx/vcpu.rs b/src/vmx/vcpu.rs index a4e2e53..d749cd1 100644 --- a/src/vmx/vcpu.rs +++ b/src/vmx/vcpu.rs @@ -8,7 +8,7 @@ use core::{ use raw_cpuid::CpuId; use x86::{ bits64::vmx, - controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}, + controlregs::Xcr0, dtables::{self, DescriptorTablePointer}, segmentation::SegmentSelector, }; @@ -32,23 +32,13 @@ use super::vmcs::{ self, ApicAccessExitType, VmcsControl32, VmcsControl64, VmcsControlNW, VmcsGuest16, VmcsGuest32, VmcsGuest64, VmcsGuestNW, VmcsHost16, VmcsHost32, VmcsHost64, VmcsHostNW, }; -use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters}; +use crate::{ept::GuestPageWalkInfo, msr::Msr, regs::GeneralRegisters, xstate::XState}; const VMX_PREEMPTION_TIMER_SET_VALUE: u32 = 1_000_000; const QEMU_EXIT_PORT: u16 = 0x604; const QEMU_EXIT_MAGIC: u64 = 0x2000; -pub struct XState { - host_xcr0: u64, - guest_xcr0: u64, - host_xss: u64, - guest_xss: u64, - - xsave_available: bool, - xsaves_available: bool, -} - #[derive(PartialEq, Eq, Debug)] pub enum VmCpuMode { Real, @@ -57,97 +47,6 @@ pub enum VmCpuMode { Mode64, // IA-32E mode (CS.L = 1) } -impl XState { - /// Create a new [`XState`] instance with current host state - fn new() -> Self { - // Check if XSAVE is available - let xsave_available = Self::xsave_available(); - // Check if XSAVES and XRSTORS (as well as IA32_XSS) are available - let xsaves_available = if xsave_available { - Self::xsaves_available() - } else { - false - }; - - // Read XCR0 iff XSAVE is available - let xcr0 = if xsave_available { - unsafe { xcr0_read().bits() } - } else { - 0 - }; - // Read IA32_XSS iff XSAVES is available - let xss = if xsaves_available { - Msr::IA32_XSS.read() - } else { - 0 - }; - - Self { - host_xcr0: xcr0, - guest_xcr0: xcr0, - host_xss: xss, - guest_xss: xss, - xsave_available, - xsaves_available, - } - } - - /// Enable extended processor state management instructions, including XGETBV and XSAVE. - pub fn enable_xsave() { - if Self::xsave_available() { - unsafe { Cr4::write(Cr4::read() | Cr4Flags::OSXSAVE) }; - } - } - - /// Check if XSAVE is available on the current CPU. - pub fn xsave_available() -> bool { - let cpuid = CpuId::new(); - cpuid - .get_feature_info() - .map(|f| f.has_xsave()) - .unwrap_or(false) - } - - /// Check if XSAVES and XRSTORS (as well as IA32_XSS) are available on the current CPU. - pub fn xsaves_available() -> bool { - let cpuid = CpuId::new(); - cpuid - .get_extended_state_info() - .map(|f| f.has_xsaves_xrstors()) - .unwrap_or(false) - } - - /// Save the current host XCR0 and IA32_XSS values and load the guest values. - pub fn switch_to_guest(&mut self) { - unsafe { - if self.xsave_available { - self.host_xcr0 = xcr0_read().bits(); - xcr0_write(Xcr0::from_bits_unchecked(self.guest_xcr0)); - - if self.xsaves_available { - self.host_xss = Msr::IA32_XSS.read(); - Msr::IA32_XSS.write(self.guest_xss); - } - } - } - } - - /// Save the current guest XCR0 and IA32_XSS values and load the host values. - pub fn switch_to_host(&mut self) { - unsafe { - if self.xsave_available { - self.guest_xcr0 = xcr0_read().bits(); - xcr0_write(Xcr0::from_bits_unchecked(self.host_xcr0)); - - if self.xsaves_available { - self.guest_xss = Msr::IA32_XSS.read(); - Msr::IA32_XSS.write(self.host_xss); - } - } - } - } -} - const MSR_IA32_EFER_LMA_BIT: u64 = 1 << 10; const CR0_PE: usize = 1 << 0; @@ -1235,7 +1134,7 @@ impl VmxVcpu { }) .ok_or(ax_err_type!(InvalidInput)) .and_then(|x| { - self.xstate.guest_xcr0 = x.bits(); + self.xstate.guest.xcr0 = x.bits(); self.advance_rip(VM_EXIT_INSTR_LEN_XSETBV) }) } else { diff --git a/src/xstate.rs b/src/xstate.rs new file mode 100644 index 0000000..3b0f015 --- /dev/null +++ b/src/xstate.rs @@ -0,0 +1,137 @@ +use raw_cpuid::CpuId; +use x86::controlregs::{Xcr0, xcr0 as xcr0_read, xcr0_write}; +use x86_64::registers::control::{Cr4, Cr4Flags}; + +use crate::msr::Msr; + +/// Indicates the availability of extended processor state management features. +#[derive(Debug, Clone, Copy)] +pub struct XAvailable { + /// Indicates if XSAVE (as well as xcr0) is available. + pub xsave: bool, + /// Indicates if XSAVES and XRSTORS (as well as IA32_XSS) are available. + pub xsaves: bool, +} + +impl XAvailable { + /// Create a new [`XAvailable`] instance by querying the CPU features. + pub fn new() -> Self { + let xsave_avail = xsave_available(); + let xsaves_avail = xsave_avail && xsaves_available(); + + Self { + xsave: xsave_avail, + xsaves: xsaves_avail, + } + } +} + +/// Control and state registers for extended processor state management. +#[derive(Debug, Clone, Copy)] +pub struct XRegs { + /// The xcr0 extended control register. + pub xcr0: u64, + /// The IA32_XSS model-specific register. + pub xss: u64, +} + +impl XRegs { + /// Create a new [`XRegs`] instance by querying the current CPU state. + pub fn new(avail: XAvailable) -> Self { + let xcr0 = if avail.xsave { + unsafe { xcr0_read().bits() } + } else { + 0 + }; + + let xss = if avail.xsaves { + Msr::IA32_XSS.read() + } else { + 0 + }; + + Self { xcr0, xss } + } + + /// Load the extended processor state registers from this instance. + pub fn load(&self, avail: XAvailable) { + unsafe { + if avail.xsave { + xcr0_write(Xcr0::from_bits_unchecked(self.xcr0)); + + if avail.xsaves { + Msr::IA32_XSS.write(self.xss); + } + } + } + } + + /// Save the current extended processor state registers into this instance. + pub fn save(&mut self, avail: XAvailable) { + unsafe { + if avail.xsave { + self.xcr0 = xcr0_read().bits(); + + if avail.xsaves { + self.xss = Msr::IA32_XSS.read(); + } + } + } + } +} + +/// Extended processor state storage for vcpus. +#[derive(Debug)] +pub struct XState { + pub host: XRegs, + pub guest: XRegs, + pub avail: XAvailable, +} + +impl XState { + /// Create a new [`XState`] instance with current host state. + pub fn new() -> Self { + let avail = XAvailable::new(); + let host = XRegs::new(avail); + let guest = host; + + Self { host, guest, avail } + } + + /// Save the current host XCR0 and IA32_XSS values and load the guest values. + pub fn switch_to_guest(&mut self) { + self.host.save(self.avail); + self.guest.load(self.avail); + } + + /// Save the current guest XCR0 and IA32_XSS values and load the host values. + pub fn switch_to_host(&mut self) { + self.guest.save(self.avail); + self.host.load(self.avail); + } +} + +/// Check if XSAVE is available on the current CPU. +pub fn xsave_available() -> bool { + let cpuid = CpuId::new(); + cpuid + .get_feature_info() + .map(|f| f.has_xsave()) + .unwrap_or(false) +} + +/// Check if XSAVES and XRSTORS (as well as IA32_XSS) are available on the current CPU. +pub fn xsaves_available() -> bool { + let cpuid = CpuId::new(); + cpuid + .get_extended_state_info() + .map(|f| f.has_xsaves_xrstors()) + .unwrap_or(false) +} + +/// Enable extended processor state management instructions, including XGETBV and XSAVE. +pub fn enable_xsave() { + if xsave_available() { + unsafe { Cr4::write(Cr4::read() | Cr4Flags::OSXSAVE) }; + } +} From 7fe181bd49ed0b5809968090203a3cd3a8f13a17 Mon Sep 17 00:00:00 2001 From: aarkegz Date: Fri, 9 Jan 2026 06:50:35 +0000 Subject: [PATCH 5/5] temp - Fri Jan 9 06:50:35 AM UTC 2026 --- src/svm/vcpu.rs | 14 ++++++++++---- src/xstate.rs | 7 +++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/svm/vcpu.rs b/src/svm/vcpu.rs index 0030244..25cea23 100644 --- a/src/svm/vcpu.rs +++ b/src/svm/vcpu.rs @@ -1,6 +1,7 @@ use alloc::collections::VecDeque; use axvisor_api::vmm::{VCpuId, VMId}; use bit_field::BitField; +use x86::io::outl; use core::arch::asm; use core::fmt::{Debug, Formatter, Result}; use core::{arch::naked_asm, mem::size_of}; @@ -184,6 +185,7 @@ impl VmLoadSaveStates { } } +#[repr(C)] pub struct SvmVcpu { // DO NOT modify `guest_regs` and `host_stack_top` and their order unless you do know what you are doing! // DO NOT add anything before or between them unless you do know what you are doing! @@ -293,8 +295,6 @@ impl SvmVcpu { self.load_host_xstate(); - panic!("fall through after vmrun"); - // Handle vm-exits let exit_info = self.exit_info().unwrap(); panic!("VM exit: {:#x?}", exit_info); @@ -432,8 +432,12 @@ impl SvmVcpu { // CS: P S CODE READ (bit 7, 4, 3, 1) = 0x9a // seg!(cs, 0x9b); - st.cs.selector.set(0xf000); - st.cs.base.set(0xffff0000); + // st.cs.selector.set(0xf000); + // st.cs.base.set(0xffff0000); + // st.cs.limit.set(0xffff); + // st.cs.attr.set(0x9b); + st.cs.selector.set(0); + st.cs.base.set(0); st.cs.limit.set(0xffff); st.cs.attr.set(0x9b); @@ -647,6 +651,7 @@ impl SvmVcpu { } pub unsafe fn svm_run(&mut self) { + let self_addr = self as *mut Self as u64; let vmcb = self.vmcb.phys_addr().as_usize() as u64; self.before_vmrun(); @@ -664,6 +669,7 @@ impl SvmVcpu { restore_regs_from_stack!(norax), // Restore host gpr except RAX host_stack_top = const size_of::(), in("rax") vmcb, + in("rdi") self_addr, ) } diff --git a/src/xstate.rs b/src/xstate.rs index 3b0f015..be72056 100644 --- a/src/xstate.rs +++ b/src/xstate.rs @@ -57,6 +57,7 @@ impl XRegs { pub fn load(&self, avail: XAvailable) { unsafe { if avail.xsave { + // info!("Loading XCR0: {:#x}", self.xcr0); xcr0_write(Xcr0::from_bits_unchecked(self.xcr0)); if avail.xsaves { @@ -100,13 +101,19 @@ impl XState { /// Save the current host XCR0 and IA32_XSS values and load the guest values. pub fn switch_to_guest(&mut self) { + // info!("Switching to guest xstate"); self.host.save(self.avail); + // info!("Host xstate saved: {:?}", self.host); + // info!("Guest xstate loading: {:?}", self.guest); self.guest.load(self.avail); } /// Save the current guest XCR0 and IA32_XSS values and load the host values. pub fn switch_to_host(&mut self) { + // info!("Switching to host xstate"); self.guest.save(self.avail); + // info!("Guest xstate saved: {:?}", self.guest); + // info!("Host xstate loading: {:?}", self.host); self.host.load(self.avail); } }