diff options
author | 2019-08-23 17:24:35 -0700 | |
---|---|---|
committer | 2019-08-23 17:24:35 -0700 | |
commit | 46617737ffe418286a7d9fb3e3d53f032a7489e0 (patch) | |
tree | 8f17d447b937717ca592fbe83a91c92f55f6fd8c /x86test/src/hypervisor/mod.rs | |
parent | adb8d50b7d57fe52ebdbeaebfd1c79e9ab53342a (diff) | |
download | rust-x86-46617737ffe418286a7d9fb3e3d53f032a7489e0.tar.gz rust-x86-46617737ffe418286a7d9fb3e3d53f032a7489e0.tar.zst rust-x86-46617737ffe418286a7d9fb3e3d53f032a7489e0.zip |
Rename kvmtest to x86test.
Diffstat (limited to 'x86test/src/hypervisor/mod.rs')
-rw-r--r-- | x86test/src/hypervisor/mod.rs | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/x86test/src/hypervisor/mod.rs b/x86test/src/hypervisor/mod.rs new file mode 100644 index 0000000..177300a --- /dev/null +++ b/x86test/src/hypervisor/mod.rs @@ -0,0 +1,294 @@ +//! Silly little hypervisor based on KVM +use std::fs::File; +use std::io; +use std::io::{BufRead, BufReader, Write}; +use std::slice; + +use kvm::{Capability, IoDirection, Segment, System, Vcpu, VirtualMachine}; +use mmap::{MemoryMap, MapOption}; + +use crate::X86TestFn; +use x86::bits64::paging::*; +use x86::controlregs::*; + +mod vspace; + +use hypervisor::vspace::VSpace; + +pub(crate) struct PhysicalMemory { + offset: usize, + allocated: usize, + size: usize, + backing_memory: MemoryMap, +} + +impl PhysicalMemory { + /// Allocate a chunk of memory that is handed out as "physical memory" + /// to allocate page-tables etc. + pub(crate) fn new(offset: u64) -> PhysicalMemory { + let size = 4 * (1<<20); + + let options = [MapOption::MapAddr(offset as *const u8), MapOption::MapReadable, MapOption::MapWritable, MapOption::MapExecutable]; + let mm = MemoryMap::new(size, &options).unwrap(); + + PhysicalMemory { + offset: offset as usize, + allocated: 0, + size: size, + backing_memory: mm, + } + } + + fn as_mut_slice<'a>(&'a mut self) -> &'a mut [u8] { + unsafe { slice::from_raw_parts_mut(self.backing_memory.data(), self.size) } + } + + fn len(&self) -> usize { + self.size + } + + fn alloc_pages(&mut self, how_many: u64) -> *mut u8 { + let to_allocate = how_many as usize * BASE_PAGE_SIZE; + if self.allocated + to_allocate > self.size { + panic!("OOM") + } + + let ptr = (self.offset + self.allocated) as *mut u8; + self.allocated += to_allocate; + ptr + } +} + +pub(crate) struct TestEnvironment<'a> { + sys: &'a System, + heap: &'a mut PhysicalMemory, + stack: &'a mut PhysicalMemory, + vspace: VSpace<'a>, + vm: VirtualMachine<'a>, +} + +impl<'a> TestEnvironment<'a> { + pub(crate) fn new( + sys: &'a System, + stack: &'a mut PhysicalMemory, + heap: &'a mut PhysicalMemory, + ptables: &'a mut PhysicalMemory, + //vspace: VSpace, + ) -> TestEnvironment<'a> { + let vspace = VSpace::new(ptables); + let mut vm = VirtualMachine::create(sys).unwrap(); + // Ensure that the VM supports memory backing with user memory + assert!(vm.check_capability(Capability::UserMemory) > 0); + + TestEnvironment { + heap: heap, + stack: stack, + vspace: vspace, + sys: sys, + vm: vm, + } + } + + /// Map the page table memory and stack memory + pub(crate) fn create_vcpu(&'a mut self, init_fn: VAddr) -> kvm::Vcpu { + + // Map the process + let f = File::open("/proc/self/maps").unwrap(); + let reader = BufReader::new(f); + + for line in reader.lines() { + let line = line.unwrap(); + let mut s = line.split(' '); + let mut s2 = s.next().unwrap().split('-'); + let begin = usize::from_str_radix(s2.next().unwrap(), 16).unwrap(); + let end = usize::from_str_radix(s2.next().unwrap(), 16).unwrap(); + if end <= 0x800000000000 { + //println!("{:#X} -- {:#X} {}", begin, end, s.next().unwrap()); + let slice = { + let begin_ptr: *mut u8 = begin as *const u8 as _; + unsafe { ::std::slice::from_raw_parts_mut(begin_ptr, end - begin) } + }; + + //Set-up hypervisor by making all our memory available to the "guest"-test + self.vm + .set_user_memory_region(begin as _, slice, 0) + .expect("Can't set user memory region!"); + + // Set-up guest page-table by 1:1 mapping everything + self.vspace.map_identity(PAddr::from(begin), PAddr::from(end), vspace::MapAction::ReadWriteExecuteKernel); + } + } + + let mut vcpu = Vcpu::create(&mut self.vm).unwrap(); + // Set supported CPUID (KVM fails without doing this) + let mut cpuid = self.sys.get_supported_cpuid().unwrap(); + vcpu.set_cpuid2(&mut cpuid).unwrap(); + + // Setup the special registers + let mut sregs = vcpu.get_sregs().unwrap(); + + // Set the code segment to have base 0, limit 4GB (flat segmentation) + let segment_template = Segment { + base: 0x0, + limit: 0xffffffff, + selector: 0, + _type: 0, + present: 0, + dpl: 0, + db: 1, + s: 0, + l: 0, + g: 1, + avl: 0, + ..Default::default() + }; + + sregs.cs = Segment { + selector: 0x8, + _type: 0xb, + present: 1, + db: 0, + s: 1, + l: 1, + ..segment_template + }; + sregs.ss = Segment { ..segment_template }; + sregs.ds = Segment { ..segment_template }; + sregs.es = Segment { ..segment_template }; + sregs.fs = Segment { ..segment_template }; + sregs.gs = Segment { ..segment_template }; + + // We don't need to populate the GDT if we have our segments setup + // cr0 - protected mode on, paging enabled + sregs.cr0 = (Cr0::CR0_PROTECTED_MODE + | Cr0::CR0_MONITOR_COPROCESSOR + | Cr0::CR0_EXTENSION_TYPE + | Cr0::CR0_ENABLE_PAGING + | Cr0::CR0_NUMERIC_ERROR + | Cr0::CR0_WRITE_PROTECT + | Cr0::CR0_ALIGNMENT_MASK + | Cr0::CR0_ENABLE_PAGING) + .bits() as u64; + sregs.cr3 = 0x9000000; + sregs.cr4 = (Cr4::CR4_ENABLE_PSE + | Cr4::CR4_ENABLE_PAE + | Cr4::CR4_ENABLE_GLOBAL_PAGES + | Cr4::CR4_ENABLE_SSE + | Cr4::CR4_UNMASKED_SSE + | Cr4::CR4_ENABLE_OS_XSAVE + | Cr4::CR4_ENABLE_SMEP + | Cr4::CR4_ENABLE_VME) + .bits() as u64; + sregs.efer = 0xd01; // XXX + + // Set the special registers + vcpu.set_sregs(&sregs).unwrap(); + + let mut regs = vcpu.get_regs().unwrap(); + + // Set the instruction and stack pointer + let stack_size = self.stack.len(); + regs.rip = init_fn.as_usize() as u64; + regs.rflags = 0x246; // XXX + regs.rsp = 0x3000000 + stack_size as u64 - 8; + regs.rbp = regs.rsp; + + vcpu.set_regs(®s).unwrap(); + + vcpu + } +} + +pub(crate) struct SerialPrinter { + buffer: String, +} + +impl Write for SerialPrinter { + fn write(&mut self, buf: &[u8]) -> io::Result<usize> { + assert!(buf.len() == 1); + self.buffer.push(buf[0] as char); + match buf[0] as char { + '\n' => { + std::io::stdout().write(self.buffer.as_bytes())?; + self.buffer.clear(); + } + _ => {} + } + + Ok(1) + } + + fn flush(&mut self) -> io::Result<()> { + std::io::stdout().write(self.buffer.as_bytes())?; + self.buffer.clear(); + Ok(()) + } +} + +impl SerialPrinter { + pub(crate) fn new() -> SerialPrinter { + SerialPrinter { + buffer: String::new(), + } + } +} + +#[derive(Debug)] +pub(crate) enum IoHandleError { + UnexpectedWrite(u16, u32), + UnexpectedRead(u16), +} + +pub(crate) enum IoHandleStatus { + Handled, + TestSuccessful, + TestPanic(u8), +} + +pub(crate) fn handle_ioexit( + meta: &X86TestFn, + cpu: &mut Vcpu, + run: &kvm::Run, + printer: &mut SerialPrinter, +) -> Result<IoHandleStatus, IoHandleError> { + let io = unsafe { *run.io() }; + + match io.direction { + IoDirection::In => { + let mut regs = cpu.get_regs().unwrap(); + if io.port == 0x3fd { + regs.rax = 0x20; // Mark serial line ready to write + cpu.set_regs(®s).unwrap(); + return Ok(IoHandleStatus::Handled); + } else if io.port == 0x2fd { + regs.rax = 0x20; // Mark serial line ready to write + cpu.set_regs(®s).unwrap(); + return Ok(IoHandleStatus::Handled); + } else if io.port == meta.ioport_reads.0 { + regs.rax = meta.ioport_reads.1 as u64; + cpu.set_regs(®s).unwrap(); + return Ok(IoHandleStatus::Handled); + } + return Err(IoHandleError::UnexpectedRead(io.port)); + } + IoDirection::Out => { + let regs = cpu.get_regs().unwrap(); + if io.port == 0x3f8 { + printer.write(&[regs.rax as u8]).ok(); + return Ok(IoHandleStatus::Handled); + } else if io.port == 0x2f8 { + // ignore the other serial port that klogger outputs to by default + return Ok(IoHandleStatus::Handled); + } else if io.port == 0xf4 && regs.rax as u8 == 0x0 { + // Magic shutdown command for exiting the test. + // The line unsafe { x86::shared::io::outw(0xf4, 0x00); } + // is automatically inserted at the end of every test! + return Ok(IoHandleStatus::TestSuccessful); + } else if io.port == 0xf4 { + return Ok(IoHandleStatus::TestPanic(regs.rax as u8)); + } + + return Err(IoHandleError::UnexpectedWrite(io.port, regs.rax as u32)); + } + }; +} |