logicaffeine_system/
stack.rs1pub const MAIN_STACK_BYTES: usize = 64 * 1024 * 1024;
16
17#[inline]
22pub fn run_main<F: FnOnce() + Send + 'static>(main: F) {
23 switch::run_main(main);
24}
25
26#[allow(dead_code)]
29fn on_a_thread<F: FnOnce() + Send + 'static>(main: F) {
30 let outcome = std::thread::Builder::new()
31 .stack_size(MAIN_STACK_BYTES)
32 .spawn(main)
33 .expect("the program's thread starts")
34 .join();
35 if outcome.is_err() {
36 std::process::exit(1);
37 }
38}
39
40#[cfg(unix)]
41mod switch {
42 psm::psm_stack_manipulation! {
43 yes {
44 #[inline]
45 pub(super) fn run_main<F: FnOnce() + Send + 'static>(main: F) {
46 let Some(stack) = super::mapped::Stack::map(super::MAIN_STACK_BYTES) else {
47 return super::on_a_thread(main);
48 };
49 super::overflow::watch(stack.guards());
50 struct NoUnwind;
52 impl Drop for NoUnwind {
53 fn drop(&mut self) {
54 std::process::abort();
55 }
56 }
57 unsafe {
60 psm::on_stack(stack.base(), stack.size(), || {
61 let no_unwind = NoUnwind;
62 main();
63 std::mem::forget(no_unwind);
64 });
65 }
66 }
67 }
68 no {
69 pub(super) fn run_main<F: FnOnce() + Send + 'static>(main: F) {
70 super::on_a_thread(main);
71 }
72 }
73 }
74}
75
76#[cfg(all(not(unix), not(target_arch = "wasm32")))]
77mod switch {
78 pub(super) fn run_main<F: FnOnce() + Send + 'static>(main: F) {
79 super::on_a_thread(main);
80 }
81}
82
83#[cfg(target_arch = "wasm32")]
84mod switch {
85 pub(super) fn run_main<F: FnOnce() + Send + 'static>(main: F) {
86 main();
87 }
88}
89
90#[cfg(unix)]
91mod mapped {
92 pub(super) struct Stack {
95 mapping: *mut u8,
96 total: usize,
97 page: usize,
98 size: usize,
99 }
100
101 impl Stack {
102 pub(super) fn map(size: usize) -> Option<Stack> {
104 let page = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
106 let page = usize::try_from(page).ok().filter(|p| p.is_power_of_two())?;
107 let size = size.checked_add(page - 1)? & !(page - 1);
108 let total = size.checked_add(2 * page)?;
109 #[cfg(target_os = "linux")]
110 let flags = libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_NORESERVE | libc::MAP_STACK;
111 #[cfg(not(target_os = "linux"))]
112 let flags = libc::MAP_PRIVATE | libc::MAP_ANON;
113 let mapping = unsafe { libc::mmap(std::ptr::null_mut(), total, libc::PROT_NONE, flags, -1, 0) };
115 if mapping == libc::MAP_FAILED {
116 return None;
117 }
118 let stack = Stack {
119 mapping: mapping.cast(),
120 total,
121 page,
122 size,
123 };
124 let opened = unsafe {
126 libc::mprotect(
127 stack.mapping.add(page).cast(),
128 size,
129 libc::PROT_READ | libc::PROT_WRITE,
130 )
131 };
132 (opened == 0).then_some(stack)
133 }
134
135 pub(super) fn base(&self) -> *mut u8 {
137 unsafe { self.mapping.add(self.page) }
139 }
140
141 pub(super) fn size(&self) -> usize {
143 self.size
144 }
145
146 pub(super) fn guards(&self) -> [(usize, usize); 2] {
148 let start = self.mapping as usize;
149 [
150 (start, start + self.page),
151 (start + self.page + self.size, start + self.total),
152 ]
153 }
154 }
155
156 impl Drop for Stack {
157 fn drop(&mut self) {
158 unsafe { libc::munmap(self.mapping.cast(), self.total) };
160 }
161 }
162}
163
164#[cfg(unix)]
167mod overflow {
168 use std::sync::atomic::{AtomicUsize, Ordering};
169 use std::sync::Once;
170
171 static GUARDS: [(AtomicUsize, AtomicUsize); 2] = [
172 (AtomicUsize::new(0), AtomicUsize::new(0)),
173 (AtomicUsize::new(0), AtomicUsize::new(0)),
174 ];
175 static mut PREVIOUS: [std::mem::MaybeUninit<libc::sigaction>; 2] =
176 [std::mem::MaybeUninit::uninit(), std::mem::MaybeUninit::uninit()];
177 const SIGNALS: [libc::c_int; 2] = [libc::SIGSEGV, libc::SIGBUS];
178 const SIGNAL_STACK_BYTES: usize = 64 * 1024;
180
181 pub(super) fn watch(guards: [(usize, usize); 2]) {
183 for (slot, (start, end)) in GUARDS.iter().zip(guards) {
184 slot.0.store(start, Ordering::SeqCst);
185 slot.1.store(end, Ordering::SeqCst);
186 }
187 ensure_signal_stack();
188 static INSTALL: Once = Once::new();
189 INSTALL.call_once(|| {
190 for (i, signal) in SIGNALS.into_iter().enumerate() {
191 unsafe {
193 let mut action: libc::sigaction = std::mem::zeroed();
194 action.sa_sigaction = on_fault as *const () as usize;
195 action.sa_flags = libc::SA_SIGINFO | libc::SA_ONSTACK;
196 libc::sigemptyset(&mut action.sa_mask);
197 let previous = std::ptr::addr_of_mut!(PREVIOUS[i]).cast::<libc::sigaction>();
198 libc::sigaction(signal, &action, previous);
199 }
200 }
201 });
202 }
203
204 fn ensure_signal_stack() {
207 unsafe {
210 let mut current: libc::stack_t = std::mem::zeroed();
211 if libc::sigaltstack(std::ptr::null(), &mut current) != 0 {
212 return;
213 }
214 if current.ss_flags & libc::SS_DISABLE == 0 && current.ss_size >= SIGNAL_STACK_BYTES {
215 return;
216 }
217 let Some(stack) = super::mapped::Stack::map(SIGNAL_STACK_BYTES) else {
218 return;
219 };
220 let installed = libc::stack_t {
221 ss_sp: stack.base().cast(),
222 ss_flags: 0,
223 ss_size: stack.size(),
224 };
225 if libc::sigaltstack(&installed, std::ptr::null_mut()) == 0 {
226 std::mem::forget(stack);
227 }
228 }
229 }
230
231 fn in_a_guard(address: usize) -> bool {
232 GUARDS.iter().any(|(start, end)| {
233 let (start, end) = (start.load(Ordering::Relaxed), end.load(Ordering::Relaxed));
234 start <= address && address < end
235 })
236 }
237
238 extern "C" fn on_fault(signal: libc::c_int, info: *mut libc::siginfo_t, context: *mut libc::c_void) {
239 let address = unsafe { (*info).si_addr() } as usize;
241 if in_a_guard(address) {
242 crate::io::flush_stdout_if_free();
243 let report = logicaffeine_data::failure::CALL_DEPTH_EXCEEDED;
244 unsafe {
246 libc::write(2, report.as_ptr().cast(), report.len());
247 libc::write(2, b"\n".as_ptr().cast(), 1);
248 libc::_exit(1);
249 }
250 }
251 let i = SIGNALS.iter().position(|s| *s == signal).unwrap_or(0);
252 unsafe {
254 let previous = &*std::ptr::addr_of!(PREVIOUS[i]).cast::<libc::sigaction>();
255 if previous.sa_flags & libc::SA_SIGINFO != 0 && previous.sa_sigaction > libc::SIG_IGN {
256 let handler: extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void) =
257 std::mem::transmute(previous.sa_sigaction);
258 handler(signal, info, context);
259 } else {
260 libc::sigaction(signal, previous, std::ptr::null_mut());
262 }
263 }
264 }
265}
266
267#[cfg(all(test, unix))]
268#[path = "../../logicaffeine_tests/unit/logicaffeine_system/stack/tests.rs"]
269mod tests;