Skip to content
Permalink
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
KarimAllah committed Oct 20, 2019
1 parent 4eb6998 commit 40d2c0271aa7eb0062df10679e4aba95282b5e21
@@ -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() }
}
}

0 comments on commit 40d2c02

Please sign in to comment.
You can’t perform that action at this time.