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 | |
15 | macro_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" )] |
27 | macro_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" )] |
37 | macro_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" )] |
47 | macro_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 |
58 | extern_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)] |
100 | unsafe 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)] |
111 | unsafe 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)] |
181 | pub 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)] |
253 | pub 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)] |
277 | pub enum StackDirection { |
278 | Ascending = 1, |
279 | Descending = 2, |
280 | } |
281 | |
282 | impl 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)] |
318 | pub 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 ] |
339 | macro_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 ] |
360 | macro_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 ] |
382 | macro_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 ] |
404 | macro_rules! psm_stack_information { |
405 | (yes { $($yes: tt)* } no { $($no: tt)* }) => { $($no)* }; |
406 | } |
407 | |