diff --git a/arca/src/table.rs b/arca/src/table.rs index fed4ead3..d0b84e50 100644 --- a/arca/src/table.rs +++ b/arca/src/table.rs @@ -1,5 +1,10 @@ use super::prelude::*; +pub enum MapError { + Runtime(R::Error), + MapExists, +} + impl Table { pub fn new(len: usize) -> Self { R::create_table(len) @@ -25,7 +30,11 @@ impl Table { Ok(()) } - pub fn map(&mut self, address: usize, entry: Entry) -> Result, R::Error> { + // map behaves somewhat like Linux's MAP_FIXED_NOREPLACE (rejects an overlapping map with MapExists). + // Given the heterogeneity of page sizes, handling overlapping map requests involves + // a bunch of cases (e.g. attempt to map a 4 KiB page in the middle of an existing 2 MiB page, etc.) + pub fn map(&mut self, address: usize, entry: Entry) -> Result, MapError> { + use MapError::*; if entry.is_empty() { return Ok(entry); } @@ -34,26 +43,28 @@ impl Table { let mut embiggened = R::create_table(this.len() * 512); embiggened.set(0, Entry::RWTable(this))?; Ok(embiggened) - })?; + }) + .map_err(Runtime)?; self.map(address, entry)? } else if entry.len() == self.len() / 512 { let shift = entry.len().ilog2(); let index = address >> shift; assert!(index < 512); - self.set(index, entry)? + self.set(index, entry).map_err(Runtime)? } else { let shift = (self.len() / 512).ilog2(); let index = (address >> shift) & 0x1ff; let offset = address & !(0x1ff << shift); - let mut smaller = match self.set(index, Entry::Null(0))? { + let mut smaller = match self.set(index, Entry::Null(0)).map_err(Runtime)? { Entry::ROTable(table) => table, Entry::RWTable(table) => table, - _ => R::create_table(self.len() / 512), + Entry::Null(_) => R::create_table(self.len() / 512), + Entry::ROPage(_) | Entry::RWPage(_) => return Err(MapExists), }; assert!(self.len() > smaller.len()); smaller.map(offset, entry)?; - self.set(index, Entry::RWTable(smaller))? + self.set(index, Entry::RWTable(smaller)).map_err(Runtime)? }; Ok(result) } diff --git a/fix/shell/src/lib.rs b/fix/shell/src/lib.rs index 9d589a33..f36053bd 100644 --- a/fix/shell/src/lib.rs +++ b/fix/shell/src/lib.rs @@ -117,3 +117,21 @@ pub fn main() -> ! { }; os::exit(&result[..]); } + +const KERNEL_PAGE_SIZE: usize = 4096; + +/// Maps `length` bytes at `address` with the given `mode`. +/// Panics if the kernel returns an error code or maps a different length than request +/// +/// # Safety +/// +/// [address] must refer to an unused region of memory at least `length` bytes long; +/// there must be no Rust references pointing into that region. +pub unsafe fn mmap(address: *mut c_void, length: usize, mode: u32) { + let expected = length.next_multiple_of(KERNEL_PAGE_SIZE); + let mapped = unsafe { arcane::arca_compat_mmap(address, length, mode) }; + assert_eq!( + mapped, expected as i64, + "mmap of {length} bytes at {address:?} unexpectedly returned {mapped}" + ); +} diff --git a/fix/shell/src/rt.rs b/fix/shell/src/rt.rs index 9dbcfc05..d7325367 100644 --- a/fix/shell/src/rt.rs +++ b/fix/shell/src/rt.rs @@ -4,7 +4,7 @@ use core::sync::atomic::{AtomicUsize, Ordering}; -use arcane::{__MODE_read_write, arca_compat_mmap}; +use arcane::__MODE_read_write; use user::error; include!(concat!(env!("OUT_DIR"), "/wasm_rt.rs")); @@ -50,7 +50,7 @@ pub extern "C" fn wasm_rt_allocate_memory( assert!(max_pages <= (1u64 << 32) / PAGE_SIZE as u64); let data = ((1 << 32) * idx) as *mut u8; let size = initial_pages * PAGE_SIZE as u64; - arca_compat_mmap(data as *mut _, size as usize, __MODE_read_write); + crate::mmap(data as *mut _, size as usize, __MODE_read_write); memory.write(wasm_rt_memory_t { data, pages: initial_pages, @@ -86,7 +86,7 @@ pub extern "C" fn wasm_rt_grow_memory(memory: *mut wasm_rt_memory_t, pages: u64) let start = unsafe { memory.data.byte_add(current as usize * PAGE_SIZE as usize) }; let size = pages * PAGE_SIZE as u64; unsafe { - arca_compat_mmap(start as *mut _, size as usize, __MODE_read_write); + crate::mmap(start as *mut _, size as usize, __MODE_read_write); memory.pages += pages; memory.size += size; } @@ -113,7 +113,7 @@ pub extern "C" fn wasm_rt_allocate_externref_table( max_elements = 1 << (32 - 5); } let data = ((1 << 32) * (64 + idx)) as *mut u8; - arca_compat_mmap(data as *mut _, (elements * 32) as usize, __MODE_read_write); + crate::mmap(data as *mut _, (elements * 32) as usize, __MODE_read_write); table.write(wasm_rt_externref_table_t { data: data as *mut _, size: elements, @@ -137,7 +137,7 @@ pub extern "C" fn wasm_rt_grow_externref_table( let start = unsafe { table.data.byte_add(current as usize * 32) }; let size = delta * 32; unsafe { - arca_compat_mmap(start as *mut _, size as usize, __MODE_read_write); + crate::mmap(start as *mut _, size as usize, __MODE_read_write); table.size += delta; } current @@ -163,7 +163,7 @@ pub extern "C" fn wasm_rt_allocate_funcref_table( max_elements = 1 << (32 - 5); } let data = ((1 << 32) * (64 + 32 + idx)) as *mut u8; - arca_compat_mmap( + crate::mmap( data as *mut _, (elements as usize * core::mem::size_of::()) as usize, __MODE_read_write, diff --git a/fix/shell/src/shell.rs b/fix/shell/src/shell.rs index a22f94c5..2fde0da7 100644 --- a/fix/shell/src/shell.rs +++ b/fix/shell/src/shell.rs @@ -3,8 +3,7 @@ use arca::{Blob, Function, Table, Word}; use arca::{Runtime as _, Tuple}; use arcane::{ __MODE_read_only, __MODE_read_write, __NR_length, __TYPE_table, arca_argument, - arca_blob_create, arca_blob_read, arca_compat_mmap, arca_entry, arca_mmap, arca_table_map, - arcad, + arca_blob_create, arca_blob_read, arca_entry, arca_mmap, arca_table_map, arcad, }; use core::arch::x86_64::*; @@ -52,7 +51,7 @@ pub unsafe fn fixpoint_attach_blob(addr: *mut c_void, handle: [u8; 32]) -> usize let len = fixpoint_len(handle); unsafe { - arca_compat_mmap(addr, len, __MODE_read_write); + crate::mmap(addr, len, __MODE_read_write); blob.read(0, core::slice::from_raw_parts_mut(addr as *mut u8, len)); }; // user::error::log_int("attached memory", len as u64); @@ -86,7 +85,7 @@ pub unsafe fn fixpoint_attach_tree(addr: *mut c_void, handle: [u8; 32]) -> usize // user::error::log_int("attached tree", len as u64); unsafe { - arca_compat_mmap(addr, len * 32, __MODE_read_write); + crate::mmap(addr, len * 32, __MODE_read_write); let slice = core::slice::from_raw_parts_mut(addr as *mut u8, len * 32); tree.read(0, slice) }; diff --git a/kernel/src/cpu.rs b/kernel/src/cpu.rs index ea3919d8..87548482 100644 --- a/kernel/src/cpu.rs +++ b/kernel/src/cpu.rs @@ -309,7 +309,10 @@ impl Cpu { }; let table = Table::from(CowPage::Unique(pdpt)); let mut table = arca::Table::from_inner(table); - let result = table.map(address, entry)?; + let result = table.map(address, entry).map_err(|e| match e { + arca::table::MapError::Runtime(e) => e, + arca::table::MapError::MapExists => crate::types::Error::MapError, + })?; match table.into_inner() { Table::Table512GB(page) => pml4.entry_mut(i_512gb).chain_unique(page.unique()), _ => todo!(), diff --git a/kernel/src/types/function/syscall.rs b/kernel/src/types/function/syscall.rs index baa2370c..fb56a84f 100644 --- a/kernel/src/types/function/syscall.rs +++ b/kernel/src/types/function/syscall.rs @@ -460,7 +460,9 @@ pub fn sys_compat_mmap(args: [u64; 6], arca: &mut LoadedArca) -> Result { let mut p = addr; while p < addr + len { - if p.is_multiple_of(Page1GB::SIZE) && len >= Page1GB::SIZE { + // Remaining number of bytes to mmap + let remaining = addr + len - p; + if p.is_multiple_of(Page1GB::SIZE) && remaining >= Page1GB::SIZE { if mode == arcane::__MODE_none { let entry = Entry::Null(Page1GB::SIZE); arca.cpu().map(p, entry).unwrap(); @@ -473,7 +475,7 @@ pub fn sys_compat_mmap(args: [u64; 6], arca: &mut LoadedArca) -> Result { p += Page1GB::SIZE; continue; } - if p.is_multiple_of(Page2MB::SIZE) && len >= Page2MB::SIZE { + if p.is_multiple_of(Page2MB::SIZE) && remaining >= Page2MB::SIZE { if mode == arcane::__MODE_none { let entry = Entry::Null(Page2MB::SIZE); arca.cpu().map(p, entry).unwrap(); @@ -500,7 +502,10 @@ pub fn sys_compat_mmap(args: [u64; 6], arca: &mut LoadedArca) -> Result { } panic!("unaligned mmap or bad size: {p:#x}+{len:#x}"); } - Ok(p - addr) + + let size_mapped = p - addr; + debug_assert_eq!(size_mapped, len.next_multiple_of(Page4KB::SIZE)); + Ok(size_mapped) } pub fn sys_call_with_current_continuation( @@ -670,6 +675,28 @@ impl From for SyscallError { crate::types::Error::InvalidTableEntry(_) => SyscallError::BadArgument, crate::types::Error::InvalidIndex(_) => SyscallError::BadIndex, crate::types::Error::InvalidValue => SyscallError::BadArgument, + crate::types::Error::MapError => SyscallError::BadArgument, } } } + +#[cfg(test)] +mod tests { + use super::*; + + // Verifies memory mapping rounds up to the nearest 4KB page without over allocation + #[test] + fn test_mmap_fits_request() { + let len: usize = Page2MB::SIZE + Page4KB::SIZE; + let mut cpu = CPU.borrow_mut(); + let mut loaded_arca = Arca::new().load(&mut cpu); + let args = [0, len as u64, arcane::__MODE_read_write as u64, 0, 0, 0]; + sys_compat_mmap(args, &mut loaded_arca).expect("mmap failed"); + let mut arca = loaded_arca.unload(); + let mappings = arca.mappings_mut(); + + assert_eq!(mappings.unmap(0).unwrap().len(), Page2MB::SIZE); + assert_eq!(mappings.unmap(Page2MB::SIZE).unwrap().len(), Page4KB::SIZE); + assert!(matches!(mappings.unmap(len), Some(Entry::Null(_)))); + } +} diff --git a/kernel/src/types/runtime.rs b/kernel/src/types/runtime.rs index 6d197837..982ad7ce 100644 --- a/kernel/src/types/runtime.rs +++ b/kernel/src/types/runtime.rs @@ -8,6 +8,7 @@ pub enum Error { InvalidTableEntry(super::Entry), InvalidIndex(usize), InvalidValue, + MapError, } impl arca::Runtime for Runtime {