Skip to content

Commit f5474e5

Browse files
committed
Add some vm unit tests
Signed-off-by: Ludvig Liljenberg <4257730+ludfjig@users.noreply.github.com>
1 parent ac309a1 commit f5474e5

File tree

1 file changed

+262
-0
lines changed
  • src/hyperlight_host/src/hypervisor/virtual_machine

1 file changed

+262
-0
lines changed

src/hyperlight_host/src/hypervisor/virtual_machine/mod.rs

Lines changed: 262 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,29 @@ pub(crate) trait VirtualMachine: Debug + Send {
351351

352352
#[cfg(test)]
353353
mod 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

Comments
 (0)