Softpage improvements

This commit is contained in:
Erin 2023-08-18 01:28:02 +02:00 committed by ondra05
parent af1de4b9ec
commit bd9b4e0364
9 changed files with 161 additions and 32 deletions

View file

@ -1,2 +1,3 @@
[workspace] [workspace]
members = ["hbasm", "hbbytecode", "hbvm"] resolver = "2"
members = ["hbasm", "hbbytecode", "hbvm"]

View file

@ -35,12 +35,12 @@ macro_rules! constmod {
/// - I: Immediate /// - I: Immediate
/// - L: Memory load / store size (u16) /// - L: Memory load / store size (u16)
/// - Other types are identity-mapped /// - Other types are identity-mapped
/// ///
/// # BRC special-case /// # BRC special-case
/// BRC's 3rd operand is plain byte, not a register. Encoding is the same, but for some cases it may matter. /// 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. /// Please, if you distinguish in your API between byte and register, special case this one.
/// ///
/// Sorry for that :( /// Sorry for that :(
#[macro_export] #[macro_export]
macro_rules! invoke_with_def { macro_rules! invoke_with_def {
@ -134,34 +134,29 @@ constmod!(pub opcode(u8) {
#[repr(packed)] #[repr(packed)]
pub struct ParamBBBB(pub u8, pub u8, pub u8, pub u8); pub struct ParamBBBB(pub u8, pub u8, pub u8, pub u8);
#[repr(packed)] #[repr(packed)]
pub struct ParamBBB(pub u8, pub u8, pub u8); pub struct ParamBBB(pub u8, pub u8, pub u8);
#[repr(packed)] #[repr(packed)]
pub struct ParamBBDH(pub u8, pub u8, pub u64, pub u16); pub struct ParamBBDH(pub u8, pub u8, pub u64, pub u16);
#[repr(packed)] #[repr(packed)]
pub struct ParamBBD(pub u8, pub u8, pub u64); pub struct ParamBBD(pub u8, pub u8, pub u64);
#[repr(packed)] #[repr(packed)]
pub struct ParamBBW(pub u8, pub u8, pub u32); pub struct ParamBBW(pub u8, pub u8, pub u32);
#[repr(packed)] #[repr(packed)]
pub struct ParamBB(pub u8, pub u8); pub struct ParamBB(pub u8, pub u8);
#[repr(packed)] #[repr(packed)]
pub struct ParamBD(pub u8, pub u64); pub struct ParamBD(pub u8, pub u64);
/// # Safety /// # Safety
/// Has to be valid to be decoded from bytecode. /// Has to be valid to be decoded from bytecode.
pub unsafe trait OpParam {} pub unsafe trait ProgramVal {}
unsafe impl OpParam for ParamBBBB {} unsafe impl ProgramVal for ParamBBBB {}
unsafe impl OpParam for ParamBBB {} unsafe impl ProgramVal for ParamBBB {}
unsafe impl OpParam for ParamBBDH {} unsafe impl ProgramVal for ParamBBDH {}
unsafe impl OpParam for ParamBBD {} unsafe impl ProgramVal for ParamBBD {}
unsafe impl OpParam for ParamBBW {} unsafe impl ProgramVal for ParamBBW {}
unsafe impl OpParam for ParamBB {} unsafe impl ProgramVal for ParamBB {}
unsafe impl OpParam for ParamBD {} unsafe impl ProgramVal for ParamBD {}
unsafe impl OpParam for u64 {} unsafe impl ProgramVal for u64 {}
unsafe impl OpParam for () {} unsafe impl ProgramVal for u8 {} // Opcode
unsafe impl ProgramVal for () {}

View file

@ -16,10 +16,11 @@ fuzz_target!(|data: &[u8]| {
if validate(data).is_ok() { if validate(data).is_ok() {
let mut vm = unsafe { let mut vm = unsafe {
Vm::<_, 16384>::new( Vm::<_, 16384>::new(
SoftPagedMem { SoftPagedMem::<_, true> {
pf_handler: TestTrapHandler, pf_handler: TestTrapHandler,
program: data, program: data,
root_pt: Box::into_raw(Default::default()), root_pt: Box::into_raw(Default::default()),
icache: Default::default(),
}, },
0, 0,
) )
@ -31,6 +32,8 @@ fuzz_target!(|data: &[u8]| {
alloc_and_map(&mut vm.memory, 4096), alloc_and_map(&mut vm.memory, 4096),
]; ];
unsafe { vm.memory.write() };
// Run VM // Run VM
let _ = vm.run(); let _ = vm.run();
@ -38,6 +41,8 @@ fuzz_target!(|data: &[u8]| {
for (i, page) in pages.into_iter().enumerate() { for (i, page) in pages.into_iter().enumerate() {
unmap_and_dealloc(&mut vm.memory, page, i as u64 * 4096); unmap_and_dealloc(&mut vm.memory, page, i as u64 * 4096);
} }
let _ = unsafe { Box::from_raw(vm.memory.root_pt) };
} }
}); });

View file

@ -26,7 +26,9 @@ use {
bmc::BlockCopier, bmc::BlockCopier,
core::{cmp::Ordering, mem::size_of, ops}, core::{cmp::Ordering, mem::size_of, ops},
derive_more::Display, derive_more::Display,
hbbytecode::{OpParam, ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD}, hbbytecode::{
ParamBB, ParamBBB, ParamBBBB, ParamBBD, ParamBBDH, ParamBBW, ParamBD, ProgramVal,
},
value::{Value, ValueVariant}, value::{Value, ValueVariant},
}; };
@ -366,7 +368,7 @@ where
/// Decode instruction operands /// Decode instruction operands
#[inline(always)] #[inline(always)]
unsafe fn decode<T: OpParam>(&mut self) -> T { unsafe fn decode<T: ProgramVal>(&mut self) -> T {
let pc1 = self.pc + 1; let pc1 = self.pc + 1;
let data = self.memory.prog_read_unchecked::<T>(pc1 as _); let data = self.memory.prog_read_unchecked::<T>(pc1 as _);
self.pc += 1 + size_of::<T>(); self.pc += 1 + size_of::<T>();
@ -515,13 +517,13 @@ pub trait Memory {
/// ///
/// # Safety /// # Safety
/// - Data read have to be valid /// - Data read have to be valid
unsafe fn prog_read<T>(&mut self, addr: u64) -> Option<T>; unsafe fn prog_read<T: ProgramVal>(&mut self, addr: u64) -> Option<T>;
/// Read from program memory to exectue /// Read from program memory to exectue
/// ///
/// # Safety /// # Safety
/// - You have to be really sure that these bytes are there, understand? /// - You have to be really sure that these bytes are there, understand?
unsafe fn prog_read_unchecked<T>(&mut self, addr: u64) -> T; unsafe fn prog_read_unchecked<T: ProgramVal>(&mut self, addr: u64) -> T;
} }
/// Unhandled load access trap /// Unhandled load access trap

View file

@ -17,10 +17,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
} else { } else {
unsafe { unsafe {
let mut vm = Vm::<_, 0>::new( let mut vm = Vm::<_, 0>::new(
SoftPagedMem { SoftPagedMem::<_, true> {
pf_handler: TestTrapHandler, pf_handler: TestTrapHandler,
program: &prog, program: &prog,
root_pt: Box::into_raw(Default::default()), root_pt: Box::into_raw(Default::default()),
icache: Default::default(),
}, },
4, 4,
); );
@ -46,12 +47,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("Program interrupt: {:?}", vm.run()); println!("Program interrupt: {:?}", vm.run());
println!("{:?}", vm.registers); println!("{:?}", vm.registers);
println!("{:?}", core::slice::from_raw_parts(data, 4096));
std::alloc::dealloc( std::alloc::dealloc(
data, data,
std::alloc::Layout::from_size_align_unchecked(4096, 4096), std::alloc::Layout::from_size_align_unchecked(4096, 4096),
); );
vm.memory.unmap(8192).unwrap(); vm.memory.unmap(8192).unwrap();
let _ = Box::from_raw(vm.memory.root_pt);
} }
} }
Ok(()) Ok(())

View file

@ -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<NonNull<u8>>,
/// 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<T>(&mut self, addr: u64, root_pt: *const PageTable) -> Option<T> {
let mut ret = MaybeUninit::<T>::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::<T>();
// 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::<u8>(), 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::<u8>().add(first_copy),
rem,
);
}
Some(ret.assume_init())
}
/// Fetch a page
unsafe fn fetch_page(&mut self, addr: u64, pt: *const PageTable) -> Option<NonNull<u8>> {
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
}
}

View file

@ -111,7 +111,7 @@ impl Iterator for AddrPageLookuper {
}; };
// Get available byte count in the selected page with offset // 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); self.bump(size);
Some(Ok(AddrPageLookupOk { Some(Ok(AddrPageLookupOk {

View file

@ -10,7 +10,7 @@ use {
derive_more::Display, 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 /// Maps host's memory into VM's memory
/// ///
/// # Safety /// # Safety

View file

@ -2,6 +2,9 @@
use core::mem::size_of; use core::mem::size_of;
use self::icache::ICache;
pub mod icache;
pub mod lookup; pub mod lookup;
pub mod paging; pub mod paging;
@ -15,17 +18,25 @@ use {
}; };
/// HoleyBytes software paged memory /// 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)] #[derive(Clone, Debug)]
pub struct SoftPagedMem<'p, PfH> { pub struct SoftPagedMem<'p, PfH, const OUT_PROG_EXEC: bool = true> {
/// 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: PfH, pub pf_handler: PfH,
/// Program memory segment /// Program memory segment
pub program: &'p [u8], 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 /// Load value from an address
/// ///
/// # Safety /// # Safety
@ -65,6 +76,10 @@ impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> {
#[inline(always)] #[inline(always)]
unsafe fn prog_read<T>(&mut self, addr: u64) -> Option<T> { unsafe fn prog_read<T>(&mut self, addr: u64) -> Option<T> {
if OUT_PROG_EXEC && addr as usize > self.program.len() {
return self.icache.fetch::<T>(addr, self.root_pt);
}
let addr = addr as usize; let addr = addr as usize;
self.program self.program
.get(addr..addr + size_of::<T>()) .get(addr..addr + size_of::<T>())
@ -73,11 +88,18 @@ impl<'p, PfH: HandlePageFault> Memory for SoftPagedMem<'p, PfH> {
#[inline(always)] #[inline(always)]
unsafe fn prog_read_unchecked<T>(&mut self, addr: u64) -> T { unsafe fn prog_read_unchecked<T>(&mut self, addr: u64) -> T {
if OUT_PROG_EXEC && addr as usize > self.program.len() {
return self
.icache
.fetch::<T>(addr as _, self.root_pt)
.unwrap_or_else(|| core::mem::zeroed());
}
self.program.as_ptr().add(addr as _).cast::<T>().read() self.program.as_ptr().add(addr as _).cast::<T>().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! // 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