HLT VMExit

This commit is contained in:
Masato Imai
2025-04-22 14:04:19 +00:00
parent 4c2629ec14
commit eebd64f0c2
5 changed files with 303 additions and 37 deletions

7
Cargo.lock generated
View File

@ -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"

View File

@ -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"

View File

@ -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);

View File

@ -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() };
}

View File

@ -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()
}
}