Von-Neumann?

This commit is contained in:
Erin 2023-08-09 02:33:03 +02:00
parent eadf9e0a1f
commit 430ccd170d
5 changed files with 126 additions and 76 deletions

View file

@ -13,6 +13,9 @@ libfuzzer-sys = "0.4"
[dependencies.hbvm] [dependencies.hbvm]
path = ".." path = ".."
[dependencies.hbbytecode]
path = "../../hbbytecode"
# Prevent this from interfering with workspaces # Prevent this from interfering with workspaces
[workspace] [workspace]
members = ["."] members = ["."]

View file

@ -1,15 +1,30 @@
#![no_main] #![no_main]
use { use {
hbbytecode::valider::validate,
hbvm::{ hbvm::{
mem::{HandlePageFault, Memory, MemoryAccessReason, PageSize}, softpaging::{
Vm, paging::{PageTable, Permission},
HandlePageFault, PageSize, SoftPagedMem,
},
MemoryAccessReason, Vm,
}, },
libfuzzer_sys::fuzz_target, libfuzzer_sys::fuzz_target,
}; };
fuzz_target!(|data: &[u8]| { 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 // Alloc and map some memory
let pages = [ let pages = [
alloc_and_map(&mut vm.memory, 0), 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<TestTrapHandler>, at: u64) -> *mut u8 {
let ptr = Box::into_raw(Box::<Page>::default()).cast(); let ptr = Box::into_raw(Box::<Page>::default()).cast();
unsafe { unsafe {
memory memory
.map( .map(ptr, at, Permission::Write, PageSize::Size4K)
ptr,
at,
hbvm::mem::paging::Permission::Write,
PageSize::Size4K,
)
.unwrap() .unwrap()
}; };
ptr ptr
} }
fn unmap_and_dealloc(memory: &mut Memory, ptr: *mut u8, from: u64) { fn unmap_and_dealloc(memory: &mut SoftPagedMem<TestTrapHandler>, ptr: *mut u8, from: u64) {
memory.unmap(from).unwrap(); memory.unmap(from).unwrap();
let _ = unsafe { Box::from_raw(ptr.cast::<Page>()) }; let _ = unsafe { Box::from_raw(ptr.cast::<Page>()) };
} }
@ -59,7 +69,7 @@ impl HandlePageFault for TestTrapHandler {
fn page_fault( fn page_fault(
&mut self, &mut self,
_: MemoryAccessReason, _: MemoryAccessReason,
_: &mut Memory, _: &mut PageTable,
_: u64, _: u64,
_: PageSize, _: PageSize,
_: *mut u8, _: *mut u8,

View file

@ -14,8 +14,6 @@
#![cfg_attr(feature = "nightly", feature(fn_align))] #![cfg_attr(feature = "nightly", feature(fn_align))]
#![warn(missing_docs, clippy::missing_docs_in_private_items)] #![warn(missing_docs, clippy::missing_docs_in_private_items)]
use core::marker::PhantomData;
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
extern crate alloc; extern crate alloc;
@ -26,16 +24,14 @@ mod bmc;
use { use {
bmc::BlockCopier, bmc::BlockCopier,
core::{cmp::Ordering, mem::size_of, ops}, core::{cmp::Ordering, mem::size_of, ops, slice::SliceIndex},
derive_more::Display, derive_more::Display,
hbbytecode::{ hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD},
valider, OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD,
},
value::{Value, ValueVariant}, value::{Value, ValueVariant},
}; };
/// HoleyBytes Virtual Machine /// HoleyBytes Virtual Machine
pub struct Vm<'a, Mem, const TIMER_QUOTIENT: usize> { pub struct Vm<Mem, const TIMER_QUOTIENT: usize> {
/// Holds 256 registers /// Holds 256 registers
/// ///
/// Writing to register 0 is considered undefined behaviour /// Writing to register 0 is considered undefined behaviour
@ -48,15 +44,6 @@ pub struct Vm<'a, Mem, const TIMER_QUOTIENT: usize> {
/// Program counter /// Program counter
pub pc: usize, 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 /// Program timer
timer: usize, timer: usize,
@ -64,7 +51,7 @@ pub struct Vm<'a, Mem, const TIMER_QUOTIENT: usize> {
copier: Option<BlockCopier>, copier: Option<BlockCopier>,
} }
impl<'a, Mem, const TIMER_QUOTIENT: usize> Vm<'a, Mem, TIMER_QUOTIENT> impl<Mem, const TIMER_QUOTIENT: usize> Vm<Mem, TIMER_QUOTIENT>
where where
Mem: Memory, Mem: Memory,
{ {
@ -72,25 +59,16 @@ where
/// ///
/// # Safety /// # Safety
/// Program code has to be validated /// 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 { Self {
registers: [Value::from(0_u64); 256], registers: [Value::from(0_u64); 256],
memory, memory,
pc: 0, pc: entry as _,
program_len: program.len() - 12,
program: program[4..].as_ptr(),
_program_lt: Default::default(),
timer: 0, timer: 0,
copier: None, 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<Self, valider::Error> {
valider::validate(program)?;
Ok(unsafe { Self::new_unchecked(program, memory) })
}
/// Execute program /// Execute program
/// ///
/// Program can return [`VmRunError`] if a trap handling failed /// Program can return [`VmRunError`] if a trap handling failed
@ -98,11 +76,6 @@ where
pub fn run(&mut self) -> Result<VmRunOk, VmRunError> { pub fn run(&mut self) -> Result<VmRunOk, VmRunError> {
use hbbytecode::opcode::*; use hbbytecode::opcode::*;
loop { loop {
// Check instruction boundary
if self.pc >= self.program_len {
return Err(VmRunError::AddrOutOfBounds);
}
// Big match // Big match
// //
// Contribution guide: // Contribution guide:
@ -123,7 +96,11 @@ where
// - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU // - Yes, we assume you run 64 bit CPU. Else ?conradluget a better CPU
// sorry 8 bit fans, HBVM won't run on your Speccy :( // sorry 8 bit fans, HBVM won't run on your Speccy :(
unsafe { unsafe {
match *self.program.add(self.pc) { match *self
.memory
.load_prog(self.pc)
.ok_or(VmRunError::ProgramFetchLoadEx(self.pc as _))?
{
UN => { UN => {
self.decode::<()>(); self.decode::<()>();
return Err(VmRunError::Unreachable); return Err(VmRunError::Unreachable);
@ -388,15 +365,22 @@ where
} }
/// Decode instruction operands /// Decode instruction operands
#[inline] #[inline(always)]
unsafe fn decode<T: OpParam>(&mut self) -> T { unsafe fn decode<T: OpParam>(&mut self) -> T {
let data = self.program.add(self.pc + 1).cast::<T>().read(); let pc1 = self.pc + 1;
let data = self
.memory
.load_prog_unchecked(pc1..pc1 + size_of::<T>())
.as_ptr()
.cast::<T>()
.read();
self.pc += 1 + size_of::<T>(); self.pc += 1 + size_of::<T>();
data data
} }
/// Perform binary operating over two registers /// Perform binary operating over two registers
#[inline] #[inline(always)]
unsafe fn binary_op<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) { unsafe fn binary_op<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) {
let ParamBBB(tg, a0, a1) = self.decode(); let ParamBBB(tg, a0, a1) = self.decode();
self.write_reg( self.write_reg(
@ -406,7 +390,7 @@ where
} }
/// Perform binary operation over register and immediate /// Perform binary operation over register and immediate
#[inline] #[inline(always)]
unsafe fn binary_op_imm<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) { unsafe fn binary_op_imm<T: ValueVariant>(&mut self, op: impl Fn(T, T) -> T) {
let ParamBBD(tg, reg, imm) = self.decode(); let ParamBBD(tg, reg, imm) = self.decode();
self.write_reg( self.write_reg(
@ -416,14 +400,14 @@ where
} }
/// Perform binary operation over register and shift immediate /// Perform binary operation over register and shift immediate
#[inline] #[inline(always)]
unsafe fn binary_op_ims<T: ValueVariant>(&mut self, op: impl Fn(T, u32) -> T) { unsafe fn binary_op_ims<T: ValueVariant>(&mut self, op: impl Fn(T, u32) -> T) {
let ParamBBW(tg, reg, imm) = self.decode(); let ParamBBW(tg, reg, imm) = self.decode();
self.write_reg(tg, op(self.read_reg(reg).cast::<T>(), imm)); self.write_reg(tg, op(self.read_reg(reg).cast::<T>(), imm));
} }
/// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected /// Jump at `#3` if ordering on `#0 <=> #1` is equal to expected
#[inline] #[inline(always)]
unsafe fn cond_jmp<T: ValueVariant + Ord>(&mut self, expected: Ordering) { unsafe fn cond_jmp<T: ValueVariant + Ord>(&mut self, expected: Ordering) {
let ParamBBD(a0, a1, ja) = self.decode(); let ParamBBD(a0, a1, ja) = self.decode();
if self if self
@ -437,14 +421,14 @@ where
} }
/// Read register /// Read register
#[inline] #[inline(always)]
unsafe fn read_reg(&self, n: u8) -> Value { unsafe fn read_reg(&self, n: u8) -> Value {
*self.registers.get_unchecked(n as usize) *self.registers.get_unchecked(n as usize)
} }
/// Write a register. /// Write a register.
/// Writing to register 0 is no-op. /// Writing to register 0 is no-op.
#[inline] #[inline(always)]
unsafe fn write_reg(&mut self, n: u8, value: impl Into<Value>) { unsafe fn write_reg(&mut self, n: u8, value: impl Into<Value>) {
if n != 0 { if n != 0 {
*self.registers.get_unchecked_mut(n as usize) = value.into(); *self.registers.get_unchecked_mut(n as usize) = value.into();
@ -452,7 +436,7 @@ where
} }
/// Load / Store Address check-computation überfunction /// Load / Store Address check-computation überfunction
#[inline] #[inline(always)]
unsafe fn ldst_addr_uber( unsafe fn ldst_addr_uber(
&self, &self,
dst: u8, dst: u8,
@ -485,6 +469,9 @@ pub enum VmRunError {
/// Unhandled load access exception /// Unhandled load access exception
LoadAccessEx(u64), LoadAccessEx(u64),
/// Unhandled instruction load access exception
ProgramFetchLoadEx(u64),
/// Unhandled store access exception /// Unhandled store access exception
StoreAccessEx(u64), StoreAccessEx(u64),
@ -529,6 +516,40 @@ pub trait Memory {
source: *const u8, source: *const u8,
count: usize, count: usize,
) -> Result<(), StoreError>; ) -> 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<I>(&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<I>(&mut self, index: I) -> &I::Output
where
I: SliceIndex<[u8]>;
} }
/// Unhandled load access trap /// Unhandled load access trap

View file

@ -10,15 +10,20 @@ use {
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut prog = vec![]; let mut prog = vec![];
stdin().read_to_end(&mut prog)?; stdin().read_to_end(&mut prog)?;
println!("{prog:?}");
if let Err(e) = validate(&prog) { if let Err(e) = validate(&prog) {
eprintln!("Program validation error: {e:?}"); eprintln!("Program validation error: {e:?}");
return Ok(()); return Ok(());
} else { } else {
unsafe { unsafe {
let mut vm = let mut vm = Vm::<_, 0>::new(
Vm::<_, 0>::new_unchecked(&prog, SoftPagedMem::<TestTrapHandler>::default()); SoftPagedMem {
pf_handler: TestTrapHandler,
program: &prog,
root_pt: Box::into_raw(Default::default()),
},
0,
);
let data = { let data = {
let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked( let ptr = std::alloc::alloc_zeroed(std::alloc::Layout::from_size_align_unchecked(
4096, 4096, 4096, 4096,
@ -32,7 +37,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
vm.memory vm.memory
.map( .map(
data, data,
0, 8192,
hbvm::softpaging::paging::Permission::Write, hbvm::softpaging::paging::Permission::Write,
PageSize::Size4K, PageSize::Size4K,
) )
@ -46,7 +51,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
data, data,
std::alloc::Layout::from_size_align_unchecked(4096, 4096), std::alloc::Layout::from_size_align_unchecked(4096, 4096),
); );
vm.memory.unmap(0).unwrap(); vm.memory.unmap(8192).unwrap();
} }
} }
Ok(()) Ok(())

View file

@ -4,6 +4,7 @@ pub mod paging;
use { use {
super::{LoadError, Memory, MemoryAccessReason, StoreError}, super::{LoadError, Memory, MemoryAccessReason, StoreError},
core::slice::SliceIndex,
derive_more::Display, derive_more::Display,
paging::{PageTable, Permission}, paging::{PageTable, Permission},
}; };
@ -13,14 +14,16 @@ use {alloc::boxed::Box, paging::PtEntry};
/// HoleyBytes software paged memory /// HoleyBytes software paged memory
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SoftPagedMem<PfHandler> { pub struct SoftPagedMem<'p, PfH> {
/// Root page table /// Root page table
pub root_pt: *mut PageTable, pub root_pt: *mut PageTable,
/// Page fault handler /// Page fault handler
pub pf_handler: PfHandler, pub pf_handler: PfH,
/// Program memory segment
pub program: &'p [u8],
} }
impl<PfHandler: HandlePageFault> Memory for SoftPagedMem<PfHandler> { impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> {
/// Load value from an address /// Load value from an address
/// ///
/// # Safety /// # Safety
@ -57,9 +60,27 @@ impl<PfHandler: HandlePageFault> Memory for SoftPagedMem<PfHandler> {
) )
.map_err(StoreError) .map_err(StoreError)
} }
/// Fetch slice from program memory section
#[inline(always)]
fn load_prog<I>(&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<I>(&mut self, index: I) -> &I::Output
where
I: SliceIndex<[u8]>,
{
self.program.get_unchecked(index)
}
} }
impl<PfHandler: HandlePageFault> SoftPagedMem<PfHandler> { impl<'p, PfH: HandlePageFault> SoftPagedMem<'p, PfH> {
// Everyone behold, the holy function, the god of HBVM memory accesses! // Everyone behold, the holy function, the god of HBVM memory accesses!
/// Split address to pages, check their permissions and feed pointers with offset /// Split address to pages, check their permissions and feed pointers with offset
@ -239,24 +260,14 @@ impl Iterator for AddrPageLookuper {
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
impl<PfHandler: Default> Default for SoftPagedMem<PfHandler> { impl<'p, A> Drop for SoftPagedMem<'p, A> {
fn default() -> Self {
Self {
root_pt: Box::into_raw(Default::default()),
pf_handler: Default::default(),
}
}
}
#[cfg(feature = "alloc")]
impl<A> Drop for SoftPagedMem<A> {
fn drop(&mut self) { fn drop(&mut self) {
let _ = unsafe { Box::from_raw(self.root_pt) }; let _ = unsafe { Box::from_raw(self.root_pt) };
} }
} }
#[cfg(feature = "alloc")] #[cfg(feature = "alloc")]
impl<A> SoftPagedMem<A> { impl<'p, A> SoftPagedMem<'p, A> {
/// Maps host's memory into VM's memory /// Maps host's memory into VM's memory
/// ///
/// # Safety /// # Safety