1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub fn install() {}

#[cfg(any(target_os = "macos", target_os = "linux"))]
pub fn install() {
    use std::io::Write;
    use std::sync::atomic;
    use std::thread;

    use crate::backtrace;

    extern "C" fn handler(sig: i32) {
        // Only print crash message and backtrace the first time, to avoid
        // infinite recursion if the printing causes another signal.
        static BEEN_HERE_BEFORE: atomic::AtomicBool = atomic::AtomicBool::new(false);
        if !BEEN_HERE_BEFORE.swap(true, atomic::Ordering::SeqCst) {
            // stderr is unbuffered, so we won’t lose output if we crash later
            // in this handler, and the std::io::stderr() call never allocates.
            // std::io::stdout() allocates the first time it’s called, which in
            // practice will often segfault (see below).
            let stderr = std::io::stderr();
            let mut stderr = stderr.lock();
            let _ = write!(&mut stderr, "Caught signal {sig}");
            if let Some(name) = thread::current().name() {
                let _ = write!(&mut stderr, " in thread \"{}\"", name);
            }
            let _ = writeln!(&mut stderr);

            // This call always allocates, which in practice will segfault if
            // we’re handling a non-main-thread (e.g. layout) segfault. Strictly
            // speaking in POSIX terms, this is also undefined behaviour.
            let _ = backtrace::print(&mut stderr);
        }

        // Outside the BEEN_HERE_BEFORE check, we must only call functions we
        // know to be “async-signal-safe”, which includes sigaction(), raise(),
        // and _exit(), but generally doesn’t include anything that allocates.
        // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03_03
        raise_signal_or_exit_with_error(sig);
    }

    signal!(libc::SIGSEGV, handler); // handle segfaults
    signal!(libc::SIGILL, handler); // handle stack overflow and unsupported CPUs
    signal!(libc::SIGIOT, handler); // handle double panics
    signal!(libc::SIGBUS, handler); // handle invalid memory access
}

#[cfg(not(any(target_os = "macos", target_os = "linux")))]
pub(crate) fn raise_signal_or_exit_with_error(_signal: i32) {
    std::process::exit(1);
}

#[cfg(any(target_os = "macos", target_os = "linux"))]
pub(crate) fn raise_signal_or_exit_with_error(signal: i32) {
    unsafe {
        // Reset the signal to the default action, and reraise the signal.
        // Unlike libc::_exit(sig), which terminates the process normally,
        // this terminates abnormally just like an uncaught signal, allowing
        // mach (or your shell) to distinguish it from an ordinary exit, and
        // allows your kernel to make a core dump if configured to do so.
        let mut action: libc::sigaction = std::mem::zeroed();
        action.sa_sigaction = libc::SIG_DFL;
        libc::sigaction(signal, &action, std::ptr::null_mut());
        libc::raise(signal);
    }
}