From f47315cda8e02f86fb23f2d98dca8c591b2c3f0f Mon Sep 17 00:00:00 2001 From: Masato Imai Date: Sun, 13 Jul 2025 17:17:41 +0000 Subject: [PATCH] AMD emulation --- src/vmm/cpuid.rs | 6 +- src/vmm/emulation/opcode.rs | 122 +++++++++++++----------------------- src/vmm/invlpg.rs | 105 +++++++++++++++++++++++++++++++ src/vmm/mod.rs | 1 + src/vmm/vcpu.rs | 48 ++++++++++++++ 5 files changed, 203 insertions(+), 79 deletions(-) create mode 100644 src/vmm/invlpg.rs diff --git a/src/vmm/cpuid.rs b/src/vmm/cpuid.rs index abc5445..114242a 100644 --- a/src/vmm/cpuid.rs +++ b/src/vmm/cpuid.rs @@ -12,7 +12,11 @@ pub fn handle_cpuid_exit(vcpu: &mut VCpu) { b"miHypervisor" }; - let brand_string: &[u8; 48] = b"mii Hypervisor CPU on Intel VT-x \0"; + let brand_string: &[u8; 48] = if vcpu.emulate_amd { + b"AMD EPYC 9965 192-Core Processor \0" + } else { + b"mii Hypervisor CPU on Intel VT-x \0" + }; let vendor = unsafe { core::mem::transmute::<&[u8; 12], &[u32; 3]>(vendor) }; let brand_string = unsafe { core::mem::transmute::<&[u8; 48], &[u32; 12]>(brand_string) }; diff --git a/src/vmm/emulation/opcode.rs b/src/vmm/emulation/opcode.rs index f355c27..f259550 100644 --- a/src/vmm/emulation/opcode.rs +++ b/src/vmm/emulation/opcode.rs @@ -1,6 +1,7 @@ use crate::{ info, vmm::{ + invlpg::{invept_single_context, invvpid_individual_address}, vcpu::VCpu, vmcs::{DescriptorType, EntryControls, Granularity, SegmentRights}, }, @@ -72,13 +73,9 @@ pub fn emulate_opcode(vcpu: &mut VCpu, instruction_bytes: [u8; 16], valid_bytes: pub fn handle_vmcall(vcpu: &mut VCpu, guest_phys_addr: u64) -> bool { if let Some(replaced_addr) = vcpu.opcode_emulator.replaced_address { if replaced_addr == guest_phys_addr { - info!("Handling VMCall at {:#x}", guest_phys_addr); - match vcpu.opcode_emulator.vmcall_control { Some(VmcallControl::ReturnTo32Bit) => { - if return_to_32_bit(vcpu) { - info!("Successfully returned to 32-bit mode."); - } else { + if !return_to_32_bit(vcpu) { info!("Failed to return to 32-bit mode."); } } @@ -87,9 +84,7 @@ pub fn handle_vmcall(vcpu: &mut VCpu, guest_phys_addr: u64) -> bool { } } - if restore_replaced_opcode(vcpu) { - info!("VMCall handled successfully, original opcode restored."); - } else { + if !restore_replaced_opcode(vcpu) { info!("Failed to restore original opcode."); } // Clear saved selectors after handling @@ -124,10 +119,24 @@ fn restore_replaced_opcode(vcpu: &mut VCpu) -> bool { } vcpu.opcode_emulator.original_opcode = None; vcpu.opcode_emulator.replaced_address = None; - info!( - "Restoring original opcode at {:#x}: {:?}", - guest_phys_addr, original_opcode - ); + + // Invalidate TLB and EPT after modifying code + unsafe { + // Get EPTP for EPT invalidation + let eptp = vmread(vmcs::control::EPTP_FULL).unwrap(); + let _ = invept_single_context(eptp); // Ignore errors + + // Get VPID for TLB invalidation (if VPID is enabled) + let secondary_controls = + vmread(vmcs::control::SECONDARY_PROCBASED_EXEC_CONTROLS).unwrap(); + if secondary_controls & (1 << 5) != 0 { + // VPID enabled + let vpid = vmread(vmcs::control::VPID).unwrap() as u16; + let rip = vmread(vmcs::guest::RIP).unwrap(); + let _ = invvpid_individual_address(vpid, rip); // Ignore errors + } + } + return true; } } @@ -153,17 +162,25 @@ fn replace_opcode(vcpu: &mut VCpu, instruction_bytes: [u8; 16], replace: &[u8]) vcpu.ept.set(guest_phys_addr + i as u64, byte).unwrap(); } - info!( - "Replacing opcode with: {:?} at {:#x}", - replace, guest_phys_addr - ); + // Invalidate TLB and EPT after modifying code + unsafe { + // Get EPTP for EPT invalidation + let eptp = vmread(vmcs::control::EPTP_FULL).unwrap(); + let _ = invept_single_context(eptp); // Ignore errors + + // Get VPID for TLB invalidation (if VPID is enabled) + let secondary_controls = vmread(vmcs::control::SECONDARY_PROCBASED_EXEC_CONTROLS).unwrap(); + if secondary_controls & (1 << 5) != 0 { + // VPID enabled + let vpid = vmread(vmcs::control::VPID).unwrap() as u16; + let _ = invvpid_individual_address(vpid, rip); // Ignore errors + } + } true } fn return_to_32_bit(vcpu: &mut VCpu) -> bool { - // 32bitモードへ戻す処理 - info!("Returning to 32-bit mode"); if !vcpu.emulate_amd { return false; } @@ -229,9 +246,9 @@ fn return_to_32_bit(vcpu: &mut VCpu) -> bool { }; vmwrite(vmcs::guest::SS_ACCESS_RIGHTS, ss_rights.0 as u64).unwrap(); - // Set 32-bit data segment selectors - vmwrite(vmcs::guest::DS_SELECTOR, 0).unwrap(); - vmwrite(vmcs::guest::ES_SELECTOR, 0).unwrap(); + // Set 32-bit data segment selectors - use same as SS for compatibility mode + vmwrite(vmcs::guest::DS_SELECTOR, user_ss_selector as u64).unwrap(); + vmwrite(vmcs::guest::ES_SELECTOR, user_ss_selector as u64).unwrap(); vmwrite(vmcs::guest::FS_SELECTOR, 0).unwrap(); // Restore GS selector and base @@ -249,16 +266,16 @@ fn return_to_32_bit(vcpu: &mut VCpu) -> bool { vmwrite(vmcs::guest::ES_BASE, 0).unwrap(); vmwrite(vmcs::guest::FS_BASE, 0).unwrap(); - // Set segment limits - vmwrite(vmcs::guest::DS_LIMIT, 0).unwrap(); - vmwrite(vmcs::guest::ES_LIMIT, 0).unwrap(); + // Set segment limits - 32-bit segments need proper limits + vmwrite(vmcs::guest::DS_LIMIT, 0xFFFFFFFF).unwrap(); + vmwrite(vmcs::guest::ES_LIMIT, 0xFFFFFFFF).unwrap(); vmwrite(vmcs::guest::FS_LIMIT, 0).unwrap(); vmwrite(vmcs::guest::GS_LIMIT, 0xFFFFFFFF).unwrap(); - // Set segment access rights for null segments (unusable) + // Set segment access rights for data segments (same as SS) + vmwrite(vmcs::guest::DS_ACCESS_RIGHTS, ss_rights.0 as u64).unwrap(); + vmwrite(vmcs::guest::ES_ACCESS_RIGHTS, ss_rights.0 as u64).unwrap(); let null_rights = 0x10000; // Unusable bit set - vmwrite(vmcs::guest::DS_ACCESS_RIGHTS, null_rights).unwrap(); - vmwrite(vmcs::guest::ES_ACCESS_RIGHTS, null_rights).unwrap(); vmwrite(vmcs::guest::FS_ACCESS_RIGHTS, null_rights).unwrap(); // Set GS access rights for 32-bit data segment @@ -278,16 +295,6 @@ fn return_to_32_bit(vcpu: &mut VCpu) -> bool { }; vmwrite(vmcs::guest::GS_ACCESS_RIGHTS, gs_rights).unwrap(); - info!("Restoring user mode segments:"); - info!(" CS: selector={:#x} -> {:#x}, base={:#x} -> {:#x}, limit={:#x} -> {:#x}, rights={:#x} -> {:#x}", - current_cs_val, user_cs_selector, current_cs_base, 0, current_cs_limit, 0xFFFFFFFFu64, current_cs_rights, cs_rights.0); - info!(" SS: selector={:#x} -> {:#x}, base={:#x} -> {:#x}, limit={:#x} -> {:#x}, rights={:#x} -> {:#x}", - current_ss_val, user_ss_selector, current_ss_base, 0, current_ss_limit, 0xFFFFFFFFu64, current_ss_rights, ss_rights.0); - info!( - " GS: selector={:#x} -> {:#x}, base={:#x} -> {:#x}", - current_gs_val, gs_selector, current_gs_base, gs_base - ); - // Ensure CR0, CR4, and EFER are properly set for compatibility mode let mut cr0 = vmread(vmcs::guest::CR0).unwrap(); cr0 |= (1 << 31) | (1 << 0); // PG and PE bits @@ -305,28 +312,12 @@ fn return_to_32_bit(vcpu: &mut VCpu) -> bool { // Only the CS.L bit determines if we're in compatibility mode // Log guest registers that might be important - info!( - "Restored to user mode: RIP={:#x}, RFLAGS={:#x}, CS={:#x}, SS={:#x}, GS={:#x}, GS_BASE={:#x}", - return_rip + 2, - return_rflags, - user_cs_selector, - user_ss_selector, - gs_selector, - gs_base - ); - info!("Guest registers: RAX={:#x}, RCX={:#x}, RDX={:#x}, RSI={:#x}, RDI={:#x}", - vcpu.guest_registers.rax, - vcpu.guest_registers.rcx, - vcpu.guest_registers.rdx, - vcpu.guest_registers.rsi, - vcpu.guest_registers.rdi); } true } fn emulate_syscall(vcpu: &mut VCpu, instruction_bytes: [u8; 16]) -> bool { - info!("Emulating SYSCALL instruction"); if !vcpu.emulate_amd { return false; } @@ -355,20 +346,6 @@ fn emulate_syscall(vcpu: &mut VCpu, instruction_bytes: [u8; 16]) -> bool { let current_gs_limit = unsafe { vmread(vmcs::guest::GS_LIMIT).unwrap() }; let current_gs_rights = unsafe { vmread(vmcs::guest::GS_ACCESS_RIGHTS).unwrap() }; - info!("Current segments before SYSCALL:"); - info!( - " CS: selector={:#x}, base={:#x}, limit={:#x}, rights={:#x}", - current_cs, current_cs_base, current_cs_limit, current_cs_rights - ); - info!( - " SS: selector={:#x}, base={:#x}, limit={:#x}, rights={:#x}", - current_ss, current_ss_base, current_ss_limit, current_ss_rights - ); - info!( - " GS: selector={:#x}, base={:#x}, limit={:#x}, rights={:#x}", - current_gs, current_gs_base, current_gs_limit, current_gs_rights - ); - vcpu.opcode_emulator.saved_cs_selector = Some(current_cs); vcpu.opcode_emulator.saved_ss_selector = Some(current_ss); vcpu.opcode_emulator.saved_gs_selector = Some(current_gs); @@ -407,17 +384,6 @@ fn emulate_syscall(vcpu: &mut VCpu, instruction_bytes: [u8; 16]) -> bool { rights }; - info!("Setting RIP:{:x} to {:x}", current_rip, lstar); - info!("Setting kernel segments for SYSCALL:"); - info!( - " CS: selector={:#x} -> {:#x}, base=0 -> 0, limit={:#x} -> {:#x}, rights={:#x} -> {:#x}", - current_cs, cs_selector, current_cs_limit, 0xFFFFFFFFu64, current_cs_rights, cs_rights.0 - ); - info!( - " SS: selector={:#x} -> {:#x}, base=0 -> 0, limit={:#x} -> {:#x}, rights={:#x} -> {:#x}", - current_ss, ss_selector, current_ss_limit, 0xFFFFFFFFu64, current_ss_rights, ss_rights.0 - ); - unsafe { // Set segment registers for kernel mode vmwrite(vmcs::guest::RIP, lstar).unwrap(); diff --git a/src/vmm/invlpg.rs b/src/vmm/invlpg.rs new file mode 100644 index 0000000..93d9b82 --- /dev/null +++ b/src/vmm/invlpg.rs @@ -0,0 +1,105 @@ +use core::arch::asm; +use x86_64::PhysAddr; + +#[repr(C, packed)] +pub struct InveptDesc { + eptp: u64, + reserved: u64, +} + +#[repr(C, packed)] +pub struct InvvpidDesc { + vpid: u16, + reserved1: u16, + reserved2: u32, + linear_address: u64, +} + +pub const INVEPT_TYPE_SINGLE_CONTEXT: u64 = 1; +pub const INVEPT_TYPE_ALL_CONTEXT: u64 = 2; + +pub const INVVPID_TYPE_INDIVIDUAL_ADDRESS: u64 = 0; +pub const INVVPID_TYPE_SINGLE_CONTEXT: u64 = 1; +pub const INVVPID_TYPE_ALL_CONTEXT: u64 = 2; +pub const INVVPID_TYPE_SINGLE_CONTEXT_RETAINING_GLOBALS: u64 = 3; + +/// Invalidate EPT entries +pub unsafe fn invept(invept_type: u64, eptp: u64) -> Result<(), &'static str> { + let desc = InveptDesc { + eptp, + reserved: 0, + }; + + let mut error: u64; + asm!( + "xor {0}, {0}", + "invept {1}, [{2}]", + "jnc 2f", + "mov {0}, 1", + "2:", + inout(reg) 0u64 => error, + in(reg) invept_type, + in(reg) &desc as *const _ as u64, + options(nostack) + ); + + if error == 0 { + Ok(()) + } else { + Err("INVEPT failed") + } +} + +/// Invalidate VPID entries +pub unsafe fn invvpid(invvpid_type: u64, vpid: u16, linear_address: u64) -> Result<(), &'static str> { + let desc = InvvpidDesc { + vpid, + reserved1: 0, + reserved2: 0, + linear_address, + }; + + let mut error: u64; + asm!( + "xor {0}, {0}", + "invvpid {1}, [{2}]", + "jnc 2f", + "mov {0}, 1", + "2:", + inout(reg) 0u64 => error, + in(reg) invvpid_type, + in(reg) &desc as *const _ as u64, + options(nostack) + ); + + if error == 0 { + Ok(()) + } else { + Err("INVVPID failed") + } +} + +/// Invalidate all EPT entries for a specific EPTP +pub unsafe fn invept_single_context(eptp: u64) -> Result<(), &'static str> { + invept(INVEPT_TYPE_SINGLE_CONTEXT, eptp) +} + +/// Invalidate all EPT entries for all contexts +pub unsafe fn invept_all_contexts() -> Result<(), &'static str> { + invept(INVEPT_TYPE_ALL_CONTEXT, 0) +} + +/// Invalidate all TLB entries for a specific VPID +pub unsafe fn invvpid_single_context(vpid: u16) -> Result<(), &'static str> { + invvpid(INVVPID_TYPE_SINGLE_CONTEXT, vpid, 0) +} + +/// Invalidate all TLB entries for all VPIDs +pub unsafe fn invvpid_all_contexts() -> Result<(), &'static str> { + invvpid(INVVPID_TYPE_ALL_CONTEXT, 0, 0) +} + +/// Invalidate a specific linear address for a specific VPID +pub unsafe fn invvpid_individual_address(vpid: u16, linear_address: u64) -> Result<(), &'static str> { + invvpid(INVVPID_TYPE_INDIVIDUAL_ADDRESS, vpid, linear_address) +} \ No newline at end of file diff --git a/src/vmm/mod.rs b/src/vmm/mod.rs index f999bf6..9da4f8c 100644 --- a/src/vmm/mod.rs +++ b/src/vmm/mod.rs @@ -5,6 +5,7 @@ pub mod emulation; pub mod ept; pub mod error; pub mod fpu; +pub mod invlpg; pub mod io; pub mod linux; pub mod msr; diff --git a/src/vmm/vcpu.rs b/src/vmm/vcpu.rs index d11bc60..99eb8f8 100644 --- a/src/vmm/vcpu.rs +++ b/src/vmm/vcpu.rs @@ -1017,6 +1017,12 @@ impl VCpu { if let Err(e) = self.ept.map_4k(gpa, hpa, frame_allocator) { panic!("Failed to map page at GPA {:#x}: {}", gpa, e); } + + // Invalidate EPT after mapping new page + // Note: INVEPT might not be available on all processors + use crate::vmm::invlpg::invept_single_context; + let eptp = vmread(vmcs::control::EPTP_FULL).unwrap(); + let _ = invept_single_context(eptp); // Ignore errors as INVEPT might not be supported } None => { panic!( @@ -1054,6 +1060,48 @@ impl VCpu { match reason { 33 => { info!(" Reason: VM-entry failure due to invalid guest state"); + // Read VM-instruction error for more details + let vm_instruction_error = unsafe { + use x86::msr::rdmsr; + vmread(vmcs::ro::VM_INSTRUCTION_ERROR).unwrap_or(0) + }; + info!(" VM-instruction error: {}", vm_instruction_error); + + // Dump guest state for debugging + unsafe { + info!(" Guest state dump:"); + info!(" CS: selector={:#x}, base={:#x}, limit={:#x}, rights={:#x}", + vmread(vmcs::guest::CS_SELECTOR).unwrap(), + vmread(vmcs::guest::CS_BASE).unwrap(), + vmread(vmcs::guest::CS_LIMIT).unwrap(), + vmread(vmcs::guest::CS_ACCESS_RIGHTS).unwrap()); + info!(" SS: selector={:#x}, base={:#x}, limit={:#x}, rights={:#x}", + vmread(vmcs::guest::SS_SELECTOR).unwrap(), + vmread(vmcs::guest::SS_BASE).unwrap(), + vmread(vmcs::guest::SS_LIMIT).unwrap(), + vmread(vmcs::guest::SS_ACCESS_RIGHTS).unwrap()); + info!(" DS: selector={:#x}, base={:#x}, limit={:#x}, rights={:#x}", + vmread(vmcs::guest::DS_SELECTOR).unwrap(), + vmread(vmcs::guest::DS_BASE).unwrap(), + vmread(vmcs::guest::DS_LIMIT).unwrap(), + vmread(vmcs::guest::DS_ACCESS_RIGHTS).unwrap()); + info!(" ES: selector={:#x}, base={:#x}, limit={:#x}, rights={:#x}", + vmread(vmcs::guest::ES_SELECTOR).unwrap(), + vmread(vmcs::guest::ES_BASE).unwrap(), + vmread(vmcs::guest::ES_LIMIT).unwrap(), + vmread(vmcs::guest::ES_ACCESS_RIGHTS).unwrap()); + info!(" RIP={:#x}, RSP={:#x}, RFLAGS={:#x}", + vmread(vmcs::guest::RIP).unwrap(), + vmread(vmcs::guest::RSP).unwrap(), + vmread(vmcs::guest::RFLAGS).unwrap()); + info!(" CR0={:#x}, CR3={:#x}, CR4={:#x}", + vmread(vmcs::guest::CR0).unwrap(), + vmread(vmcs::guest::CR3).unwrap(), + vmread(vmcs::guest::CR4).unwrap()); + info!(" EFER={:#x}", + vmread(vmcs::guest::IA32_EFER_FULL).unwrap()); + } + panic!("VM-entry failure due to invalid guest state"); } 34 => { info!(" Reason: VM-entry failure due to MSR loading");