#![no_std]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
pub use allocation::{Allocation, alloc, alloc_at};
pub use error::{Error, Result};
pub use lock::{LockGuard, lock, unlock};
pub use protect::{ProtectGuard, protect, protect_with_handle};
pub use query::{QueryIter, query, query_range};
mod allocation;
mod error;
mod lock;
mod os;
pub mod page;
mod protect;
mod query;
mod util;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Region {
base: *const (),
reserved: bool,
guarded: bool,
protection: Protection,
max_protection: Protection,
shared: bool,
size: usize,
}
impl Region {
#[inline(always)]
pub fn as_ptr<T>(&self) -> *const T {
self.base.cast()
}
#[inline(always)]
pub fn as_mut_ptr<T>(&mut self) -> *mut T {
self.base.cast_mut().cast()
}
#[inline(always)]
pub fn as_ptr_range<T>(&self) -> core::ops::Range<*const T> {
let range = self.as_range();
core::ptr::with_exposed_provenance::<T>(range.start)
..core::ptr::with_exposed_provenance::<T>(range.end)
}
#[inline(always)]
pub fn as_mut_ptr_range<T>(&mut self) -> core::ops::Range<*mut T> {
let range = self.as_range();
core::ptr::with_exposed_provenance_mut::<T>(range.start)
..core::ptr::with_exposed_provenance_mut::<T>(range.end)
}
#[inline(always)]
pub fn as_range(&self) -> core::ops::Range<usize> {
let start = self.base.addr();
start..start.saturating_add(self.size)
}
#[inline(always)]
pub fn is_committed(&self) -> bool {
!self.reserved
}
#[inline(always)]
pub fn is_readable(&self) -> bool {
self.protection.contains(Protection::READ)
}
#[inline(always)]
pub fn is_writable(&self) -> bool {
self.protection.contains(Protection::WRITE)
}
#[inline(always)]
pub fn is_executable(&self) -> bool {
self.protection.contains(Protection::EXECUTE)
}
#[inline(always)]
pub fn is_guarded(&self) -> bool {
self.guarded
}
#[inline(always)]
pub fn is_shared(&self) -> bool {
self.shared
}
#[inline(always)]
pub fn len(&self) -> usize {
self.size
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.size == 0
}
#[inline(always)]
pub fn protection(&self) -> Protection {
self.protection
}
#[inline(always)]
pub fn max_protection(&self) -> Protection {
self.max_protection
}
}
impl Default for Region {
#[inline]
fn default() -> Self {
Self {
base: core::ptr::null(),
reserved: false,
guarded: false,
protection: Protection::NONE,
max_protection: Protection::NONE,
shared: false,
size: 0,
}
}
}
unsafe impl Send for Region {}
unsafe impl Sync for Region {}
bitflags::bitflags! {
#[derive(Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
pub struct Protection: usize {
const NONE = 0;
const READ = 1 << 0;
const WRITE = 1 << 1;
const EXECUTE = 1 << 2;
const READ_EXECUTE = Self::READ.bits() | Self::EXECUTE.bits();
const READ_WRITE = Self::READ.bits() | Self::WRITE.bits();
const READ_WRITE_EXECUTE = Self::READ.bits() | Self::WRITE.bits() | Self::EXECUTE.bits();
const WRITE_EXECUTE = Self::WRITE.bits() | Self::EXECUTE.bits();
}
}
impl Protection {
#[deprecated = "use the safe `from_bits_retain` method instead"]
#[inline(always)]
pub const unsafe fn from_bits_unchecked(bits: usize) -> Self {
Self::from_bits_retain(bits)
}
}
impl core::fmt::Display for Protection {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
const MAPPINGS: &[(Protection, char)] = &[
(Protection::READ, 'r'),
(Protection::WRITE, 'w'),
(Protection::EXECUTE, 'x'),
];
for (flag, symbol) in MAPPINGS {
if self.contains(*flag) {
write!(f, "{symbol}")?;
} else {
write!(f, "-")?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn protection_implements_display() {
assert_eq!(Protection::READ.to_string(), "r--");
assert_eq!(Protection::READ_WRITE.to_string(), "rw-");
assert_eq!(Protection::READ_WRITE_EXECUTE.to_string(), "rwx");
assert_eq!(Protection::WRITE.to_string(), "-w-");
}
#[cfg(unix)]
pub mod util {
use crate::{Protection, page};
use alloc::vec;
use alloc::vec::Vec;
use core::ops::Deref;
use mmap::{MapOption, MemoryMap};
struct AllocatedPages(Vec<MemoryMap>);
impl Deref for AllocatedPages {
type Target = [u8];
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { core::slice::from_raw_parts(self.0[0].data().cast(), self.0.len() * page::size()) }
}
}
#[allow(clippy::fallible_impl_from)]
impl From<Protection> for &'static [MapOption] {
#[inline]
fn from(protection: Protection) -> Self {
match protection {
Protection::NONE => &[],
Protection::READ => &[MapOption::MapReadable],
Protection::READ_WRITE => &[MapOption::MapReadable, MapOption::MapWritable],
Protection::READ_EXECUTE => &[MapOption::MapReadable, MapOption::MapExecutable],
_ => panic!("Unsupported protection {:?}", protection),
}
}
}
pub fn alloc_pages(pages: &[Protection]) -> impl Deref<Target = [u8]> {
let region = MemoryMap::new(page::size() * pages.len(), &[]).expect("allocating pages");
let mut page_address = region.data();
core::mem::forget(region);
let allocated_pages = pages
.iter()
.map(|protection| {
let mut options = vec![MapOption::MapAddr(page_address)];
options.extend_from_slice(Into::into(*protection));
let map = MemoryMap::new(page::size(), &options).expect("allocating page");
assert_eq!(map.data(), page_address);
assert_eq!(map.len(), page::size());
page_address = unsafe { page_address.add(page::size()) };
map
})
.collect::<Vec<_>>();
AllocatedPages(allocated_pages)
}
}
#[cfg(windows)]
pub mod util {
use crate::{Protection, page};
use core::ops::Deref;
use windows_sys::Win32::System::Memory::{
MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_NOACCESS, VirtualAlloc, VirtualFree,
};
struct AllocatedPages(*const (), usize);
impl Deref for AllocatedPages {
type Target = [u8];
#[inline]
fn deref(&self) -> &Self::Target {
unsafe { core::slice::from_raw_parts(self.0.cast(), self.1) }
}
}
impl Drop for AllocatedPages {
#[inline]
fn drop(&mut self) {
unsafe {
assert_ne!(VirtualFree(self.0 as *mut _, 0, MEM_RELEASE), 0);
}
}
}
pub fn alloc_pages(pages: &[Protection]) -> impl Deref<Target = [u8]> {
let total_size = page::size() * pages.len();
let allocation_base = unsafe {
VirtualAlloc(
core::ptr::null_mut(),
total_size,
MEM_RESERVE,
PAGE_NOACCESS,
)
};
assert_ne!(allocation_base, core::ptr::null_mut());
let mut page_address = allocation_base;
for protection in pages {
let address = unsafe {
VirtualAlloc(
page_address,
page::size(),
MEM_COMMIT,
protection.to_native(),
)
};
assert_eq!(address, page_address);
page_address = unsafe { (address as *mut u8).add(page::size()) }.cast();
}
AllocatedPages(allocation_base.cast(), total_size)
}
}
}