wasmtime_runtime/sys/unix/
signals.rs

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