Skip to content

Commit cb3d676

Browse files
committed
install handler
1 parent 7e73e44 commit cb3d676

File tree

1 file changed

+181
-17
lines changed

1 file changed

+181
-17
lines changed

crates/stdlib/src/faulthandler.rs

Lines changed: 181 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ mod decl {
1212
use std::time::Duration;
1313

1414
static ENABLED: AtomicBool = AtomicBool::new(false);
15-
#[allow(dead_code)]
1615
static FATAL_ERROR_FD: AtomicI32 = AtomicI32::new(2); // stderr by default
1716

1817
// Watchdog thread state for dump_traceback_later
@@ -28,16 +27,57 @@ mod decl {
2827
type WatchdogHandle = Arc<(Mutex<WatchdogState>, Condvar)>;
2928
static WATCHDOG: Mutex<Option<WatchdogHandle>> = Mutex::new(None);
3029

31-
// Fatal signal numbers that should use enable() instead
30+
// Number of fatal signals we handle
31+
#[cfg(unix)]
32+
const NUM_FATAL_SIGNALS: usize = 5;
33+
#[cfg(windows)]
34+
const NUM_FATAL_SIGNALS: usize = 3;
35+
36+
// Fatal signals to handle (with names for error messages)
3237
#[cfg(unix)]
33-
const FATAL_SIGNALS: &[i32] = &[
34-
libc::SIGBUS,
35-
libc::SIGILL,
36-
libc::SIGFPE,
37-
libc::SIGABRT,
38-
libc::SIGSEGV,
38+
const FATAL_SIGNALS: [(libc::c_int, &str); NUM_FATAL_SIGNALS] = [
39+
(libc::SIGBUS, "Bus error"),
40+
(libc::SIGILL, "Illegal instruction"),
41+
(libc::SIGFPE, "Floating-point exception"),
42+
(libc::SIGABRT, "Aborted"),
43+
(libc::SIGSEGV, "Segmentation fault"),
44+
];
45+
46+
#[cfg(windows)]
47+
const FATAL_SIGNALS: [(libc::c_int, &str); NUM_FATAL_SIGNALS] = [
48+
(libc::SIGFPE, "Floating-point exception"),
49+
(libc::SIGABRT, "Aborted"),
50+
(libc::SIGSEGV, "Segmentation fault"),
3951
];
4052

53+
// Storage for previous signal handlers (Unix)
54+
#[cfg(unix)]
55+
static mut PREVIOUS_HANDLERS: [libc::sigaction; NUM_FATAL_SIGNALS] =
56+
unsafe { std::mem::zeroed() };
57+
58+
/// Signal-safe write function - no memory allocation
59+
#[cfg(all(not(target_arch = "wasm32"), not(windows)))]
60+
fn write_str_noraise(fd: i32, s: &str) {
61+
unsafe {
62+
libc::write(
63+
fd as libc::c_int,
64+
s.as_ptr() as *const libc::c_void,
65+
s.len(),
66+
);
67+
}
68+
}
69+
70+
#[cfg(windows)]
71+
fn write_str_noraise(fd: i32, s: &str) {
72+
unsafe {
73+
libc::write(
74+
fd as libc::c_int,
75+
s.as_ptr() as *const libc::c_void,
76+
s.len() as u32,
77+
);
78+
}
79+
}
80+
4181
const MAX_FUNCTION_NAME_LEN: usize = 500;
4282

4383
fn truncate_name(name: &str) -> String {
@@ -122,37 +162,147 @@ mod decl {
122162
ENABLED.store(true, Ordering::Relaxed);
123163

124164
// Install signal handlers for fatal errors
125-
#[cfg(not(target_arch = "wasm32"))]
165+
#[cfg(any(unix, windows))]
126166
{
127167
install_fatal_handlers(vm);
128168
}
129169

130170
Ok(())
131171
}
132172

133-
#[cfg(not(target_arch = "wasm32"))]
173+
/// Unix signal handler for fatal errors
174+
#[cfg(unix)]
175+
extern "C" fn faulthandler_fatal_error(signum: libc::c_int) {
176+
// Reentrant protection
177+
static REENTRANT: AtomicBool = AtomicBool::new(false);
178+
if REENTRANT.swap(true, Ordering::SeqCst) {
179+
return;
180+
}
181+
182+
let fd = FATAL_ERROR_FD.load(Ordering::Relaxed);
183+
184+
// Find and print signal name
185+
let signal_name = FATAL_SIGNALS
186+
.iter()
187+
.find(|(sig, _)| *sig == signum)
188+
.map(|(_, name)| *name)
189+
.unwrap_or("Unknown signal");
190+
191+
write_str_noraise(fd, "\nFatal Python error: ");
192+
write_str_noraise(fd, signal_name);
193+
write_str_noraise(fd, "\n\n");
194+
195+
// Restore previous handler
196+
if let Some(idx) = FATAL_SIGNALS.iter().position(|(sig, _)| *sig == signum) {
197+
unsafe {
198+
libc::sigaction(signum, &PREVIOUS_HANDLERS[idx], std::ptr::null_mut());
199+
}
200+
}
201+
202+
REENTRANT.store(false, Ordering::SeqCst);
203+
204+
// Re-raise signal to trigger default behavior (core dump, etc.)
205+
unsafe {
206+
libc::raise(signum);
207+
}
208+
}
209+
210+
#[cfg(unix)]
134211
fn install_fatal_handlers(_vm: &VirtualMachine) {
135-
// TODO: Install actual signal handlers for SIGSEGV, SIGFPE, etc.
136-
// This requires careful handling because signal handlers have limited capabilities.
137-
// For now, this is a placeholder that marks the module as enabled.
212+
for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() {
213+
let mut action: libc::sigaction = unsafe { std::mem::zeroed() };
214+
action.sa_sigaction = faulthandler_fatal_error as usize;
215+
action.sa_flags = libc::SA_NODEFER;
216+
217+
unsafe {
218+
libc::sigaction(*signum, &action, &mut PREVIOUS_HANDLERS[idx]);
219+
}
220+
}
221+
}
222+
223+
/// Windows signal handler for fatal errors (simpler than VEH)
224+
#[cfg(windows)]
225+
extern "C" fn faulthandler_fatal_error_windows(signum: libc::c_int) {
226+
// Reentrant protection
227+
static REENTRANT: AtomicBool = AtomicBool::new(false);
228+
if REENTRANT.swap(true, Ordering::SeqCst) {
229+
return;
230+
}
231+
232+
let fd = FATAL_ERROR_FD.load(Ordering::Relaxed);
233+
234+
// Find and print signal name
235+
let signal_name = FATAL_SIGNALS
236+
.iter()
237+
.find(|(sig, _)| *sig == signum)
238+
.map(|(_, name)| *name)
239+
.unwrap_or("Unknown signal");
240+
241+
write_str_noraise(fd, "\nFatal Python error: ");
242+
write_str_noraise(fd, signal_name);
243+
write_str_noraise(fd, "\n\n");
244+
245+
REENTRANT.store(false, Ordering::SeqCst);
246+
247+
// For SIGSEGV on Windows, need to loop raise
248+
if signum == libc::SIGSEGV {
249+
loop {
250+
unsafe {
251+
libc::raise(signum);
252+
}
253+
}
254+
} else {
255+
unsafe {
256+
libc::raise(signum);
257+
}
258+
}
259+
}
260+
261+
#[cfg(windows)]
262+
static mut PREVIOUS_HANDLERS_WIN: [libc::sighandler_t; NUM_FATAL_SIGNALS] =
263+
[0; NUM_FATAL_SIGNALS];
264+
265+
#[cfg(windows)]
266+
fn install_fatal_handlers(_vm: &VirtualMachine) {
267+
for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() {
268+
unsafe {
269+
PREVIOUS_HANDLERS_WIN[idx] = libc::signal(
270+
*signum,
271+
faulthandler_fatal_error_windows as libc::sighandler_t,
272+
);
273+
}
274+
}
138275
}
139276

140277
#[pyfunction]
141278
fn disable() -> bool {
142279
let was_enabled = ENABLED.swap(false, Ordering::Relaxed);
143280

144281
// Restore default signal handlers
145-
#[cfg(not(target_arch = "wasm32"))]
282+
#[cfg(any(unix, windows))]
146283
{
147284
uninstall_fatal_handlers();
148285
}
149286

150287
was_enabled
151288
}
152289

153-
#[cfg(not(target_arch = "wasm32"))]
290+
#[cfg(unix)]
291+
fn uninstall_fatal_handlers() {
292+
for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() {
293+
unsafe {
294+
libc::sigaction(*signum, &PREVIOUS_HANDLERS[idx], std::ptr::null_mut());
295+
}
296+
}
297+
}
298+
299+
#[cfg(windows)]
154300
fn uninstall_fatal_handlers() {
155-
// TODO: Restore original signal handlers
301+
for (idx, (signum, _)) in FATAL_SIGNALS.iter().enumerate() {
302+
unsafe {
303+
libc::signal(*signum, PREVIOUS_HANDLERS_WIN[idx]);
304+
}
305+
}
156306
}
157307

158308
#[pyfunction]
@@ -251,6 +401,15 @@ mod decl {
251401
#[cfg(not(target_arch = "wasm32"))]
252402
{
253403
let header_bytes = header.as_bytes();
404+
#[cfg(windows)]
405+
unsafe {
406+
libc::write(
407+
fd,
408+
header_bytes.as_ptr() as *const libc::c_void,
409+
header_bytes.len() as u32,
410+
);
411+
}
412+
#[cfg(not(windows))]
254413
unsafe {
255414
libc::write(
256415
fd,
@@ -263,6 +422,11 @@ mod decl {
263422
// because we don't have access to the VM's frame stack.
264423
// Just output a message indicating timeout occurred.
265424
let msg = b"<timeout: cannot dump traceback from watchdog thread>\n";
425+
#[cfg(windows)]
426+
unsafe {
427+
libc::write(fd, msg.as_ptr() as *const libc::c_void, msg.len() as u32);
428+
}
429+
#[cfg(not(windows))]
266430
unsafe {
267431
libc::write(fd, msg.as_ptr() as *const libc::c_void, msg.len());
268432
}
@@ -473,7 +637,7 @@ mod decl {
473637
#[cfg(unix)]
474638
fn check_signum(signum: i32, vm: &VirtualMachine) -> PyResult<()> {
475639
// Check if it's a fatal signal
476-
if FATAL_SIGNALS.contains(&signum) {
640+
if FATAL_SIGNALS.iter().any(|(sig, _)| *sig == signum) {
477641
return Err(vm.new_runtime_error(format!(
478642
"signal {} cannot be registered, use enable() instead",
479643
signum

0 commit comments

Comments
 (0)