use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use arcbox_vz::VirtualMachineState;
use objc2::runtime::AnyObject;
use crate::{
error::HypervisorError,
traits::Vcpu,
types::{CpuArch, Registers, VcpuExit, VcpuSnapshot},
};
pub struct DarwinVcpu {
id: u32,
running: Arc<AtomicBool>,
regs: Registers,
vz_vm: *mut AnyObject,
}
unsafe impl Send for DarwinVcpu {}
impl DarwinVcpu {
#[allow(dead_code)]
pub(crate) fn new(id: u32) -> Self {
Self {
id,
running: Arc::new(AtomicBool::new(false)),
regs: Registers::default(),
vz_vm: std::ptr::null_mut(),
}
}
pub(crate) fn new_managed(id: u32, vz_vm: *mut AnyObject) -> Self {
Self {
id,
running: Arc::new(AtomicBool::new(false)),
regs: Registers::default(),
vz_vm,
}
}
fn query_vm_state(&self) -> Option<VirtualMachineState> {
if self.vz_vm.is_null() {
return None;
}
unsafe {
let sel = objc2::sel!(state);
let func: unsafe extern "C" fn(*const AnyObject, objc2::runtime::Sel) -> i64 =
std::mem::transmute(objc2::ffi::objc_msgSend as *const std::ffi::c_void);
let state = func(self.vz_vm as *const AnyObject, sel);
Some(VirtualMachineState::from(state))
}
}
#[must_use]
pub fn is_running(&self) -> bool {
self.running.load(Ordering::SeqCst)
}
#[must_use]
pub fn running_flag(&self) -> Arc<AtomicBool> {
Arc::clone(&self.running)
}
pub const fn set_instruction_pointer(&mut self, ip: u64) {
self.regs.rip = ip;
}
pub const fn set_stack_pointer(&mut self, sp: u64) {
self.regs.rsp = sp;
}
#[cfg(target_arch = "x86_64")]
pub fn setup_linux_boot(&mut self, entry_point: u64, boot_params_addr: u64) {
self.regs = Registers::default();
self.regs.rip = entry_point;
self.regs.rsi = boot_params_addr;
self.regs.rflags = 0x2;
tracing::debug!(
"vCPU {} setup for Linux boot: entry={:#x}, boot_params={:#x}",
self.id,
entry_point,
boot_params_addr
);
}
#[cfg(target_arch = "aarch64")]
pub fn setup_linux_boot(&mut self, entry_point: u64, dtb_addr: u64) {
self.regs = Registers::default();
self.regs.rip = entry_point; self.regs.rax = dtb_addr;
tracing::debug!(
"vCPU {} setup for Linux boot: entry={:#x}, dtb={:#x}",
self.id,
entry_point,
dtb_addr
);
}
}
impl Vcpu for DarwinVcpu {
fn run(&mut self) -> Result<VcpuExit, HypervisorError> {
self.running.store(true, Ordering::SeqCst);
let poll_interval = Duration::from_millis(100);
if self.vz_vm.is_null() {
tracing::warn!(
"vCPU {} run() called without VZ VM pointer, returning Halt",
self.id
);
self.running.store(false, Ordering::SeqCst);
return Ok(VcpuExit::Halt);
}
tracing::debug!("vCPU {} entering managed execution wait loop", self.id);
loop {
let state = if let Some(s) = self.query_vm_state() {
s
} else {
self.running.store(false, Ordering::SeqCst);
return Ok(VcpuExit::Halt);
};
match state {
VirtualMachineState::Running => {
std::thread::sleep(poll_interval);
}
VirtualMachineState::Stopped => {
tracing::debug!("vCPU {} detected VM stopped", self.id);
self.running.store(false, Ordering::SeqCst);
return Ok(VcpuExit::Shutdown);
}
VirtualMachineState::Paused => {
tracing::debug!("vCPU {} detected VM paused", self.id);
self.running.store(false, Ordering::SeqCst);
return Ok(VcpuExit::Halt);
}
VirtualMachineState::Error => {
tracing::error!("vCPU {} detected VM error state", self.id);
self.running.store(false, Ordering::SeqCst);
return Err(HypervisorError::VcpuRunError(
"VM entered error state".to_string(),
));
}
VirtualMachineState::Starting
| VirtualMachineState::Pausing
| VirtualMachineState::Resuming
| VirtualMachineState::Stopping => {
tracing::trace!("vCPU {} VM in transitional state: {:?}", self.id, state);
std::thread::sleep(poll_interval);
}
VirtualMachineState::Saving | VirtualMachineState::Restoring => {
tracing::trace!("vCPU {} VM performing snapshot operation", self.id);
std::thread::sleep(poll_interval);
}
}
}
}
fn get_regs(&self) -> Result<Registers, HypervisorError> {
tracing::trace!(
"get_regs() called on vCPU {} - returning cached values (not live)",
self.id
);
Ok(self.regs.clone())
}
fn set_regs(&mut self, regs: &Registers) -> Result<(), HypervisorError> {
tracing::trace!(
"set_regs() called on vCPU {} - caching values (not applied to vCPU)",
self.id
);
self.regs = regs.clone();
Ok(())
}
fn id(&self) -> u32 {
self.id
}
fn set_io_result(&mut self, _value: u64) -> Result<(), HypervisorError> {
Ok(())
}
fn set_mmio_result(&mut self, _value: u64) -> Result<(), HypervisorError> {
Ok(())
}
fn snapshot(&self) -> Result<VcpuSnapshot, HypervisorError> {
tracing::warn!(
"vCPU {} snapshot: returning cached registers (not live state)",
self.id
);
#[cfg(target_arch = "aarch64")]
{
let mut arm_regs = crate::types::Arm64Registers::default();
arm_regs.pc = self.regs.rip;
arm_regs.sp = self.regs.rsp;
arm_regs.x[0] = self.regs.rax;
arm_regs.x[1] = self.regs.rbx;
arm_regs.x[2] = self.regs.rcx;
arm_regs.x[3] = self.regs.rdx;
Ok(VcpuSnapshot::new_arm64(self.id, arm_regs))
}
#[cfg(target_arch = "x86_64")]
{
Ok(VcpuSnapshot::new_x86(self.id, self.regs.clone()))
}
}
fn restore(&mut self, snapshot: &VcpuSnapshot) -> Result<(), HypervisorError> {
if snapshot.id != self.id {
return Err(HypervisorError::SnapshotError(format!(
"vCPU ID mismatch: expected {}, got {}",
self.id, snapshot.id
)));
}
tracing::warn!(
"vCPU {} restore: caching registers (not applied to vCPU)",
self.id
);
match snapshot.arch {
CpuArch::Aarch64 => {
if let Some(arm_regs) = &snapshot.arm64_regs {
self.regs.rip = arm_regs.pc;
self.regs.rsp = arm_regs.sp;
self.regs.rax = arm_regs.x[0];
self.regs.rbx = arm_regs.x[1];
self.regs.rcx = arm_regs.x[2];
self.regs.rdx = arm_regs.x[3];
}
}
CpuArch::X86_64 => {
if let Some(x86_regs) = &snapshot.x86_regs {
self.regs = x86_regs.clone();
}
}
}
Ok(())
}
}
#[cfg(target_arch = "x86_64")]
#[derive(Debug, Clone, Default)]
pub struct SpecialRegisters {
pub cs: SegmentRegister,
pub ds: SegmentRegister,
pub ss: SegmentRegister,
pub es: SegmentRegister,
pub fs: SegmentRegister,
pub gs: SegmentRegister,
pub gdt: DescriptorTable,
pub idt: DescriptorTable,
pub cr0: u64,
pub cr3: u64,
pub cr4: u64,
pub efer: u64,
}
#[cfg(target_arch = "x86_64")]
#[derive(Debug, Clone, Default)]
pub struct SegmentRegister {
pub base: u64,
pub limit: u32,
pub selector: u16,
pub type_: u8,
pub present: u8,
pub dpl: u8,
pub db: u8,
pub granularity: u8,
pub long_mode: u8,
}
#[cfg(target_arch = "x86_64")]
#[derive(Debug, Clone, Default)]
pub struct DescriptorTable {
pub base: u64,
pub limit: u16,
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_vcpu_creation() {
let vcpu = DarwinVcpu::new(0);
assert_eq!(vcpu.id(), 0);
assert!(!vcpu.is_running());
assert!(vcpu.vz_vm.is_null());
}
#[test]
fn test_vcpu_creation_managed() {
let vcpu = DarwinVcpu::new_managed(1, std::ptr::null_mut());
assert_eq!(vcpu.id(), 1);
assert!(!vcpu.is_running());
assert!(vcpu.vz_vm.is_null());
}
#[test]
fn test_vcpu_creation_with_different_ids() {
for id in [0, 1, 7, 63, 255] {
let vcpu = DarwinVcpu::new(id);
assert_eq!(vcpu.id(), id);
}
}
#[test]
fn test_vcpu_registers_cached() {
let mut vcpu = DarwinVcpu::new(0);
let mut regs = Registers::default();
regs.rax = 0x1234;
regs.rip = 0x5678;
vcpu.set_regs(®s).unwrap();
let read_regs = vcpu.get_regs().unwrap();
assert_eq!(read_regs.rax, 0x1234);
assert_eq!(read_regs.rip, 0x5678);
}
#[test]
fn test_vcpu_registers_all_gprs() {
let mut vcpu = DarwinVcpu::new(0);
let mut regs = Registers::default();
regs.rax = 0x1111_1111_1111_1111;
regs.rbx = 0x2222_2222_2222_2222;
regs.rcx = 0x3333_3333_3333_3333;
regs.rdx = 0x4444_4444_4444_4444;
regs.rsi = 0x5555_5555_5555_5555;
regs.rdi = 0x6666_6666_6666_6666;
regs.rsp = 0x7777_7777_7777_7777;
regs.rbp = 0x8888_8888_8888_8888;
regs.r8 = 0x9999_9999_9999_9999;
regs.r9 = 0xAAAA_AAAA_AAAA_AAAA;
regs.r10 = 0xBBBB_BBBB_BBBB_BBBB;
regs.r11 = 0xCCCC_CCCC_CCCC_CCCC;
regs.r12 = 0xDDDD_DDDD_DDDD_DDDD;
regs.r13 = 0xEEEE_EEEE_EEEE_EEEE;
regs.r14 = 0xFFFF_FFFF_FFFF_FFFF;
regs.r15 = 0x0123_4567_89AB_CDEF;
regs.rip = 0xDEAD_BEEF_CAFE_BABE;
regs.rflags = 0x0000_0000_0000_0202;
vcpu.set_regs(®s).unwrap();
let read_regs = vcpu.get_regs().unwrap();
assert_eq!(read_regs.rax, regs.rax);
assert_eq!(read_regs.rbx, regs.rbx);
assert_eq!(read_regs.rcx, regs.rcx);
assert_eq!(read_regs.rdx, regs.rdx);
assert_eq!(read_regs.rsi, regs.rsi);
assert_eq!(read_regs.rdi, regs.rdi);
assert_eq!(read_regs.rsp, regs.rsp);
assert_eq!(read_regs.rbp, regs.rbp);
assert_eq!(read_regs.r8, regs.r8);
assert_eq!(read_regs.r9, regs.r9);
assert_eq!(read_regs.r10, regs.r10);
assert_eq!(read_regs.r11, regs.r11);
assert_eq!(read_regs.r12, regs.r12);
assert_eq!(read_regs.r13, regs.r13);
assert_eq!(read_regs.r14, regs.r14);
assert_eq!(read_regs.r15, regs.r15);
assert_eq!(read_regs.rip, regs.rip);
assert_eq!(read_regs.rflags, regs.rflags);
}
#[test]
fn test_vcpu_registers_multiple_updates() {
let mut vcpu = DarwinVcpu::new(0);
for i in 0..10u64 {
let mut regs = Registers::default();
regs.rax = i * 1000;
regs.rip = 0x1000 + i * 0x100;
vcpu.set_regs(®s).unwrap();
let read_regs = vcpu.get_regs().unwrap();
assert_eq!(read_regs.rax, i * 1000);
assert_eq!(read_regs.rip, 0x1000 + i * 0x100);
}
}
#[test]
fn test_vcpu_run_without_vm() {
let mut vcpu = DarwinVcpu::new(0);
let exit = vcpu.run().unwrap();
assert!(matches!(exit, VcpuExit::Halt));
assert!(!vcpu.is_running());
}
#[test]
fn test_vcpu_run_with_null_managed() {
let mut vcpu = DarwinVcpu::new_managed(0, std::ptr::null_mut());
let exit = vcpu.run().unwrap();
assert!(matches!(exit, VcpuExit::Halt));
assert!(!vcpu.is_running());
}
#[test]
fn test_vcpu_run_multiple_times() {
let mut vcpu = DarwinVcpu::new(0);
for _ in 0..5 {
let exit = vcpu.run().unwrap();
assert!(matches!(exit, VcpuExit::Halt));
assert!(!vcpu.is_running());
}
}
#[test]
fn test_vcpu_running_flag_transitions() {
let mut vcpu = DarwinVcpu::new(0);
assert!(!vcpu.is_running());
let running_flag = vcpu.running_flag();
assert!(!running_flag.load(std::sync::atomic::Ordering::SeqCst));
let _ = vcpu.run();
assert!(!running_flag.load(std::sync::atomic::Ordering::SeqCst));
}
#[test]
fn test_vcpu_query_vm_state_without_vm() {
let vcpu = DarwinVcpu::new(0);
assert!(vcpu.query_vm_state().is_none());
}
#[test]
fn test_vcpu_query_vm_state_null_managed() {
let vcpu = DarwinVcpu::new_managed(0, std::ptr::null_mut());
assert!(vcpu.query_vm_state().is_none());
}
#[test]
fn test_vcpu_linux_setup() {
let mut vcpu = DarwinVcpu::new(0);
#[cfg(target_arch = "x86_64")]
{
vcpu.setup_linux_boot(0x100000, 0x10000);
let regs = vcpu.get_regs().unwrap();
assert_eq!(regs.rip, 0x100000);
assert_eq!(regs.rsi, 0x10000);
}
#[cfg(target_arch = "aarch64")]
{
vcpu.setup_linux_boot(0x40000000, 0x44000000);
let regs = vcpu.get_regs().unwrap();
assert_eq!(regs.rip, 0x40000000); assert_eq!(regs.rax, 0x44000000); }
}
#[test]
fn test_vcpu_linux_setup_clears_previous_regs() {
let mut vcpu = DarwinVcpu::new(0);
let mut garbage = Registers::default();
garbage.rax = 0xDEADBEEF;
garbage.rbx = 0xCAFEBABE;
vcpu.set_regs(&garbage).unwrap();
#[cfg(target_arch = "x86_64")]
{
vcpu.setup_linux_boot(0x100000, 0x10000);
let regs = vcpu.get_regs().unwrap();
assert_eq!(regs.rax, 0);
assert_eq!(regs.rbx, 0);
assert_eq!(regs.rip, 0x100000);
assert_eq!(regs.rsi, 0x10000);
assert_eq!(regs.rflags, 0x2); }
#[cfg(target_arch = "aarch64")]
{
vcpu.setup_linux_boot(0x40000000, 0x44000000);
let regs = vcpu.get_regs().unwrap();
assert_eq!(regs.rip, 0x40000000);
assert_eq!(regs.rax, 0x44000000);
}
}
#[test]
fn test_vcpu_set_instruction_pointer() {
let mut vcpu = DarwinVcpu::new(0);
vcpu.set_instruction_pointer(0xFFFF_0000_0000_1000);
let regs = vcpu.get_regs().unwrap();
assert_eq!(regs.rip, 0xFFFF_0000_0000_1000);
}
#[test]
fn test_vcpu_set_stack_pointer() {
let mut vcpu = DarwinVcpu::new(0);
vcpu.set_stack_pointer(0x7FFF_FFFF_FFFF_FF00);
let regs = vcpu.get_regs().unwrap();
assert_eq!(regs.rsp, 0x7FFF_FFFF_FFFF_FF00);
}
#[test]
fn test_vcpu_set_io_result_noop() {
let mut vcpu = DarwinVcpu::new(0);
let result = vcpu.set_io_result(0x1234);
assert!(result.is_ok());
for value in [0u64, 0xFF, 0xFFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF] {
assert!(vcpu.set_io_result(value).is_ok());
}
}
#[test]
fn test_vcpu_set_mmio_result_noop() {
let mut vcpu = DarwinVcpu::new(0);
let result = vcpu.set_mmio_result(0xDEAD_BEEF);
assert!(result.is_ok());
for value in [0u64, 0xFF, 0xFFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF] {
assert!(vcpu.set_mmio_result(value).is_ok());
}
}
#[test]
fn test_vcpu_running_flag_is_shared() {
let vcpu = DarwinVcpu::new(0);
let flag1 = vcpu.running_flag();
let flag2 = vcpu.running_flag();
flag1.store(true, std::sync::atomic::Ordering::SeqCst);
assert!(flag2.load(std::sync::atomic::Ordering::SeqCst));
flag2.store(false, std::sync::atomic::Ordering::SeqCst);
assert!(!flag1.load(std::sync::atomic::Ordering::SeqCst));
}
#[test]
fn test_vcpu_send_trait() {
let vcpu = DarwinVcpu::new(0);
let handle = thread::spawn(move || {
assert_eq!(vcpu.id(), 0);
});
handle.join().unwrap();
}
#[test]
fn test_vz_state_from_i64() {
assert_eq!(VirtualMachineState::from(0), VirtualMachineState::Stopped);
assert_eq!(VirtualMachineState::from(1), VirtualMachineState::Running);
assert_eq!(VirtualMachineState::from(2), VirtualMachineState::Paused);
assert_eq!(VirtualMachineState::from(3), VirtualMachineState::Error);
assert_eq!(VirtualMachineState::from(4), VirtualMachineState::Starting);
assert_eq!(VirtualMachineState::from(5), VirtualMachineState::Pausing);
assert_eq!(VirtualMachineState::from(6), VirtualMachineState::Resuming);
assert_eq!(VirtualMachineState::from(7), VirtualMachineState::Stopping);
assert_eq!(VirtualMachineState::from(8), VirtualMachineState::Saving);
assert_eq!(VirtualMachineState::from(9), VirtualMachineState::Restoring);
assert_eq!(VirtualMachineState::from(10), VirtualMachineState::Error);
assert_eq!(VirtualMachineState::from(-1), VirtualMachineState::Error);
assert_eq!(VirtualMachineState::from(100), VirtualMachineState::Error);
}
}