From 430ccd170dc9d06e96c636fc628d2e5afd7970c9 Mon Sep 17 00:00:00 2001 From: Erin Date: Wed, 9 Aug 2023 02:33:03 +0200 Subject: [PATCH] Von-Neumann? --- hbvm/fuzz/Cargo.toml | 3 + hbvm/fuzz/fuzz_targets/vm.rs | 34 +++++++---- hbvm/src/lib.rs | 107 +++++++++++++++++++++-------------- hbvm/src/main.rs | 15 +++-- hbvm/src/softpaging/mod.rs | 43 ++++++++------ 5 files changed, 126 insertions(+), 76 deletions(-) diff --git a/hbvm/fuzz/Cargo.toml b/hbvm/fuzz/Cargo.toml index f6bc61644..42566bd34 100644 --- a/hbvm/fuzz/Cargo.toml +++ b/hbvm/fuzz/Cargo.toml @@ -13,6 +13,9 @@ libfuzzer-sys = "0.4" [dependencies.hbvm] path = ".." +[dependencies.hbbytecode] +path = "../../hbbytecode" + # Prevent this from interfering with workspaces [workspace] members = ["."] diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index e1d426645..b10c6d960 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -1,15 +1,30 @@ #![no_main] use { + hbbytecode::valider::validate, hbvm::{ - mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}, - Vm, + softpaging::{ + paging::{PageTable, Permission}, + HandlePageFault, PageSize, SoftPagedMem, + }, + MemoryAccessReason, Vm, }, libfuzzer_sys::fuzz_target, }; fuzz_target!(|data: &[u8]| { - if let Ok(mut vm) = Vm::<_, 16384>::new_validated(data, TestTrapHandler, Default::default()) { + if validate(data).is_ok() { + let mut vm = unsafe { + Vm::<_, 16384>::new( + SoftPagedMem { + pf_handler: TestTrapHandler, + program: data, + root_pt: Box::into_raw(Default::default()), + }, + 0, + ) + }; + // Alloc and map some memory let pages = [ alloc_and_map(&mut vm.memory, 0), @@ -26,22 +41,17 @@ fuzz_target!(|data: &[u8]| { } }); -fn alloc_and_map(memory: &mut Memory, at: u64) -> *mut u8 { +fn alloc_and_map(memory: &mut SoftPagedMem, at: u64) -> *mut u8 { let ptr = Box::into_raw(Box::::default()).cast(); unsafe { memory - .map( - ptr, - at, - hbvm::mem::paging::Permission::Write, - PageSize::Size4K, - ) + .map(ptr, at, Permission::Write, PageSize::Size4K) .unwrap() }; ptr } -fn unmap_and_dealloc(memory: &mut Memory, ptr: *mut u8, from: u64) { +fn unmap_and_dealloc(memory: &mut SoftPagedMem, ptr: *mut u8, from: u64) { memory.unmap(from).unwrap(); let _ = unsafe { Box::from_raw(ptr.cast::()) }; } @@ -59,7 +69,7 @@ impl HandlePageFault for TestTrapHandler { fn page_fault( &mut self, _: MemoryAccessReason, - _: &mut Memory, + _: &mut PageTable, _: u64, _: PageSize, _: *mut u8, diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index 0cad1cd7d..752bf515d 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -14,8 +14,6 @@ #![cfg_attr(feature = "nightly", feature(fn_align))] #![warn(missing_docs, clippy::missing_docs_in_private_items)] -use core::marker::PhantomData; - #[cfg(feature = "alloc")] extern crate alloc; @@ -26,16 +24,14 @@ mod bmc; use { bmc::BlockCopier, - core::{cmp::Ordering, mem::size_of, ops}, + core::{cmp::Ordering, mem::size_of, ops, slice::SliceIndex}, derive_more::Display, - hbbytecode::{ - valider, OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, - }, + hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD}, value::{Value, ValueVariant}, }; /// HoleyBytes Virtual Machine -pub struct Vm<'a, Mem, const TIMER_QUOTIENT: usize> { +pub struct Vm { /// Holds 256 registers /// /// Writing to register 0 is considered undefined behaviour @@ -48,15 +44,6 @@ pub struct Vm<'a, Mem, const TIMER_QUOTIENT: usize> { /// Program counter pub pc: usize, - /// Program - program: *const u8, - - /// Cached program length (without unreachable end) - program_len: usize, - - /// Program lifetime - _program_lt: PhantomData<&'a [u8]>, - /// Program timer timer: usize, @@ -64,7 +51,7 @@ pub struct Vm<'a, Mem, const TIMER_QUOTIENT: usize> { copier: Option, } -impl<'a, Mem, const TIMER_QUOTIENT: usize> Vm<'a, Mem, TIMER_QUOTIENT> +impl Vm where Mem: Memory, { @@ -72,25 +59,16 @@ where /// /// # Safety /// Program code has to be validated - pub unsafe fn new_unchecked(program: &'a [u8], memory: Mem) -> Self { + pub unsafe fn new(memory: Mem, entry: u64) -> Self { Self { registers: [Value::from(0_u64); 256], memory, - pc: 0, - program_len: program.len() - 12, - program: program[4..].as_ptr(), - _program_lt: Default::default(), + pc: entry as _, timer: 0, copier: None, } } - /// Create a new VM with program and trap handler only if it passes validation - pub fn new_validated(program: &'a [u8], memory: Mem) -> Result { - valider::validate(program)?; - Ok(unsafe { Self::new_unchecked(program, memory) }) - } - /// Execute program /// /// Program can return [`VmRunError`] if a trap handling failed @@ -98,11 +76,6 @@ where pub fn run(&mut self) -> Result { use hbbytecode::opcode::*; loop { - // Check instruction boundary - if self.pc >= self.program_len { - return Err(VmRunError::AddrOutOfBounds); - } - // Big match // // Contribution guide: @@ -123,7 +96,11 @@ where // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU // sorry 8 bit fans, HBVM won't run on your Speccy :( unsafe { - match *self.program.add(self.pc) { + match *self + .memory + .load_prog(self.pc) + .ok_or(VmRunError::ProgramFetchLoadEx(self.pc as _))? + { UN => { self.decode::<()>(); return Err(VmRunError::Unreachable); @@ -388,15 +365,22 @@ where } /// Decode instruction operands - #[inline] + #[inline(always)] unsafe fn decode(&mut self) -> T { - let data = self.program.add(self.pc + 1).cast::().read(); + let pc1 = self.pc + 1; + let data = self + .memory + .load_prog_unchecked(pc1..pc1 + size_of::()) + .as_ptr() + .cast::() + .read(); + self.pc += 1 + size_of::(); data } /// Perform binary operating over two registers - #[inline] + #[inline(always)] unsafe fn binary_op(&mut self, op: impl Fn(T, T) -> T) { let ParamBBB(tg, a0, a1) = self.decode(); self.write_reg( @@ -406,7 +390,7 @@ where } /// Perform binary operation over register and immediate - #[inline] + #[inline(always)] unsafe fn binary_op_imm(&mut self, op: impl Fn(T, T) -> T) { let ParamBBD(tg, reg, imm) = self.decode(); self.write_reg( @@ -416,14 +400,14 @@ where } /// Perform binary operation over register and shift immediate - #[inline] + #[inline(always)] unsafe fn binary_op_ims(&mut self, op: impl Fn(T, u32) -> T) { let ParamBBW(tg, reg, imm) = self.decode(); self.write_reg(tg, op(self.read_reg(reg).cast::(), imm)); } /// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected - #[inline] + #[inline(always)] unsafe fn cond_jmp(&mut self, expected: Ordering) { let ParamBBD(a0, a1, ja) = self.decode(); if self @@ -437,14 +421,14 @@ where } /// Read register - #[inline] + #[inline(always)] unsafe fn read_reg(&self, n: u8) -> Value { *self.registers.get_unchecked(n as usize) } /// Write a register. /// Writing to register 0 is no-op. - #[inline] + #[inline(always)] unsafe fn write_reg(&mut self, n: u8, value: impl Into) { if n != 0 { *self.registers.get_unchecked_mut(n as usize) = value.into(); @@ -452,7 +436,7 @@ where } /// Load / Store Address check-computation überfunction - #[inline] + #[inline(always)] unsafe fn ldst_addr_uber( &self, dst: u8, @@ -485,6 +469,9 @@ pub enum VmRunError { /// Unhandled load access exception LoadAccessEx(u64), + /// Unhandled instruction load access exception + ProgramFetchLoadEx(u64), + /// Unhandled store access exception StoreAccessEx(u64), @@ -529,6 +516,40 @@ pub trait Memory { source: *const u8, count: usize, ) -> Result<(), StoreError>; + + /// Fetch bytes from program section + /// + /// # Why? + /// Even Holey Bytes programs operate with + /// single address space, the actual implementation + /// may be different, so for these reasons there is a + /// separate function. + /// + /// Also if your memory implementation differentiates between + /// readable and executable memory, this is the way to distinguish + /// the loads. + /// + /// # Notice for implementors + /// This is a hot function. This is called on each opcode fetch + /// and instruction decode. Inlining the implementation is highly + /// recommended! + /// + /// If you utilise some more heavy memory implementation, consider + /// performing caching as HBVM does not do that for you. + /// + /// Has to return all the requested data. If cannot fetch data of requested + /// length, return [`None`]. + fn load_prog(&mut self, index: I) -> Option<&I::Output> + where + I: SliceIndex<[u8]>; + + /// Fetch bytes from program section, unchecked. + /// + /// # Safety + /// You really have to be sure you get the bytes, got me? + unsafe fn load_prog_unchecked(&mut self, index: I) -> &I::Output + where + I: SliceIndex<[u8]>; } /// Unhandled load access trap diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index a2325b258..11e08dde7 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -10,15 +10,20 @@ use { fn main() -> Result<(), Box> { let mut prog = vec![]; stdin().read_to_end(&mut prog)?; - println!("{prog:?}"); if let Err(e) = validate(&prog) { eprintln!("Program validation error: {e:?}"); return Ok(()); } else { unsafe { - let mut vm = - Vm::<_, 0>::new_unchecked(&prog, SoftPagedMem::::default()); + let mut vm = Vm::<_, 0>::new( + SoftPagedMem { + pf_handler: TestTrapHandler, + program: &prog, + root_pt: Box::into_raw(Default::default()), + }, + 0, + ); let data = { let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked( 4096, 4096, @@ -32,7 +37,7 @@ fn main() -> Result<(), Box> { vm.memory .map( data, - 0, + 8192, hbvm::softpaging::paging::Permission::Write, PageSize::Size4K, ) @@ -46,7 +51,7 @@ fn main() -> Result<(), Box> { data, std::alloc::Layout::from_size_align_unchecked(4096, 4096), ); - vm.memory.unmap(0).unwrap(); + vm.memory.unmap(8192).unwrap(); } } Ok(()) diff --git a/hbvm/src/softpaging/mod.rs b/hbvm/src/softpaging/mod.rs index 96005a417..ca2d3cd2c 100644 --- a/hbvm/src/softpaging/mod.rs +++ b/hbvm/src/softpaging/mod.rs @@ -4,6 +4,7 @@ pub mod paging; use { super::{LoadError, Memory, MemoryAccessReason, StoreError}, + core::slice::SliceIndex, derive_more::Display, paging::{PageTable, Permission}, }; @@ -13,14 +14,16 @@ use {alloc::boxed::Box, paging::PtEntry}; /// HoleyBytes software paged memory #[derive(Clone, Debug)] -pub struct SoftPagedMem { +pub struct SoftPagedMem<'p, PfH> { /// Root page table pub root_pt: *mut PageTable, /// Page fault handler - pub pf_handler: PfHandler, + pub pf_handler: PfH, + /// Program memory segment + pub program: &'p [u8], } -impl Memory for SoftPagedMem { +impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { /// Load value from an address /// /// # Safety @@ -57,9 +60,27 @@ impl Memory for SoftPagedMem { ) .map_err(StoreError) } + + /// Fetch slice from program memory section + #[inline(always)] + fn load_prog(&mut self, index: I) -> Option<&I::Output> + where + I: SliceIndex<[u8]>, + { + self.program.get(index) + } + + /// Fetch slice from program memory section, unchecked! + #[inline(always)] + unsafe fn load_prog_unchecked(&mut self, index: I) -> &I::Output + where + I: SliceIndex<[u8]>, + { + self.program.get_unchecked(index) + } } -impl SoftPagedMem { +impl<'p, PfH: HandlePageFault> SoftPagedMem<'p, PfH> { // Everyone behold, the holy function, the god of HBVM memory accesses! /// Split address to pages, check their permissions and feed pointers with offset @@ -239,24 +260,14 @@ impl Iterator for AddrPageLookuper { } #[cfg(feature = "alloc")] -impl Default for SoftPagedMem { - fn default() -> Self { - Self { - root_pt: Box::into_raw(Default::default()), - pf_handler: Default::default(), - } - } -} - -#[cfg(feature = "alloc")] -impl Drop for SoftPagedMem { +impl<'p, A> Drop for SoftPagedMem<'p, A> { fn drop(&mut self) { let _ = unsafe { Box::from_raw(self.root_pt) }; } } #[cfg(feature = "alloc")] -impl SoftPagedMem { +impl<'p, A> SoftPagedMem<'p, A> { /// Maps host's memory into VM's memory /// /// # Safety