wasmtime_runtime/traphandlers/
unix.rs

1use crate::traphandlers::{tls, wasmtime_longjmp};
2use std::cell::RefCell;
3use std::io;
4use std::mem::{self, MaybeUninit};
5use std::ptr::{self, null_mut};
6
7/// Function which may handle custom signals while processing traps.
8pub type SignalHandler<'a> =
9    dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool + Send + Sync + 'a;
10
11static mut PREV_SIGSEGV: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
12static mut PREV_SIGBUS: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
13static mut PREV_SIGILL: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
14static mut PREV_SIGFPE: MaybeUninit<libc::sigaction> = MaybeUninit::uninit();
15
16pub unsafe fn platform_init() {
17    let register = |slot: &mut MaybeUninit<libc::sigaction>, signal: i32| {
18        let mut handler: libc::sigaction = mem::zeroed();
19        // The flags here are relatively careful, and they are...
20        //
21        // SA_SIGINFO gives us access to information like the program
22        // counter from where the fault happened.
23        //
24        // SA_ONSTACK allows us to handle signals on an alternate stack,
25        // so that the handler can run in response to running out of
26        // stack space on the main stack. Rust installs an alternate
27        // stack with sigaltstack, so we rely on that.
28        //
29        // SA_NODEFER allows us to reenter the signal handler if we
30        // crash while handling the signal, and fall through to the
31        // Breakpad handler by testing handlingSegFault.
32        handler.sa_flags = libc::SA_SIGINFO | libc::SA_NODEFER | libc::SA_ONSTACK;
33        handler.sa_sigaction = trap_handler as usize;
34        libc::sigemptyset(&mut handler.sa_mask);
35        if libc::sigaction(signal, &handler, slot.as_mut_ptr()) != 0 {
36            panic!(
37                "unable to install signal handler: {}",
38                io::Error::last_os_error(),
39            );
40        }
41    };
42
43    // Allow handling OOB with signals on all architectures
44    register(&mut PREV_SIGSEGV, libc::SIGSEGV);
45
46    // Handle `unreachable` instructions which execute `ud2` right now
47    register(&mut PREV_SIGILL, libc::SIGILL);
48
49    // x86 and s390x use SIGFPE to report division by zero
50    if cfg!(target_arch = "x86_64") || cfg!(target_arch = "s390x") {
51        register(&mut PREV_SIGFPE, libc::SIGFPE);
52    }
53
54    // Sometimes we need to handle SIGBUS too:
55    // - On Darwin, guard page accesses are raised as SIGBUS.
56    if cfg!(target_os = "macos") || cfg!(target_os = "freebsd") {
57        register(&mut PREV_SIGBUS, libc::SIGBUS);
58    }
59
60    // TODO(#1980): x86-32, if we support it, will also need a SIGFPE handler.
61    // TODO(#1173): ARM32, if we support it, will also need a SIGBUS handler.
62}
63
64unsafe extern "C" fn trap_handler(
65    signum: libc::c_int,
66    siginfo: *mut libc::siginfo_t,
67    context: *mut libc::c_void,
68) {
69    let previous = match signum {
70        libc::SIGSEGV => &PREV_SIGSEGV,
71        libc::SIGBUS => &PREV_SIGBUS,
72        libc::SIGFPE => &PREV_SIGFPE,
73        libc::SIGILL => &PREV_SIGILL,
74        _ => panic!("unknown signal: {}", signum),
75    };
76    let handled = tls::with(|info| {
77        // If no wasm code is executing, we don't handle this as a wasm
78        // trap.
79        let info = match info {
80            Some(info) => info,
81            None => return false,
82        };
83
84        // If we hit an exception while handling a previous trap, that's
85        // quite bad, so bail out and let the system handle this
86        // recursive segfault.
87        //
88        // Otherwise flag ourselves as handling a trap, do the trap
89        // handling, and reset our trap handling flag. Then we figure
90        // out what to do based on the result of the trap handling.
91        let (pc, fp) = get_pc_and_fp(context, signum);
92        let jmp_buf = info.take_jmp_buf_if_trap(pc, |handler| handler(signum, siginfo, context));
93
94        // Figure out what to do based on the result of this handling of
95        // the trap. Note that our sentinel value of 1 means that the
96        // exception was handled by a custom exception handler, so we
97        // keep executing.
98        if jmp_buf.is_null() {
99            return false;
100        }
101        if jmp_buf as usize == 1 {
102            return true;
103        }
104        let faulting_addr = match signum {
105            libc::SIGSEGV | libc::SIGBUS => Some((*siginfo).si_addr() as usize),
106            _ => None,
107        };
108        info.set_jit_trap(pc, fp, faulting_addr);
109        // On macOS this is a bit special, unfortunately. If we were to
110        // `siglongjmp` out of the signal handler that notably does
111        // *not* reset the sigaltstack state of our signal handler. This
112        // seems to trick the kernel into thinking that the sigaltstack
113        // is still in use upon delivery of the next signal, meaning
114        // that the sigaltstack is not ever used again if we immediately
115        // call `wasmtime_longjmp` here.
116        //
117        // Note that if we use `longjmp` instead of `siglongjmp` then
118        // the problem is fixed. The problem with that, however, is that
119        // `setjmp` is much slower than `sigsetjmp` due to the
120        // preservation of the proceses signal mask. The reason
121        // `longjmp` appears to work is that it seems to call a function
122        // (according to published macOS sources) called
123        // `_sigunaltstack` which updates the kernel to say the
124        // sigaltstack is no longer in use. We ideally want to call that
125        // here but I don't think there's a stable way for us to call
126        // that.
127        //
128        // Given all that, on macOS only, we do the next best thing. We
129        // return from the signal handler after updating the register
130        // context. This will cause control to return to our shim
131        // function defined here which will perform the
132        // `wasmtime_longjmp` (`siglongjmp`) for us. The reason this
133        // works is that by returning from the signal handler we'll
134        // trigger all the normal machinery for "the signal handler is
135        // done running" which will clear the sigaltstack flag and allow
136        // reusing it for the next signal. Then upon resuming in our custom
137        // code we blow away the stack anyway with a longjmp.
138        if cfg!(target_os = "macos") {
139            unsafe extern "C" fn wasmtime_longjmp_shim(jmp_buf: *const u8) {
140                wasmtime_longjmp(jmp_buf)
141            }
142            set_pc(context, wasmtime_longjmp_shim as usize, jmp_buf as usize);
143            return true;
144        }
145        wasmtime_longjmp(jmp_buf)
146    });
147
148    if handled {
149        return;
150    }
151
152    // This signal is not for any compiled wasm code we expect, so we
153    // need to forward the signal to the next handler. If there is no
154    // next handler (SIG_IGN or SIG_DFL), then it's time to crash. To do
155    // this, we set the signal back to its original disposition and
156    // return. This will cause the faulting op to be re-executed which
157    // will crash in the normal way. If there is a next handler, call
158    // it. It will either crash synchronously, fix up the instruction
159    // so that execution can continue and return, or trigger a crash by
160    // returning the signal to it's original disposition and returning.
161    let previous = &*previous.as_ptr();
162    if previous.sa_flags & libc::SA_SIGINFO != 0 {
163        mem::transmute::<usize, extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void)>(
164            previous.sa_sigaction,
165        )(signum, siginfo, context)
166    } else if previous.sa_sigaction == libc::SIG_DFL || previous.sa_sigaction == libc::SIG_IGN {
167        libc::sigaction(signum, previous, ptr::null_mut());
168    } else {
169        mem::transmute::<usize, extern "C" fn(libc::c_int)>(previous.sa_sigaction)(signum)
170    }
171}
172
173unsafe fn get_pc_and_fp(cx: *mut libc::c_void, _signum: libc::c_int) -> (*const u8, usize) {
174    cfg_if::cfg_if! {
175        if #[cfg(all(target_os = "linux", target_arch = "x86_64"))] {
176            let cx = &*(cx as *const libc::ucontext_t);
177            (
178                cx.uc_mcontext.gregs[libc::REG_RIP as usize] as *const u8,
179                cx.uc_mcontext.gregs[libc::REG_RBP as usize] as usize
180            )
181        } else if #[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "aarch64"))] {
182            let cx = &*(cx as *const libc::ucontext_t);
183            (
184                cx.uc_mcontext.pc as *const u8,
185                cx.uc_mcontext.regs[29] as usize,
186            )
187        } else if #[cfg(all(target_os = "linux", target_arch = "s390x"))] {
188            // On s390x, SIGILL and SIGFPE are delivered with the PSW address
189            // pointing *after* the faulting instruction, while SIGSEGV and
190            // SIGBUS are delivered with the PSW address pointing *to* the
191            // faulting instruction.  To handle this, the code generator registers
192            // any trap that results in one of "late" signals on the last byte
193            // of the instruction, and any trap that results in one of the "early"
194            // signals on the first byte of the instruction (as usual).  This
195            // means we simply need to decrement the reported PSW address by
196            // one in the case of a "late" signal here to ensure we always
197            // correctly find the associated trap handler.
198            let trap_offset = match _signum {
199                libc::SIGILL | libc::SIGFPE => 1,
200                _ => 0,
201            };
202            let cx = &*(cx as *const libc::ucontext_t);
203            (
204                (cx.uc_mcontext.psw.addr - trap_offset) as *const u8,
205                *(cx.uc_mcontext.gregs[15] as *const usize),
206            )
207        } else if #[cfg(all(target_os = "macos", target_arch = "x86_64"))] {
208            let cx = &*(cx as *const libc::ucontext_t);
209            (
210                (*cx.uc_mcontext).__ss.__rip as *const u8,
211                (*cx.uc_mcontext).__ss.__rbp as usize,
212            )
213        } else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] {
214            let cx = &*(cx as *const libc::ucontext_t);
215            (
216                (*cx.uc_mcontext).__ss.__pc as *const u8,
217                (*cx.uc_mcontext).__ss.__fp as usize,
218            )
219        } else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
220            let cx = &*(cx as *const libc::ucontext_t);
221            (
222                cx.uc_mcontext.mc_rip as *const u8,
223                cx.uc_mcontext.mc_rbp as usize,
224            )
225        } else if #[cfg(all(target_os = "linux", target_arch = "riscv64"))] {
226            let cx = &*(cx as *const libc::ucontext_t);
227            (
228                cx.uc_mcontext.__gregs[libc::REG_PC] as *const u8,
229                cx.uc_mcontext.__gregs[libc::REG_S0] as usize,
230            )
231        } else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
232            let cx = &*(cx as *const libc::mcontext_t);
233            (
234                cx.mc_gpregs.gp_elr as *const u8,
235                cx.mc_gpregs.gp_x[29] as usize,
236            )
237        }
238        else {
239            compile_error!("unsupported platform");
240        }
241    }
242}
243
244// This is only used on macOS targets for calling an unwinding shim
245// function to ensure that we return from the signal handler.
246//
247// See more comments above where this is called for what it's doing.
248unsafe fn set_pc(cx: *mut libc::c_void, pc: usize, arg1: usize) {
249    cfg_if::cfg_if! {
250        if #[cfg(not(target_os = "macos"))] {
251            drop((cx, pc, arg1));
252            unreachable!(); // not used on these platforms
253        } else if #[cfg(target_arch = "x86_64")] {
254            let cx = &mut *(cx as *mut libc::ucontext_t);
255            (*cx.uc_mcontext).__ss.__rip = pc as u64;
256            (*cx.uc_mcontext).__ss.__rdi = arg1 as u64;
257            // We're simulating a "pseudo-call" so we need to ensure
258            // stack alignment is properly respected, notably that on a
259            // `call` instruction the stack is 8/16-byte aligned, then
260            // the function adjusts itself to be 16-byte aligned.
261            //
262            // Most of the time the stack pointer is 16-byte aligned at
263            // the time of the trap but for more robust-ness with JIT
264            // code where it may ud2 in a prologue check before the
265            // stack is aligned we double-check here.
266            if (*cx.uc_mcontext).__ss.__rsp % 16 == 0 {
267                (*cx.uc_mcontext).__ss.__rsp -= 8;
268            }
269        } else if #[cfg(target_arch = "aarch64")] {
270            let cx = &mut *(cx as *mut libc::ucontext_t);
271            (*cx.uc_mcontext).__ss.__pc = pc as u64;
272            (*cx.uc_mcontext).__ss.__x[0] = arg1 as u64;
273        } else {
274            compile_error!("unsupported macos target architecture");
275        }
276    }
277}
278
279/// A function for registering a custom alternate signal stack (sigaltstack).
280///
281/// Rust's libstd installs an alternate stack with size `SIGSTKSZ`, which is not
282/// always large enough for our signal handling code. Override it by creating
283/// and registering our own alternate stack that is large enough and has a guard
284/// page.
285#[cold]
286pub fn lazy_per_thread_init() {
287    // This thread local is purely used to register a `Stack` to get deallocated
288    // when the thread exists. Otherwise this function is only ever called at
289    // most once per-thread.
290    thread_local! {
291        static STACK: RefCell<Option<Stack>> = const { RefCell::new(None) };
292    }
293
294    /// The size of the sigaltstack (not including the guard, which will be
295    /// added). Make this large enough to run our signal handlers.
296    ///
297    /// The main current requirement of the signal handler in terms of stack
298    /// space is that `malloc`/`realloc` are called to create a `Backtrace` of
299    /// wasm frames.
300    ///
301    /// Historically this was 16k. Turns out jemalloc requires more than 16k of
302    /// stack space in debug mode, so this was bumped to 64k.
303    const MIN_STACK_SIZE: usize = 64 * 4096;
304
305    struct Stack {
306        mmap_ptr: *mut libc::c_void,
307        mmap_size: usize,
308    }
309
310    return STACK.with(|s| {
311        *s.borrow_mut() = unsafe { allocate_sigaltstack() };
312    });
313
314    unsafe fn allocate_sigaltstack() -> Option<Stack> {
315        // Check to see if the existing sigaltstack, if it exists, is big
316        // enough. If so we don't need to allocate our own.
317        let mut old_stack = mem::zeroed();
318        let r = libc::sigaltstack(ptr::null(), &mut old_stack);
319        assert_eq!(
320            r,
321            0,
322            "learning about sigaltstack failed: {}",
323            io::Error::last_os_error()
324        );
325        if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
326            return None;
327        }
328
329        // ... but failing that we need to allocate our own, so do all that
330        // here.
331        let page_size = crate::page_size();
332        let guard_size = page_size;
333        let alloc_size = guard_size + MIN_STACK_SIZE;
334
335        let ptr = rustix::mm::mmap_anonymous(
336            null_mut(),
337            alloc_size,
338            rustix::mm::ProtFlags::empty(),
339            rustix::mm::MapFlags::PRIVATE,
340        )
341        .expect("failed to allocate memory for sigaltstack");
342
343        // Prepare the stack with readable/writable memory and then register it
344        // with `sigaltstack`.
345        let stack_ptr = (ptr as usize + guard_size) as *mut std::ffi::c_void;
346        rustix::mm::mprotect(
347            stack_ptr,
348            MIN_STACK_SIZE,
349            rustix::mm::MprotectFlags::READ | rustix::mm::MprotectFlags::WRITE,
350        )
351        .expect("mprotect to configure memory for sigaltstack failed");
352        let new_stack = libc::stack_t {
353            ss_sp: stack_ptr,
354            ss_flags: 0,
355            ss_size: MIN_STACK_SIZE,
356        };
357        let r = libc::sigaltstack(&new_stack, ptr::null_mut());
358        assert_eq!(
359            r,
360            0,
361            "registering new sigaltstack failed: {}",
362            io::Error::last_os_error()
363        );
364
365        Some(Stack {
366            mmap_ptr: ptr,
367            mmap_size: alloc_size,
368        })
369    }
370
371    impl Drop for Stack {
372        fn drop(&mut self) {
373            unsafe {
374                // Deallocate the stack memory.
375                let r = rustix::mm::munmap(self.mmap_ptr, self.mmap_size);
376                debug_assert!(r.is_ok(), "munmap failed during thread shutdown");
377            }
378        }
379    }
380}