AMD emulation

This commit is contained in:
Masato Imai
2025-07-13 17:17:41 +00:00
parent 3913dbfe23
commit f47315cda8
5 changed files with 203 additions and 79 deletions

View File

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

View File

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

105
src/vmm/invlpg.rs Normal file
View File

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

View File

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

View File

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