Skip to main content

pager_lang/
lib.rs

1//! # pager_lang
2//!
3//! Virtual and executable memory management for JIT compilers and language runtimes.
4//!
5//! A just-in-time compiler emits machine code at run time and then jumps into it. To do that
6//! it needs memory the operating system will let the CPU *execute*, which ordinary heap
7//! allocations are not. pager-lang is the small, focused layer that hands out that memory:
8//! page-aligned regions whose access permissions — read, write, execute — can be changed on
9//! demand, with optional guard pages, and nothing more.
10//!
11//! The whole surface is three types and one function:
12//!
13//! - [`Region`] — an owned block of page-aligned virtual memory, freed when dropped.
14//! - [`Protection`] — the access a region permits: read, write, execute, in the useful
15//!   combinations.
16//! - [`PagerError`] — the reason an operation failed.
17//! - [`page_size`] — the host page size, the unit regions are measured in.
18//!
19//! ## Write xor execute
20//!
21//! The intended flow is *write xor execute* (W^X), the discipline that keeps memory from
22//! ever being writable and executable at the same time. A region is born
23//! [`ReadWrite`](Protection::ReadWrite); you write code into it; you flip it to
24//! [`ReadExecute`](Protection::ReadExecute); then you run it. At no point is the same page
25//! both writable and runnable, which closes off a large class of exploits.
26//!
27//! ```
28//! use pager_lang::{Protection, Region};
29//!
30//! // Reserve a writable page.
31//! let mut code = Region::new(4096)?;
32//!
33//! // Write some bytes (here a harmless pattern — real code is target-specific).
34//! code.write(0, &[0x90, 0x90, 0xC3])?; // x86: nop; nop; ret
35//!
36//! // Flip to read+execute. The region is no longer writable.
37//! code.protect(Protection::ReadExecute)?;
38//! assert!(code.as_mut_slice().is_none());
39//!
40//! // `code.as_ptr()` now points at executable memory. Transmuting it to a function
41//! // pointer and calling it is `unsafe` and the caller's responsibility — see
42//! // `examples/jit_function.rs` for a complete, runnable example on x86-64.
43//! # Ok::<(), pager_lang::PagerError>(())
44//! ```
45//!
46//! ## Guard pages
47//!
48//! [`Region::with_guard_pages`] flanks the usable region with inaccessible pages, so a write
49//! that runs off either end faults immediately instead of silently corrupting adjacent
50//! memory — cheap insurance for a code buffer a compiler is filling.
51//!
52//! ## Platforms
53//!
54//! Regions are backed by `mmap`/`mprotect`/`munmap` on Unix and
55//! `VirtualAlloc`/`VirtualProtect`/`VirtualFree` on Windows, reached through direct FFI with
56//! no third-party dependencies. Linux, macOS, and Windows (x86-64 and ARM64) are the
57//! supported targets; a target with no operating system cannot provide these calls and will
58//! not compile.
59//!
60//! Getting memory into an executable state is portable; *running* freshly written code is
61//! not entirely. On architectures with separate instruction and data caches — AArch64, for
62//! instance — code written into a region must have the instruction cache synchronized before
63//! it is executed. On x86 and x86-64 this is automatic. This crate manages the memory and
64//! its protections; instruction-cache synchronization for the architectures that need it is
65//! the caller's responsibility at the point of execution.
66//!
67//! ## Features
68//!
69//! - `std` (default) — links the standard library. Without it the crate is `#![no_std]` and
70//!   needs only `core`; it reaches the operating system through FFI either way, so behavior
71//!   is identical.
72//! - `serde` — derives `Serialize` / `Deserialize` for [`Protection`] and [`PagerError`], so
73//!   a region's configuration and failure reasons can cross a serialization boundary.
74//!
75//! ## Stability
76//!
77//! The public surface is frozen and stable as of `1.0.0`: it follows Semantic Versioning,
78//! with no breaking changes before `2.0`. [`PagerError`] is `#[non_exhaustive]`, so a new
79//! failure variant is an additive, non-breaking change. The full surface and the SemVer
80//! promise are catalogued in
81//! [`docs/API.md`](https://github.com/jamesgober/pager-lang/blob/main/docs/API.md#semver-promise).
82
83#![cfg_attr(not(feature = "std"), no_std)]
84#![cfg_attr(docsrs, feature(doc_cfg))]
85#![deny(missing_docs)]
86#![deny(unsafe_op_in_unsafe_fn)]
87#![deny(clippy::undocumented_unsafe_blocks)]
88#![deny(
89    clippy::unwrap_used,
90    clippy::expect_used,
91    clippy::panic,
92    clippy::todo,
93    clippy::unimplemented,
94    clippy::unreachable,
95    clippy::dbg_macro,
96    clippy::print_stdout,
97    clippy::print_stderr
98)]
99
100mod error;
101mod protection;
102mod region;
103mod sys;
104
105pub use error::PagerError;
106pub use protection::Protection;
107pub use region::{Region, page_size};