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 71 72
/* 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", target_os = "android")))]
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 sig::signal;
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", target_os = "android")))]
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);
}
}