mirror of
https://github.com/servo/servo
synced 2026-04-25 17:15:48 +02:00
Testing: We do not currently have a way to test signal handling in the servoshell binary, so this change does not include tests. Fixes: #43836 --------- Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
79 lines
3.6 KiB
Rust
79 lines
3.6 KiB
Rust
/* 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 libc::siginfo_t;
|
||
|
||
use crate::backtrace;
|
||
|
||
fn handler(siginfo: &siginfo_t) {
|
||
// 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 {}", siginfo.si_signo);
|
||
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(siginfo.si_signo);
|
||
}
|
||
|
||
unsafe {
|
||
signal_hook_registry::register_unchecked(libc::SIGSEGV, handler)
|
||
.expect("Could not register SIGSEGV handler");
|
||
signal_hook_registry::register_unchecked(libc::SIGILL, handler)
|
||
.expect("Could not register SIGILL handler");
|
||
signal_hook_registry::register_unchecked(libc::SIGIOT, handler)
|
||
.expect("Could not register SIGIOT handler");
|
||
signal_hook_registry::register_unchecked(libc::SIGBUS, handler)
|
||
.expect("Could not register SIGBUS handler");
|
||
}
|
||
}
|
||
|
||
#[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);
|
||
}
|
||
}
|