use super::crash_handler::handle_posix_sigaction;
use crate::shared::configuration::CrashtrackerConfiguration;
use crate::signal_from_signum;
use core::ptr;
use core::sync::atomic::AtomicBool;
use core::sync::atomic::Ordering::SeqCst;
use libc::{
c_void, mmap, sigaltstack, siginfo_t, MAP_ANON, MAP_FAILED, MAP_PRIVATE, PROT_NONE, PROT_READ,
PROT_WRITE, SIGSTKSZ,
};
use libdd_common::unix_utils::terminate;
use nix::sys::signal::{self, SaFlags, SigAction, SigHandler};
const MAX_SIGNALS: usize = 65;
static mut HANDLERS: [Option<(signal::Signal, SigAction)>; MAX_SIGNALS] = [None; MAX_SIGNALS];
static INIT_STARTED: AtomicBool = AtomicBool::new(false);
static INIT_FINISHED: AtomicBool = AtomicBool::new(false);
pub fn register_crash_handlers(config: &CrashtrackerConfiguration) -> anyhow::Result<()> {
anyhow::ensure!(
INIT_STARTED
.compare_exchange(false, true, SeqCst, SeqCst)
.is_ok(),
"Attempted to double register crash handlers"
);
for signum in config.signals() {
anyhow::ensure!(*signum >= 0 && *signum < MAX_SIGNALS as i32);
}
if config.create_alt_stack() {
unsafe { create_alt_stack()? };
}
let mut errors = vec![];
for signum in config.signals() {
let index = *signum as usize;
match unsafe { register_signal_handler(*signum, config) } {
Ok(handler) => unsafe { HANDLERS[index] = Some(handler) },
Err(e) => errors.push(format!("Unable to register signal for {signum}: {e:?}")),
};
}
INIT_FINISHED.store(true, SeqCst);
anyhow::ensure!(
errors.is_empty(),
"Errors registering signal handlers {errors:?}"
);
Ok(())
}
pub(crate) unsafe fn chain_signal_handler(
signum: i32,
sig_info: *mut siginfo_t,
ucontext: *mut c_void,
) {
if !INIT_FINISHED.load(SeqCst) {
eprintln!("Crashed during signal handler setup, cannot chain {signum}, aborting");
terminate()
}
if signum < 0 || signum >= MAX_SIGNALS as i32 {
eprintln!("Unexpected value for {signum}, cannot chain, aborting");
terminate()
}
if let Some((signal, sigaction)) = &mut unsafe { HANDLERS[signum as usize] } {
match sigaction.handler() {
SigHandler::SigDfl => {
unsafe { signal::sigaction(*signal, sigaction) }.unwrap_or_else(|_| terminate());
unsafe { libc::raise(signum) };
}
SigHandler::SigIgn => (), SigHandler::Handler(f) => f(signum),
SigHandler::SigAction(f) => f(signum, sig_info, ucontext),
}
} else {
eprintln!("Missing chain handler for {signum}, cannot chain, aborting");
terminate()
}
}
unsafe fn create_alt_stack() -> anyhow::Result<()> {
let page_size = page_size::get();
let sigalstack_base_size = core::cmp::max(SIGSTKSZ, 16 * page_size);
let stackp = mmap(
ptr::null_mut(),
sigalstack_base_size + page_size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANON,
-1,
0,
);
anyhow::ensure!(
stackp != MAP_FAILED,
"failed to allocate an alternative stack"
);
let guard_result = libc::mprotect(stackp, page_size, PROT_NONE);
anyhow::ensure!(
guard_result == 0,
"failed to set up alternative stack guard page"
);
let stackp = stackp.add(page_size);
let stack = libc::stack_t {
ss_sp: stackp,
ss_flags: 0,
ss_size: sigalstack_base_size,
};
let rval = sigaltstack(&stack, ptr::null_mut());
anyhow::ensure!(rval == 0, "sigaltstack failed {rval}");
Ok(())
}
unsafe fn register_signal_handler(
signum: i32,
config: &CrashtrackerConfiguration,
) -> anyhow::Result<(signal::Signal, SigAction)> {
let signal_type = signal_from_signum(signum)?;
let extra_saflags = if config.use_alt_stack() {
SaFlags::SA_ONSTACK
} else {
SaFlags::empty()
};
let sig_action = SigAction::new(
SigHandler::SigAction(handle_posix_sigaction),
SaFlags::SA_NODEFER | extra_saflags,
signal::SigSet::empty(),
);
let old_handler = signal::sigaction(signal_type, &sig_action)?;
Ok((signal_type, old_handler))
}
#[cfg(test)]
mod tests {
#[cfg(target_os = "linux")]
#[test]
fn test_max_signals() {
assert!(super::MAX_SIGNALS as libc::c_int > libc::SIGRTMAX());
}
}