diff options
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | test_harness/Cargo.toml | 3 | ||||
-rw-r--r-- | test_harness/src/lib.rs | 206 | ||||
-rw-r--r-- | test_macros/src/lib.rs | 43 | ||||
-rw-r--r-- | tests/kvm/bin.rs | 21 |
5 files changed, 192 insertions, 86 deletions
@@ -46,11 +46,6 @@ version = "0.7.7" optional = true features = ["core"] -#[target.x86_64-utest-qemu.dev-dependencies.utest-macros] -#path = "../utest/macros" -#[target.x86_64-utest-qemu.dev-dependencies.utest-x86-64-qemu] -#path = "../utest/x86-64-qemu" - [dev-dependencies] klogger = { git = "https://github.com/gz/rust-klogger.git" } test = { path = "test_harness" } diff --git a/test_harness/Cargo.toml b/test_harness/Cargo.toml index cac40e8..2e9e739 100644 --- a/test_harness/Cargo.toml +++ b/test_harness/Cargo.toml @@ -8,4 +8,5 @@ syn = "0.11.*" quote = "0.3.*" x86 = "0.*" memmap = "0.2.1" -kvm = { path = "../../kvm" } +kvm = { git = "https://github.com/gz/kvm.git" } +#kvm = { path = "../../kvm" } diff --git a/test_harness/src/lib.rs b/test_harness/src/lib.rs index 9e2d745..4456cbb 100644 --- a/test_harness/src/lib.rs +++ b/test_harness/src/lib.rs @@ -7,17 +7,23 @@ extern crate x86; use kvm::{Capability, Exit, IoDirection, Segment, System, Vcpu, VirtualMachine}; use memmap::{Mmap, Protection}; use std::fs::File; -use std::io::{BufRead, BufReader}; +use std::io::{BufRead, BufReader, Write}; +use std::io; use x86::shared::control_regs::*; use x86::shared::paging::*; use x86::bits64::paging::*; +#[no_mangle] +#[used] +pub static mut __TEST_PANICKED: bool = false; struct PageTable { backing_memory: Mmap } + type PageTableMemoryLayout = (PML4, [PDPT; 512]); + static PAGE_TABLE_P: PAddr = PAddr::from_u64(0x1000); // XXX: impl PageTable { @@ -73,6 +79,7 @@ impl PageTable { struct Stack { backing_memory: Mmap } + static STACK_BASE_T: PAddr = PAddr::from_u64(0x2000000); impl Stack { @@ -227,6 +234,7 @@ pub struct KvmTestMetaData { pub meta: &'static str, pub identity_map: bool, pub physical_memory: (u64, u64), + pub ioport_reads: (u16, u32), } /// Linker generates symbols that are inserted at the start and end of the kvm section. @@ -272,7 +280,7 @@ pub fn test_ignored(name: &str) { } pub fn test_before_run(name: &str) -> Option<&KvmTestMetaData> { - print!("test {} ... ", name); + println!("test {} ... ", name); find_meta_data(name) } @@ -303,77 +311,143 @@ pub fn test_summary(passed: usize, failed: usize, ignored: usize) { } } -#[no_mangle] -#[used] -pub static mut __TEST_PANICKED: bool = false; +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 { + fn new() -> SerialPrinter { + SerialPrinter { buffer: String::new() } + } +} + +#[derive(Debug)] +enum IoHandleError { + UnexpectedWrite(u16, u32), + UnexpectedRead(u16) +} + +enum IoHandleStatus { + Handled, + TestCompleted, +} + +fn handle_ioexit(meta: &KvmTestMetaData, 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 == 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]); + 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::TestCompleted); + } + + return Err(IoHandleError::UnexpectedWrite(io.port, regs.rax as u32)); + } + }; +} pub fn test_main_static(tests: &[TestDescAndFn]) { - unsafe { - test_start(tests.len()); - - let mut failed = 0; - let mut ignored = 0; - let mut passed = 0; - for test in tests { - if test.desc.ignore { - ignored += 1; - test_ignored(test.desc.name.0); - } else { - let meta_data = test_before_run(test.desc.name.0); - - __TEST_PANICKED = false; - match meta_data { - Some(mtd) => { - let sys = System::initialize().unwrap(); - let mut st = Stack::new(); - let mut pt = PageTable::new(); - pt.setup_identity_mapping(); - - let mut test_environment = TestEnvironment::new(&sys, &mut st, &mut pt); - let test_fn_vaddr = VAddr::from_usize(test.testfn.0 as *const () as usize); - let mut vcpu = test_environment.create_vcpu(test_fn_vaddr); - - let mut vm_is_done = false; - let mut new_regs = kvm::Regs::default(); - while !vm_is_done { - { - let (run, mut regs) = unsafe { vcpu.run_regs() }.unwrap(); - match run.exit_reason { - Exit::Io => { - let io = unsafe { *run.io() }; - match io.direction { - IoDirection::In => { - if io.port == 0x3fd { - regs.rax = 0x20; // Mark serial line as ready to write - } else { - println!("IO on unknown port: {}", io.port); - } - } - IoDirection::Out => { - if io.port == 0x3f8 { - println!("got char {:#?}", regs.rax as u8 as char); - } - } - } - } - Exit::Shutdown => { - println!("Shutting down"); + test_start(tests.len()); + + let mut failed = 0; + let mut ignored = 0; + let mut passed = 0; + for test in tests { + if test.desc.ignore { + ignored += 1; + test_ignored(test.desc.name.0); + } else { + let meta_data = test_before_run(test.desc.name.0); + + unsafe { __TEST_PANICKED = false; } + + match meta_data { + // Run this in a Virtual machine + Some(mtd) => { + let sys = System::initialize().unwrap(); + let mut st = Stack::new(); + let mut pt = PageTable::new(); + pt.setup_identity_mapping(); + let mut test_environment = TestEnvironment::new(&sys, &mut st, &mut pt); + let mut printer: SerialPrinter = SerialPrinter::new(); + + let test_fn_vaddr = VAddr::from_usize(test.testfn.0 as *const () as usize); + let mut vcpu = test_environment.create_vcpu(test_fn_vaddr); + + let mut vm_is_done = false; + while !vm_is_done { + let run = unsafe { vcpu.run() }.unwrap(); + match run.exit_reason { + Exit::Io => { + match handle_ioexit(&mtd, &mut vcpu, &run, &mut printer) { + Result::Ok(IoHandleStatus::Handled) => {/* Continue */} + Result::Ok(IoHandleStatus::TestCompleted) => vm_is_done = true, + Result::Err(err) => { + println!("Test failed due to unexpected IO: {:?}", err); vm_is_done = true; } - _ => { - println!("Unknown exit reason: {:?}", run.exit_reason); - } } - - new_regs = regs; + }, + Exit::Shutdown => { + println!("Exit::Shutdown"); + vm_is_done = true; + } + _ => { + println!("Unknown exit reason: {:?}", run.exit_reason); } - vcpu.set_regs(&new_regs).unwrap(); } + } - }, - _ => test.testfn.0() // Regular test, not running inside virtual machine - } + }, + // Regular test, execute as usual: + _ => test.testfn.0() // Regular test, not running inside virtual machine + } + unsafe { if __TEST_PANICKED == (test.desc.should_panic == ShouldPanic::Yes) { passed += 1; test_success(test.desc.name.0); @@ -382,11 +456,11 @@ pub fn test_main_static(tests: &[TestDescAndFn]) { test_failed(test.desc.name.0); } } - } - test_summary(passed, failed, ignored); } + + test_summary(passed, failed, ignored); } // required for compatibility with the `rustc --test` interface diff --git a/test_macros/src/lib.rs b/test_macros/src/lib.rs index a211112..bd89f87 100644 --- a/test_macros/src/lib.rs +++ b/test_macros/src/lib.rs @@ -31,8 +31,9 @@ use syn::parse::IResult; use std::string; use proc_macro::TokenStream; use quote::ToTokens; +use std::collections::HashMap; -fn parse_kvmtest_args(args: &syn::DeriveInput) -> ((u64, u64), bool) { +fn parse_kvmtest_args(args: &syn::DeriveInput) -> ((u64, u64), (u16, u32), bool) { if args.ident.as_ref() != "Dummy" { panic!("Get rid of this hack!"); } @@ -40,6 +41,8 @@ fn parse_kvmtest_args(args: &syn::DeriveInput) -> ((u64, u64), bool) { // If syn ever implements visitor for MetaItems, this can probably be written simpler: let mut identity_map: bool = false; let mut physical_memory: Vec<u64> = Vec::with_capacity(2); + let mut ioport_reads: Vec<u32> = vec![0,0]; + for attr in &args.attrs { match attr.value { syn::MetaItem::List(ref name, ref kvmattrs) => { @@ -59,7 +62,18 @@ fn parse_kvmtest_args(args: &syn::DeriveInput) -> ((u64, u64), bool) { &syn::NestedMetaItem::Literal(syn::Lit::Int(n, _)) => { physical_memory.push(n); }, - _ => panic!("Type mismatch in ram().") + _ => panic!("Type mismatch in ram() arguments.") + } + } + } + "ioport" => { + ioport_reads.clear(); + for (idx, innerattr) in innerattrs.iter().enumerate() { + match innerattr { + &syn::NestedMetaItem::Literal(syn::Lit::Int(n, _)) => { + ioport_reads.push(n as u32); + }, + _ => panic!("Type mismatch in ioport() arguments.") } } } @@ -87,8 +101,11 @@ fn parse_kvmtest_args(args: &syn::DeriveInput) -> ((u64, u64), bool) { if physical_memory.len() != 2 { panic!("ram() takes two values (x,y)"); } + if ioport_reads.len() != 2 { + panic!("in() takes two values (x,y)"); + } - ((physical_memory[0], physical_memory[1]) , identity_map) + ((physical_memory[0], physical_memory[1]), (ioport_reads[0] as u16, ioport_reads[1]), identity_map) } /// Add a additional meta-data and setup functions for a KVM based test. @@ -98,7 +115,7 @@ fn generate_kvmtest_meta_data(test_ident: &syn::Ident, args: &syn::DeriveInput) let setup_fn_ident = syn::Ident::new(String::from(test_name) + "_setup"); let struct_ident = syn::Ident::new(String::from(test_name) + "_kvm_meta_data"); - let (physical_memory, identity_map) = parse_kvmtest_args(args); + let (physical_memory, ioport_reads, identity_map) = parse_kvmtest_args(args); (struct_ident.clone(), quote! { @@ -108,7 +125,8 @@ fn generate_kvmtest_meta_data(test_ident: &syn::Ident, args: &syn::DeriveInput) mbz: 0, meta: #test_name, identity_map: #identity_map, - physical_memory: #physical_memory + physical_memory: #physical_memory, + ioport_reads: #ioport_reads }; }) @@ -130,6 +148,19 @@ fn insert_meta_data_reference(struct_ident: &syn::Ident, test_block: &mut syn::B test_block.stmts.insert(0, stmt); } +/// Inserts an IO out (outw) instruction at the end of the test function +/// that writes to port 0xf4 with value 0x0. outw will cause a vmexit. +/// This particular port, payload combination is handled as a special case +/// in the test runner to signal that the test has completed. +fn insert_test_shutdown(struct_ident: &syn::Ident, test_block: &mut syn::Block) { + let stmt_exit_test = String::from("unsafe { x86::shared::io::outw(0xf4, 0x00); }"); + let stmt = match syn::parse::stmt(stmt_exit_test.as_str()) { + IResult::Done(stmt_str, stmt) => stmt, + IResult::Error => panic!("Unable to generate test exit instruction."), + }; + test_block.stmts.push(stmt); +} + #[proc_macro_attribute] pub fn kvmattrs(args: TokenStream, input: TokenStream) -> TokenStream { @@ -141,6 +172,7 @@ pub fn kvmattrs(args: TokenStream, input: TokenStream) -> TokenStream { // FIXME, see also https://github.com/dtolnay/syn/issues/86 let derive_input_hack = format!("#[kvmattrs{}] struct Dummy;", args); let args_ast = syn::parse_derive_input(&derive_input_hack).unwrap(); + println!("1"); // Generate meta-data struct let ident = ast.ident.clone(); @@ -150,6 +182,7 @@ pub fn kvmattrs(args: TokenStream, input: TokenStream) -> TokenStream { match &mut ast.node { &mut syn::ItemKind::Fn(_, _, _, _, _, ref mut block) => { insert_meta_data_reference(&meta_data_ident, block); + insert_test_shutdown(&meta_data_ident, block); } _ => panic!("Not a function!"), }; diff --git a/tests/kvm/bin.rs b/tests/kvm/bin.rs index edd4095..dd16dfa 100644 --- a/tests/kvm/bin.rs +++ b/tests/kvm/bin.rs @@ -1,31 +1,34 @@ -#![feature(linkage, naked_functions, asm, const_fn, proc_macro, used)] -//RUSTFLAGS="-C relocation-model=dynamic-no-pic -C code-model=kernel" RUST_BACKTRACE=1 cargo test --verbose --test kvm -- --nocapture +#![feature(proc_macro)] +// RUSTFLAGS="-C relocation-model=dynamic-no-pic -C code-model=kernel" RUST_BACKTRACE=1 cargo test --verbose --test kvm -- --nocapture extern crate x86; extern crate core; - #[macro_use] extern crate klogger; extern crate test_macros; use test_macros::kvmattrs; - -// Needed for code generated by `kvmattrs` +// Needed for code generated by `kvmattrs`: extern crate test; use self::test::KvmTestMetaData; #[test] -#[kvmattrs(identity_map, ram(0x30000000, 0x31000000))] +#[kvmattrs(identity_map, ram(0x30000000, 0x31000000), ioport(0x1, 0xfe))] fn use_the_port() { - log!("1"); + sprintln!("1"); unsafe { - asm!("inb $0, %al" :: "i"(0x01) :: "volatile"); + if (x86::shared::io::inw(0x1) == 0xfe) { + sprintln!("worked"); + } } + sprintln!("2"); } #[test] #[kvmattrs(identity_map, ram(0x30000000, 0x31000000))] fn io_example2() { - assert!(1==1); + sprintln!("1"); + //assert!(1 == 0); + sprintln!("2"); } |