Skip to main content

servoshell/
crash_handler.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "android")))]
6pub fn install() {}
7
8#[cfg(any(target_os = "macos", target_os = "linux"))]
9pub fn install() {
10    use std::io::Write;
11    use std::sync::atomic;
12    use std::thread;
13
14    use libc::siginfo_t;
15
16    use crate::backtrace;
17
18    fn handler(siginfo: &siginfo_t) {
19        // Only print crash message and backtrace the first time, to avoid
20        // infinite recursion if the printing causes another signal.
21        static BEEN_HERE_BEFORE: atomic::AtomicBool = atomic::AtomicBool::new(false);
22        if !BEEN_HERE_BEFORE.swap(true, atomic::Ordering::SeqCst) {
23            // stderr is unbuffered, so we won’t lose output if we crash later
24            // in this handler, and the std::io::stderr() call never allocates.
25            // std::io::stdout() allocates the first time it’s called, which in
26            // practice will often segfault (see below).
27            let stderr = std::io::stderr();
28            let mut stderr = stderr.lock();
29            let _ = write!(&mut stderr, "Caught signal {}", siginfo.si_signo);
30            if let Some(name) = thread::current().name() {
31                let _ = write!(&mut stderr, " in thread \"{}\"", name);
32            }
33            let _ = writeln!(&mut stderr);
34
35            // This call always allocates, which in practice will segfault if
36            // we’re handling a non-main-thread (e.g. layout) segfault. Strictly
37            // speaking in POSIX terms, this is also undefined behaviour.
38            let _ = backtrace::print(&mut stderr);
39        }
40
41        // Outside the BEEN_HERE_BEFORE check, we must only call functions we
42        // know to be “async-signal-safe”, which includes sigaction(), raise(),
43        // and _exit(), but generally doesn’t include anything that allocates.
44        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03_03
45        raise_signal_or_exit_with_error(siginfo.si_signo);
46    }
47
48    unsafe {
49        signal_hook_registry::register_unchecked(libc::SIGSEGV, handler)
50            .expect("Could not register SIGSEGV handler");
51        signal_hook_registry::register_unchecked(libc::SIGILL, handler)
52            .expect("Could not register SIGILL handler");
53        signal_hook_registry::register_unchecked(libc::SIGIOT, handler)
54            .expect("Could not register SIGIOT handler");
55        signal_hook_registry::register_unchecked(libc::SIGBUS, handler)
56            .expect("Could not register SIGBUS handler");
57    }
58}
59
60#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "android")))]
61pub(crate) fn raise_signal_or_exit_with_error(_signal: i32) {
62    std::process::exit(1);
63}
64
65#[cfg(any(target_os = "macos", target_os = "linux"))]
66pub(crate) fn raise_signal_or_exit_with_error(signal: i32) {
67    unsafe {
68        // Reset the signal to the default action, and reraise the signal.
69        // Unlike libc::_exit(sig), which terminates the process normally,
70        // this terminates abnormally just like an uncaught signal, allowing
71        // mach (or your shell) to distinguish it from an ordinary exit, and
72        // allows your kernel to make a core dump if configured to do so.
73        let mut action: libc::sigaction = std::mem::zeroed();
74        action.sa_sigaction = libc::SIG_DFL;
75        libc::sigaction(signal, &action, std::ptr::null_mut());
76        libc::raise(signal);
77    }
78}