Permalink
Please
sign in to comment.
Browse files
testing: Add baremetal testing infrastructure
... and use it to test launching a guest using "libvmm" and validate register accesses from VMX non-root mode.
- Loading branch information
Showing
with
852 additions
and 0 deletions.
- +5 −0 testing/.cargo/config
- +22 −0 testing/Cargo.toml
- +15 −0 testing/Makefile
- +2 −0 testing/Xargo.toml
- +22 −0 testing/src/heap.rs
- +48 −0 testing/src/kernel.rs
- +82 −0 testing/src/lib.rs
- +16 −0 testing/src/msr.rs
- +37 −0 testing/src/output.rs
- +66 −0 testing/src/page_alloc.rs
- +10 −0 testing/src/test.s
- +24 −0 testing/src/vm.rs
- +488 −0 testing/src/vmm.rs
- +15 −0 testing/x86_64-bare-metal.json
| @@ -0,0 +1,5 @@ | ||
| [build] | ||
| target = "x86_64-bare-metal" | ||
|
|
||
| [target.'cfg(target_os = "none")'] | ||
| runner = "bootimage runner" |
| @@ -0,0 +1,22 @@ | ||
| [package] | ||
| name = "testing" | ||
| version = "0.1.0" | ||
| authors = ["KarimAllah Ahmed <karim.allah.ahmed@gmail.com>"] | ||
| edition = "2018" | ||
|
|
||
| [dependencies] | ||
| bootloader = { version = "0.6.4", features = ["map_physical_memory"]} | ||
| x86_64 = "0.7.5" | ||
| rlibc = "1.0.0" | ||
| libvmm = { path = "../../libvmm" } | ||
| lazy_static = { version = "1.3.0", features = ["spin_no_std"] } | ||
|
|
||
| [package.metadata.bootimage] | ||
| test-args = [ | ||
| "-enable-kvm", | ||
| "-cpu", "kvm64,+vmx", | ||
| "-device", "isa-debug-exit,iobase=0xf4,iosize=0x04", | ||
| "-serial", "stdio", | ||
| "-display", "none", | ||
| ] | ||
| test-success-exit-code = 33 |
| @@ -0,0 +1,15 @@ | ||
| export RUST_TARGET_PATH:=${PWD} | ||
|
|
||
| .PHONY: test | ||
|
|
||
| run: test | ||
| xargo xtest | ||
|
|
||
| test: ./src/test.s | ||
| gcc -c ./src/test.s -o ./src/test.o | ||
| objcopy -O binary ./src/test.o ./src/test | ||
| rm ./src/test.o | ||
|
|
||
| clean: | ||
| xargo clean | ||
| rm ./src/test |
| @@ -0,0 +1,2 @@ | ||
| [dependencies] | ||
| alloc = {} |
| @@ -0,0 +1,22 @@ | ||
| use alloc::alloc::{GlobalAlloc, Layout}; | ||
| use core::ptr::null_mut; | ||
|
|
||
| #[global_allocator] | ||
| static ALLOCATOR: Dummy = Dummy; | ||
|
|
||
| pub struct Dummy; | ||
|
|
||
| unsafe impl GlobalAlloc for Dummy { | ||
| unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { | ||
| null_mut() | ||
| } | ||
|
|
||
| unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { | ||
| panic!("dealloc should be never called") | ||
| } | ||
| } | ||
|
|
||
| #[alloc_error_handler] | ||
| pub fn alloc_error_handler(layout: Layout) -> ! { | ||
| panic!("allocation error: {:?}", layout) | ||
| } |
| @@ -0,0 +1,48 @@ | ||
| use lazy_static::lazy_static; | ||
| use bootloader::BootInfo; | ||
| use x86_64::registers::control::{Cr0, Cr0Flags}; | ||
| use x86_64::structures::gdt::*; | ||
| use x86_64::structures::tss::*; | ||
| use x86_64::instructions::segmentation::*; | ||
| use x86_64::instructions::tables::*; | ||
| use crate::vm::VM; | ||
| use crate::page_alloc::page_alloc_init; | ||
|
|
||
| static TSS: TaskStateSegment = TaskStateSegment::new(); | ||
|
|
||
| lazy_static! { | ||
| static ref GDT: GlobalDescriptorTable = { | ||
| let mut gdt = GlobalDescriptorTable::new(); | ||
|
|
||
| let code = Descriptor::kernel_code_segment(); | ||
| let tss = Descriptor::tss_segment(&TSS); | ||
|
|
||
| gdt.add_entry(code); | ||
| gdt.add_entry(tss); | ||
| gdt | ||
| }; | ||
| } | ||
|
|
||
| fn init_segmentation() { | ||
| GDT.load(); | ||
|
|
||
| unsafe { | ||
| load_es(SegmentSelector(0)); | ||
| load_ss(SegmentSelector(0)); | ||
| load_ds(SegmentSelector(0)); | ||
| load_fs(SegmentSelector(0)); | ||
| load_gs(SegmentSelector(0)); | ||
| set_cs(SegmentSelector(0x8)); | ||
| load_tss(SegmentSelector(0x10)) | ||
| } | ||
| } | ||
|
|
||
| pub fn kernel_init(boot_info: &'static BootInfo) { | ||
| unsafe { | ||
| Cr0::write_raw(Cr0::read_raw() | Cr0Flags::NUMERIC_ERROR.bits()); | ||
| } | ||
|
|
||
| VM::set_phys_offset(boot_info.physical_memory_offset); | ||
| page_alloc_init(boot_info); | ||
| init_segmentation(); | ||
| } |
| @@ -0,0 +1,82 @@ | ||
| #![no_std] | ||
| #![cfg_attr(test, no_main)] | ||
| #![feature(custom_test_frameworks)] | ||
| #![test_runner(crate::test_runner)] | ||
| #![reexport_test_harness_main = "test_main"] | ||
| #![feature(alloc_error_handler)] | ||
| #![feature(asm)] | ||
|
|
||
| extern crate alloc; | ||
| extern crate lazy_static; | ||
| extern crate bootloader; | ||
| extern crate x86_64; | ||
| extern crate rlibc; | ||
| #[macro_use] | ||
| extern crate libvmm; | ||
|
|
||
| #[macro_use] | ||
| mod output; | ||
| mod vmm; | ||
| mod kernel; | ||
| mod vm; | ||
| mod msr; | ||
| mod page_alloc; | ||
| mod heap; | ||
|
|
||
| use core::panic::PanicInfo; | ||
| use bootloader::{BootInfo, entry_point}; | ||
| use crate::kernel::*; | ||
| use crate::vmm::run_guest; | ||
|
|
||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| #[repr(u32)] | ||
| pub enum QemuExitCode { | ||
| Success = 0x10, | ||
| Failed = 0x11, | ||
| } | ||
|
|
||
| pub fn exit_qemu(exit_code: QemuExitCode) { | ||
| use x86_64::instructions::port::Port; | ||
|
|
||
| unsafe { | ||
| let mut port = Port::new(0xf4); | ||
| port.write(exit_code as u32); | ||
| } | ||
| } | ||
|
|
||
| #[test_case] | ||
| fn simple_register_access() { | ||
| assert_eq!(run_guest(), true); | ||
| println!("[PASS ] simple register access"); | ||
| } | ||
|
|
||
| pub fn test_runner(tests: &[&dyn Fn()]) { | ||
| println!("[START]"); | ||
| for test in tests { | ||
| test(); | ||
| } | ||
| println!("[END ]"); | ||
| exit_qemu(QemuExitCode::Success); | ||
| } | ||
|
|
||
|
|
||
| #[cfg(test)] | ||
| entry_point!(kernel_main); | ||
| fn kernel_main(boot_info: &'static BootInfo) -> ! { | ||
| kernel_init(boot_info); | ||
|
|
||
| #[cfg(test)] | ||
| test_main(); | ||
|
|
||
| exit_qemu(QemuExitCode::Success); | ||
| loop {} | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| #[panic_handler] | ||
| fn panic(info: &PanicInfo) -> ! { | ||
| println!("[FAIL ]\n"); | ||
| println!("Error: {}\n", info); | ||
| exit_qemu(QemuExitCode::Failed); | ||
| loop {} | ||
| } |
| @@ -0,0 +1,16 @@ | ||
| pub const MSR_IA32_APIC_BASE: u32 = 0x1b; | ||
| pub const MSR_IA32_FEATURE_CONTROL: u32 = 0x3a; | ||
| pub const MSR_IA32_SYSENTER_CS: u32 = 0x174; | ||
| pub const MSR_IA32_SYSENTER_ESP: u32 = 0x175; | ||
| pub const MSR_IA32_SYSENTER_EIP: u32 = 0x176; | ||
| pub const MSR_IA32_DEBUGCTLMSR: u32 = 0x1d9; | ||
| pub const MSR_IA32_CR_PAT: u32 = 0x277; | ||
| pub const MSR_IA32_VMX_BASIC: u32 = 0x480; | ||
| pub const MSR_IA32_VMX_PINBASED_CTLS: u32 = 0x481; | ||
| pub const MSR_IA32_VMX_PROCBASED_CTLS: u32 = 0x482; | ||
| pub const MSR_IA32_VMX_EXIT_CTLS: u32 = 0x483; | ||
| pub const MSR_IA32_VMX_ENTRY_CTLS: u32 = 0x484; | ||
| pub const MSR_IA32_VMX_PROCBASED_CTLS2: u32 = 0x48b; | ||
| pub const MSR_IA32_TSC_DEADLINE: u32 = 0x6e0; | ||
| pub const MSR_FS_BASE: u32 = 0xc0000100; | ||
| pub const MSR_GS_BASE: u32 = 0xc0000101; |
| @@ -0,0 +1,37 @@ | ||
| use core::fmt; | ||
| use core::fmt::Write; | ||
|
|
||
| pub struct Writer; | ||
|
|
||
| impl Writer { | ||
| pub fn write_string(&mut self, s: &str) { | ||
| for byte in s.bytes() { | ||
| unsafe { | ||
| x86_64::instructions::port::PortWrite::write_to_port(0x3f8, byte as u8); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl fmt::Write for Writer { | ||
| fn write_str(&mut self, s: &str) -> fmt::Result { | ||
| self.write_string(s); | ||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[macro_export] | ||
| macro_rules! print { | ||
| ($($arg:tt)*) => ($crate::output::_print(format_args!($($arg)*))); | ||
| } | ||
|
|
||
| #[macro_export] | ||
| macro_rules! println { | ||
| () => ($crate::print!("\n")); | ||
| ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); | ||
| } | ||
|
|
||
| #[doc(hidden)] | ||
| pub fn _print(args: fmt::Arguments) { | ||
| Writer.write_fmt(args).unwrap(); | ||
| } |
| @@ -0,0 +1,66 @@ | ||
| use bootloader::BootInfo; | ||
| use bootloader::bootinfo::{MemoryMap, MemoryRegionType}; | ||
| use x86_64::structures::paging::FrameAllocator; | ||
| use x86_64::structures::paging::PhysFrame; | ||
| use x86_64::PhysAddr; | ||
| use x86_64::structures::paging::Size4KiB; | ||
|
|
||
| static mut ALLOCATOR: Option<BootInfoFrameAllocator> = None; | ||
|
|
||
| /// A FrameAllocator that returns usable frames from the bootloader's memory map. | ||
| pub struct BootInfoFrameAllocator { | ||
| memory_map: &'static MemoryMap, | ||
| next: usize, | ||
| } | ||
|
|
||
| impl BootInfoFrameAllocator { | ||
| /// Create a FrameAllocator from the passed memory map. | ||
| /// | ||
| /// This function is unsafe because the caller must guarantee that the passed | ||
| /// memory map is valid. The main requirement is that all frames that are marked | ||
| /// as `USABLE` in it are really unused. | ||
| pub unsafe fn init(memory_map: &'static MemoryMap) -> Self { | ||
| BootInfoFrameAllocator { | ||
| memory_map, | ||
| next: 0, | ||
| } | ||
| } | ||
|
|
||
| /// Returns an iterator over the usable frames specified in the memory map. | ||
| fn usable_frames(&self) -> impl Iterator<Item = PhysFrame> { | ||
| // get usable regions from memory map | ||
| let regions = self.memory_map.iter(); | ||
| let usable_regions = regions | ||
| .filter(|r| r.region_type == MemoryRegionType::Usable); | ||
| // map each region to its address range | ||
| let addr_ranges = usable_regions | ||
| .map(|r| r.range.start_addr()..r.range.end_addr()); | ||
| // transform to an iterator of frame start addresses | ||
| let frame_addresses = addr_ranges.flat_map(|r| r.step_by(4096)); | ||
| // create `PhysFrame` types from the start addresses | ||
| frame_addresses | ||
| .map(|addr|PhysFrame::containing_address(PhysAddr::new(addr))) | ||
| } | ||
| } | ||
|
|
||
| unsafe impl FrameAllocator<Size4KiB> for BootInfoFrameAllocator { | ||
| fn allocate_frame(&mut self) -> Option<PhysFrame> { | ||
| let frame = self.usable_frames().nth(self.next); | ||
| self.next += 1; | ||
| frame | ||
| } | ||
| } | ||
|
|
||
| pub fn page_alloc() -> &'static mut BootInfoFrameAllocator { | ||
| unsafe { | ||
| ALLOCATOR.as_mut().unwrap() | ||
| } | ||
| } | ||
|
|
||
| pub fn page_alloc_init(boot_info: &'static BootInfo) { | ||
| unsafe { | ||
| ALLOCATOR.replace( | ||
| BootInfoFrameAllocator::init(&boot_info.memory_map) | ||
| ); | ||
| } | ||
| } |
| @@ -0,0 +1,10 @@ | ||
| .code16 | ||
| mov $0x1, %al | ||
| mov $0x2, %bl | ||
| mov $0x3, %cl | ||
| mov $0x4, %dl | ||
| mov $0x5, %di | ||
| mov $0x6, %si | ||
| mov $0x7, %bp | ||
|
|
||
| hlt |
| @@ -0,0 +1,24 @@ | ||
| use x86_64::structures::paging::PhysFrame; | ||
|
|
||
| pub struct VM; | ||
|
|
||
| static mut PHYS_OFFSET: Option<u64> = None; | ||
| static mut ROOT: Option<PhysFrame> = None; | ||
|
|
||
| impl VM { | ||
| pub fn phys_offset() -> u64 { | ||
| unsafe { PHYS_OFFSET.unwrap() } | ||
| } | ||
|
|
||
| pub fn set_phys_offset(offset: u64) { | ||
| unsafe { PHYS_OFFSET.replace(offset); } | ||
| } | ||
|
|
||
| pub fn phys_to_virt(phys: u64) -> u64 { | ||
| phys + VM::phys_offset() | ||
| } | ||
|
|
||
| pub fn root_mm() -> PhysFrame { | ||
| unsafe { ROOT.unwrap() } | ||
| } | ||
| } |
Oops, something went wrong.
0 comments on commit
40d2c02