From d282b3d111e75413a47fabf6166288e6c5d06307 Mon Sep 17 00:00:00 2001 From: Erin Date: Fri, 18 Aug 2023 01:28:02 +0200 Subject: [PATCH] Softpage improvements --- Cargo.toml | 3 +- hbbytecode/src/lib.rs | 33 ++++----- hbvm/fuzz/fuzz_targets/vm.rs | 7 +- hbvm/src/lib.rs | 10 +-- hbvm/src/main.rs | 5 +- hbvm/src/mem/softpaging/icache.rs | 103 +++++++++++++++++++++++++++++ hbvm/src/mem/softpaging/lookup.rs | 2 +- hbvm/src/mem/softpaging/mapping.rs | 2 +- hbvm/src/mem/softpaging/mod.rs | 28 +++++++- 9 files changed, 161 insertions(+), 32 deletions(-) create mode 100644 hbvm/src/mem/softpaging/icache.rs diff --git a/Cargo.toml b/Cargo.toml index 7fc1ade..1c77a26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,2 +1,3 @@ [workspace] -members = ["hbasm", "hbbytecode", "hbvm"] +resolver = "2" +members = ["hbasm", "hbbytecode", "hbvm"] diff --git a/hbbytecode/src/lib.rs b/hbbytecode/src/lib.rs index 6dda4da..3f1c14b 100644 --- a/hbbytecode/src/lib.rs +++ b/hbbytecode/src/lib.rs @@ -35,12 +35,12 @@ macro_rules! constmod { /// - I: Immediate /// - L: Memory load / store size (u16) /// - Other types are identity-mapped -/// +/// /// # BRC special-case /// BRC's 3rd operand is plain byte, not a register. Encoding is the same, but for some cases it may matter. -/// +/// /// Please, if you distinguish in your API between byte and register, special case this one. -/// +/// /// Sorry for that :( #[macro_export] macro_rules! invoke_with_def { @@ -134,34 +134,29 @@ constmod!(pub opcode(u8) { #[repr(packed)] pub struct ParamBBBB(pub u8, pub u8, pub u8, pub u8); - #[repr(packed)] pub struct ParamBBB(pub u8, pub u8, pub u8); - #[repr(packed)] pub struct ParamBBDH(pub u8, pub u8, pub u64, pub u16); - #[repr(packed)] pub struct ParamBBD(pub u8, pub u8, pub u64); - #[repr(packed)] pub struct ParamBBW(pub u8, pub u8, pub u32); - #[repr(packed)] pub struct ParamBB(pub u8, pub u8); - #[repr(packed)] pub struct ParamBD(pub u8, pub u64); /// # Safety /// Has to be valid to be decoded from bytecode. -pub unsafe trait OpParam {} -unsafe impl OpParam for ParamBBBB {} -unsafe impl OpParam for ParamBBB {} -unsafe impl OpParam for ParamBBDH {} -unsafe impl OpParam for ParamBBD {} -unsafe impl OpParam for ParamBBW {} -unsafe impl OpParam for ParamBB {} -unsafe impl OpParam for ParamBD {} -unsafe impl OpParam for u64 {} -unsafe impl OpParam for () {} +pub unsafe trait ProgramVal {} +unsafe impl ProgramVal for ParamBBBB {} +unsafe impl ProgramVal for ParamBBB {} +unsafe impl ProgramVal for ParamBBDH {} +unsafe impl ProgramVal for ParamBBD {} +unsafe impl ProgramVal for ParamBBW {} +unsafe impl ProgramVal for ParamBB {} +unsafe impl ProgramVal for ParamBD {} +unsafe impl ProgramVal for u64 {} +unsafe impl ProgramVal for u8 {} // Opcode +unsafe impl ProgramVal for () {} diff --git a/hbvm/fuzz/fuzz_targets/vm.rs b/hbvm/fuzz/fuzz_targets/vm.rs index e79679f..f733600 100644 --- a/hbvm/fuzz/fuzz_targets/vm.rs +++ b/hbvm/fuzz/fuzz_targets/vm.rs @@ -16,10 +16,11 @@ fuzz_target!(|data: &[u8]| { if validate(data).is_ok() { let mut vm = unsafe { Vm::<_, 16384>::new( - SoftPagedMem { + SoftPagedMem::<_, true> { pf_handler: TestTrapHandler, program: data, root_pt: Box::into_raw(Default::default()), + icache: Default::default(), }, 0, ) @@ -31,6 +32,8 @@ fuzz_target!(|data: &[u8]| { alloc_and_map(&mut vm.memory, 4096), ]; + unsafe { vm.memory.write() }; + // Run VM let _ = vm.run(); @@ -38,6 +41,8 @@ fuzz_target!(|data: &[u8]| { for (i, page) in pages.into_iter().enumerate() { unmap_and_dealloc(&mut vm.memory, page, i as u64 * 4096); } + + let _ = unsafe { Box::from_raw(vm.memory.root_pt) }; } }); diff --git a/hbvm/src/lib.rs b/hbvm/src/lib.rs index a87366b..22d39fe 100644 --- a/hbvm/src/lib.rs +++ b/hbvm/src/lib.rs @@ -26,7 +26,9 @@ use { bmc::BlockCopier, core::{cmp::Ordering, mem::size_of, ops}, derive_more::Display, - hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD}, + hbbytecode::{ + ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, ProgramVal, + }, value::{Value, ValueVariant}, }; @@ -366,7 +368,7 @@ where /// Decode instruction operands #[inline(always)] - unsafe fn decode(&mut self) -> T { + unsafe fn decode(&mut self) -> T { let pc1 = self.pc + 1; let data = self.memory.prog_read_unchecked::(pc1 as _); self.pc += 1 + size_of::(); @@ -515,13 +517,13 @@ pub trait Memory { /// /// # Safety /// - Data read have to be valid - unsafe fn prog_read(&mut self, addr: u64) -> Option; + unsafe fn prog_read(&mut self, addr: u64) -> Option; /// Read from program memory to exectue /// /// # Safety /// - You have to be really sure that these bytes are there, understand? - unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T; + unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T; } /// Unhandled load access trap diff --git a/hbvm/src/main.rs b/hbvm/src/main.rs index d61d329..04d654d 100644 --- a/hbvm/src/main.rs +++ b/hbvm/src/main.rs @@ -17,10 +17,11 @@ fn main() -> Result<(), Box> { } else { unsafe { let mut vm = Vm::<_, 0>::new( - SoftPagedMem { + SoftPagedMem::<_, true> { pf_handler: TestTrapHandler, program: &prog, root_pt: Box::into_raw(Default::default()), + icache: Default::default(), }, 4, ); @@ -46,12 +47,12 @@ fn main() -> Result<(), Box> { println!("Program interrupt: {:?}", vm.run()); println!("{:?}", vm.registers); - println!("{:?}", core::slice::from_raw_parts(data, 4096)); std::alloc::dealloc( data, std::alloc::Layout::from_size_align_unchecked(4096, 4096), ); vm.memory.unmap(8192).unwrap(); + let _ = Box::from_raw(vm.memory.root_pt); } } Ok(()) diff --git a/hbvm/src/mem/softpaging/icache.rs b/hbvm/src/mem/softpaging/icache.rs new file mode 100644 index 0000000..049d26b --- /dev/null +++ b/hbvm/src/mem/softpaging/icache.rs @@ -0,0 +1,103 @@ +//! Program instruction cache + +use { + super::{lookup::AddrPageLookuper, paging::PageTable, PageSize}, + core::{ + mem::{size_of, MaybeUninit}, + ptr::{copy_nonoverlapping, NonNull}, + }, +}; + +/// Instruction cache +#[derive(Clone, Debug)] +pub struct ICache { + /// Current page address base + base: u64, + /// Curent page pointer + data: Option>, + /// Current page size + size: PageSize, + /// Address mask + mask: u64, +} + +impl Default for ICache { + fn default() -> Self { + Self { + base: Default::default(), + data: Default::default(), + size: PageSize::Size4K, + mask: Default::default(), + } + } +} + +impl ICache { + /// Fetch instruction from cache + /// + /// # Safety + /// `T` should be valid to read from instruction memory + pub(super) unsafe fn fetch(&mut self, addr: u64, root_pt: *const PageTable) -> Option { + let mut ret = MaybeUninit::::uninit(); + + let pbase = self + .data + .or_else(|| self.fetch_page(self.base.checked_add(self.size as _)?, root_pt))?; + + // Get address base + let base = addr & self.mask; + + // Base not matching, fetch anew + if base != self.base { + self.fetch_page(base, root_pt)?; + }; + + let offset = addr & !self.mask; + let requ_size = size_of::(); + + // Page overflow + let rem = (offset as usize) + .saturating_add(requ_size) + .saturating_sub(self.size as _); + let first_copy = requ_size.saturating_sub(rem); + + // Copy non-overflowing part + copy_nonoverlapping(pbase.as_ptr(), ret.as_mut_ptr().cast::(), first_copy); + + // Copy overflow + if rem != 0 { + let pbase = self.fetch_page(self.base.checked_add(self.size as _)?, root_pt)?; + + // Unlikely, unsupported scenario + if rem > self.size as _ { + return None; + } + + copy_nonoverlapping( + pbase.as_ptr(), + ret.as_mut_ptr().cast::().add(first_copy), + rem, + ); + } + + Some(ret.assume_init()) + } + + /// Fetch a page + unsafe fn fetch_page(&mut self, addr: u64, pt: *const PageTable) -> Option> { + let res = AddrPageLookuper::new(addr, 0, pt).next()?.ok()?; + if !super::perm_check::executable(res.perm) { + return None; + } + + (self.size, self.mask) = match res.size { + 4096 => (PageSize::Size4K, !((1 << 8) - 1)), + 2097152 => (PageSize::Size2M, !((1 << (8 * 2)) - 1)), + 1073741824 => (PageSize::Size1G, !((1 << (8 * 3)) - 1)), + _ => return None, + }; + self.data = Some(NonNull::new(res.ptr)?); + self.base = addr & self.mask; + self.data + } +} diff --git a/hbvm/src/mem/softpaging/lookup.rs b/hbvm/src/mem/softpaging/lookup.rs index 9111390..1e9c3e3 100644 --- a/hbvm/src/mem/softpaging/lookup.rs +++ b/hbvm/src/mem/softpaging/lookup.rs @@ -111,7 +111,7 @@ impl Iterator for AddrPageLookuper { }; // Get available byte count in the selected page with offset - let avail = (size as usize - offset).clamp(0, self.size); + let avail = (size as usize).saturating_sub(offset).clamp(0, self.size); self.bump(size); Some(Ok(AddrPageLookupOk { diff --git a/hbvm/src/mem/softpaging/mapping.rs b/hbvm/src/mem/softpaging/mapping.rs index 0d6858e..8f406a4 100644 --- a/hbvm/src/mem/softpaging/mapping.rs +++ b/hbvm/src/mem/softpaging/mapping.rs @@ -10,7 +10,7 @@ use { derive_more::Display, }; -impl<'p, A> SoftPagedMem<'p, A> { +impl<'p, A, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, A, OUT_PROG_EXEC> { /// Maps host's memory into VM's memory /// /// # Safety diff --git a/hbvm/src/mem/softpaging/mod.rs b/hbvm/src/mem/softpaging/mod.rs index 6ed66dc..11fa465 100644 --- a/hbvm/src/mem/softpaging/mod.rs +++ b/hbvm/src/mem/softpaging/mod.rs @@ -2,6 +2,9 @@ use core::mem::size_of; +use self::icache::ICache; + +pub mod icache; pub mod lookup; pub mod paging; @@ -15,17 +18,25 @@ use { }; /// HoleyBytes software paged memory +/// +/// - `OUT_PROG_EXEC`: set to `false` to disable executing program +/// not contained in initially provided program, even the pages +/// are executable #[derive(Clone, Debug)] -pub struct SoftPagedMem<'p, PfH> { +pub struct SoftPagedMem<'p, PfH, const OUT_PROG_EXEC: bool = true> { /// Root page table pub root_pt: *mut PageTable, /// Page fault handler pub pf_handler: PfH, /// Program memory segment pub program: &'p [u8], + /// Program instruction cache + pub icache: ICache, } -impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { +impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> Memory + for SoftPagedMem<'p, PfH, OUT_PROG_EXEC> +{ /// Load value from an address /// /// # Safety @@ -65,6 +76,10 @@ impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { #[inline(always)] unsafe fn prog_read(&mut self, addr: u64) -> Option { + if OUT_PROG_EXEC && addr as usize > self.program.len() { + return self.icache.fetch::(addr, self.root_pt); + } + let addr = addr as usize; self.program .get(addr..addr + size_of::()) @@ -73,11 +88,18 @@ impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> { #[inline(always)] unsafe fn prog_read_unchecked(&mut self, addr: u64) -> T { + if OUT_PROG_EXEC && addr as usize > self.program.len() { + return self + .icache + .fetch::(addr as _, self.root_pt) + .unwrap_or_else(|| core::mem::zeroed()); + } + self.program.as_ptr().add(addr as _).cast::().read() } } -impl<'p, PfH: HandlePageFault> SoftPagedMem<'p, PfH> { +impl<'p, PfH: HandlePageFault, const OUT_PROG_EXEC: bool> SoftPagedMem<'p, PfH, OUT_PROG_EXEC> { // Everyone behold, the holy function, the god of HBVM memory accesses! /// Split address to pages, check their permissions and feed pointers with offset