From eebd64f0c2f296506abcc69d6308aa91310f931b Mon Sep 17 00:00:00 2001 From: Masato Imai Date: Tue, 22 Apr 2025 14:04:19 +0000 Subject: [PATCH] HLT VMExit --- Cargo.lock | 7 ++ Cargo.toml | 1 + src/main.rs | 7 +- src/vmm/vcpu.rs | 107 ++++++++++++++++-------- src/vmm/vmcs.rs | 218 ++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 303 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9bd0ddf..0e61eb4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,6 +88,7 @@ dependencies = [ "bootloader", "lazy_static", "linked_list_allocator", + "numeric-enum-macro", "pc-keyboard", "pic8259", "spin 0.5.2", @@ -97,6 +98,12 @@ dependencies = [ "x86_64 0.14.13", ] +[[package]] +name = "numeric-enum-macro" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e4bdb6b46b592948e700ea1ef24a4296491f6a0ee722b258040abd15a3714" + [[package]] name = "pc-keyboard" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 5dd89e5..842453b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ pc-keyboard = "0.8.0" linked_list_allocator = "0.9.0" x86 = "0.52.0" bitfield = "0.19.0" +numeric-enum-macro = "0.2.0" [dependencies.lazy_static] version = "1.0" diff --git a/src/main.rs b/src/main.rs index fbe8103..0f74684 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,10 @@ use nel_os::{ vmcs::InstructionError, }, }; -use x86::bits64::vmx; +use x86::{ + bits64::vmx::{self, vmread}, + vmx::vmcs, +}; use x86_64::VirtAddr; #[cfg(not(test))] @@ -62,6 +65,8 @@ fn kernel_main(boot_info: &'static BootInfo) -> ! { info!("Starting the Virtual Machine..."); unsafe { + asm!("cli"); + let vmlaunch = vmx::vmlaunch(); info!("VMLaunch: {:?}", vmlaunch); diff --git a/src/vmm/vcpu.rs b/src/vmm/vcpu.rs index 27e3d70..51360f7 100644 --- a/src/vmm/vcpu.rs +++ b/src/vmm/vcpu.rs @@ -1,20 +1,21 @@ use x86::{ - bits64::vmx::vmwrite, + bits64::vmx::{vmread, vmwrite}, controlregs::{cr0, cr3, cr4}, dtables::{self, DescriptorTablePointer}, halt, msr::{rdmsr, IA32_EFER, IA32_FS_BASE}, vmx::{vmcs, VmFail}, }; +use x86_64::VirtAddr; -use core::arch::naked_asm; +use core::arch::{asm, naked_asm}; use crate::{ info, memory::BootInfoFrameAllocator, vmm::vmcs::{ DescriptorType, EntryControls, Granularity, PrimaryExitControls, - PrimaryProcessorBasedVmExecutionControls, SegmentRights, + PrimaryProcessorBasedVmExecutionControls, SegmentRights, VmxExitInfo, }, }; @@ -29,6 +30,9 @@ pub struct VCpu { pub phys_mem_offset: u64, } +const TEMP_STACK_SIZE: usize = 4096; +static mut TEMP_STACK: [u8; TEMP_STACK_SIZE + 0x10] = [0; TEMP_STACK_SIZE + 0x10]; + impl VCpu { pub fn new(phys_mem_offset: u64, frame_allocator: &mut BootInfoFrameAllocator) -> Self { let mut vmxon = Vmxon::new(frame_allocator); @@ -81,7 +85,7 @@ impl VCpu { primary_exec_ctrl.0 |= (reserved_bits & 0xFFFFFFFF) as u32; primary_exec_ctrl.0 &= (reserved_bits >> 32) as u32; - primary_exec_ctrl.set_hlt(true); + primary_exec_ctrl.set_hlt(false); primary_exec_ctrl.set_activate_secondary_controls(false); primary_exec_ctrl.write(); @@ -142,6 +146,10 @@ impl VCpu { vmwrite(vmcs::host::CR4, cr4().bits() as u64)?; vmwrite(vmcs::host::RIP, Self::vmexit as u64)?; + vmwrite( + vmcs::host::RSP, + VirtAddr::from_ptr(&raw mut TEMP_STACK).as_u64() + TEMP_STACK_SIZE as u64, + )?; vmwrite( vmcs::host::ES_SELECTOR, @@ -204,16 +212,16 @@ impl VCpu { vmwrite(vmcs::guest::IDTR_BASE, 0)?; vmwrite(vmcs::guest::LDTR_BASE, 0xDEAD00)?; - vmwrite(vmcs::guest::CS_LIMIT, u32::MAX as u64)?; - vmwrite(vmcs::guest::SS_LIMIT, u32::MAX as u64)?; - vmwrite(vmcs::guest::DS_LIMIT, u32::MAX as u64)?; - vmwrite(vmcs::guest::ES_LIMIT, u32::MAX as u64)?; - vmwrite(vmcs::guest::FS_LIMIT, u32::MAX as u64)?; - vmwrite(vmcs::guest::GS_LIMIT, u32::MAX as u64)?; - vmwrite(vmcs::guest::TR_LIMIT, u32::MAX as u64)?; - vmwrite(vmcs::guest::GDTR_LIMIT, u32::MAX as u64)?; - vmwrite(vmcs::guest::IDTR_LIMIT, u32::MAX as u64)?; - vmwrite(vmcs::guest::LDTR_LIMIT, u32::MAX as u64)?; + vmwrite(vmcs::guest::CS_LIMIT, 0xffff)?; + vmwrite(vmcs::guest::SS_LIMIT, 0xffff)?; + vmwrite(vmcs::guest::DS_LIMIT, 0xffff)?; + vmwrite(vmcs::guest::ES_LIMIT, 0xffff)?; + vmwrite(vmcs::guest::FS_LIMIT, 0xffff)?; + vmwrite(vmcs::guest::GS_LIMIT, 0xffff)?; + vmwrite(vmcs::guest::TR_LIMIT, 0)?; + vmwrite(vmcs::guest::GDTR_LIMIT, 0)?; + vmwrite(vmcs::guest::IDTR_LIMIT, 0)?; + vmwrite(vmcs::guest::LDTR_LIMIT, 0)?; vmwrite( vmcs::guest::CS_SELECTOR, @@ -227,8 +235,8 @@ impl VCpu { vmwrite(vmcs::guest::TR_SELECTOR, 0)?; vmwrite(vmcs::guest::LDTR_SELECTOR, 0)?; - let cs_rights = { - let mut rights = SegmentRights(0); + let cs_right = { + let mut rights = SegmentRights::default(); rights.set_rw(true); rights.set_dc(false); rights.set_executable(true); @@ -237,12 +245,12 @@ impl VCpu { rights.set_granularity_raw(Granularity::KByte as u8); rights.set_long(true); rights.set_db(false); + rights }; - vmwrite(vmcs::guest::CS_ACCESS_RIGHTS, cs_rights.0 as u64)?; - let ds_rights = { - let mut rights = SegmentRights(0); + let ds_right = { + let mut rights = SegmentRights::default(); rights.set_rw(true); rights.set_dc(false); rights.set_executable(false); @@ -251,12 +259,12 @@ impl VCpu { rights.set_granularity_raw(Granularity::KByte as u8); rights.set_long(false); rights.set_db(true); + rights }; - vmwrite(vmcs::guest::DS_ACCESS_RIGHTS, ds_rights.0 as u64)?; - let tr_rights = { - let mut rights = SegmentRights(0); + let tr_right = { + let mut rights = SegmentRights::default(); rights.set_rw(true); rights.set_dc(false); rights.set_executable(true); @@ -265,12 +273,12 @@ impl VCpu { rights.set_granularity_raw(Granularity::Byte as u8); rights.set_long(false); rights.set_db(false); + rights }; - vmwrite(vmcs::guest::TR_ACCESS_RIGHTS, tr_rights.0 as u64)?; - let ldtr_rights = { - let mut rights = SegmentRights(0); + let ldtr_right = { + let mut rights = SegmentRights::default(); rights.set_accessed(false); rights.set_rw(true); rights.set_dc(false); @@ -280,9 +288,18 @@ impl VCpu { rights.set_granularity_raw(Granularity::Byte as u8); rights.set_long(false); rights.set_db(false); + rights }; - vmwrite(vmcs::guest::LDTR_ACCESS_RIGHTS, ldtr_rights.0 as u64)?; + + vmwrite(vmcs::guest::CS_ACCESS_RIGHTS, cs_right.0 as u64)?; + vmwrite(vmcs::guest::SS_ACCESS_RIGHTS, ds_right.0 as u64)?; + vmwrite(vmcs::guest::DS_ACCESS_RIGHTS, ds_right.0 as u64)?; + vmwrite(vmcs::guest::ES_ACCESS_RIGHTS, ds_right.0 as u64)?; + vmwrite(vmcs::guest::FS_ACCESS_RIGHTS, ds_right.0 as u64)?; + vmwrite(vmcs::guest::GS_ACCESS_RIGHTS, ds_right.0 as u64)?; + vmwrite(vmcs::guest::TR_ACCESS_RIGHTS, tr_right.0 as u64)?; + vmwrite(vmcs::guest::LDTR_ACCESS_RIGHTS, ldtr_right.0 as u64)?; info!("RIP: {:#x}", Self::guest as u64); vmwrite(vmcs::guest::RIP, Self::guest as u64)?; @@ -299,23 +316,41 @@ impl VCpu { self.vmcs.reset() } - fn guest_fn() -> ! { - loop { - unsafe { - halt(); - } - } - } - #[naked] unsafe extern "C" fn guest() -> ! { - naked_asm!("hlt"); - //naked_asm!("call {guest_fn}", guest_fn = sym Self::guest_fn); + naked_asm!("2: hlt; jmp 2b"); } fn vmexit_handler(&mut self) -> ! { info!("VMExit occurred"); + let raw_info = unsafe { vmread(vmcs::ro::EXIT_REASON) }.unwrap(); + info!("VMExit reason: {:#b}", raw_info); + + let info = VmxExitInfo::read(); + + if info.entry_failure() { + let reason = info.0 & 0xFF; + match reason { + 33 => { + info!(" Reason: VM-entry failure due to invalid guest state"); + } + 34 => { + info!(" Reason: VM-entry failure due to MSR loading"); + } + 41 => { + info!(" Reason: VM-entry failure due to machine-check event"); + } + _ => {} + } + } else { + info!( + " Reason: {:?} ({})", + info.get_reason().as_str(), + info.basic_reason() + ); + } + loop { unsafe { halt() }; } diff --git a/src/vmm/vmcs.rs b/src/vmm/vmcs.rs index 37e157d..484d968 100644 --- a/src/vmm/vmcs.rs +++ b/src/vmm/vmcs.rs @@ -1,6 +1,9 @@ #![allow(non_camel_case_types)] +use core::convert::TryInto; + use bitfield::{bitfield, BitMut}; +use numeric_enum_macro::numeric_enum; use x86::{bits64::vmx, vmx::VmFail}; use x86_64::structures::paging::{FrameAllocator, PhysFrame}; @@ -244,6 +247,19 @@ bitfield! { pub unusable, set_unusable: 16; } +impl Default for SegmentRights { + fn default() -> Self { + let mut rights = SegmentRights(0); + rights.set_accessed(true); + rights.set_present(true); + rights.set_avl(false); + rights.set_long(false); + rights.set_unusable(false); + + rights + } +} + bitfield! { pub struct EntryControls(u32); impl Debug; @@ -357,3 +373,205 @@ pub enum VmcsReadOnlyData32 { VM_EXIT_INSTRUCTION_INFO = 0x0000440E, } vmcs_read!(VmcsReadOnlyData32, u32); + +numeric_enum! { +#[repr(u16)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum VmxExitReason { + EXCEPTION = 0, + EXTERNAL_INTERRUPT = 1, + TRIPLE_FAULT = 2, + INIT = 3, + SIPI = 4, + IO_SMI = 5, + OTHER_SMI = 6, + INTERRUPT_WINDOW = 7, + NMI_WINDOW = 8, + TASK_SWITCH = 9, + CPUID = 10, + GETSEC = 11, + HLT = 12, + INVD = 13, + INVLPG = 14, + RDPMC = 15, + RDTSC = 16, + RSM = 17, + VMCALL = 18, + VMCLEAR = 19, + VMLAUNCH = 20, + VMPTRLD = 21, + VMPTRST = 22, + VMREAD = 23, + VMRESUME = 24, + VMWRITE = 25, + VMXOFF = 26, + VMXON = 27, + CONTROL_REGISTER_ACCESSES = 28, + MOV_DR = 29, + IO_INSTRUCTION = 30, + RDMSR = 31, + WRMSR = 32, + VM_ENTRY_FAILURE_INVALID_GUEST_STATE = 33, + VM_ENTRY_FAILURE_MSR_LOADING = 34, + MWAIT = 36, + MONITOR_TRAP_FLAG = 37, + MONITOR = 39, + PAUSE = 40, + VM_ENTRY_FAILURE_MACHINE_CHECK_EVENT = 41, + TPR_BELOW_THRESHOLD = 43, + APIC_ACCESS = 44, + VIRTUALIZED_EOI = 45, + ACCESS_TO_GDTR_OR_IDTR = 46, + ACCESS_TO_LDTR_OR_TR = 47, + EPT_VIOLATION = 48, + EPT_MISCONFIGURATION = 49, + INVEPT = 50, + RDTSCP = 51, + VMX_PREEMPTION_TIMER_EXPIRED = 52, + INVVPID = 53, + WBINVD = 54, + XSETBV = 55, + APIC_WRITE = 56, + RDRAND = 57, + INVPCID = 58, + VMFUNC = 59, + ENCLS = 60, + RDSEED = 61, + PAGE_MODIFICATION_LOG_FULL = 62, + XSAVES = 63, + XRSTORS = 64, + PCONFIG = 65, + SPP_RELATED_EVENT = 66, + UMWAIT = 67, + TPAUSE = 68, + LOADIWKEY = 69, + ENCLV = 70, + ENQCMD_PASID_TRANSLATION_FAILURE = 72, + ENQCMDS_PASID_TRANSLATION_FAILURE = 73, + BUS_LOCK = 74, + INSTRUCTION_TIMEOUT = 75, + SEAMCALL = 76, + TDCALL = 77, + RDMSRLIST = 78, + WRMSRLIST = 79, +} +} + +impl VmxExitReason { + pub fn read() -> Self { + let reason = VmcsReadOnlyData32::VM_EXIT_REASON.read(); + if reason.is_err() { + panic!("Failed to read VM exit reason"); + } + + (reason.unwrap() as u16).try_into().unwrap() + } + + pub fn as_str(&self) -> &'static str { + use VmxExitReason::*; + match self { + EXCEPTION => "Exception or non-maskable interrupt (NMI)", + EXTERNAL_INTERRUPT => "External interrupt", + TRIPLE_FAULT => "Triple fault", + INIT => "INIT signal", + SIPI => "Start-up IPI (SIPI)", + IO_SMI => "I/O system-management interrupt (SMI)", + OTHER_SMI => "Other SMI", + INTERRUPT_WINDOW => "Interrupt window", + NMI_WINDOW => "NMI window", + TASK_SWITCH => "Task switch", + CPUID => "CPUID instruction execution", + GETSEC => "GETSEC instruction execution", + HLT => "HLT instruction execution", + INVD => "INVD instruction execution", + INVLPG => "INVLPG instruction execution", + RDPMC => "RDPMC instruction execution", + RDTSC => "RDTSC instruction execution", + RSM => "RSM instruction execution in SMM", + VMCALL => "VMCALL instruction execution", + VMCLEAR => "VMCLEAR instruction execution", + VMLAUNCH => "VMLAUNCH instruction execution", + VMPTRLD => "VMPTRLD instruction execution", + VMPTRST => "VMPTRST instruction execution", + VMREAD => "VMREAD instruction execution", + VMRESUME => "VMRESUME instruction execution", + VMWRITE => "VMWRITE instruction execution", + VMXOFF => "VMXOFF instruction execution", + VMXON => "VMXON instruction execution", + CONTROL_REGISTER_ACCESSES => "Control-register accesses", + MOV_DR => "MOV to or from debug registers", + IO_INSTRUCTION => "I/O instruction execution", + RDMSR => "RDMSR instruction execution", + WRMSR => "WRMSR or WRMSRNS instruction execution", + VM_ENTRY_FAILURE_INVALID_GUEST_STATE => "VM-entry failure due to invalid guest state", + VM_ENTRY_FAILURE_MSR_LOADING => "VM-entry failure due to MSR loading", + MWAIT => "MWAIT instruction execution", + MONITOR_TRAP_FLAG => "Monitor trap flag", + MONITOR => "MONITOR instruction execution", + PAUSE => "PAUSE instruction execution", + VM_ENTRY_FAILURE_MACHINE_CHECK_EVENT => "VM-entry failure due to machine-check event", + TPR_BELOW_THRESHOLD => "TPR below threshold", + APIC_ACCESS => "APIC access", + VIRTUALIZED_EOI => "Virtualized EOI", + ACCESS_TO_GDTR_OR_IDTR => "Access to GDTR or IDTR", + ACCESS_TO_LDTR_OR_TR => "Access to LDTR or TR", + EPT_VIOLATION => "EPT violation", + EPT_MISCONFIGURATION => "EPT misconfiguration", + INVEPT => "INVEPT instruction execution", + RDTSCP => "RDTSCP instruction execution", + VMX_PREEMPTION_TIMER_EXPIRED => "VMX-preemption timer expired", + INVVPID => "INVVPID instruction execution", + WBINVD => "WBINVD or WBNOINVD instruction execution", + XSETBV => "XSETBV instruction execution", + APIC_WRITE => "APIC write", + RDRAND => "RDRAND instruction execution", + INVPCID => "INVPCID instruction execution", + VMFUNC => "VMFUNC instruction execution", + ENCLS => "ENCLS instruction execution", + RDSEED => "RDSEED instruction execution", + PAGE_MODIFICATION_LOG_FULL => "Page-modification log full", + XSAVES => "XSAVES instruction execution", + XRSTORS => "XRSTORS instruction execution", + PCONFIG => "PCONFIG instruction execution", + SPP_RELATED_EVENT => "SPP-related event", + UMWAIT => "UMWAIT instruction execution", + TPAUSE => "TPAUSE instruction execution", + LOADIWKEY => "LOADIWKEY instruction execution", + ENCLV => "ENCLV instruction execution", + ENQCMD_PASID_TRANSLATION_FAILURE => "ENQCMD PASID translation failure", + ENQCMDS_PASID_TRANSLATION_FAILURE => "ENQCMDS PASID translation failure", + BUS_LOCK => "Bus lock", + INSTRUCTION_TIMEOUT => "Instruction timeout", + SEAMCALL => "SEAMCALL instruction execution", + TDCALL => "TDCALL instruction execution", + RDMSRLIST => "RDMSRLIST instruction execution", + WRMSRLIST => "WRMSRLIST instruction execution", + } + } +} + +bitfield! { + pub struct VmxExitInfo(u32); + impl Debug; + + pub u16, basic_reason, set_basic_reason: 15, 0; + pub pending_mtf, set_pending_mtf: 26; + pub exit_vmxroot, set_exit_vmxroot: 27; + pub entry_failure, set_entry_failure: 31; +} + +impl VmxExitInfo { + pub fn read() -> Self { + let info = VmcsReadOnlyData32::VM_EXIT_REASON.read(); + if info.is_err() { + panic!("Failed to read VM exit reason"); + } + let info = info.unwrap(); + Self(info) + } + + pub fn get_reason(&self) -> VmxExitReason { + let reason = self.basic_reason() as u16; + reason.try_into().unwrap() + } +}