pub struct Region { /* private fields */ }Expand description
An owned region of page-aligned virtual memory, freed when it is dropped.
A region is the unit a JIT or runtime works in: a block of memory whose access
permissions can be changed independently of the rest of the address space. It is created
ReadWrite so code or data can be written in, then moved to
another protection — usually ReadExecute — with
protect. Dropping the region returns its pages to the operating system.
The requested length is rounded up to a whole number of page_size bytes, so
len is the real, page-aligned size and is at least what was asked for.
Optionally a region can be flanked by inaccessible guard pages with
with_guard_pages, so a run off either end faults at once
instead of silently corrupting a neighbour.
Region is Send and Sync: it owns its mapping the way Box<[u8]> owns its
allocation, and every method that touches the bytes takes &self or &mut self, so the
borrow checker rules out data races on the safe surface.
§Executing code
Writing machine code into a region and running it is inherently unsafe — the bytes must
be valid code for the target and match the calling convention you transmute to. The safe
surface here gets the memory into the right state; the final transmute-and-call is the
caller’s unsafe responsibility. On architectures with separate instruction and data
caches (for example AArch64), freshly written code also needs the instruction cache
synchronized before it is run; on x86 and x86-64 this is automatic.
§Examples
Write a byte pattern, read it back, then flip the region to executable (the W^X pattern, minus the architecture-specific call):
use pager_lang::{Protection, Region};
let mut region = Region::new(64)?;
assert!(region.len() >= 64);
assert_eq!(region.protection(), Protection::ReadWrite);
// Fill the first bytes and read them straight back.
region.write(0, &[0x90, 0x90, 0xC3])?;
assert_eq!(®ion.as_slice().unwrap()[..3], &[0x90, 0x90, 0xC3]);
// Make it read+execute: it is no longer writable.
region.protect(Protection::ReadExecute)?;
assert!(region.as_mut_slice().is_none());Implementations§
Source§impl Region
impl Region
Sourcepub fn new(len: usize) -> Result<Self, PagerError>
pub fn new(len: usize) -> Result<Self, PagerError>
Maps a new read/write region of at least len bytes.
The length is rounded up to a whole number of page_size bytes; the region starts
out ReadWrite. The bytes are zero-initialized by the
operating system.
§Errors
PagerError::ZeroSizeiflenis zero.PagerError::SizeOverflowif roundinglenup to whole pages overflowsusize.PagerError::Mapif the operating system cannot map the region.
§Examples
use pager_lang::Region;
let region = Region::new(1)?;
// One byte was asked for; a whole page is what you get.
assert_eq!(region.len(), pager_lang::page_size());Sourcepub fn with_guard_pages(len: usize) -> Result<Self, PagerError>
pub fn with_guard_pages(len: usize) -> Result<Self, PagerError>
Maps a new read/write region of at least len bytes, flanked on both sides by an
inaccessible guard page.
The usable region behaves exactly like one from new: rounded up to
whole pages and starting ReadWrite. The difference is the
guard page immediately before and after it — both None — so a
read or write that runs off either end faults immediately rather than corrupting
whatever happened to be mapped next. has_guard_pages
reports true. len, as_ptr, and the slice accessors
all describe the usable region only; the guards are never exposed.
§Errors
PagerError::ZeroSizeiflenis zero.PagerError::SizeOverflowif the total size including guard pages overflowsusize.PagerError::Mapif the operating system cannot map the region.
§Examples
use pager_lang::Region;
let region = Region::with_guard_pages(128)?;
assert!(region.has_guard_pages());
assert!(region.len() >= 128);Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
The length of the usable region in bytes — a whole number of pages, at least the size requested.
Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
Whether the region is empty. Always false: a region spans at least one page. Present
so the type reads naturally alongside len.
Sourcepub fn protection(&self) -> Protection
pub fn protection(&self) -> Protection
The region’s current protection.
Sourcepub fn has_guard_pages(&self) -> bool
pub fn has_guard_pages(&self) -> bool
Whether the region is flanked by guard pages (created with
with_guard_pages).
Sourcepub fn as_ptr(&self) -> *const u8
pub fn as_ptr(&self) -> *const u8
A raw const pointer to the start of the usable region.
Always valid to obtain; dereferencing or transmuting it (for example to a function
pointer to run code in the region) is unsafe and the caller’s responsibility.
Sourcepub fn as_mut_ptr(&mut self) -> *mut u8
pub fn as_mut_ptr(&mut self) -> *mut u8
A raw mutable pointer to the start of the usable region.
Always valid to obtain; dereferencing it is unsafe and only sound while the region’s
protection permits the access.
Sourcepub fn as_slice(&self) -> Option<&[u8]>
pub fn as_slice(&self) -> Option<&[u8]>
Borrows the region as a byte slice, or None if it is not currently readable.
Returns Some exactly when protection is readable (everything
but Protection::None). The None case is what keeps this safe: a region flipped
to a no-access protection cannot be read through a slice.
§Examples
use pager_lang::{Protection, Region};
let mut region = Region::new(32)?;
assert!(region.as_slice().is_some());
region.protect(Protection::None)?;
assert!(region.as_slice().is_none());Sourcepub fn as_mut_slice(&mut self) -> Option<&mut [u8]>
pub fn as_mut_slice(&mut self) -> Option<&mut [u8]>
Borrows the region as a mutable byte slice, or None if it is not currently writable.
Returns Some exactly when protection is writable
(ReadWrite or
ReadWriteExecute). Use this to fill a region in bulk;
for a single placed write, write does the bounds checking for you.
§Examples
use pager_lang::{Protection, Region};
let mut region = Region::new(8)?;
region.as_mut_slice().unwrap().fill(0xAB);
assert!(region.as_slice().unwrap().iter().all(|&b| b == 0xAB));
region.protect(Protection::ReadExecute)?;
assert!(region.as_mut_slice().is_none());Sourcepub fn write(&mut self, offset: usize, bytes: &[u8]) -> Result<(), PagerError>
pub fn write(&mut self, offset: usize, bytes: &[u8]) -> Result<(), PagerError>
Copies bytes into the region starting at offset.
The common way to load machine code or data: it checks that the region is writable and that the write fits, then copies. Writing an empty slice is a no-op.
§Errors
PagerError::NotWritableif the region’s protection does not permit writing.PagerError::OutOfBoundsifoffset + bytes.len()exceedslen.
§Examples
use pager_lang::{PagerError, Region};
let mut region = Region::new(16)?;
region.write(4, &[1, 2, 3, 4])?;
assert_eq!(®ion.as_slice().unwrap()[4..8], &[1, 2, 3, 4]);
// A write past the end is rejected, not truncated.
let len = region.len();
assert!(matches!(
region.write(len, &[0]),
Err(PagerError::OutOfBounds { .. })
));Sourcepub fn protect(&mut self, protection: Protection) -> Result<(), PagerError>
pub fn protect(&mut self, protection: Protection) -> Result<(), PagerError>
Changes the region’s protection.
Applies to the usable region only; any guard pages are left untouched. Setting the
protection it already has is a no-op that returns Ok. This is the W^X flip: write
code under ReadWrite, then protect to
ReadExecute before running it.
§Errors
PagerError::Protect if the operating system refuses the change — most often a
platform that forbids writable-and-executable pages rejecting
Protection::ReadWriteExecute.
§Examples
use pager_lang::{Protection, Region};
let mut region = Region::new(64)?;
region.protect(Protection::ReadExecute)?;
assert_eq!(region.protection(), Protection::ReadExecute);