shape_gc/trap_handler.rs
1//! SIGSEGV trap handler for concurrent relocation.
2//!
3//! When the GC relocates objects, old regions are protected with PROT_NONE.
4//! If the mutator accesses a relocated object before pointer fixup completes,
5//! a SIGSEGV fires. The trap handler:
6//! 1. Checks if the faulting address is in a protected (relocated) region.
7//! 2. If yes: looks up the forwarding table, updates the pointer, resumes.
8//! 3. If no: chains to the previous handler (real segfault).
9//!
10//! **Safety:** This is the riskiest component. Initially, relocation is done
11//! stop-the-world with synchronous fixup (no trap handler needed). The trap
12//! handler is an opt-in feature for concurrent relocation.
13
14use crate::relocator::ForwardingTable;
15use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
16
17/// Global state for the trap handler.
18static TRAP_HANDLER_INSTALLED: AtomicBool = AtomicBool::new(false);
19
20/// Pointer to the active forwarding table (set during relocation).
21static ACTIVE_FORWARDING: AtomicPtr<ForwardingTable> = AtomicPtr::new(std::ptr::null_mut());
22
23/// Protected region ranges for the trap handler to check.
24/// Stored as (base, base+size) pairs.
25static PROTECTED_RANGES: std::sync::Mutex<Vec<(usize, usize)>> = std::sync::Mutex::new(Vec::new());
26
27/// Install the SIGSEGV trap handler.
28///
29/// # Safety
30/// Must be called once before any relocation with concurrent access.
31/// The handler is process-global and affects all threads.
32pub unsafe fn install_trap_handler() -> Result<(), &'static str> {
33 if TRAP_HANDLER_INSTALLED.swap(true, Ordering::SeqCst) {
34 return Ok(()); // Already installed
35 }
36
37 unsafe {
38 // Set up sigaction
39 let mut sa: libc::sigaction = std::mem::zeroed();
40 sa.sa_sigaction = trap_handler as usize;
41 sa.sa_flags = libc::SA_SIGINFO | libc::SA_RESTART;
42 libc::sigemptyset(&mut sa.sa_mask);
43
44 let ret = libc::sigaction(libc::SIGSEGV, &sa, std::ptr::null_mut());
45 if ret != 0 {
46 TRAP_HANDLER_INSTALLED.store(false, Ordering::SeqCst);
47 return Err("Failed to install SIGSEGV handler");
48 }
49 }
50
51 Ok(())
52}
53
54/// Register a protected region range for the trap handler.
55pub fn register_protected_range(base: usize, size: usize) {
56 let mut ranges = PROTECTED_RANGES.lock().unwrap();
57 ranges.push((base, base + size));
58}
59
60/// Unregister a protected region range.
61pub fn unregister_protected_range(base: usize) {
62 let mut ranges = PROTECTED_RANGES.lock().unwrap();
63 ranges.retain(|&(b, _)| b != base);
64}
65
66/// Set the active forwarding table for the trap handler to use.
67///
68/// # Safety
69/// The forwarding table must outlive the trap handler's use of it.
70pub unsafe fn set_active_forwarding(table: *mut ForwardingTable) {
71 ACTIVE_FORWARDING.store(table, Ordering::SeqCst);
72}
73
74/// Clear the active forwarding table.
75pub fn clear_active_forwarding() {
76 ACTIVE_FORWARDING.store(std::ptr::null_mut(), Ordering::SeqCst);
77}
78
79/// The actual SIGSEGV handler.
80///
81/// # Safety
82/// This is called by the kernel in signal context. Must be async-signal-safe.
83extern "C" fn trap_handler(
84 sig: libc::c_int,
85 info: *mut libc::siginfo_t,
86 _context: *mut libc::c_void,
87) {
88 if sig != libc::SIGSEGV || info.is_null() {
89 // Not our fault — chain to default handler
90 unsafe {
91 libc::signal(libc::SIGSEGV, libc::SIG_DFL);
92 libc::raise(libc::SIGSEGV);
93 }
94 return;
95 }
96
97 let fault_addr = unsafe { (*info).si_addr() } as usize;
98
99 // Check if the faulting address is in a protected (relocated) region
100 let in_protected = {
101 if let Ok(ranges) = PROTECTED_RANGES.try_lock() {
102 ranges
103 .iter()
104 .any(|&(base, end)| fault_addr >= base && fault_addr < end)
105 } else {
106 false
107 }
108 };
109
110 if !in_protected {
111 // Real segfault — chain to default handler
112 unsafe {
113 libc::signal(libc::SIGSEGV, libc::SIG_DFL);
114 libc::raise(libc::SIGSEGV);
115 }
116 return;
117 }
118
119 // Look up the forwarding table for this address.
120 // In a real implementation, we would update the faulting instruction's
121 // memory operand. For now, the synchronous fixup path handles this case.
122 let _forwarding = ACTIVE_FORWARDING.load(Ordering::SeqCst);
123}
124
125/// Check if the trap handler is installed.
126pub fn is_trap_handler_installed() -> bool {
127 TRAP_HANDLER_INSTALLED.load(Ordering::SeqCst)
128}