@@ -351,6 +351,29 @@ pub(crate) trait VirtualMachine: Debug + Send {
351351
352352#[ cfg( test) ]
353353mod tests {
354+ use super :: * ;
355+ use crate :: hypervisor:: regs:: { CommonSegmentRegister , CommonTableRegister } ;
356+
357+ fn boxed_vm ( ) -> Box < dyn VirtualMachine > {
358+ let available_vm = get_available_hypervisor ( ) . as_ref ( ) . unwrap ( ) ;
359+ match available_vm {
360+ #[ cfg( kvm) ]
361+ HypervisorType :: Kvm => {
362+ use crate :: hypervisor:: virtual_machine:: kvm:: KvmVm ;
363+ Box :: new ( KvmVm :: new ( ) . unwrap ( ) )
364+ }
365+ #[ cfg( mshv3) ]
366+ HypervisorType :: Mshv => {
367+ use crate :: hypervisor:: virtual_machine:: mshv:: MshvVm ;
368+ Box :: new ( MshvVm :: new ( ) . unwrap ( ) )
369+ }
370+ #[ cfg( target_os = "windows" ) ]
371+ HypervisorType :: Whp => {
372+ use crate :: hypervisor:: virtual_machine:: whp:: WhpVm ;
373+ Box :: new ( WhpVm :: new ( ) . unwrap ( ) )
374+ }
375+ }
376+ }
354377
355378 #[ test]
356379 // TODO: add support for testing on WHP
@@ -370,4 +393,243 @@ mod tests {
370393 }
371394 }
372395 }
396+
397+ #[ test]
398+ fn regs ( ) {
399+ let vm = boxed_vm ( ) ;
400+
401+ let regs = CommonRegisters {
402+ rax : 1 ,
403+ rbx : 2 ,
404+ rcx : 3 ,
405+ rdx : 4 ,
406+ rsi : 5 ,
407+ rdi : 6 ,
408+ rsp : 7 ,
409+ rbp : 8 ,
410+ r8 : 9 ,
411+ r9 : 10 ,
412+ r10 : 11 ,
413+ r11 : 12 ,
414+ r12 : 13 ,
415+ r13 : 14 ,
416+ r14 : 15 ,
417+ r15 : 16 ,
418+ rip : 17 ,
419+ rflags : 0x2 ,
420+ } ;
421+
422+ vm. set_regs ( & regs) . unwrap ( ) ;
423+ let read_regs = vm. regs ( ) . unwrap ( ) ;
424+ assert_eq ! ( regs, read_regs) ;
425+ }
426+
427+ #[ test]
428+ fn fpu ( ) {
429+ let vm = boxed_vm ( ) ;
430+
431+ // x87 FPU registers are 80-bit (10 bytes), stored in 16-byte slots for alignment.
432+ // Only the first 10 bytes are preserved; the remaining 6 bytes are reserved/zeroed.
433+ // See Intel® 64 and IA-32 Architectures SDM, Vol. 1, Sec. 10.5.1.1 (x87 State)
434+ let fpr_entry: [ u8 ; 16 ] = [ 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 1 , 0 , 0 , 0 , 0 , 0 , 0 ] ;
435+ let fpu = CommonFpu {
436+ fpr : [ fpr_entry; 8 ] ,
437+ fcw : 2 ,
438+ fsw : 3 ,
439+ ftwx : 4 ,
440+ last_opcode : 5 ,
441+ last_ip : 6 ,
442+ last_dp : 7 ,
443+ xmm : [ [ 8 ; 16 ] ; 16 ] ,
444+ mxcsr : 9 ,
445+ } ;
446+ vm. set_fpu ( & fpu) . unwrap ( ) ;
447+ #[ cfg_attr( not( kvm) , allow( unused_mut) ) ]
448+ let mut read_fpu = vm. fpu ( ) . unwrap ( ) ;
449+ #[ cfg( kvm) ]
450+ {
451+ read_fpu. mxcsr = fpu. mxcsr ; // KVM get/set fpu does not preserve mxcsr
452+ }
453+ assert_eq ! ( fpu, read_fpu) ;
454+ }
455+
456+ #[ test]
457+ fn sregs ( ) {
458+ let vm = boxed_vm ( ) ;
459+
460+ let data_segment = CommonSegmentRegister {
461+ base : 1 ,
462+ limit : 2 ,
463+ selector : 3 ,
464+ type_ : 3 ,
465+ present : 1 ,
466+ dpl : 1 ,
467+ db : 0 ,
468+ s : 1 , // non-system (code/data) segment
469+ l : 0 , // must be 0 for data segments
470+ g : 0 ,
471+ avl : 1 ,
472+ unusable : 0 ,
473+ padding : 0 ,
474+ } ;
475+
476+ let cs_segment = CommonSegmentRegister {
477+ base : 1 ,
478+ limit : 0xFFFF ,
479+ selector : 0x08 ,
480+ type_ : 0b1011 , // code segment, execute/read, accessed
481+ present : 1 ,
482+ dpl : 1 ,
483+ db : 0 , // must be 0 in 64-bit mode
484+ s : 1 ,
485+ l : 1 , // 64-bit mode
486+ g : 0 , // KVM normalizes g to 0 for segments with small limits
487+ avl : 1 ,
488+ unusable : 0 ,
489+ padding : 0 ,
490+ } ;
491+
492+ let tr_segment = CommonSegmentRegister {
493+ base : 1 ,
494+ limit : 2 ,
495+ selector : 3 ,
496+ type_ : 0b1011 , // 64-bit TSS (busy)
497+ present : 1 ,
498+ dpl : 0 ,
499+ db : 0 ,
500+ s : 0 , // system segment
501+ l : 0 ,
502+ g : 0 ,
503+ avl : 0 ,
504+ unusable : 0 ,
505+ padding : 0 ,
506+ } ;
507+
508+ let ldt_segment = CommonSegmentRegister {
509+ base : 1 ,
510+ limit : 2 ,
511+ selector : 3 ,
512+ type_ : 0b0010 , // LDT
513+ present : 1 ,
514+ dpl : 0 ,
515+ db : 0 ,
516+ s : 0 , // system segment
517+ l : 0 ,
518+ g : 0 ,
519+ avl : 0 ,
520+ unusable : 0 ,
521+ padding : 0 ,
522+ } ;
523+
524+ let table = CommonTableRegister {
525+ base : 12 ,
526+ limit : 13 ,
527+ } ;
528+ let sregs = CommonSpecialRegisters {
529+ cs : cs_segment,
530+ ds : data_segment,
531+ es : data_segment,
532+ fs : data_segment,
533+ gs : data_segment,
534+ ss : data_segment,
535+ tr : tr_segment,
536+ ldt : ldt_segment,
537+ gdt : table,
538+ idt : table,
539+ cr0 : 0x80000011 , // bit 0 (PE) + bit 4 (ET) + bit 31 (PG)
540+ cr2 : 2 ,
541+ cr3 : 3 ,
542+ cr4 : 0x20 ,
543+ cr8 : 5 ,
544+ efer : 0x500 ,
545+ apic_base : 0xFEE00900 ,
546+ interrupt_bitmap : [ 0 ; 4 ] ,
547+ } ;
548+ vm. set_sregs ( & sregs) . unwrap ( ) ;
549+ let read_sregs = vm. sregs ( ) . unwrap ( ) ;
550+ assert_eq ! ( sregs, read_sregs) ;
551+ }
552+
553+ /// Helper to create a page-aligned memory region for testing
554+ #[ cfg( any( kvm, mshv3) ) ]
555+ fn create_test_memory ( size : usize ) -> crate :: mem:: shared_mem:: ExclusiveSharedMemory {
556+ use hyperlight_common:: mem:: PAGE_SIZE_USIZE ;
557+ let aligned_size = size. div_ceil ( PAGE_SIZE_USIZE ) * PAGE_SIZE_USIZE ;
558+ crate :: mem:: shared_mem:: ExclusiveSharedMemory :: new ( aligned_size) . unwrap ( )
559+ }
560+
561+ /// Helper to create a MemoryRegion from ExclusiveSharedMemory
562+ #[ cfg( any( kvm, mshv3) ) ]
563+ fn region_for_test_memory (
564+ mem : & crate :: mem:: shared_mem:: ExclusiveSharedMemory ,
565+ guest_base : usize ,
566+ flags : crate :: mem:: memory_region:: MemoryRegionFlags ,
567+ ) -> MemoryRegion {
568+ use crate :: mem:: memory_region:: MemoryRegionType ;
569+ use crate :: mem:: shared_mem:: SharedMemory ;
570+ let ptr = mem. base_addr ( ) ;
571+ let len = mem. mem_size ( ) ;
572+ MemoryRegion {
573+ host_region : ptr..( ptr + len) ,
574+ guest_region : guest_base..( guest_base + len) ,
575+ flags,
576+ region_type : MemoryRegionType :: Heap ,
577+ }
578+ }
579+
580+ #[ test]
581+ #[ cfg( any( kvm, mshv3) ) ] // Requires memory mapping support (TODO on WHP)
582+ fn map_memory ( ) {
583+ use crate :: mem:: memory_region:: MemoryRegionFlags ;
584+
585+ let mut vm = boxed_vm ( ) ;
586+
587+ let mem1 = create_test_memory ( 4096 ) ;
588+ let guest_addr: usize = 0x1000 ;
589+ let region = region_for_test_memory (
590+ & mem1,
591+ guest_addr,
592+ MemoryRegionFlags :: READ | MemoryRegionFlags :: WRITE ,
593+ ) ;
594+
595+ // SAFETY: The memory region points to valid memory allocated by ExclusiveSharedMemory,
596+ // and will live until we drop mem1 at the end of the test.
597+ // Slot 0 is not already mapped.
598+ unsafe {
599+ vm. map_memory ( ( 0 , & region) ) . unwrap ( ) ;
600+ }
601+
602+ // Unmap the region
603+ vm. unmap_memory ( ( 0 , & region) ) . unwrap ( ) ;
604+
605+ // Unmapping a region that was already unmapped should fail
606+ vm. unmap_memory ( ( 0 , & region) ) . unwrap_err ( ) ;
607+
608+ // Unmapping a region that was never mapped should fail
609+ vm. unmap_memory ( ( 99 , & region) ) . unwrap_err ( ) ;
610+
611+ // Re-map the same region to a different slot
612+ // SAFETY: Same as above - memory is still valid and slot 1 is not mapped.
613+ unsafe {
614+ vm. map_memory ( ( 1 , & region) ) . unwrap ( ) ;
615+ }
616+
617+ // Map a second region to a different slot
618+ let mem2 = create_test_memory ( 4096 ) ;
619+ let guest_addr2: usize = 0x2000 ;
620+ let region2 = region_for_test_memory (
621+ & mem2,
622+ guest_addr2,
623+ MemoryRegionFlags :: READ | MemoryRegionFlags :: WRITE ,
624+ ) ;
625+
626+ // SAFETY: Memory is valid from ExclusiveSharedMemory, slot 2 is not mapped.
627+ unsafe {
628+ vm. map_memory ( ( 2 , & region2) ) . unwrap ( ) ;
629+ }
630+
631+ // Clean up: unmap both regions
632+ vm. unmap_memory ( ( 1 , & region) ) . unwrap ( ) ;
633+ vm. unmap_memory ( ( 2 , & region2) ) . unwrap ( ) ;
634+ }
373635}
0 commit comments