From eb5b441a3b02f5118e4384c349fe624592caf572 Mon Sep 17 00:00:00 2001 From: Roy Lin Date: Tue, 21 Jul 2026 05:25:19 +0800 Subject: [PATCH] fix(windows): integrate WHPX runtime on current main Port the validated WHPX runtime fixes without importing the unrelated snapshot branch history. Also repair pre-existing main test/API mismatches exposed by the Windows test suite. --- src/cpuid/src/common.rs | 4 +- src/devices/build.rs | 3 +- src/devices/src/legacy/mod.rs | 4 +- src/devices/src/legacy/windows_apic_stub.rs | 12 +- .../src/legacy/windows_pci_cfg_stub.rs | 2 +- src/devices/src/legacy/windows_pic_stub.rs | 32 ++-- src/devices/src/lib.rs | 38 +++- src/devices/src/virtio/file_traits_windows.rs | 6 +- src/devices/src/virtio/fs/device.rs | 17 +- src/devices/src/virtio/fs/server.rs | 7 +- .../src/virtio/fs/windows/passthrough.rs | 40 +++-- src/devices/src/virtio/fs/worker.rs | 11 +- src/devices/src/virtio/linux_errno.rs | 15 +- src/devices/src/virtio/mmio.rs | 13 +- src/devices/src/virtio/vsock/mod.rs | 5 +- src/devices/src/virtio/vsock_windows.rs | 45 ++++- src/devices/src/virtio/vsock_windows/tests.rs | 49 +++++ src/kernel/src/cmdline/mod.rs | 5 +- src/libkrun/src/lib.rs | 33 +++- src/vmm/src/builder.rs | 136 ++++++++------ src/vmm/src/device_manager/shm.rs | 2 +- src/vmm/src/device_manager/whpx/mmio.rs | 3 +- src/vmm/src/lib.rs | 27 ++- src/vmm/src/vmm_config/external_kernel.rs | 2 +- src/vmm/src/vmm_config/fs.rs | 11 +- src/vmm/src/vmm_config/instance_info.rs | 2 +- src/vmm/src/vmm_config/kernel_bundle.rs | 4 +- src/vmm/src/vmm_config/kernel_cmdline.rs | 2 +- src/vmm/src/windows/mod.rs | 1 + src/vmm/src/windows/registers.rs | 128 ++++++++++++++ src/vmm/src/windows/vstate.rs | 73 ++++---- src/vmm/src/windows/whpx_vcpu.rs | 167 +++++++++--------- 32 files changed, 654 insertions(+), 245 deletions(-) create mode 100644 src/devices/src/virtio/vsock_windows/tests.rs create mode 100644 src/vmm/src/windows/registers.rs diff --git a/src/cpuid/src/common.rs b/src/cpuid/src/common.rs index b441151936..7d67505b8b 100644 --- a/src/cpuid/src/common.rs +++ b/src/cpuid/src/common.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 #[cfg(target_arch = "x86")] -use std::arch::x86::{CpuidResult, __cpuid_count, __get_cpuid_max}; +use std::arch::x86::{__cpuid_count, __get_cpuid_max, CpuidResult}; #[cfg(target_arch = "x86_64")] -use std::arch::x86_64::{CpuidResult, __cpuid_count, __get_cpuid_max}; +use std::arch::x86_64::{__cpuid_count, __get_cpuid_max, CpuidResult}; use crate::cpu_leaf::*; diff --git a/src/devices/build.rs b/src/devices/build.rs index 2cd4308bf0..d919894c38 100644 --- a/src/devices/build.rs +++ b/src/devices/build.rs @@ -12,7 +12,8 @@ fn main() { return; } - let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("missing CARGO_MANIFEST_DIR")); + let manifest_dir = + PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("missing CARGO_MANIFEST_DIR")); let out_dir = PathBuf::from(env::var("OUT_DIR").expect("missing OUT_DIR")); let rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string()); let wrapper_src = manifest_dir.join("build").join("windows_init_wrapper.rs"); diff --git a/src/devices/src/legacy/mod.rs b/src/devices/src/legacy/mod.rs index 7cb1812def..b7f5b2c469 100644 --- a/src/devices/src/legacy/mod.rs +++ b/src/devices/src/legacy/mod.rs @@ -30,9 +30,9 @@ mod vcpu; #[cfg(all(target_os = "windows", target_arch = "x86_64"))] pub mod windows_apic_stub; #[cfg(all(target_os = "windows", target_arch = "x86_64"))] -pub mod windows_pic_stub; -#[cfg(all(target_os = "windows", target_arch = "x86_64"))] pub mod windows_pci_cfg_stub; +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +pub mod windows_pic_stub; #[cfg(target_arch = "x86_64")] mod x86_64; #[cfg(target_arch = "x86_64")] diff --git a/src/devices/src/legacy/windows_apic_stub.rs b/src/devices/src/legacy/windows_apic_stub.rs index a9a11ef0b0..77d831f1cc 100644 --- a/src/devices/src/legacy/windows_apic_stub.rs +++ b/src/devices/src/legacy/windows_apic_stub.rs @@ -7,14 +7,22 @@ fn windows_apic_debug_log(message: impl AsRef) { if !*VALUE.get_or_init(|| { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") .or_else(|_| std::env::var("LIBKRUN_WINDOWS_IO_DEBUG")) - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) }) { return; } use std::io::Write; - for path in [r"C:\Users\18770\.a3s\libkrun-whpx-io-current.log", "tmp_whpx_io.log"] { + for path in [ + r"C:\Users\18770\.a3s\libkrun-whpx-io-current.log", + "tmp_whpx_io.log", + ] { if let Ok(mut file) = std::fs::OpenOptions::new() .create(true) .append(true) diff --git a/src/devices/src/legacy/windows_pci_cfg_stub.rs b/src/devices/src/legacy/windows_pci_cfg_stub.rs index abd5107505..c712e276d2 100644 --- a/src/devices/src/legacy/windows_pci_cfg_stub.rs +++ b/src/devices/src/legacy/windows_pci_cfg_stub.rs @@ -182,7 +182,7 @@ impl BusDevice for PciConfigIoStub { #[cfg(test)] mod tests { use super::{ - CONFIG_ENABLE_BIT, PCI_CLASS_HOST_BRIDGE, PCI_SUBCLASS_HOST_BRIDGE, PciConfigIoStub, + PciConfigIoStub, CONFIG_ENABLE_BIT, PCI_CLASS_HOST_BRIDGE, PCI_SUBCLASS_HOST_BRIDGE, }; use crate::BusDevice; diff --git a/src/devices/src/legacy/windows_pic_stub.rs b/src/devices/src/legacy/windows_pic_stub.rs index 9cd05be4d3..18041d654a 100644 --- a/src/devices/src/legacy/windows_pic_stub.rs +++ b/src/devices/src/legacy/windows_pic_stub.rs @@ -7,14 +7,22 @@ fn windows_pic_debug_log(message: impl AsRef) { if !*VALUE.get_or_init(|| { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") .or_else(|_| std::env::var("LIBKRUN_WINDOWS_IO_DEBUG")) - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) }) { return; } use std::io::Write; - for path in [r"C:\Users\18770\.a3s\libkrun-whpx-io-current.log", "tmp_whpx_io.log"] { + for path in [ + r"C:\Users\18770\.a3s\libkrun-whpx-io-current.log", + "tmp_whpx_io.log", + ] { if let Ok(mut file) = std::fs::OpenOptions::new() .create(true) .append(true) @@ -117,7 +125,12 @@ impl PicState { // edge becomes deliverable again. self.clear_pending_irq_without_in_service(irq); } - log::debug!("PIC {}: specific EOI command 0x{:02x} irq={}", name, value, irq); + log::debug!( + "PIC {}: specific EOI command 0x{:02x} irq={}", + name, + value, + irq + ); windows_pic_debug_log(format!( "[PIC] {} eoi cmd=0x{:02x} specific=true irq={} irr=0x{:02x} in_service=0x{:02x}", name, value, irq, self.irr, self.in_service @@ -139,8 +152,7 @@ impl PicState { ); windows_pic_debug_log(format!( "[PIC] {} icw2 vector_base=0x{:02x}", - name, - self.vector_offset + name, self.vector_offset )); } InitState::ExpectIcw3 => { @@ -160,9 +172,7 @@ impl PicState { ); windows_pic_debug_log(format!( "[PIC] {} icw4=0x{:02x} initialized auto_eoi={}", - name, - value, - self.auto_eoi + name, value, self.auto_eoi )); } InitState::Ready => { @@ -173,11 +183,7 @@ impl PicState { self.mask, (self.mask & 0x01) != 0 ); - windows_pic_debug_log(format!( - "[PIC] {} ocw1 mask=0x{:02x}", - name, - self.mask - )); + windows_pic_debug_log(format!("[PIC] {} ocw1 mask=0x{:02x}", name, self.mask)); } } } diff --git a/src/devices/src/lib.rs b/src/devices/src/lib.rs index 8b65ed4db2..b0d75ae4f1 100644 --- a/src/devices/src/lib.rs +++ b/src/devices/src/lib.rs @@ -35,6 +35,39 @@ pub enum Error { SpuriousEvent, } +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::FailedReadingQueue { + event_type, + underlying, + } => write!(f, "FailedReadingQueue({event_type}): {underlying}"), + Self::FailedReadTap => f.write_str("FailedReadTap"), + Self::FailedSignalingUsedQueue(error) => { + write!(f, "FailedSignalingUsedQueue: {error}") + } + Self::PayloadExpected => f.write_str("PayloadExpected"), + Self::IoError(error) => write!(f, "IoError: {error}"), + Self::NoAvailBuffers => f.write_str("NoAvailBuffers"), + Self::SpuriousEvent => f.write_str("SpuriousEvent"), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::FailedReadingQueue { underlying, .. } + | Self::FailedSignalingUsedQueue(underlying) + | Self::IoError(underlying) => Some(underlying), + Self::FailedReadTap + | Self::PayloadExpected + | Self::NoAvailBuffers + | Self::SpuriousEvent => None, + } + } +} + /// Types of devices that can get attached to this platform. #[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)] pub enum DeviceType { @@ -112,7 +145,10 @@ mod tests { event_type: "RX", underlying: io_error, }; - assert_eq!(format!("{:?}", error), "FailedReadingQueue { event_type: \"RX\", underlying: Custom { kind: Other, error: \"\" } }"); + let debug = format!("{error:?}"); + assert!(debug.contains("FailedReadingQueue")); + assert!(debug.contains("event_type: \"RX\"")); + assert!(debug.contains("test")); } #[test] diff --git a/src/devices/src/virtio/file_traits_windows.rs b/src/devices/src/virtio/file_traits_windows.rs index 61a194381f..adf9b4a45e 100644 --- a/src/devices/src/virtio/file_traits_windows.rs +++ b/src/devices/src/virtio/file_traits_windows.rs @@ -74,11 +74,7 @@ pub trait FileReadWriteAtVolatile { Ok(total) } - fn write_vectored_at_volatile( - &self, - bufs: &[VolatileSlice], - mut offset: u64, - ) -> Result { + fn write_vectored_at_volatile(&self, bufs: &[VolatileSlice], mut offset: u64) -> Result { let mut total = 0usize; for &slice in bufs { if slice.is_empty() { diff --git a/src/devices/src/virtio/fs/device.rs b/src/devices/src/virtio/fs/device.rs index a83610bd5f..d77bc89edc 100644 --- a/src/devices/src/virtio/fs/device.rs +++ b/src/devices/src/virtio/fs/device.rs @@ -28,7 +28,12 @@ fn fs_device_debug_log(message: impl AsRef) { static VALUE: std::sync::OnceLock = std::sync::OnceLock::new(); if !*VALUE.get_or_init(|| { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) }) { return; @@ -87,7 +92,10 @@ impl Fs { queues: Vec, ) -> super::Result { #[cfg(target_os = "windows")] - fs_device_debug_log(format!("Fs::with_queues fs_id={} shared_dir={}", fs_id, shared_dir)); + fs_device_debug_log(format!( + "Fs::with_queues fs_id={} shared_dir={}", + fs_id, shared_dir + )); let mut queue_events = Vec::new(); for _ in 0..queues.len() { queue_events @@ -216,7 +224,10 @@ impl VirtioDevice for Fs { fn activate(&mut self, mem: GuestMemoryMmap, interrupt: InterruptTransport) -> ActivateResult { #[cfg(target_os = "windows")] - fs_device_debug_log(format!("Fs::activate tag={}", String::from_utf8_lossy(&self.config.tag))); + fs_device_debug_log(format!( + "Fs::activate tag={}", + String::from_utf8_lossy(&self.config.tag) + )); if self.worker_thread.is_some() { panic!("virtio_fs: worker thread already exists"); } diff --git a/src/devices/src/virtio/fs/server.rs b/src/devices/src/virtio/fs/server.rs index 44dce573a3..d1320f3375 100644 --- a/src/devices/src/virtio/fs/server.rs +++ b/src/devices/src/virtio/fs/server.rs @@ -38,7 +38,12 @@ fn fs_server_debug_log(message: impl AsRef) { static VALUE: std::sync::OnceLock = std::sync::OnceLock::new(); if !*VALUE.get_or_init(|| { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) }) { return; diff --git a/src/devices/src/virtio/fs/windows/passthrough.rs b/src/devices/src/virtio/fs/windows/passthrough.rs index 23d3050556..47f8b76831 100644 --- a/src/devices/src/virtio/fs/windows/passthrough.rs +++ b/src/devices/src/virtio/fs/windows/passthrough.rs @@ -32,7 +32,12 @@ fn virtiofs_debug_enabled() -> bool { static VALUE: std::sync::OnceLock = std::sync::OnceLock::new(); *VALUE.get_or_init(|| { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) }) } @@ -159,7 +164,10 @@ pub struct PassthroughFs { impl PassthroughFs { pub fn new(cfg: Config) -> io::Result { let root_dir = PathBuf::from(&cfg.root_dir); - virtiofs_debug_log(format!("PassthroughFs::new root_dir={}", root_dir.display())); + virtiofs_debug_log(format!( + "PassthroughFs::new root_dir={}", + root_dir.display() + )); // Verify root directory exists if !root_dir.exists() { @@ -360,7 +368,10 @@ impl PassthroughFs { if usr_lib64_loader.is_file() { usr_lib64 } else { - self.root_dir.join("usr").join("lib").join("x86_64-linux-gnu") + self.root_dir + .join("usr") + .join("lib") + .join("x86_64-linux-gnu") } } _ => return None, @@ -425,11 +436,7 @@ impl PassthroughFs { if parent_path == self.root_dir { if let Some(name) = name_path.to_str() { if let Some(target) = self.root_alias_target(name) { - virtiofs_debug_log(format!( - "lookup alias /{} -> {}", - name, - target.display() - )); + virtiofs_debug_log(format!("lookup alias /{} -> {}", name, target.display())); return target; } } @@ -1454,7 +1461,10 @@ impl FileSystem for PassthroughFs { // harmless close() into EIO for read-only executable loads. if let Some(handle_data) = self.handles.read().unwrap().get(&handle) { if should_trace_path(&handle_data.path) { - virtiofs_debug_log(format!("flush-ok {} handle={handle}", handle_data.path.display())); + virtiofs_debug_log(format!( + "flush-ok {} handle={handle}", + handle_data.path.display() + )); } } Ok(()) @@ -1818,7 +1828,11 @@ mod tests { ) .expect("lookup x86_64-linux-gnu"); let soname_entry = fs - .lookup(ctx(), x86_entry.inode, &CString::new("libcrypt.so.1").unwrap()) + .lookup( + ctx(), + x86_entry.inode, + &CString::new("libcrypt.so.1").unwrap(), + ) .expect("lookup libcrypt.so.1 alias"); assert_eq!(soname_entry.attr.st_size, 8); @@ -1842,7 +1856,11 @@ mod tests { .lookup(ctx(), usr_entry.inode, &CString::new("lib").unwrap()) .expect("lookup /usr/lib"); let soname_entry = fs - .lookup(ctx(), lib_entry.inode, &CString::new("libcrypt.so.1").unwrap()) + .lookup( + ctx(), + lib_entry.inode, + &CString::new("libcrypt.so.1").unwrap(), + ) .expect("lookup /usr/lib/libcrypt.so.1 alias"); assert_eq!(soname_entry.attr.st_size, 8); diff --git a/src/devices/src/virtio/fs/worker.rs b/src/devices/src/virtio/fs/worker.rs index 9b99713602..0f90c4cc7b 100644 --- a/src/devices/src/virtio/fs/worker.rs +++ b/src/devices/src/virtio/fs/worker.rs @@ -5,11 +5,11 @@ use utils::worker_message::WorkerMessage; #[cfg(not(target_os = "windows"))] use std::os::fd::AsRawFd; -#[cfg(target_os = "windows")] -use std::{fs::OpenOptions, io::Write}; use std::sync::atomic::AtomicI32; use std::sync::Arc; use std::thread; +#[cfg(target_os = "windows")] +use std::{fs::OpenOptions, io::Write}; use utils::epoll::{ControlOperation, Epoll, EpollEvent, EventSet}; use utils::eventfd::EventFd; @@ -27,7 +27,12 @@ fn fs_worker_debug_log(message: impl AsRef) { static VALUE: std::sync::OnceLock = std::sync::OnceLock::new(); if !*VALUE.get_or_init(|| { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) }) { return; diff --git a/src/devices/src/virtio/linux_errno.rs b/src/devices/src/virtio/linux_errno.rs index 4a71726584..69d1b9f9da 100644 --- a/src/devices/src/virtio/linux_errno.rs +++ b/src/devices/src/virtio/linux_errno.rs @@ -295,8 +295,14 @@ mod tests { assert_eq!(linux_errno_raw(libc::EMSGSIZE), LINUX_EMSGSIZE); assert_eq!(linux_errno_raw(libc::EPROTOTYPE), LINUX_EPROTOTYPE); assert_eq!(linux_errno_raw(libc::ENOPROTOOPT), LINUX_ENOPROTOOPT); - assert_eq!(linux_errno_raw(libc::EPROTONOSUPPORT), LINUX_EPROTONOSUPPORT); - assert_eq!(linux_errno_raw(libc::ESOCKTNOSUPPORT), LINUX_ESOCKTNOSUPPORT); + assert_eq!( + linux_errno_raw(libc::EPROTONOSUPPORT), + LINUX_EPROTONOSUPPORT + ); + assert_eq!( + linux_errno_raw(libc::ESOCKTNOSUPPORT), + LINUX_ESOCKTNOSUPPORT + ); assert_eq!(linux_errno_raw(libc::EPFNOSUPPORT), LINUX_EPFNOSUPPORT); assert_eq!(linux_errno_raw(libc::EAFNOSUPPORT), LINUX_EAFNOSUPPORT); assert_eq!(linux_errno_raw(libc::EADDRINUSE), LINUX_EADDRINUSE); @@ -338,7 +344,10 @@ mod tests { assert_eq!(linux_errno_raw(libc::EPROTO), LINUX_EPROTO); assert_eq!(linux_errno_raw(libc::ETIME), LINUX_ETIME); assert_eq!(linux_errno_raw(libc::EOPNOTSUPP), LINUX_EOPNOTSUPP); - assert_eq!(linux_errno_raw(libc::ENOTRECOVERABLE), LINUX_ENOTRECOVERABLE); + assert_eq!( + linux_errno_raw(libc::ENOTRECOVERABLE), + LINUX_ENOTRECOVERABLE + ); assert_eq!(linux_errno_raw(libc::EOWNERDEAD), LINUX_EOWNERDEAD); } diff --git a/src/devices/src/virtio/mmio.rs b/src/devices/src/virtio/mmio.rs index 3f9fe30779..8ddf624052 100644 --- a/src/devices/src/virtio/mmio.rs +++ b/src/devices/src/virtio/mmio.rs @@ -34,7 +34,12 @@ fn mmio_debug_enabled() -> bool { static VALUE: std::sync::OnceLock = std::sync::OnceLock::new(); *VALUE.get_or_init(|| { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) }) } @@ -58,7 +63,11 @@ fn mmio_debug_log(message: impl AsRef) { r"C:\Users\18770\.a3s\libkrun-virtio-mmio.log", r"D:\code\libkrun\tmp_virtio_mmio.log", ] { - if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) { + if let Ok(mut file) = std::fs::OpenOptions::new() + .create(true) + .append(true) + .open(path) + { let _ = writeln!(file, "{message}"); } } diff --git a/src/devices/src/virtio/vsock/mod.rs b/src/devices/src/virtio/vsock/mod.rs index 4c3dd7dd82..52abefaacf 100644 --- a/src/devices/src/virtio/vsock/mod.rs +++ b/src/devices/src/virtio/vsock/mod.rs @@ -229,7 +229,10 @@ mod tests { assert_eq!(format!("{:?}", err), "HdrDescTooSmall(42)"); let err = VsockError::EventFd(std::io::Error::from_raw_os_error(0)); - assert_eq!(format!("{:?}", err), "EventFd(Custom { kind: Other, error: \"\" })"); + assert_eq!( + format!("{:?}", err), + "EventFd(Custom { kind: Other, error: \"\" })" + ); } #[test] diff --git a/src/devices/src/virtio/vsock_windows.rs b/src/devices/src/virtio/vsock_windows.rs index 5d3b578888..33e157f56d 100644 --- a/src/devices/src/virtio/vsock_windows.rs +++ b/src/devices/src/virtio/vsock_windows.rs @@ -22,6 +22,7 @@ use windows::Win32::Storage::FileSystem::{ }; use windows::Win32::System::Pipes::{PeekNamedPipe, WaitNamedPipeA}; +use super::linux_errno::linux_errno_raw; use super::{ ActivateError, ActivateResult, DescriptorChain, DeviceState, InterruptTransport, Queue, VirtioDevice, @@ -52,6 +53,8 @@ const VSOCK_FLAGS_SHUTDOWN_RCV: u32 = 1; const VSOCK_FLAGS_SHUTDOWN_SEND: u32 = 2; const VSOCK_TYPE_STREAM: u16 = 1; const VSOCK_TYPE_DGRAM: u16 = 3; +const TSI_LISTEN_PORT: u32 = 1029; +const TSI_LISTEN_REQUEST_LEN: usize = 4 * 4 + 128; const DEFAULT_BUF_ALLOC: u32 = 256 * 1024; // Keep stream credit advertisement aligned with the existing muxer/TSI paths. @@ -116,6 +119,7 @@ pub struct Vsock { pending_by_guest_port: HashMap, connect_timeout_ms: u64, // Configurable connection timeout defer_control_rx: bool, + tsi_flags: TsiFlags, } // Trait to abstract TCP streams and Named Pipes @@ -347,7 +351,7 @@ impl Vsock { cid: u64, host_port_map: Option>, unix_ipc_port_map: Option>, - _tsi_flags: TsiFlags, + tsi_flags: TsiFlags, ) -> Result { let queues = vec![Queue::new(QUEUE_SIZE); NUM_QUEUES]; let mut queue_events = Vec::with_capacity(NUM_QUEUES); @@ -402,6 +406,7 @@ impl Vsock { pending_by_guest_port: HashMap::new(), connect_timeout_ms: CONNECT_TIMEOUT_MS, // Use default timeout defer_control_rx: false, + tsi_flags, }) } @@ -780,6 +785,34 @@ impl Vsock { self.queue_response(incoming_hdr, VSOCK_OP_CREDIT_UPDATE, Vec::new()); } + fn reject_unsupported_tsi_listen(&mut self, incoming_hdr: &[u8; 44]) -> bool { + if !self.tsi_flags.tsi_enabled() + || Self::hdr_u64(incoming_hdr, 8) != VSOCK_HOST_CID + || Self::hdr_u32(incoming_hdr, 20) != TSI_LISTEN_PORT + || Self::hdr_u16(incoming_hdr, 28) != VSOCK_TYPE_DGRAM + || Self::hdr_u16(incoming_hdr, 30) != VSOCK_OP_RW + { + return false; + } + + let request_len = Self::hdr_u32(incoming_hdr, 24) as usize; + if request_len != TSI_LISTEN_REQUEST_LEN { + warn!( + "vsock(windows): rejecting TSI_LISTEN request with unexpected length \ + (actual={request_len}, expected={TSI_LISTEN_REQUEST_LEN})" + ); + } + + // Published ports on Windows are implemented by guest-init's named-pipe + // bridge, not by a host-side TSI listener. The guest kernel only falls + // back to its already-bound native INET socket for selected Linux errors. + // Returning -EPERM lets that socket enter listen() so the bridge can + // connect to it over guest loopback. + let result = -linux_errno_raw(libc::EPERM); + self.queue_response(incoming_hdr, VSOCK_OP_RW, result.to_le_bytes().to_vec()); + true + } + fn read_tx_payload( mem: &GuestMemoryMmap, hdr_desc: &DescriptorChain<'_>, @@ -1042,8 +1075,7 @@ impl Vsock { vsock_control_trace_log(|| { format!( "defer_control_pipe_read src_port={} wait_ms={}", - port, - wait_ms + port, wait_ms ) }); // Timer jitter can wake us before the control stream's @@ -1463,6 +1495,10 @@ impl Vsock { // Handle DGRAM type if pkt_type == VSOCK_TYPE_DGRAM { + if self.reject_unsupported_tsi_listen(&hdr) { + continue; + } + // For DGRAM, create socket on first use if !self.dgram_sockets.contains_key(&src_port) { // Create UDP socket bound to any port @@ -2176,3 +2212,6 @@ impl Subscriber for Vsock { )] } } + +#[cfg(test)] +mod tests; diff --git a/src/devices/src/virtio/vsock_windows/tests.rs b/src/devices/src/virtio/vsock_windows/tests.rs new file mode 100644 index 0000000000..408393f8ce --- /dev/null +++ b/src/devices/src/virtio/vsock_windows/tests.rs @@ -0,0 +1,49 @@ +use super::*; + +fn tsi_listen_header(dst_port: u32) -> [u8; 44] { + let mut hdr = [0_u8; 44]; + Vsock::set_u64(&mut hdr, 0, 3); + Vsock::set_u64(&mut hdr, 8, VSOCK_HOST_CID); + Vsock::set_u32(&mut hdr, 16, 49_152); + Vsock::set_u32(&mut hdr, 20, dst_port); + Vsock::set_u32(&mut hdr, 24, TSI_LISTEN_REQUEST_LEN as u32); + Vsock::set_u16(&mut hdr, 28, VSOCK_TYPE_DGRAM); + Vsock::set_u16(&mut hdr, 30, VSOCK_OP_RW); + hdr +} + +#[test] +fn rejects_unsupported_tsi_listener_with_linux_eperm() { + let mut vsock = Vsock::new(3, None, None, TsiFlags::HIJACK_INET).expect("create vsock"); + let request = tsi_listen_header(TSI_LISTEN_PORT); + + assert!(vsock.reject_unsupported_tsi_listen(&request)); + + let response = vsock.pending_rx.pop_front().expect("queued response"); + assert_eq!(Vsock::hdr_u64(&response.hdr, 0), VSOCK_HOST_CID); + assert_eq!(Vsock::hdr_u64(&response.hdr, 8), 3); + assert_eq!(Vsock::hdr_u32(&response.hdr, 16), TSI_LISTEN_PORT); + assert_eq!(Vsock::hdr_u32(&response.hdr, 20), 49_152); + assert_eq!(Vsock::hdr_u32(&response.hdr, 24), 4); + assert_eq!(Vsock::hdr_u16(&response.hdr, 28), VSOCK_TYPE_DGRAM); + assert_eq!(Vsock::hdr_u16(&response.hdr, 30), VSOCK_OP_RW); + assert_eq!( + i32::from_le_bytes(response.payload.try_into().expect("four-byte response")), + -linux_errno_raw(libc::EPERM) + ); +} + +#[test] +fn leaves_non_tsi_datagrams_unchanged() { + let request = tsi_listen_header(TSI_LISTEN_PORT + 1); + let mut vsock = Vsock::new(3, None, None, TsiFlags::HIJACK_INET).expect("create vsock"); + + assert!(!vsock.reject_unsupported_tsi_listen(&request)); + assert!(vsock.pending_rx.is_empty()); + + let request = tsi_listen_header(TSI_LISTEN_PORT); + let mut vsock = Vsock::new(3, None, None, TsiFlags::empty()).expect("create vsock"); + + assert!(!vsock.reject_unsupported_tsi_listen(&request)); + assert!(vsock.pending_rx.is_empty()); +} diff --git a/src/kernel/src/cmdline/mod.rs b/src/kernel/src/cmdline/mod.rs index b0c68d7b72..ba37577bb0 100644 --- a/src/kernel/src/cmdline/mod.rs +++ b/src/kernel/src/cmdline/mod.rs @@ -159,7 +159,10 @@ impl Cmdline { /// (e.g., from macOS system info) that would otherwise cause InvalidAscii errors. pub fn insert_str_safe>(&mut self, slug: T) -> Result<()> { let s = slug.as_ref(); - let sanitized: String = s.chars().map(|c| if valid_char(c) { c } else { '?' }).collect(); + let sanitized: String = s + .chars() + .map(|c| if valid_char(c) { c } else { '?' }) + .collect(); let trimmed = sanitized.trim(); if trimmed.is_empty() { return Ok(()); diff --git a/src/libkrun/src/lib.rs b/src/libkrun/src/lib.rs index ea05162474..ee50c90328 100644 --- a/src/libkrun/src/lib.rs +++ b/src/libkrun/src/lib.rs @@ -238,7 +238,10 @@ impl ContextConfig { #[cfg(feature = "blk")] match &self.block_root { Some(block_root) => { - let mut res = format!("KRUN_BLOCK_ROOT_DEVICE={}", sanitize_to_ascii(&block_root.device)); + let mut res = format!( + "KRUN_BLOCK_ROOT_DEVICE={}", + sanitize_to_ascii(&block_root.device) + ); if let Some(fstype) = &block_root.fstype { res += &format!(" KRUN_BLOCK_ROOT_FSTYPE={}", sanitize_to_ascii(fstype)); } @@ -482,7 +485,12 @@ fn windows_verbose_debug_enabled() -> bool { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") .or_else(|_| std::env::var("LIBKRUN_WINDOWS_IO_DEBUG")) .or_else(|_| std::env::var("LIBKRUN_WINDOWS_VCPU_DEBUG")) - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) } @@ -576,7 +584,9 @@ pub unsafe extern "C" fn krun_init_log(target: RawFd, level: u32, style: u32, op ) } else { let mut builder = env_logger::Builder::new(); - builder.parse_filters(&filter).parse_write_style(write_style); + builder + .parse_filters(&filter) + .parse_write_style(write_style); builder }; builder.target(target).init(); @@ -2998,6 +3008,23 @@ pub extern "C" fn krun_start_enter(ctx_id: u32) -> i32 { return -libc::EINVAL; } } + + #[cfg(target_os = "windows")] + { + let host_return_exit_code = { + let mut vmm = _vmm.lock().unwrap(); + vmm.take_host_return_exit_code() + }; + if let Some(exit_code) = host_return_exit_code { + // The opt-in caller exits immediately after draining its + // host-side logs. Avoid running incomplete VMM teardown in this + // narrow return path; the process releases all resources + // moments later. + std::mem::forget(_vmm); + std::mem::forget(event_manager); + return exit_code; + } + } } } diff --git a/src/vmm/src/builder.rs b/src/vmm/src/builder.rs index 6235ebd1fb..7f0933c44e 100644 --- a/src/vmm/src/builder.rs +++ b/src/vmm/src/builder.rs @@ -82,6 +82,11 @@ use crate::vstate::MeasuredRegion; use crate::vstate::{Error as VstateError, Vcpu, VcpuConfig, Vm}; #[cfg(all(target_arch = "x86_64", target_os = "windows"))] use crate::windows::interrupts::{PendingInterrupt, PendingInterruptQueue}; +#[cfg(all(target_arch = "x86_64", target_os = "windows"))] +use crate::windows::registers::{ + get_virtual_processor_registers as WHvGetVirtualProcessorRegisters, + set_virtual_processor_registers as WHvSetVirtualProcessorRegisters, +}; use arch::{ArchMemoryInfo, InitrdConfig}; use device_manager::shm::ShmManager; #[cfg(feature = "gpu")] @@ -190,7 +195,12 @@ fn windows_irq_debug_log(message: impl AsRef) { if !*ENABLED.get_or_init(|| { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") .or_else(|_| std::env::var("LIBKRUN_WINDOWS_IO_DEBUG")) - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) }) { return; @@ -324,9 +334,7 @@ fn make_whpx_interrupt_control_bitfield( destination_mode: windows::Win32::System::Hypervisor::WHV_INTERRUPT_DESTINATION_MODE, trigger_mode: windows::Win32::System::Hypervisor::WHV_INTERRUPT_TRIGGER_MODE, ) -> u64 { - (interrupt_type.0 as u64) - | ((destination_mode.0 as u64) << 8) - | ((trigger_mode.0 as u64) << 12) + (interrupt_type.0 as u64) | ((destination_mode.0 as u64) << 8) | ((trigger_mode.0 as u64) << 12) } #[cfg(all(target_arch = "x86_64", target_os = "windows"))] @@ -402,7 +410,8 @@ impl WhpxIrqChip { } if irq_line < 16 { - if let Some(vector) = devices::legacy::windows_pic_stub::query_irq_vector(irq_line as u8) + if let Some(vector) = + devices::legacy::windows_pic_stub::query_irq_vector(irq_line as u8) { return WhpxInterruptRoute::PicExtIntRequest { irq: irq_line as u8, @@ -436,16 +445,17 @@ impl WhpxIrqChip { fn replay_deferred_pic_interrupt(&self, irq_line: u32) -> Result { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvX64RegisterRflags, WHvX64RegisterRip, - WHV_REGISTER_NAME, WHV_REGISTER_VALUE, + WHvX64RegisterRflags, WHvX64RegisterRip, WHV_REGISTER_NAME, WHV_REGISTER_VALUE, }; let pending_vector = { let pending_interrupt = self.pending_interrupt.lock().unwrap(); - pending_interrupt.front().and_then(|pending| match *pending { - PendingInterrupt::PicExtInt { irq, vector } => Some((irq, vector)), - PendingInterrupt::PicFixed { irq, vector } => Some((irq, vector)), - }) + pending_interrupt + .front() + .and_then(|pending| match *pending { + PendingInterrupt::PicExtInt { irq, vector } => Some((irq, vector)), + PendingInterrupt::PicFixed { irq, vector } => Some((irq, vector)), + }) }; let Some((pending_irq, vector)) = pending_vector else { @@ -493,9 +503,8 @@ impl WhpxIrqChip { fn pending_interrupt_slots_busy(&self) -> Option<(bool, bool, u64)> { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvRegisterInternalActivityState, - WHvRegisterPendingEvent, WHvRegisterPendingInterruption, WHV_REGISTER_NAME, - WHV_REGISTER_VALUE, + WHvRegisterInternalActivityState, WHvRegisterPendingEvent, + WHvRegisterPendingInterruption, WHV_REGISTER_NAME, WHV_REGISTER_VALUE, }; let reg_names: [WHV_REGISTER_NAME; 3] = [ @@ -521,7 +530,11 @@ impl WhpxIrqChip { let pending_event_busy = unsafe { (reg_values[1].ExtIntEvent.AsUINT128.Anonymous.Low64 & 1) != 0 }; let internal_activity = unsafe { reg_values[2].InternalActivity.AsUINT64 }; - Some((pending_interrupt_busy, pending_event_busy, internal_activity)) + Some(( + pending_interrupt_busy, + pending_event_busy, + internal_activity, + )) } Err(e) => { windows_irq_debug_log(format!( @@ -577,11 +590,11 @@ impl WhpxIrqChip { fn dump_vcpu_irq_state(&self, label: &str, irq_line: u32, route: &WhpxInterruptRoute) { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvX64RegisterDeliverabilityNotifications, WHvRegisterInterruptState, WHvRegisterPendingEvent, WHvRegisterPendingInterruption, WHvX64RegisterApicBase, WHvX64RegisterApicLvtLint0, WHvX64RegisterApicLvtLint1, WHvX64RegisterApicSpurious, WHvX64RegisterApicTpr, WHvX64RegisterCr8, - WHvX64RegisterRflags, WHvX64RegisterRip, WHV_REGISTER_NAME, WHV_REGISTER_VALUE, + WHvX64RegisterDeliverabilityNotifications, WHvX64RegisterRflags, WHvX64RegisterRip, + WHV_REGISTER_NAME, WHV_REGISTER_VALUE, }; let core_reg_names: [WHV_REGISTER_NAME; 6] = [ @@ -645,11 +658,7 @@ impl WhpxIrqChip { ) { Ok(()) => windows_irq_debug_log(format!( "[IRQSTATE] label={} irq={} route={:?} {}=0x{:016x}", - label, - irq_line, - route, - name, - value[0].Reg64 + label, irq_line, route, name, value[0].Reg64 )), Err(e) => windows_irq_debug_log(format!( "[IRQSTATE] label={} irq={} route={:?} {}_read_failed hr=0x{:x}", @@ -671,7 +680,6 @@ impl WhpxIrqChip { route: &WhpxInterruptRoute, ) -> Result<(), devices::Error> { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvSetVirtualProcessorRegisters, WHvX64RegisterDeliverabilityNotifications, WHV_REGISTER_VALUE, WHV_X64_DELIVERABILITY_NOTIFICATIONS_REGISTER, }; @@ -735,9 +743,8 @@ impl WhpxIrqChip { vector: u8, ) -> Result { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvRegisterInternalActivityState, - WHvRegisterPendingInterruption, WHvSetVirtualProcessorRegisters, - WHvX64RegisterRflags, WHvX64RegisterRip, WHV_REGISTER_NAME, WHV_REGISTER_VALUE, + WHvRegisterInternalActivityState, WHvRegisterPendingInterruption, WHvX64RegisterRflags, + WHvX64RegisterRip, WHV_REGISTER_NAME, WHV_REGISTER_VALUE, }; let reg_name = [WHvRegisterPendingInterruption]; @@ -913,9 +920,8 @@ impl WhpxIrqChip { vector: u8, ) -> Result { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvRegisterInternalActivityState, - WHvRegisterPendingEvent, WHvRegisterPendingInterruption, - WHvSetVirtualProcessorRegisters, WHvX64PendingEventExtInt, WHvX64RegisterRflags, + WHvRegisterInternalActivityState, WHvRegisterPendingEvent, + WHvRegisterPendingInterruption, WHvX64PendingEventExtInt, WHvX64RegisterRflags, WHvX64RegisterRip, WHV_REGISTER_NAME, WHV_REGISTER_VALUE, WHV_X64_PENDING_EXT_INT_EVENT, }; @@ -1052,7 +1058,6 @@ impl WhpxIrqChip { let _ = self.irq_pending_evt.write(1); Ok(true) } - } #[cfg(all(target_arch = "x86_64", target_os = "windows"))] @@ -1076,8 +1081,8 @@ impl IrqChipT for WhpxIrqChip { use std::sync::atomic::{AtomicU64, Ordering}; use windows::Win32::System::Hypervisor::{ WHvRequestInterrupt, WHvX64InterruptDestinationModeLogical, - WHvX64InterruptDestinationModePhysical, - WHvX64InterruptTriggerModeEdge, WHvX64InterruptTypeFixed, WHV_INTERRUPT_CONTROL, + WHvX64InterruptDestinationModePhysical, WHvX64InterruptTriggerModeEdge, + WHvX64InterruptTypeFixed, WHV_INTERRUPT_CONTROL, }; let irq_line = irq_line.ok_or_else(|| { @@ -1189,8 +1194,12 @@ impl IrqChipT for WhpxIrqChip { { let mut pending_interrupt = self.pending_interrupt.lock().unwrap(); let already_queued = pending_interrupt.iter().any(|pending| match *pending { - PendingInterrupt::PicExtInt { irq: pending_irq, .. } - | PendingInterrupt::PicFixed { irq: pending_irq, .. } => pending_irq == irq, + PendingInterrupt::PicExtInt { + irq: pending_irq, .. + } + | PendingInterrupt::PicFixed { + irq: pending_irq, .. + } => pending_irq == irq, }); if already_queued { windows_irq_debug_log(format!( @@ -1203,9 +1212,11 @@ impl IrqChipT for WhpxIrqChip { )); drop(pending_interrupt); match self.pending_interrupt_slots_busy() { - Some((pending_interrupt_busy, pending_event_busy, internal_activity)) - if pending_interrupt_busy || pending_event_busy => - { + Some(( + pending_interrupt_busy, + pending_event_busy, + internal_activity, + )) if pending_interrupt_busy || pending_event_busy => { if pending_event_busy && (internal_activity & 0x2) != 0 && Self::should_cancel_halted_pending_event_duplicate() @@ -1267,8 +1278,12 @@ impl IrqChipT for WhpxIrqChip { { let mut pending_interrupt = self.pending_interrupt.lock().unwrap(); let already_queued = pending_interrupt.iter().any(|pending| match *pending { - PendingInterrupt::PicExtInt { irq: pending_irq, .. } - | PendingInterrupt::PicFixed { irq: pending_irq, .. } => pending_irq == irq, + PendingInterrupt::PicExtInt { + irq: pending_irq, .. + } + | PendingInterrupt::PicFixed { + irq: pending_irq, .. + } => pending_irq == irq, }); if already_queued { windows_irq_debug_log(format!( @@ -1281,9 +1296,11 @@ impl IrqChipT for WhpxIrqChip { )); drop(pending_interrupt); match self.pending_interrupt_slots_busy() { - Some((pending_interrupt_busy, pending_event_busy, internal_activity)) - if pending_interrupt_busy || pending_event_busy => - { + Some(( + pending_interrupt_busy, + pending_event_busy, + internal_activity, + )) if pending_interrupt_busy || pending_event_busy => { if pending_event_busy && (internal_activity & 0x2) != 0 && Self::should_cancel_halted_pending_event_duplicate() @@ -1334,10 +1351,7 @@ impl IrqChipT for WhpxIrqChip { } windows_irq_debug_log(format!( "[IRQ] queued irq={} pic_irq={} route={} vector=0x{:02x} delivery=cancel-exit", - irq_line, - irq, - request_kind, - vector + irq_line, irq, request_kind, vector )); self.kick_after_queue_update("queue_push_complete"); return Ok(()); @@ -1961,7 +1975,11 @@ pub fn build_microvm( .map_err(|e| { // Log the offending string for debugging but convert to a proper error // that won't panic - this is cross-platform compatible - format!("Failed to insert krun_env into kernel cmdline: {:?}. krun_env was: {:?}", e, cmdline.as_str()) + format!( + "Failed to insert krun_env into kernel cmdline: {:?}. krun_env was: {:?}", + e, + cmdline.as_str() + ) }) .unwrap(); } @@ -1983,7 +2001,9 @@ pub fn build_microvm( #[cfg(target_os = "windows")] if let Some(extra_cmdline) = windows_kernel_cmdline_append() { - kernel_cmdline.insert_str_safe(extra_cmdline.as_str()).unwrap(); + kernel_cmdline + .insert_str_safe(extra_cmdline.as_str()) + .unwrap(); } #[cfg(all(not(feature = "tee"), not(target_os = "windows")))] @@ -2176,15 +2196,15 @@ pub fn build_microvm( // PortIODeviceManager::register_devices() skips COM1 registration entirely. #[cfg(target_os = "windows")] if serial_devices.is_empty() { - let output: Option> = if let Some(path) = &vm_resources.console_output - { - Some(Box::new( - open_windows_console_output_file(path) - .map_err(StartMicrovmError::OpenConsoleFile)?, - )) - } else { - Some(Box::new(io::stdout())) - }; + let output: Option> = + if let Some(path) = &vm_resources.console_output { + Some(Box::new( + open_windows_console_output_file(path) + .map_err(StartMicrovmError::OpenConsoleFile)?, + )) + } else { + Some(Box::new(io::stdout())) + }; let input: Option> = crate::windows::stdin_reader::WindowsStdinInput::new() .ok() @@ -2426,6 +2446,8 @@ pub fn build_microvm( exit_evt, exit_observers: Vec::new(), exit_code: exit_code.clone(), + #[cfg(target_os = "windows")] + host_return_exit_code: None, vm, mmio_device_manager, #[cfg(target_arch = "x86_64")] diff --git a/src/vmm/src/device_manager/shm.rs b/src/vmm/src/device_manager/shm.rs index 83d9117f4d..dcf64ada04 100644 --- a/src/vmm/src/device_manager/shm.rs +++ b/src/vmm/src/device_manager/shm.rs @@ -173,7 +173,7 @@ mod tests { let result = manager.create_gpu_region(8192); assert!(result.is_ok()); - let gpu_region = manager.gpu_region(); + let gpu_region = manager.gpu_region.as_ref(); assert!(gpu_region.is_some()); assert_eq!(gpu_region.unwrap().size, 8192); } diff --git a/src/vmm/src/device_manager/whpx/mmio.rs b/src/vmm/src/device_manager/whpx/mmio.rs index 6016f934f2..a07582f136 100644 --- a/src/vmm/src/device_manager/whpx/mmio.rs +++ b/src/vmm/src/device_manager/whpx/mmio.rs @@ -129,8 +129,7 @@ impl MMIODeviceManager { && !matches!( std::env::var("LIBKRUN_WHPX_SHARE_VSOCK_IRQ_WITH_PREV").as_deref(), Ok("0") - ) - { + ) { // WHPX virtio-vsock still needs the old shared-IRQ behavior in the // current Windows bring-up path. Keep it as the default, but allow // opt-out experiments with LIBKRUN_WHPX_SHARE_VSOCK_IRQ_WITH_PREV=0. diff --git a/src/vmm/src/lib.rs b/src/vmm/src/lib.rs index cd7ae88d9c..c9ce3f9de9 100644 --- a/src/vmm/src/lib.rs +++ b/src/vmm/src/lib.rs @@ -86,7 +86,8 @@ fn windows_vmm_debug_log(message: impl AsRef) { #[cfg(all(target_os = "windows", target_arch = "x86_64"))] fn windows_format_hex(bytes: &[u8]) -> String { - bytes.iter() + bytes + .iter() .map(|byte| format!("{byte:02x}")) .collect::>() .join(" ") @@ -102,7 +103,10 @@ fn windows_dump_guest_boot_layout(guest_memory: &GuestMemoryMmap) { let mut mp_window = [0u8; 32]; let mp_bytes = if guest_memory - .read_slice(&mut mp_window, GuestAddress(arch::x86_64::layout::EBDA_START)) + .read_slice( + &mut mp_window, + GuestAddress(arch::x86_64::layout::EBDA_START), + ) .is_ok() { windows_format_hex(&mp_window) @@ -258,6 +262,8 @@ pub struct Vmm { vm: Vm, exit_observers: Vec>>, exit_code: Arc, + #[cfg(target_os = "windows")] + host_return_exit_code: Option, // Guest VM devices. mmio_device_manager: MMIODeviceManager, @@ -449,6 +455,16 @@ impl Vmm { .on_vmm_exit(); } + // A3S runs libkrun inside a dedicated shim. Its Windows guest streams + // live in the shared rootfs, so the shim needs a bounded opportunity to + // drain them before exiting. Keep libkrun's process-takeover contract + // unless that host explicitly opts into returning. + #[cfg(target_os = "windows")] + if std::env::var_os("LIBKRUN_WINDOWS_RETURN_ON_EXIT").is_some_and(|value| value == "1") { + self.host_return_exit_code = Some(exit_code); + return; + } + // Exit from Firecracker using the provided exit code. Safe because we're terminating // the process anyway. unsafe { @@ -456,6 +472,11 @@ impl Vmm { } } + #[cfg(target_os = "windows")] + pub fn take_host_return_exit_code(&mut self) -> Option { + self.host_return_exit_code.take() + } + /// Returns a reference to the inner KVM Vm object. pub fn kvm_vm(&self) -> &Vm { &self.vm @@ -555,7 +576,7 @@ mod tests { struct TestObserver; impl VmmEventsObserver for TestObserver {} - let observer = TestObserver; + let mut observer = TestObserver; assert!(observer.on_vmm_boot().is_ok()); assert!(observer.on_vmm_stop().is_ok()); } diff --git a/src/vmm/src/vmm_config/external_kernel.rs b/src/vmm/src/vmm_config/external_kernel.rs index 0339cbcadb..ca22350061 100644 --- a/src/vmm/src/vmm_config/external_kernel.rs +++ b/src/vmm/src/vmm_config/external_kernel.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub enum KernelFormat { // Raw image, ready to be loaded into the VM. #[default] diff --git a/src/vmm/src/vmm_config/fs.rs b/src/vmm/src/vmm_config/fs.rs index ff9e6b8e58..8e62c6c2ff 100644 --- a/src/vmm/src/vmm_config/fs.rs +++ b/src/vmm/src/vmm_config/fs.rs @@ -1,4 +1,4 @@ -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct FsDeviceConfig { pub fs_id: String, pub shared_dir: String, @@ -17,12 +17,14 @@ mod tests { fs_id: "myfs".to_string(), shared_dir: "/shared".to_string(), shm_size: Some(256 * 1024 * 1024), + #[cfg(target_os = "macos")] no_fsync: false, }; let cloned = config.clone(); assert_eq!(config.fs_id, cloned.fs_id); assert_eq!(config.shared_dir, cloned.shared_dir); assert_eq!(config.shm_size, cloned.shm_size); + #[cfg(target_os = "macos")] assert_eq!(config.no_fsync, cloned.no_fsync); } @@ -32,12 +34,14 @@ mod tests { fs_id: "myfs".to_string(), shared_dir: "/shared".to_string(), shm_size: Some(256 * 1024 * 1024), + #[cfg(target_os = "macos")] no_fsync: false, }; let config2 = FsDeviceConfig { fs_id: "myfs".to_string(), shared_dir: "/shared".to_string(), shm_size: Some(256 * 1024 * 1024), + #[cfg(target_os = "macos")] no_fsync: false, }; assert_eq!(config1, config2); @@ -49,12 +53,14 @@ mod tests { fs_id: "fs1".to_string(), shared_dir: "/shared".to_string(), shm_size: None, + #[cfg(target_os = "macos")] no_fsync: true, }; let config2 = FsDeviceConfig { fs_id: "fs2".to_string(), shared_dir: "/shared".to_string(), shm_size: None, + #[cfg(target_os = "macos")] no_fsync: true, }; assert_ne!(config1, config2); @@ -66,11 +72,13 @@ mod tests { fs_id: "myfs".to_string(), shared_dir: "/tmp/share".to_string(), shm_size: Some(1024), + #[cfg(target_os = "macos")] no_fsync: true, }; let debug_str = format!("{:?}", config); assert!(debug_str.contains("myfs")); assert!(debug_str.contains("/tmp/share")); + #[cfg(target_os = "macos")] assert!(debug_str.contains("no_fsync")); } @@ -80,6 +88,7 @@ mod tests { fs_id: "fs0".to_string(), shared_dir: "/tmp".to_string(), shm_size: None, + #[cfg(target_os = "macos")] no_fsync: false, }; assert!(config.shm_size.is_none()); diff --git a/src/vmm/src/vmm_config/instance_info.rs b/src/vmm/src/vmm_config/instance_info.rs index f5e646b34b..9fa40146dd 100644 --- a/src/vmm/src/vmm_config/instance_info.rs +++ b/src/vmm/src/vmm_config/instance_info.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 /// The strongly typed that contains general information about the microVM. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct InstanceInfo { /// The ID of the microVM. pub id: String, diff --git a/src/vmm/src/vmm_config/kernel_bundle.rs b/src/vmm/src/vmm_config/kernel_bundle.rs index 3601433345..5936ebcef4 100644 --- a/src/vmm/src/vmm_config/kernel_bundle.rs +++ b/src/vmm/src/vmm_config/kernel_bundle.rs @@ -4,7 +4,7 @@ use std::fmt::{Display, Formatter, Result}; /// Data structure holding the attributes read from the `libkrunfw` kernel config. -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct KernelBundle { pub host_addr: u64, pub guest_addr: u64, @@ -58,7 +58,7 @@ impl Display for QbootBundleError { } /// Data structure holding the attributes read from the `libkrunfw` initrd config. -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct InitrdBundle { pub host_addr: u64, pub size: usize, diff --git a/src/vmm/src/vmm_config/kernel_cmdline.rs b/src/vmm/src/vmm_config/kernel_cmdline.rs index dacd2cffaa..cc818370c4 100644 --- a/src/vmm/src/vmm_config/kernel_cmdline.rs +++ b/src/vmm/src/vmm_config/kernel_cmdline.rs @@ -17,7 +17,7 @@ pub const DEFAULT_KERNEL_CMDLINE: &str = /// Strongly typed data structure used to configure the boot source of the /// microvm. -#[derive(Debug, Default, Eq, PartialEq)] +#[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct KernelCmdlineConfig { pub prolog: Option, pub krun_env: Option, diff --git a/src/vmm/src/windows/mod.rs b/src/vmm/src/windows/mod.rs index 9bfa6e00fa..f4781770e4 100644 --- a/src/vmm/src/windows/mod.rs +++ b/src/vmm/src/windows/mod.rs @@ -3,6 +3,7 @@ pub(crate) mod acpi; pub(crate) mod interrupts; +pub(crate) mod registers; pub mod stdin_reader; pub mod vstate; mod whpx_vcpu; diff --git a/src/vmm/src/windows/registers.rs b/src/vmm/src/windows/registers.rs new file mode 100644 index 0000000000..78e0d0cb70 --- /dev/null +++ b/src/vmm/src/windows/registers.rs @@ -0,0 +1,128 @@ +// Copyright 2026 A3S Lab +// SPDX-License-Identifier: Apache-2.0 + +use std::{mem, ptr, slice}; + +use windows::Win32::System::Hypervisor::{ + WHvGetVirtualProcessorRegisters as raw_get_virtual_processor_registers, + WHvSetVirtualProcessorRegisters as raw_set_virtual_processor_registers, WHV_PARTITION_HANDLE, + WHV_REGISTER_NAME, WHV_REGISTER_VALUE, +}; + +// WinHvPlatformDefs.h declares WHV_UINT128 with DECLSPEC_ALIGN(16). The +// generated windows-rs type currently loses that alignment, which also leaves +// WHV_REGISTER_VALUE aligned to only 8 bytes. Recent WinHvPlatform builds use +// aligned SIMD loads and stores for these arrays, so an 8-byte-aligned Rust +// array can crash inside WinHvPlatform.dll instead of returning an HRESULT. +#[repr(C, align(16))] +#[derive(Clone, Copy)] +struct AlignedRegisterValue { + value: WHV_REGISTER_VALUE, +} + +impl Default for AlignedRegisterValue { + fn default() -> Self { + Self { + value: WHV_REGISTER_VALUE::default(), + } + } +} + +fn is_aligned(value: *const WHV_REGISTER_VALUE) -> bool { + (value as usize) % mem::align_of::() == 0 +} + +/// Calls `WHvGetVirtualProcessorRegisters` with a 16-byte-aligned value array. +/// +/// # Safety +/// +/// `register_names` and `register_values` must reference arrays containing at +/// least `register_count` elements, as required by WinHvPlatform. +pub(crate) unsafe fn get_virtual_processor_registers( + partition: WHV_PARTITION_HANDLE, + vp_index: u32, + register_names: *const WHV_REGISTER_NAME, + register_count: u32, + register_values: *mut WHV_REGISTER_VALUE, +) -> windows::core::Result<()> { + if register_count == 0 || is_aligned(register_values) { + return raw_get_virtual_processor_registers( + partition, + vp_index, + register_names, + register_count, + register_values, + ); + } + + let count = register_count as usize; + let mut aligned_values = vec![AlignedRegisterValue::default(); count]; + raw_get_virtual_processor_registers( + partition, + vp_index, + register_names, + register_count, + aligned_values.as_mut_ptr().cast(), + )?; + ptr::copy_nonoverlapping( + aligned_values.as_ptr().cast::(), + register_values, + count, + ); + Ok(()) +} + +/// Calls `WHvSetVirtualProcessorRegisters` with a 16-byte-aligned value array. +/// +/// # Safety +/// +/// `register_names` and `register_values` must reference arrays containing at +/// least `register_count` elements, as required by WinHvPlatform. +pub(crate) unsafe fn set_virtual_processor_registers( + partition: WHV_PARTITION_HANDLE, + vp_index: u32, + register_names: *const WHV_REGISTER_NAME, + register_count: u32, + register_values: *const WHV_REGISTER_VALUE, +) -> windows::core::Result<()> { + if register_count == 0 || is_aligned(register_values) { + return raw_set_virtual_processor_registers( + partition, + vp_index, + register_names, + register_count, + register_values, + ); + } + + let values = slice::from_raw_parts(register_values, register_count as usize); + let aligned_values: Vec<_> = values + .iter() + .copied() + .map(|value| AlignedRegisterValue { value }) + .collect(); + raw_set_virtual_processor_registers( + partition, + vp_index, + register_names, + register_count, + aligned_values.as_ptr().cast(), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn aligned_register_value_preserves_layout() { + assert_eq!( + mem::size_of::(), + mem::size_of::() + ); + assert_eq!(mem::align_of::(), 16); + + let values = [AlignedRegisterValue::default(); 2]; + assert!(is_aligned(values.as_ptr().cast())); + } +} diff --git a/src/vmm/src/windows/vstate.rs b/src/vmm/src/windows/vstate.rs index 3285cdd37f..4b52783469 100644 --- a/src/vmm/src/windows/vstate.rs +++ b/src/vmm/src/windows/vstate.rs @@ -10,6 +10,7 @@ use vm_memory::{Address, Bytes, GuestAddress, GuestMemory, GuestMemoryMmap, Gues use windows::Win32::System::Hypervisor::*; use super::interrupts::PendingInterruptQueue; +use super::registers::{get_virtual_processor_registers, set_virtual_processor_registers}; use super::whpx_vcpu::{VcpuEmulation, VcpuExit, WhpxVcpu}; use crate::{FC_EXIT_CODE_GENERIC_ERROR, FC_EXIT_CODE_OK}; @@ -134,7 +135,12 @@ fn windows_vcpu_debug_log(message: impl AsRef) { if !*ENABLED.get_or_init(|| { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") .or_else(|_| std::env::var("LIBKRUN_WINDOWS_VCPU_DEBUG")) - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) }) { return; @@ -358,7 +364,9 @@ impl Vm { if effective_mask != 0 { let mut synthetic_features: WHV_PARTITION_PROPERTY = std::mem::zeroed(); - synthetic_features.SyntheticProcessorFeaturesBanks.BanksCount = 1; + synthetic_features + .SyntheticProcessorFeaturesBanks + .BanksCount = 1; synthetic_features .SyntheticProcessorFeaturesBanks .Anonymous @@ -603,8 +611,8 @@ impl Vcpu { let (event_sender, event_receiver) = crossbeam_channel::unbounded(); let (response_sender, response_receiver) = crossbeam_channel::unbounded(); - let whpx_vcpu = - WhpxVcpu::new(partition, id as u32, pending_interrupt.clone()).map_err(Error::VcpuSpawn)?; + let whpx_vcpu = WhpxVcpu::new(partition, id as u32, pending_interrupt.clone()) + .map_err(Error::VcpuSpawn)?; Ok(Vcpu { id, @@ -734,7 +742,7 @@ impl Vcpu { }; unsafe { - WHvSetVirtualProcessorRegisters( + set_virtual_processor_registers( self.partition, self.id as u32, reg_names.as_ptr(), @@ -768,16 +776,16 @@ impl Vcpu { fn configure_lint(&self) -> Result<()> { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorInterruptControllerState, WHvGetVirtualProcessorRegisters, - WHvSetVirtualProcessorInterruptControllerState, WHvSetVirtualProcessorRegisters, - WHvX64RegisterApicLvtLint0, WHvX64RegisterApicLvtLint1, WHV_REGISTER_VALUE, + WHvGetVirtualProcessorInterruptControllerState, + WHvSetVirtualProcessorInterruptControllerState, WHvX64RegisterApicLvtLint0, + WHvX64RegisterApicLvtLint1, WHV_REGISTER_VALUE, }; let reg_names = [WHvX64RegisterApicLvtLint0, WHvX64RegisterApicLvtLint1]; let mut reg_values = [WHV_REGISTER_VALUE::default(); 2]; unsafe { - if let Err(e) = WHvGetVirtualProcessorRegisters( + if let Err(e) = get_virtual_processor_registers( self.partition, self.id as u32, reg_names.as_ptr(), @@ -923,7 +931,7 @@ impl Vcpu { reg_values[1].Reg64 = u64::from(set_apic_delivery_mode(lint1_before as u32, APIC_MODE_NMI)); unsafe { - if let Err(e) = WHvSetVirtualProcessorRegisters( + if let Err(e) = set_virtual_processor_registers( self.partition, self.id as u32, reg_names.as_ptr(), @@ -1019,7 +1027,6 @@ impl Vcpu { log::debug!("Monitor thread started for vCPU {}", vcpu_id); use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvSetVirtualProcessorRegisters, WHvX64RegisterApicCurrentCount, WHvX64RegisterApicDivide, WHvX64RegisterApicLvtTimer, WHvX64RegisterRip, WHvX64RegisterRsp, WHvX64RegisterRflags, @@ -1029,8 +1036,7 @@ impl Vcpu { WHvX64RegisterRsi, WHvX64RegisterRdi, WHvX64RegisterRbp, WHvX64RegisterR8, WHvX64RegisterR9, WHvX64RegisterR10, WHvX64RegisterR11, WHvX64RegisterR12, WHvX64RegisterR13, - WHvX64RegisterR14, WHvX64RegisterR15, - WHV_REGISTER_NAME, WHV_REGISTER_VALUE, + WHvX64RegisterR14, WHvX64RegisterR15, WHV_REGISTER_VALUE, }; let mut last_rip: Option = None; @@ -1056,7 +1062,7 @@ impl Vcpu { unsafe { std::mem::zeroed() }; unsafe { - if WHvGetVirtualProcessorRegisters( + if get_virtual_processor_registers( partition_handle, vcpu_id as u32, reg_names.as_ptr(), @@ -1076,7 +1082,7 @@ impl Vcpu { ]; let mut apic_values: [WHV_REGISTER_VALUE; 3] = std::mem::zeroed(); - let _ = WHvGetVirtualProcessorRegisters( + let _ = get_virtual_processor_registers( partition_handle, vcpu_id as u32, apic_names.as_ptr(), @@ -1213,7 +1219,7 @@ impl Vcpu { let mut rip_value: [WHV_REGISTER_VALUE; 1] = std::mem::zeroed(); rip_value[0].Reg64 = AFTER_LOOP_RIP; - let set_result = WHvSetVirtualProcessorRegisters( + let set_result = set_virtual_processor_registers( partition_handle, vcpu_id as u32, rip_name.as_ptr(), @@ -1262,7 +1268,7 @@ impl Vcpu { let mut reg_values: [WHV_REGISTER_VALUE; 23] = unsafe { std::mem::zeroed() }; unsafe { - if WHvGetVirtualProcessorRegisters( + if get_virtual_processor_registers( partition_handle, vcpu_id as u32, reg_names.as_ptr(), @@ -1542,7 +1548,7 @@ impl Vcpu { } } VcpuExit::Halted => { - log::warn!("vCPU {} halted - kernel may have failed to boot or executed HLT instruction", self.id); + log::debug!("vCPU {} halted - waiting for the next interrupt", self.id); windows_vcpu_exit_state_log(self.id, "vcpu_exit=Halted"); self.whpx_vcpu.clear_pending_mmio(); self.whpx_vcpu.clear_pending_io(); @@ -1570,16 +1576,13 @@ impl Vcpu { /// Reads just RIP to check if CPU is still executing fn read_rip(&self) -> Option { - use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvX64RegisterRip, WHV_REGISTER_NAME, - WHV_REGISTER_VALUE, - }; + use windows::Win32::System::Hypervisor::{WHvX64RegisterRip, WHV_REGISTER_VALUE}; let reg_names = [WHvX64RegisterRip]; let mut reg_values: [WHV_REGISTER_VALUE; 1] = unsafe { std::mem::zeroed() }; unsafe { - match WHvGetVirtualProcessorRegisters( + match get_virtual_processor_registers( self.partition, self.id as u32, reg_names.as_ptr(), @@ -1598,13 +1601,12 @@ impl Vcpu { /// Reads and logs current CPU register state for debugging fn dump_cpu_state(&self) { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvX64RegisterCr0, WHvX64RegisterCr3, - WHvX64RegisterCr4, WHvX64RegisterGdtr, WHvX64RegisterIdtr, WHvX64RegisterRflags, - WHvX64RegisterRip, WHvX64RegisterRsp, WHvX64RegisterRax, WHvX64RegisterRbx, - WHvX64RegisterRcx, WHvX64RegisterRdx, WHvX64RegisterRsi, WHvX64RegisterRdi, - WHvX64RegisterRbp, WHvX64RegisterR8, WHvX64RegisterR9, WHvX64RegisterR10, - WHvX64RegisterR11, WHvX64RegisterR12, WHvX64RegisterR13, WHvX64RegisterR14, - WHvX64RegisterR15, WHV_REGISTER_NAME, WHV_REGISTER_VALUE, + WHvX64RegisterCr0, WHvX64RegisterCr3, WHvX64RegisterCr4, WHvX64RegisterGdtr, + WHvX64RegisterIdtr, WHvX64RegisterR10, WHvX64RegisterR11, WHvX64RegisterR12, + WHvX64RegisterR13, WHvX64RegisterR14, WHvX64RegisterR15, WHvX64RegisterR8, + WHvX64RegisterR9, WHvX64RegisterRax, WHvX64RegisterRbp, WHvX64RegisterRbx, + WHvX64RegisterRcx, WHvX64RegisterRdi, WHvX64RegisterRdx, WHvX64RegisterRflags, + WHvX64RegisterRip, WHvX64RegisterRsi, WHvX64RegisterRsp, WHV_REGISTER_VALUE, }; let reg_names = [ @@ -1636,7 +1638,7 @@ impl Vcpu { let mut reg_values: [WHV_REGISTER_VALUE; 23] = unsafe { std::mem::zeroed() }; unsafe { - if let Err(e) = WHvGetVirtualProcessorRegisters( + if let Err(e) = get_virtual_processor_registers( self.partition, self.id as u32, reg_names.as_ptr(), @@ -1885,6 +1887,7 @@ mod tests { io_bus, exit_evt, None, + None, ) .unwrap(); } @@ -1906,6 +1909,7 @@ mod tests { io_bus, exit_evt, None, + None, ) .unwrap(); vcpu.configure_x86_64(&guest_mem, GuestAddress(0x10000)) @@ -1939,6 +1943,7 @@ mod tests { io_bus, exit_evt, None, + None, ) .unwrap(); @@ -1981,6 +1986,7 @@ mod tests { io_bus, exit_evt, None, + None, ) .unwrap(); @@ -2114,6 +2120,7 @@ mod tests { io_bus, exit_evt, None, + None, ) .unwrap(); vcpu.configure_x86_64(&guest_mem, GuestAddress(ENTRY_ADDR)) @@ -2247,6 +2254,7 @@ mod tests { io_bus, exit_evt, None, + None, ) .unwrap(); @@ -2337,6 +2345,7 @@ mod tests { io_bus, exit_evt, None, + None, ) .unwrap(); vcpu.configure_x86_64(&guest_mem, GuestAddress(ENTRY_ADDR)) @@ -3059,6 +3068,7 @@ mod tests { io_bus, exit_evt, None, + None, ) .unwrap(); eprintln!("[load] Vcpu::new OK"); @@ -3381,6 +3391,7 @@ mod tests { io_bus, exit_evt, Some(irq_evt), + None, ) .unwrap(); eprintln!("[e2e] Vcpu::new done"); diff --git a/src/vmm/src/windows/whpx_vcpu.rs b/src/vmm/src/windows/whpx_vcpu.rs index 12655e11cf..355981f021 100644 --- a/src/vmm/src/windows/whpx_vcpu.rs +++ b/src/vmm/src/windows/whpx_vcpu.rs @@ -50,37 +50,37 @@ use vm_memory::{Bytes, GuestAddress, GuestMemoryMmap}; use windows::core::HRESULT; use windows::Win32::System::Hypervisor::{ WHvCreateVirtualProcessor, WHvDeleteVirtualProcessor, WHvEmulatorCreateEmulator, - WHvEmulatorDestroyEmulator, WHvEmulatorTryIoEmulation, WHvGetVirtualProcessorRegisters, - WHvGetVirtualProcessorInterruptControllerState, - WHvMemoryAccessExecute, WHvMemoryAccessRead, WHvMemoryAccessWrite, WHvRequestInterrupt, - WHvRunVirtualProcessor, WHvRunVpExitReasonCanceled, WHvRunVpExitReasonException, - WHvRunVpExitReasonHypercall, WHvRunVpExitReasonInvalidVpRegisterValue, - WHvRunVpExitReasonMemoryAccess, WHvRunVpExitReasonSynicSintDeliverable, - WHvRunVpExitReasonUnrecoverableException, WHvRunVpExitReasonUnsupportedFeature, - WHvRunVpExitReasonX64ApicEoi, WHvRunVpExitReasonX64ApicInitSipiTrap, - WHvRunVpExitReasonX64ApicSmiTrap, WHvRunVpExitReasonX64ApicWriteTrap, - WHvRunVpExitReasonX64Cpuid, WHvRunVpExitReasonX64Halt, WHvRunVpExitReasonX64InterruptWindow, - WHvRunVpExitReasonX64IoPortAccess, WHvRunVpExitReasonX64MsrAccess, WHvRunVpExitReasonX64Rdtsc, - WHvSetVirtualProcessorRegisters, WHvTranslateGva, WHvX64ExceptionTypeBreakpointTrap, + WHvEmulatorDestroyEmulator, WHvEmulatorTryIoEmulation, + WHvGetVirtualProcessorInterruptControllerState, WHvMemoryAccessExecute, WHvMemoryAccessRead, + WHvMemoryAccessWrite, WHvRequestInterrupt, WHvRunVirtualProcessor, WHvRunVpExitReasonCanceled, + WHvRunVpExitReasonException, WHvRunVpExitReasonHypercall, + WHvRunVpExitReasonInvalidVpRegisterValue, WHvRunVpExitReasonMemoryAccess, + WHvRunVpExitReasonSynicSintDeliverable, WHvRunVpExitReasonUnrecoverableException, + WHvRunVpExitReasonUnsupportedFeature, WHvRunVpExitReasonX64ApicEoi, + WHvRunVpExitReasonX64ApicInitSipiTrap, WHvRunVpExitReasonX64ApicSmiTrap, + WHvRunVpExitReasonX64ApicWriteTrap, WHvRunVpExitReasonX64Cpuid, WHvRunVpExitReasonX64Halt, + WHvRunVpExitReasonX64InterruptWindow, WHvRunVpExitReasonX64IoPortAccess, + WHvRunVpExitReasonX64MsrAccess, WHvRunVpExitReasonX64Rdtsc, WHvTranslateGva, + WHvX64ApicWriteTypeDfr, WHvX64ApicWriteTypeLdr, WHvX64ApicWriteTypeLint0, + WHvX64ApicWriteTypeLint1, WHvX64ApicWriteTypeSvr, WHvX64ExceptionTypeBreakpointTrap, WHvX64ExceptionTypeOverflowTrap, WHvX64InterruptDestinationModeLogical, WHvX64InterruptDestinationModePhysical, WHvX64InterruptTriggerModeEdge, - WHvX64ApicWriteTypeDfr, WHvX64ApicWriteTypeLdr, WHvX64ApicWriteTypeLint0, - WHvX64ApicWriteTypeLint1, WHvX64ApicWriteTypeSvr, WHvX64InterruptTypeFixed, WHvX64InterruptTypeInit, WHvX64InterruptTypeLocalInt1, WHvX64InterruptTypeLowestPriority, WHvX64InterruptTypeNmi, WHvX64InterruptTypeSipi, - WHvX64PendingEventExtInt, - WHvX64RegisterRax, WHvX64RegisterRbx, WHvX64RegisterRcx, WHvX64RegisterRdx, - WHvX64RegisterRip, WHV_EMULATOR_CALLBACKS, WHV_EMULATOR_IO_ACCESS_INFO, + WHvX64PendingEventExtInt, WHvX64RegisterRax, WHvX64RegisterRbx, WHvX64RegisterRcx, + WHvX64RegisterRdx, WHvX64RegisterRip, WHV_EMULATOR_CALLBACKS, WHV_EMULATOR_IO_ACCESS_INFO, WHV_EMULATOR_MEMORY_ACCESS_INFO, WHV_INTERRUPT_CONTROL, WHV_PARTITION_HANDLE, WHV_REGISTER_NAME, WHV_REGISTER_VALUE, WHV_RUN_VP_EXIT_CONTEXT, WHV_TRANSLATE_GVA_FLAGS, WHV_TRANSLATE_GVA_RESULT, WHV_TRANSLATE_GVA_RESULT_CODE, WHV_X64_DELIVERABILITY_NOTIFICATIONS_REGISTER, WHV_X64_PENDING_EXT_INT_EVENT, }; -use windows::Win32::System::Performance::{ - QueryPerformanceCounter, QueryPerformanceFrequency, -}; +use windows::Win32::System::Performance::{QueryPerformanceCounter, QueryPerformanceFrequency}; use super::interrupts::{PendingInterrupt, PendingInterruptQueue}; +use super::registers::{ + get_virtual_processor_registers as WHvGetVirtualProcessorRegisters, + set_virtual_processor_registers as WHvSetVirtualProcessorRegisters, +}; fn windows_hyperv_enlightenments_enabled() -> bool { static VALUE: std::sync::OnceLock = std::sync::OnceLock::new(); @@ -99,7 +99,12 @@ fn windows_io_debug_enabled() -> bool { *VALUE.get_or_init(|| { std::env::var("LIBKRUN_WINDOWS_VERBOSE_DEBUG") .or_else(|_| std::env::var("LIBKRUN_WINDOWS_IO_DEBUG")) - .map(|v| matches!(v.trim().to_ascii_lowercase().as_str(), "1" | "true" | "yes" | "on")) + .map(|v| { + matches!( + v.trim().to_ascii_lowercase().as_str(), + "1" | "true" | "yes" | "on" + ) + }) .unwrap_or(false) }) } @@ -279,9 +284,7 @@ fn windows_pic_fixed_request_interrupt_type() -> PicFixedRequestInterruptType { std::env::var("LIBKRUN_WHPX_PIC_FIXED_REQUEST_TYPE") .ok() .map(|value| match value.trim().to_ascii_lowercase().as_str() { - "lowest-priority" | "lowest" | "lp" => { - PicFixedRequestInterruptType::LowestPriority - } + "lowest-priority" | "lowest" | "lp" => PicFixedRequestInterruptType::LowestPriority, "nmi" => PicFixedRequestInterruptType::Nmi, "local-int1" | "lint1" | "localint1" => PicFixedRequestInterruptType::LocalInt1, _ => PicFixedRequestInterruptType::Fixed, @@ -315,9 +318,7 @@ fn make_whpx_interrupt_control_bitfield( destination_mode: windows::Win32::System::Hypervisor::WHV_INTERRUPT_DESTINATION_MODE, trigger_mode: windows::Win32::System::Hypervisor::WHV_INTERRUPT_TRIGGER_MODE, ) -> u64 { - (interrupt_type.0 as u64) - | ((destination_mode.0 as u64) << 8) - | ((trigger_mode.0 as u64) << 12) + (interrupt_type.0 as u64) | ((destination_mode.0 as u64) << 8) | ((trigger_mode.0 as u64) << 12) } fn read_u32_le(buf: &[u8], offset: usize) -> Option { @@ -389,7 +390,12 @@ fn guest_hyperv_cpuid(function: u32) -> (u64, u64, u64, u64) { match function { 0x4000_0000 => (0x4000_0006, rbx, rcx, rdx), - 0x4000_0003 => ((rax as u32 & HV_CPUID_FEATURES_MINIMAL_GUEST_MASK) as u64, 0, 0, 0), + 0x4000_0003 => ( + (rax as u32 & HV_CPUID_FEATURES_MINIMAL_GUEST_MASK) as u64, + 0, + 0, + 0, + ), 0x4000_0004 => (0x20, 0xff, 0, 0), 0x4000_0005 | 0x4000_0006 => (0, 0, 0, 0), _ => (rax, rbx, rcx, rdx), @@ -720,7 +726,6 @@ impl WhpxVcpu { pending: PendingInterrupt, ) -> io::Result<()> { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvSetVirtualProcessorRegisters, WHvX64RegisterDeliverabilityNotifications, WHV_REGISTER_VALUE, }; @@ -1117,11 +1122,10 @@ impl WhpxVcpu { let rex_r = (rex >> 2) & 1; let reg_index = reg_base + (rex_r << 3); let end_idx = Self::skip_modrm_address(instruction_bytes, idx, modrm)?; - let next_rip = rip.wrapping_add( - end_idx - .try_into() - .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Bad instruction len"))?, - ); + let next_rip = + rip.wrapping_add(end_idx.try_into().map_err(|_| { + io::Error::new(io::ErrorKind::InvalidData, "Bad instruction len") + })?); let (kind, size) = match opcode2 { // Prefetch variants: memory-touching hints with no architectural side effects. @@ -1214,7 +1218,9 @@ impl WhpxVcpu { _ => { return Err(io::Error::new( io::ErrorKind::Unsupported, - format!("Unsupported MMIO instruction opcode 0x{opcode:02x} (is_write={is_write})"), + format!( + "Unsupported MMIO instruction opcode 0x{opcode:02x} (is_write={is_write})" + ), )); } }; @@ -1328,11 +1334,10 @@ impl WhpxVcpu { fn log_invalid_vp_register_state(&self, exit_context: &WHV_RUN_VP_EXIT_CONTEXT) { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvX64RegisterDeliverabilityNotifications, WHvRegisterInterruptState, WHvRegisterPendingEvent, WHvRegisterPendingInterruption, WHvX64RegisterApicBase, WHvX64RegisterApicLvtLint0, WHvX64RegisterApicLvtLint1, WHvX64RegisterApicSpurious, WHvX64RegisterApicTpr, WHvX64RegisterCr8, - WHvX64RegisterRflags, WHvX64RegisterRip, + WHvX64RegisterDeliverabilityNotifications, WHvX64RegisterRflags, WHvX64RegisterRip, }; let core_reg_names = [ @@ -1421,10 +1426,8 @@ impl WhpxVcpu { source: &str, ) -> io::Result { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvRegisterInterruptState, WHvRegisterPendingEvent, - WHvRegisterInternalActivityState, WHvRegisterPendingInterruption, - WHvSetVirtualProcessorRegisters, - WHvX64RegisterDeliverabilityNotifications, + WHvRegisterInternalActivityState, WHvRegisterInterruptState, WHvRegisterPendingEvent, + WHvRegisterPendingInterruption, WHvX64RegisterDeliverabilityNotifications, WHV_REGISTER_VALUE, }; @@ -1626,9 +1629,8 @@ impl WhpxVcpu { self.trace_interrupt_controller_state("pic-extint-before", rip); let mut ext_int_event = WHV_X64_PENDING_EXT_INT_EVENT::default(); unsafe { - ext_int_event.Anonymous._bitfield = 1 - | ((WHvX64PendingEventExtInt.0 as u64) << 1) - | (u64::from(vector) << 8); + ext_int_event.Anonymous._bitfield = + 1 | ((WHvX64PendingEventExtInt.0 as u64) << 1) | (u64::from(vector) << 8); ext_int_event.Anonymous.Reserved2 = 0; } let reg_name = [WHvRegisterPendingEvent]; @@ -1856,7 +1858,8 @@ impl WhpxVcpu { PicFixedInjectionMode::PendingInterruption => { let reg_name = [WHvRegisterPendingInterruption]; let mut reg_value = [WHV_REGISTER_VALUE::default(); 1]; - reg_value[0].PendingInterruption.AsUINT64 = 1 | (u64::from(vector) << 16); + reg_value[0].PendingInterruption.AsUINT64 = + 1 | (u64::from(vector) << 16); WHvSetVirtualProcessorRegisters( self.partition, self.index, @@ -1944,8 +1947,8 @@ impl WhpxVcpu { fn pending_event_busy_and_halted(&self) -> io::Result { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvRegisterInternalActivityState, - WHvRegisterPendingEvent, WHV_REGISTER_NAME, WHV_REGISTER_VALUE, + WHvRegisterInternalActivityState, WHvRegisterPendingEvent, WHV_REGISTER_NAME, + WHV_REGISTER_VALUE, }; let reg_names: [WHV_REGISTER_NAME; 2] = @@ -1988,8 +1991,7 @@ impl WhpxVcpu { fn service_pending_interrupt_after_completion(&self, source: &str) -> io::Result<()> { use windows::Win32::System::Hypervisor::{ - WHvGetVirtualProcessorRegisters, WHvX64RegisterRflags, WHvX64RegisterRip, - WHV_REGISTER_VALUE, + WHvX64RegisterRflags, WHvX64RegisterRip, WHV_REGISTER_VALUE, }; let Some(queue) = self.pending_interrupt.as_ref() else { @@ -2004,7 +2006,8 @@ impl WhpxVcpu { (guard.len(), guard.front().copied()) }; - let log_post_completion = !source.starts_with("post-io") && !source.starts_with("post-mmio"); + let log_post_completion = + !source.starts_with("post-io") && !source.starts_with("post-mmio"); if queue_snapshot.0 == 0 { return Ok(()); @@ -2037,11 +2040,7 @@ impl WhpxVcpu { rip, format!( "source={} can_inject={} rflags=0x{:016x} depth={} front={:?}", - source, - can_inject_now, - rflags, - queue_snapshot.0, - queue_snapshot.1 + source, can_inject_now, rflags, queue_snapshot.0, queue_snapshot.1 ), ); } @@ -2166,14 +2165,9 @@ impl WhpxVcpu { if msr.MsrNumber >= 0x40000000 { windows_io_debug_log(format!( "[MSR-WR] 0x{:08x}=0x{:016x}", - msr.MsrNumber, - write_value + msr.MsrNumber, write_value )); - log::debug!( - "MSR WRMSR 0x{:08x} = 0x{:016x}", - msr.MsrNumber, - write_value - ); + log::debug!("MSR WRMSR 0x{:08x} = 0x{:016x}", msr.MsrNumber, write_value); } match msr.MsrNumber { @@ -2427,10 +2421,7 @@ impl WhpxVcpu { if should_log_mmio_gpa(pending.gpa) { windows_io_debug_log(format!( "[MMIOREAD-WRITEBACK] gpa=0x{:08x} reg={} merged=0x{:x} next_rip=0x{:x}", - pending.gpa, - pending.reg_index, - merged, - pending.next_rip + pending.gpa, pending.reg_index, merged, pending.next_rip )); } @@ -2456,8 +2447,7 @@ impl WhpxVcpu { if should_log_mmio_gpa(pending.gpa) { windows_io_debug_log(format!( "[MMIOWRITE-COMPLETE] gpa=0x{:08x} next_rip=0x{:x}", - pending.gpa, - pending.next_rip + pending.gpa, pending.next_rip )); } @@ -2771,10 +2761,7 @@ impl WhpxVcpu { rip, gpa, access_type_str, access_size ); } else if SAME_RIP_COUNT % 1000 == 0 { - debug!( - "STUCK: RIP={:#x} repeated {} times", - rip, SAME_RIP_COUNT - ); + debug!("STUCK: RIP={:#x} repeated {} times", rip, SAME_RIP_COUNT); } } else { // RIP changed, log if previous was stuck @@ -3255,7 +3242,11 @@ impl WhpxVcpu { can_inject_now, "post-cpuid", )?; - windows_exit_debug_log("post_cpuid_after_service", exit_context.VpContext.Rip, ""); + windows_exit_debug_log( + "post_cpuid_after_service", + exit_context.VpContext.Rip, + "", + ); } reason if reason == WHvRunVpExitReasonX64MsrAccess => { self.emulate_msr(&exit_context)?; @@ -3285,7 +3276,10 @@ impl WhpxVcpu { )? { continue; } - log::debug!("Interrupt window opened at RIP={:#x}", exit_context.VpContext.Rip); + log::debug!( + "Interrupt window opened at RIP={:#x}", + exit_context.VpContext.Rip + ); } reason if reason == WHvRunVpExitReasonX64ApicEoi => { // APIC EOI - interrupt was acknowledged by guest @@ -3299,7 +3293,11 @@ impl WhpxVcpu { // No state changes; re-enter VP run loop. } reason if reason == WHvRunVpExitReasonSynicSintDeliverable => { - windows_exit_debug_log("synic_sint_deliverable", exit_context.VpContext.Rip, ""); + windows_exit_debug_log( + "synic_sint_deliverable", + exit_context.VpContext.Rip, + "", + ); log::debug!( "WHPX SynIC SINT deliverable exit at RIP={:#x}; resuming vCPU", exit_context.VpContext.Rip @@ -3385,8 +3383,8 @@ impl WhpxVcpu { )? { continue; } - let halted_reenter = - windows_pending_event_halted_reenter() && self.pending_event_busy_and_halted()?; + let halted_reenter = windows_pending_event_halted_reenter() + && self.pending_event_busy_and_halted()?; windows_exit_debug_log( "canceled_post_probe", exit_context.VpContext.Rip, @@ -3620,8 +3618,7 @@ mod tests { )); // With REX prefix the same reg field maps to extended register, not high-8. - let decoded_rex = - WhpxVcpu::decode_mmio_access(0x3200, &[0x44, 0x8a, 0x20], false).unwrap(); + let decoded_rex = WhpxVcpu::decode_mmio_access(0x3200, &[0x44, 0x8a, 0x20], false).unwrap(); assert_eq!(decoded_rex.next_rip, 0x3203); assert!(matches!( decoded_rex.kind, @@ -3722,8 +3719,7 @@ mod tests { for (case, expected_rip, expected_reg, expected_high8) in read_reg_cases { let decoded = - WhpxVcpu::decode_mmio_access(case.rip, case.bytes, case.is_write) - .unwrap(); + WhpxVcpu::decode_mmio_access(case.rip, case.bytes, case.is_write).unwrap(); assert_eq!(decoded.next_rip, expected_rip); match decoded.kind { MmioAccessKind::ReadReg { reg_index, high8 } @@ -3760,8 +3756,7 @@ mod tests { for (case, expect_zero_extend, expected_reg) in zero_sign_cases { let decoded = - WhpxVcpu::decode_mmio_access(case.rip, case.bytes, case.is_write) - .unwrap(); + WhpxVcpu::decode_mmio_access(case.rip, case.bytes, case.is_write).unwrap(); if expect_zero_extend { assert!(matches!( decoded.kind, @@ -3813,8 +3808,7 @@ mod tests { ]; for case in invalid_data_cases { - let res = - WhpxVcpu::decode_mmio_access(0x7000, case.bytes, case.is_write); + let res = WhpxVcpu::decode_mmio_access(0x7000, case.bytes, case.is_write); assert!(matches!(res, Err(err) if err.kind() == case.kind)); } @@ -3846,8 +3840,7 @@ mod tests { ]; for case in unsupported_cases { - let res = - WhpxVcpu::decode_mmio_access(0x7100, case.bytes, case.is_write); + let res = WhpxVcpu::decode_mmio_access(0x7100, case.bytes, case.is_write); assert!(matches!(res, Err(err) if err.kind() == case.kind)); } }