1//! # **P**ortable **S**tack **M**anipulation
2//! This crate provides portable functions to control the stack pointer and inspect the properties
3//! of the stack. This crate does not attempt to provide safe abstractions to any operations, the
4//! only goals are correctness, portability and efficiency (in that exact order). As a consequence
5//! most functions you will find in this crate are unsafe.
6//!
7//! Note, that the stack allocation is left up to the user. Unless you’re writing a safe
8//! abstraction over stack manipulation, this is unlikely to be the crate you want. Instead
9//! consider one of the safe abstractions over this crate such as `stacker`. Another good place to
10//! look at is the crates.io’s reverse dependency list.
11
12#![allow(unused_macros)]
13#![no_std]
14
15macro_rules! extern_item {
16 (unsafe $($toks: tt)+) => {
17 unsafe extern "C" $($toks)+
18 };
19 ($($toks: tt)+) => {
20 extern "C" $($toks)+
21 };
22}
23
24// Surprising: turns out subsequent macro_rules! override previous definitions, instead of
25// erroring? Convenient for us in this case, though.
26#[cfg(target_arch = "x86_64")]
27macro_rules! extern_item {
28 (unsafe $($toks: tt)+) => {
29 unsafe extern "sysv64" $($toks)+
30 };
31 ($($toks: tt)+) => {
32 extern "sysv64" $($toks)+
33 };
34}
35
36#[cfg(target_arch = "x86")]
37macro_rules! extern_item {
38 (unsafe $($toks: tt)+) => {
39 unsafe extern "fastcall" $($toks)+
40 };
41 ($($toks: tt)+) => {
42 extern "fastcall" $($toks)+
43 };
44}
45
46#[cfg(target_arch = "arm")]
47macro_rules! extern_item {
48 (unsafe $($toks: tt)+) => {
49 unsafe extern "aapcs" $($toks)+
50 };
51 ($($toks: tt)+) => {
52 extern "aapcs" $($toks)+
53 };
54}
55
56// NB: this could be nicer across multiple blocks but we cannot do it because of
57// https://github.com/rust-lang/rust/issues/65847
58extern_item! { {
59 #![cfg_attr(asm, link(name="psm_s"))]
60
61 #[cfg(asm)]
62 fn rust_psm_stack_direction() -> u8;
63 #[cfg(asm)]
64 fn rust_psm_stack_pointer() -> *mut u8;
65
66 #[cfg(all(switchable_stack, not(target_os = "windows")))]
67 #[link_name="rust_psm_replace_stack"]
68 fn _rust_psm_replace_stack(
69 data: usize,
70 callback: extern_item!(unsafe fn(usize) -> !),
71 sp: *mut u8
72 ) -> !;
73 #[cfg(all(switchable_stack, not(target_os = "windows")))]
74 #[link_name="rust_psm_on_stack"]
75 fn _rust_psm_on_stack(
76 data: usize,
77 return_ptr: usize,
78 callback: extern_item!(unsafe fn(usize, usize)),
79 sp: *mut u8,
80 );
81 #[cfg(all(switchable_stack, target_os = "windows"))]
82 fn rust_psm_replace_stack(
83 data: usize,
84 callback: extern_item!(unsafe fn(usize) -> !),
85 sp: *mut u8,
86 stack_base: *mut u8
87 ) -> !;
88 #[cfg(all(switchable_stack, target_os = "windows"))]
89 fn rust_psm_on_stack(
90 data: usize,
91 return_ptr: usize,
92 callback: extern_item!(unsafe fn(usize, usize)),
93 sp: *mut u8,
94 stack_base: *mut u8
95 );
96} }
97
98#[cfg(all(switchable_stack, not(target_os = "windows")))]
99#[inline(always)]
100unsafe fn rust_psm_replace_stack(
101 data: usize,
102 callback: extern_item!(unsafe fn(usize) -> !),
103 sp: *mut u8,
104 _: *mut u8,
105) -> ! {
106 _rust_psm_replace_stack(data, callback, sp)
107}
108
109#[cfg(all(switchable_stack, not(target_os = "windows")))]
110#[inline(always)]
111unsafe fn rust_psm_on_stack(
112 data: usize,
113 return_ptr: usize,
114 callback: extern_item!(unsafe fn(usize, usize)),
115 sp: *mut u8,
116 _: *mut u8,
117) {
118 _rust_psm_on_stack(data, return_ptr, callback, sp)
119}
120
121/// Run the closure on the provided stack.
122///
123/// Once the closure completes its execution, the original stack pointer is restored and execution
124/// returns to the caller.
125///
126/// `base` address must be the low address of the stack memory region, regardless of the stack
127/// growth direction. It is not necessary for the whole region `[base; base + size]` to be usable
128/// at the time this function called, however it is required that at least the following hold:
129///
130/// * Both `base` and `base + size` are aligned up to the target-specific requirements;
131/// * Depending on `StackDirection` value for the platform, the end of the stack memory region,
132/// which would end up containing the first frame(s), must have sufficient number of pages
133/// allocated to execute code until more pages are commited. The other end should contain a guard
134/// page (not writable, readable or executable) to ensure Rust’s soundness guarantees.
135///
136/// Note, that some or all of these considerations are irrelevant to some applications. For
137/// example, Rust’s soundness story relies on all stacks having a guard-page, however if the user
138/// is able to guarantee that the memory region used for stack cannot be exceeded, a guard page may
139/// end up being an expensive unnecessity.
140///
141/// The previous stack may not be deallocated. If an ability to deallocate the old stack is desired
142/// consider `replace_stack` instead.
143///
144/// # Guidelines
145///
146/// Memory regions that are aligned to a single page (usually 4kB) are an extremely portable choice
147/// for stacks.
148///
149/// Allocate at least 4kB of stack. Some architectures (such as SPARC) consume stack memory
150/// significantly faster compared to the more usual architectures such as x86 or ARM. Allocating
151/// less than 4kB of memory may make it impossible to commit more pages without overflowing the
152/// stack later on.
153///
154/// # Unsafety
155///
156/// The stack `base` address must be aligned as appropriate for the target.
157///
158/// The stack `size` must be a multiple of stack alignment required by target.
159///
160/// The `size` must not overflow `isize`.
161///
162/// `callback` must not unwind or return control flow by any other means than directly returning.
163///
164/// # Examples
165///
166/// ```
167/// use std::alloc;
168/// const STACK_ALIGN: usize = 4096;
169/// const STACK_SIZE: usize = 4096;
170/// unsafe {
171/// let layout = alloc::Layout::from_size_align(STACK_SIZE, STACK_ALIGN).unwrap();
172/// let new_stack = alloc::alloc(layout);
173/// assert!(!new_stack.is_null(), "allocations must succeed!");
174/// let (stack, result) = psm::on_stack(new_stack, STACK_SIZE, || {
175/// (psm::stack_pointer(), 4 + 4)
176/// });
177/// println!("4 + 4 = {} has been calculated on stack {:p}", result, stack);
178/// }
179/// ```
180#[cfg(switchable_stack)]
181pub unsafe fn on_stack<R, F: FnOnce() -> R>(base: *mut u8, size: usize, callback: F) -> R {
182 use core::mem::MaybeUninit;
183
184 extern_item! {
185 unsafe fn with_on_stack<R, F: FnOnce() -> R>(callback_ptr: usize, return_ptr: usize) {
186 let return_ptr = (*(return_ptr as *mut MaybeUninit<R>)).as_mut_ptr();
187 let callback = (*(callback_ptr as *mut MaybeUninit<F>)).as_ptr();
188 // Safe to move out from `F`, because closure in is forgotten in `on_stack` and dropping
189 // only occurs in this callback.
190 return_ptr.write((callback.read())());
191 }
192 }
193 let sp = match StackDirection::new() {
194 StackDirection::Ascending => base,
195 StackDirection::Descending => base.offset(size as isize),
196 };
197 let mut callback: MaybeUninit<F> = MaybeUninit::new(callback);
198 let mut return_value: MaybeUninit<R> = MaybeUninit::uninit();
199 rust_psm_on_stack(
200 &mut callback as *mut MaybeUninit<F> as usize,
201 &mut return_value as *mut MaybeUninit<R> as usize,
202 with_on_stack::<R, F>,
203 sp,
204 base,
205 );
206 return return_value.assume_init();
207}
208
209/// Run the provided non-terminating computation on an entirely new stack.
210///
211/// `base` address must be the low address of the stack memory region, regardless of the stack
212/// growth direction. It is not necessary for the whole region `[base; base + size]` to be usable
213/// at the time this function called, however it is required that at least the following hold:
214///
215/// * Both `base` and `base + size` are aligned up to the target-specific requirements;
216/// * Depending on `StackDirection` value for the platform, the end of the stack memory region,
217/// which would end up containing the first frame(s), must have sufficient number of pages
218/// allocated to execute code until more pages are commited. The other end should contain a guard
219/// page (not writable, readable or executable) to ensure Rust’s soundness guarantees.
220///
221/// Note, that some or all of these considerations are irrelevant to some applications. For
222/// example, Rust’s soundness story relies on all stacks having a guard-page, however if the user
223/// is able to guarantee that the memory region used for stack cannot be exceeded, a guard page may
224/// end up being an expensive unnecessity.
225///
226/// The previous stack is not deallocated and may not be deallocated unless the data on the old
227/// stack is not referenced in any way (by e.g. the `callback` closure).
228///
229/// On platforms where multiple stack pointers are available, the “current” stack pointer is
230/// replaced.
231///
232/// # Guidelines
233///
234/// Memory regions that are aligned to a single page (usually 4kB) are an extremely portable choice
235/// for stacks.
236///
237/// Allocate at least 4kB of stack. Some architectures (such as SPARC) consume stack memory
238/// significantly faster compared to the more usual architectures such as x86 or ARM. Allocating
239/// less than 4kB of memory may make it impossible to commit more pages without overflowing the
240/// stack later on.
241///
242/// # Unsafety
243///
244/// The stack `base` address must be aligned as appropriate for the target.
245///
246/// The stack `size` must be a multiple of stack alignment required by target.
247///
248/// The `size` must not overflow `isize`.
249///
250/// `callback` must not return (not enforced by typesystem currently because `!` is unstable),
251/// unwind or otherwise return control flow to any of the previous frames.
252#[cfg(switchable_stack)]
253pub unsafe fn replace_stack<F: FnOnce()>(base: *mut u8, size: usize, callback: F) -> ! {
254 extern_item! { unsafe fn with_replaced_stack<F: FnOnce()>(d: usize) -> ! {
255 // Safe to move out, because the closure is essentially forgotten by
256 // this being required to never return...
257 ::core::ptr::read(d as *const F)();
258 ::core::hint::unreachable_unchecked();
259 } }
260 let sp: *mut u8 = match StackDirection::new() {
261 StackDirection::Ascending => base,
262 StackDirection::Descending => base.offset(count:size as isize),
263 };
264 rust_psm_replace_stack(
265 &callback as *const F as usize,
266 callback:with_replaced_stack::<F>,
267 sp,
268 base,
269 );
270}
271
272/// The direction into which stack grows as stack frames are made.
273///
274/// This is a target-specific property that can be obtained at runtime by calling
275/// `StackDirection::new()`.
276#[derive(Clone, Copy, PartialEq, Eq, Debug)]
277pub enum StackDirection {
278 Ascending = 1,
279 Descending = 2,
280}
281
282impl StackDirection {
283 /// Obtain the stack growth direction.
284 #[cfg(asm)]
285 pub fn new() -> StackDirection {
286 const ASC: u8 = StackDirection::Ascending as u8;
287 const DSC: u8 = StackDirection::Descending as u8;
288 unsafe {
289 match rust_psm_stack_direction() {
290 ASC => StackDirection::Ascending,
291 DSC => StackDirection::Descending,
292 _ => ::core::hint::unreachable_unchecked(),
293 }
294 }
295 }
296}
297
298/// Returns current stack pointer.
299///
300/// Note, that the stack pointer returned is from the perspective of the caller. From the
301/// perspective of `stack_pointer` function the pointer returned is the frame pointer.
302///
303/// While it is a goal to minimize the amount of stack used by this function, implementations for
304/// some targets may be unable to avoid allocating a stack frame. This makes this function
305/// suitable for stack exhaustion detection only in conjunction with sufficient padding.
306///
307/// Using `stack_pointer` to check for stack exhaustion is tricky to get right. It is impossible to
308/// know the callee’s frame size, therefore such value must be derived some other way. A common
309/// approach is to use stack padding (reserve enough stack space for any function to be called) and
310/// check against the padded threshold. If padding is chosen incorrectly, a situation similar to
311/// one described below may occur:
312///
313/// 1. For stack exhaustion check, remaining stack is checked against `stack_pointer` with the
314/// padding applied;
315/// 2. Callee allocates more stack than was accounted for with padding, and accesses pages outside
316/// the stack, invalidating the execution (by e.g. crashing).
317#[cfg(asm)]
318pub fn stack_pointer() -> *mut u8 {
319 unsafe { rust_psm_stack_pointer() }
320}
321
322/// Macro that outputs its tokens only if `psm::on_stack` and `psm::replace_stack` are available.
323///
324/// # Examples
325///
326/// ```
327/// # use psm::psm_stack_manipulation;
328/// psm_stack_manipulation! {
329/// yes {
330/// /* Functions `on_stack` and `replace_stack` are available here */
331/// }
332/// no {
333/// /* Functions `on_stack` and `replace_stack` are not available here */
334/// }
335/// }
336/// ```
337#[cfg(switchable_stack)]
338#[macro_export]
339macro_rules! psm_stack_manipulation {
340 (yes { $($yes: tt)* } no { $($no: tt)* }) => { $($yes)* };
341}
342
343/// Macro that outputs its tokens only if `psm::on_stack` and `psm::replace_stack` are available.
344///
345/// # Examples
346///
347/// ```
348/// # use psm::psm_stack_manipulation;
349/// psm_stack_manipulation! {
350/// yes {
351/// /* Functions `on_stack` and `replace_stack` are available here */
352/// }
353/// no {
354/// /* Functions `on_stack` and `replace_stack` are not available here */
355/// }
356/// }
357/// ```
358#[cfg(not(switchable_stack))]
359#[macro_export]
360macro_rules! psm_stack_manipulation {
361 (yes { $($yes: tt)* } no { $($no: tt)* }) => { $($no)* };
362}
363
364/// Macro that outputs its tokens only if `psm::stack_pointer` and `psm::StackDirection::new` are
365/// available.
366///
367/// # Examples
368///
369/// ```
370/// # use psm::psm_stack_information;
371/// psm_stack_information! {
372/// yes {
373/// /* `psm::stack_pointer` and `psm::StackDirection::new` are available here */
374/// }
375/// no {
376/// /* `psm::stack_pointer` and `psm::StackDirection::new` are not available here */
377/// }
378/// }
379/// ```
380#[cfg(asm)]
381#[macro_export]
382macro_rules! psm_stack_information {
383 (yes { $($yes: tt)* } no { $($no: tt)* }) => { $($yes)* };
384}
385
386/// Macro that outputs its tokens only if `psm::stack_pointer` and `psm::StackDirection::new` are
387/// available.
388///
389/// # Examples
390///
391/// ```
392/// # use psm::psm_stack_information;
393/// psm_stack_information! {
394/// yes {
395/// /* `psm::stack_pointer` and `psm::StackDirection::new` are available here */
396/// }
397/// no {
398/// /* `psm::stack_pointer` and `psm::StackDirection::new` are not available here */
399/// }
400/// }
401/// ```
402#[cfg(not(asm))]
403#[macro_export]
404macro_rules! psm_stack_information {
405 (yes { $($yes: tt)* } no { $($no: tt)* }) => { $($no)* };
406}
407