rsgc/heap/signals/
unix.rs

1use std::{mem::MaybeUninit, ptr::null_mut};
2
3use libc::*;
4
5use crate::{
6    heap::{heap::heap, safepoint, stack::approximate_stack_pointer, thread::Thread},
7    utils::machine_context::{registers_from_ucontext, PlatformRegisters},
8};
9
10unsafe extern "C" fn sigdie_handler(sig: i32, _info: *mut siginfo_t, _context: *mut c_void) {
11    let mut sset = MaybeUninit::<libc::sigset_t>::zeroed().assume_init();
12    sigfillset(&mut sset);
13    sigprocmask(SIG_UNBLOCK, &mut sset, null_mut());
14    signal(sig, SIG_DFL);
15
16    if sig != SIGSEGV && sig != SIGBUS && sig != SIGILL {
17        raise(sig);
18    }
19
20    // fall-through return to re-execute faulting statement (but without the error handler)
21}
22
23pub unsafe extern "C" fn handle_sigsegv(
24    _sig: i32,
25    info: *mut siginfo_t,
26    context: *mut ucontext_t,
27) -> bool {
28    if safepoint::addr_in_safepoint((*info).si_addr() as _) {
29        let thread = Thread::current();
30        thread.platform_registers = registers_from_ucontext(context);
31
32        log::trace!(target: "gc-safepoint", "{:?} reached safepoint", std::thread::current().id());
33        // basically spin-loop that waits for safepoint to be disabled
34        thread.enter_safepoint(get_sp_from_ucontext(context).cast());
35        thread.platform_registers = null_mut();
36        log::trace!(target: "gc-safepoint", "{:?} exit safepoint", std::thread::current().id());
37        true
38    } else {
39        false
40    }
41}
42
43pub unsafe extern "C" fn segv_handler(sig: i32, info: *mut siginfo_t, context: *mut ucontext_t) {
44    // polling page was protected and some thread tried to read from it
45    // and we got here. Polling page gets protected only when safepoint is requested.
46    if safepoint::addr_in_safepoint((*info).si_addr() as usize) {
47        let thread = Thread::current();
48        thread.platform_registers = registers_from_ucontext(context);
49
50        log::trace!(target: "gc-safepoint", "{:?} reached safepoint", std::thread::current().id());
51        // basically spin-loop that waits for safepoint to be disabled
52        thread.enter_safepoint(get_sp_from_ucontext(context).cast());
53        thread.platform_registers = null_mut();
54        log::trace!(target: "gc-safepoint", "{:?} exit safepoint", std::thread::current().id());
55        return;
56    } else {
57        let heap = heap();
58
59        if heap.is_in((*info).si_addr().cast()) {
60            // we got here because we tried to access heap memory that is not mapped
61            // this can happen if we try to access memory that is not allocated yet
62            // or if we try to access memory that was already freed
63            // we can't do anything about it, so we just die
64
65            let backtrace = std::backtrace::Backtrace::force_capture();
66
67            eprintln!(
68                "FATAL: Heap Out of Bounds Access of {:p}",
69                (*info).si_addr()
70            );
71            eprintln!("Probably tried to access uncommited region or it is a bug in GC: report it to https://github.com/playxe/rsgc");
72            eprintln!("{}", backtrace);
73
74            return sigdie_handler(sig, info, context as _);
75        }
76    }
77
78    println!(
79        "FATAL: Unhandled signal {}: {:p}, backtrace: \n{}",
80        sig,
81        (*info).si_addr(),
82        std::backtrace::Backtrace::force_capture()
83    );
84
85    sigdie_handler(sig, info, context as _);
86}
87
88unsafe fn get_sp_from_ucontext(uc: *mut ucontext_t) -> *mut () {
89    cfg_if::cfg_if! {
90        if #[cfg(any(target_os="macos", target_os="ios", target_os="tvos", target_os="watchos"))]
91        {
92            #[cfg(any(target_arch="aarch64", target_arch="arm"))]
93            {
94
95                (*(*uc).uc_mcontext).__ss.__sp as _
96            }
97            #[cfg(target_arch="x86_64")]
98            {
99                (*(*uc).uc_mcontext).__ss.__rsp as _
100            }
101            #[cfg(target_arch="x86")]
102            {
103                (*(*uc).uc_mcontext).__ss.__esp as _
104            }
105        } else {
106            let _ = uc;
107            // Darwin has the sanest ucontext_t impl, others don't so we use our own code
108            approximate_stack_pointer() as _
109        }
110    }
111}
112
113pub fn install_signal_handlers() {
114    unsafe {
115        let mut act: sigaction = std::mem::MaybeUninit::<sigaction>::zeroed().assume_init();
116
117        sigemptyset(&mut act.sa_mask);
118        act.sa_sigaction = segv_handler as _;
119        act.sa_flags = SA_SIGINFO;
120
121        if sigaction(SIGSEGV, &act, null_mut()) < 0 {
122            panic!("failed to set SIGSEGV handler for safepoints");
123        }
124
125        // on AArch64 SIGBUS is thrown when accessing undefined memory.
126        if sigaction(SIGBUS, &act, null_mut()) < 0 {
127            panic!("failed to set SIGBUS handler for safepoints");
128        }
129    }
130}