#![deny(
clippy::all,
clippy::missing_inline_in_public_items,
clippy::ptr_as_ptr,
clippy::print_stdout,
missing_docs,
nonstandard_style,
unused,
warnings
)]
#[macro_use]
extern crate bitflags;
pub use alloc::{alloc, alloc_at, Allocation};
pub use error::{Error, Result};
pub use lock::{lock, unlock, LockGuard};
pub use protect::{protect, protect_with_handle, ProtectGuard};
pub use query::{query, query_range, QueryIter};
mod alloc;
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,
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 as *mut T
}
#[inline(always)]
pub fn as_ptr_range<T>(&self) -> std::ops::Range<*const T> {
let range = self.as_range();
(range.start as *const T)..(range.end as *const T)
}
#[inline(always)]
pub fn as_mut_ptr_range<T>(&mut self) -> std::ops::Range<*mut T> {
let range = self.as_range();
(range.start as *mut T)..(range.end as *mut T)
}
#[inline(always)]
pub fn as_range(&self) -> std::ops::Range<usize> {
(self.base as usize)..(self.base as usize).saturating_add(self.size)
}
#[inline(always)]
pub fn is_committed(&self) -> bool {
!self.reserved
}
#[inline(always)]
pub fn is_readable(&self) -> bool {
self.protection & Protection::READ == Protection::READ
}
#[inline(always)]
pub fn is_writable(&self) -> bool {
self.protection & Protection::WRITE == Protection::WRITE
}
#[inline(always)]
pub fn is_executable(&self) -> bool {
self.protection & Protection::EXECUTE == 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
}
}
impl Default for Region {
#[inline]
fn default() -> Self {
Self {
base: std::ptr::null(),
reserved: false,
guarded: false,
protection: Protection::NONE,
shared: false,
size: 0,
}
}
}
unsafe impl Send for Region {}
unsafe impl Sync for Region {}
bitflags! {
#[derive(Default)]
pub struct Protection: usize {
const NONE = 0;
const READ = (1 << 1);
const WRITE = (1 << 2);
const EXECUTE = (1 << 3);
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 std::fmt::Display for Protection {
#[inline]
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::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::*;
#[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::{page, Protection};
use mmap::{MapOption, MemoryMap};
use std::ops::Deref;
struct AllocatedPages(Vec<MemoryMap>);
impl Deref for AllocatedPages {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe { std::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] {
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();
std::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 = (page_address as usize + page::size()) as *mut _;
map
})
.collect::<Vec<_>>();
AllocatedPages(allocated_pages)
}
}
#[cfg(windows)]
pub mod util {
use crate::{page, Protection};
use std::ops::Deref;
use winapi::um::memoryapi::{VirtualAlloc, VirtualFree};
use winapi::um::winnt::{MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_NOACCESS};
struct AllocatedPages(*const (), usize);
impl Deref for AllocatedPages {
type Target = [u8];
fn deref(&self) -> &Self::Target {
unsafe { std::slice::from_raw_parts(self.0 as *const _, self.1) }
}
}
impl Drop for AllocatedPages {
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(std::ptr::null_mut(), total_size, MEM_RESERVE, PAGE_NOACCESS) };
assert_ne!(allocation_base, std::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 = (address as usize + page::size()) as *mut _;
}
AllocatedPages(allocation_base as *const _, total_size)
}
}
}