pub(crate) use decl::module_def;
#[allow(static_mut_refs)] #[pymodule(name = "faulthandler")]
mod decl {
use crate::vm::{
PyObjectRef, PyResult, VirtualMachine,
frame::Frame,
function::{ArgIntoFloat, OptionalArg},
};
use alloc::sync::Arc;
use core::sync::atomic::{AtomicBool, AtomicI32, Ordering};
use core::time::Duration;
use parking_lot::{Condvar, Mutex};
#[cfg(any(unix, windows))]
use rustpython_common::os::{get_errno, set_errno};
use std::thread;
#[cfg(unix)]
struct FaultHandler {
signum: libc::c_int,
enabled: bool,
name: &'static str,
previous: libc::sigaction,
}
#[cfg(windows)]
struct FaultHandler {
signum: libc::c_int,
enabled: bool,
name: &'static str,
previous: libc::sighandler_t,
}
#[cfg(unix)]
impl FaultHandler {
const fn new(signum: libc::c_int, name: &'static str) -> Self {
Self {
signum,
enabled: false,
name,
previous: unsafe { core::mem::zeroed() },
}
}
}
#[cfg(windows)]
impl FaultHandler {
const fn new(signum: libc::c_int, name: &'static str) -> Self {
Self {
signum,
enabled: false,
name,
previous: 0,
}
}
}
#[cfg(unix)]
const FAULTHANDLER_NSIGNALS: usize = 5;
#[cfg(windows)]
const FAULTHANDLER_NSIGNALS: usize = 4;
#[cfg(unix)]
static mut FAULTHANDLER_HANDLERS: [FaultHandler; FAULTHANDLER_NSIGNALS] = [
FaultHandler::new(libc::SIGBUS, "Bus error"),
FaultHandler::new(libc::SIGILL, "Illegal instruction"),
FaultHandler::new(libc::SIGFPE, "Floating-point exception"),
FaultHandler::new(libc::SIGABRT, "Aborted"),
FaultHandler::new(libc::SIGSEGV, "Segmentation fault"),
];
#[cfg(windows)]
static mut FAULTHANDLER_HANDLERS: [FaultHandler; FAULTHANDLER_NSIGNALS] = [
FaultHandler::new(libc::SIGILL, "Illegal instruction"),
FaultHandler::new(libc::SIGFPE, "Floating-point exception"),
FaultHandler::new(libc::SIGABRT, "Aborted"),
FaultHandler::new(libc::SIGSEGV, "Segmentation fault"),
];
struct FatalErrorState {
enabled: AtomicBool,
fd: AtomicI32,
all_threads: AtomicBool,
}
static FATAL_ERROR: FatalErrorState = FatalErrorState {
enabled: AtomicBool::new(false),
fd: AtomicI32::new(2), all_threads: AtomicBool::new(true),
};
#[cfg(feature = "threading")]
type ThreadFrameSlot = Arc<rustpython_vm::vm::thread::ThreadSlot>;
struct WatchdogState {
cancel: bool,
fd: i32,
timeout_us: u64,
repeat: bool,
exit: bool,
header: String,
#[cfg(feature = "threading")]
thread_frame_slots: Vec<(u64, ThreadFrameSlot)>,
}
type WatchdogHandle = Arc<(Mutex<WatchdogState>, Condvar)>;
static WATCHDOG: Mutex<Option<WatchdogHandle>> = Mutex::new(None);
#[cfg(any(unix, windows))]
fn puts(fd: i32, s: &str) {
let _ = unsafe {
#[cfg(windows)]
{
libc::write(fd, s.as_ptr() as *const libc::c_void, s.len() as u32)
}
#[cfg(not(windows))]
{
libc::write(fd, s.as_ptr() as *const libc::c_void, s.len())
}
};
}
#[cfg(any(unix, windows))]
fn puts_bytes(fd: i32, s: &[u8]) {
let _ = unsafe {
#[cfg(windows)]
{
libc::write(fd, s.as_ptr() as *const libc::c_void, s.len() as u32)
}
#[cfg(not(windows))]
{
libc::write(fd, s.as_ptr() as *const libc::c_void, s.len())
}
};
}
#[cfg(any(unix, windows))]
fn dump_hexadecimal(fd: i32, value: u64, width: usize) {
const HEX_CHARS: &[u8; 16] = b"0123456789abcdef";
let mut buf = [0u8; 18]; buf[0] = b'0';
buf[1] = b'x';
for i in 0..width {
let digit = ((value >> (4 * (width - 1 - i))) & 0xf) as usize;
buf[2 + i] = HEX_CHARS[digit];
}
let _ = unsafe {
#[cfg(windows)]
{
libc::write(fd, buf.as_ptr() as *const libc::c_void, (2 + width) as u32)
}
#[cfg(not(windows))]
{
libc::write(fd, buf.as_ptr() as *const libc::c_void, 2 + width)
}
};
}
#[cfg(any(unix, windows))]
fn dump_decimal(fd: i32, value: usize) {
let mut buf = [0u8; 20];
let mut v = value;
let mut i = buf.len();
if v == 0 {
puts(fd, "0");
return;
}
while v > 0 {
i -= 1;
buf[i] = b'0' + (v % 10) as u8;
v /= 10;
}
let len = buf.len() - i;
let _ = unsafe {
#[cfg(windows)]
{
libc::write(fd, buf[i..].as_ptr() as *const libc::c_void, len as u32)
}
#[cfg(not(windows))]
{
libc::write(fd, buf[i..].as_ptr() as *const libc::c_void, len)
}
};
}
#[cfg(unix)]
fn current_thread_id() -> u64 {
unsafe { libc::pthread_self() as u64 }
}
#[cfg(windows)]
fn current_thread_id() -> u64 {
unsafe { windows_sys::Win32::System::Threading::GetCurrentThreadId() as u64 }
}
#[cfg(any(unix, windows))]
fn write_thread_id(fd: i32, thread_id: u64, is_current: bool) {
if is_current {
puts(fd, "Current thread ");
} else {
puts(fd, "Thread ");
}
dump_hexadecimal(fd, thread_id, core::mem::size_of::<usize>() * 2);
puts(fd, " (most recent call first):\n");
}
#[cfg(any(unix, windows))]
fn dump_live_frames(fd: i32) {
const MAX_FRAME_DEPTH: usize = 100;
let mut frame_ptr = crate::vm::vm::thread::get_current_frame();
if frame_ptr.is_null() {
puts(fd, " <no Python frame>\n");
return;
}
let mut depth = 0;
while !frame_ptr.is_null() && depth < MAX_FRAME_DEPTH {
let frame = unsafe { &*frame_ptr };
dump_frame_from_raw(fd, frame);
frame_ptr = frame.previous_frame();
depth += 1;
}
if depth >= MAX_FRAME_DEPTH && !frame_ptr.is_null() {
puts(fd, " ...\n");
}
}
#[cfg(any(unix, windows))]
fn dump_frame_from_raw(fd: i32, frame: &Frame) {
let filename = frame.code.source_path().as_str();
let funcname = frame.code.obj_name.as_str();
let lasti = frame.lasti();
let lineno = if lasti == 0 {
frame.code.first_line_number.map(|n| n.get()).unwrap_or(1) as u32
} else {
let idx = (lasti as usize).saturating_sub(1);
if idx < frame.code.locations.len() {
frame.code.locations[idx].0.line.get() as u32
} else {
frame.code.first_line_number.map(|n| n.get()).unwrap_or(0) as u32
}
};
puts(fd, " File \"");
dump_ascii(fd, filename);
puts(fd, "\", line ");
dump_decimal(fd, lineno as usize);
puts(fd, " in ");
dump_ascii(fd, funcname);
puts(fd, "\n");
}
#[cfg(any(unix, windows))]
fn faulthandler_dump_traceback(fd: i32, all_threads: bool) {
static REENTRANT: AtomicBool = AtomicBool::new(false);
if REENTRANT.swap(true, Ordering::SeqCst) {
return;
}
if all_threads {
write_thread_id(fd, current_thread_id(), true);
} else {
puts(fd, "Stack (most recent call first):\n");
}
dump_live_frames(fd);
REENTRANT.store(false, Ordering::SeqCst);
}
const MAX_STRING_LENGTH: usize = 500;
#[cfg(any(unix, windows))]
fn safe_truncate(s: &str, max_bytes: usize) -> (&str, bool) {
if s.len() <= max_bytes {
return (s, false);
}
let mut end = max_bytes;
while end > 0 && !s.is_char_boundary(end) {
end -= 1;
}
(&s[..end], true)
}
#[cfg(any(unix, windows))]
fn dump_ascii(fd: i32, s: &str) {
let (truncated_s, was_truncated) = safe_truncate(s, MAX_STRING_LENGTH);
puts(fd, truncated_s);
if was_truncated {
puts(fd, "...");
}
}
#[cfg(any(unix, windows))]
fn dump_frame_from_ref(fd: i32, frame: &crate::vm::Py<Frame>) {
let funcname = frame.code.obj_name.as_str();
let filename = frame.code.source_path().as_str();
let lineno = if frame.lasti() == 0 {
frame.code.first_line_number.map(|n| n.get()).unwrap_or(1) as u32
} else {
frame.current_location().line.get() as u32
};
puts(fd, " File \"");
dump_ascii(fd, filename);
puts(fd, "\", line ");
dump_decimal(fd, lineno as usize);
puts(fd, " in ");
dump_ascii(fd, funcname);
puts(fd, "\n");
}
#[cfg(all(any(unix, windows), feature = "threading"))]
fn dump_traceback_thread_frames(
fd: i32,
thread_id: u64,
is_current: bool,
frames: &[rustpython_vm::vm::FramePtr],
) {
write_thread_id(fd, thread_id, is_current);
if frames.is_empty() {
puts(fd, " <no Python frame>\n");
} else {
for fp in frames.iter().rev() {
dump_frame_from_ref(fd, unsafe { fp.as_ref() });
}
}
}
#[derive(FromArgs)]
struct DumpTracebackArgs {
#[pyarg(any, default)]
file: OptionalArg<PyObjectRef>,
#[pyarg(any, default = true)]
all_threads: bool,
}
#[pyfunction]
fn dump_traceback(args: DumpTracebackArgs, vm: &VirtualMachine) -> PyResult<()> {
let fd = get_fd_from_file_opt(args.file, vm)?;
#[cfg(any(unix, windows))]
{
if args.all_threads {
dump_all_threads(fd, vm);
} else {
puts(fd, "Stack (most recent call first):\n");
let frames = vm.frames.borrow();
for fp in frames.iter().rev() {
dump_frame_from_ref(fd, unsafe { fp.as_ref() });
}
}
}
#[cfg(not(any(unix, windows)))]
{
let _ = (fd, args.all_threads);
}
Ok(())
}
#[cfg(any(unix, windows))]
fn dump_all_threads(fd: i32, vm: &VirtualMachine) {
#[cfg(feature = "threading")]
{
let current_tid = rustpython_vm::stdlib::_thread::get_ident();
let registry = vm.state.thread_frames.lock();
for (&tid, slot) in registry.iter() {
if tid == current_tid {
continue;
}
let frames_guard = slot.frames.lock();
dump_traceback_thread_frames(fd, tid, false, &frames_guard);
puts(fd, "\n");
}
write_thread_id(fd, current_tid, true);
let frames = vm.frames.borrow();
if frames.is_empty() {
puts(fd, " <no Python frame>\n");
} else {
for fp in frames.iter().rev() {
dump_frame_from_ref(fd, unsafe { fp.as_ref() });
}
}
}
#[cfg(not(feature = "threading"))]
{
write_thread_id(fd, current_thread_id(), true);
let frames = vm.frames.borrow();
for fp in frames.iter().rev() {
dump_frame_from_ref(fd, unsafe { fp.as_ref() });
}
}
}
#[derive(FromArgs)]
#[allow(unused)]
struct EnableArgs {
#[pyarg(any, default)]
file: OptionalArg<PyObjectRef>,
#[pyarg(any, default = true)]
all_threads: bool,
}
#[pyfunction]
fn enable(args: EnableArgs, vm: &VirtualMachine) -> PyResult<()> {
let fd = get_fd_from_file_opt(args.file, vm)?;
FATAL_ERROR.fd.store(fd, Ordering::Relaxed);
FATAL_ERROR
.all_threads
.store(args.all_threads, Ordering::Relaxed);
if !faulthandler_enable_internal() {
return Err(vm.new_runtime_error("Failed to enable faulthandler"));
}
Ok(())
}
#[cfg(unix)]
unsafe fn faulthandler_disable_fatal_handler(handler: &mut FaultHandler) {
if !handler.enabled {
return;
}
handler.enabled = false;
unsafe {
libc::sigaction(handler.signum, &handler.previous, core::ptr::null_mut());
}
}
#[cfg(windows)]
unsafe fn faulthandler_disable_fatal_handler(handler: &mut FaultHandler) {
if !handler.enabled {
return;
}
handler.enabled = false;
unsafe {
libc::signal(handler.signum, handler.previous);
}
}
#[cfg(unix)]
extern "C" fn faulthandler_fatal_error(signum: libc::c_int) {
let save_errno = get_errno();
if !FATAL_ERROR.enabled.load(Ordering::Relaxed) {
return;
}
let fd = FATAL_ERROR.fd.load(Ordering::Relaxed);
let handler = unsafe {
FAULTHANDLER_HANDLERS
.iter_mut()
.find(|h| h.signum == signum)
};
if let Some(h) = handler {
unsafe {
faulthandler_disable_fatal_handler(h);
}
puts(fd, "Fatal Python error: ");
puts(fd, h.name);
puts(fd, "\n\n");
} else {
puts(fd, "Fatal Python error from unexpected signum: ");
dump_decimal(fd, signum as usize);
puts(fd, "\n\n");
}
let all_threads = FATAL_ERROR.all_threads.load(Ordering::Relaxed);
faulthandler_dump_traceback(fd, all_threads);
set_errno(save_errno);
unsafe {
libc::signal(signum, libc::SIG_DFL);
libc::raise(signum);
}
unsafe {
libc::_exit(1);
}
}
#[cfg(windows)]
extern "C" fn faulthandler_fatal_error(signum: libc::c_int) {
let save_errno = get_errno();
if !FATAL_ERROR.enabled.load(Ordering::Relaxed) {
return;
}
let fd = FATAL_ERROR.fd.load(Ordering::Relaxed);
let handler = unsafe {
FAULTHANDLER_HANDLERS
.iter_mut()
.find(|h| h.signum == signum)
};
if let Some(h) = handler {
unsafe {
faulthandler_disable_fatal_handler(h);
}
puts(fd, "Fatal Python error: ");
puts(fd, h.name);
puts(fd, "\n\n");
} else {
puts(fd, "Fatal Python error from unexpected signum: ");
dump_decimal(fd, signum as usize);
puts(fd, "\n\n");
}
let all_threads = FATAL_ERROR.all_threads.load(Ordering::Relaxed);
faulthandler_dump_traceback(fd, all_threads);
set_errno(save_errno);
unsafe {
libc::signal(signum, libc::SIG_DFL);
libc::raise(signum);
}
std::process::exit(1);
}
#[cfg(windows)]
static EXC_HANDLER: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
#[cfg(windows)]
fn faulthandler_ignore_exception(code: u32) -> bool {
if (code & 0x80000000) == 0 {
return true;
}
if code == 0xE06D7363 || code == 0xE0434352 {
return true;
}
false
}
#[cfg(windows)]
unsafe extern "system" fn faulthandler_exc_handler(
exc_info: *mut windows_sys::Win32::System::Diagnostics::Debug::EXCEPTION_POINTERS,
) -> i32 {
const EXCEPTION_CONTINUE_SEARCH: i32 = 0;
if !FATAL_ERROR.enabled.load(Ordering::Relaxed) {
return EXCEPTION_CONTINUE_SEARCH;
}
let record = unsafe { &*(*exc_info).ExceptionRecord };
let code = record.ExceptionCode as u32;
if faulthandler_ignore_exception(code) {
return EXCEPTION_CONTINUE_SEARCH;
}
let fd = FATAL_ERROR.fd.load(Ordering::Relaxed);
puts(fd, "Windows fatal exception: ");
match code {
0xC0000005 => puts(fd, "access violation"),
0xC000008C => puts(fd, "float divide by zero"),
0xC0000091 => puts(fd, "float overflow"),
0xC0000094 => puts(fd, "int divide by zero"),
0xC0000095 => puts(fd, "integer overflow"),
0xC0000006 => puts(fd, "page error"),
0xC00000FD => puts(fd, "stack overflow"),
0xC000001D => puts(fd, "illegal instruction"),
_ => {
puts(fd, "code ");
dump_hexadecimal(fd, code as u64, 8);
}
}
puts(fd, "\n\n");
if code == 0xC0000005 {
unsafe {
for handler in FAULTHANDLER_HANDLERS.iter_mut() {
if handler.signum == libc::SIGSEGV {
faulthandler_disable_fatal_handler(handler);
break;
}
}
}
}
let all_threads = FATAL_ERROR.all_threads.load(Ordering::Relaxed);
faulthandler_dump_traceback(fd, all_threads);
EXCEPTION_CONTINUE_SEARCH
}
#[cfg(unix)]
fn faulthandler_enable_internal() -> bool {
if FATAL_ERROR.enabled.load(Ordering::Relaxed) {
return true;
}
unsafe {
for handler in FAULTHANDLER_HANDLERS.iter_mut() {
if handler.enabled {
continue;
}
let mut action: libc::sigaction = core::mem::zeroed();
action.sa_sigaction = faulthandler_fatal_error as *const () as libc::sighandler_t;
action.sa_flags = libc::SA_NODEFER;
if libc::sigaction(handler.signum, &action, &mut handler.previous) != 0 {
return false;
}
handler.enabled = true;
}
}
FATAL_ERROR.enabled.store(true, Ordering::Relaxed);
true
}
#[cfg(windows)]
fn faulthandler_enable_internal() -> bool {
if FATAL_ERROR.enabled.load(Ordering::Relaxed) {
return true;
}
unsafe {
for handler in FAULTHANDLER_HANDLERS.iter_mut() {
if handler.enabled {
continue;
}
handler.previous = libc::signal(
handler.signum,
faulthandler_fatal_error as *const () as libc::sighandler_t,
);
if handler.previous == libc::SIG_ERR as libc::sighandler_t {
return false;
}
handler.enabled = true;
}
}
#[cfg(windows)]
{
use windows_sys::Win32::System::Diagnostics::Debug::AddVectoredExceptionHandler;
let h = unsafe { AddVectoredExceptionHandler(1, Some(faulthandler_exc_handler)) };
EXC_HANDLER.store(h as usize, Ordering::Relaxed);
}
FATAL_ERROR.enabled.store(true, Ordering::Relaxed);
true
}
#[cfg(any(unix, windows))]
fn faulthandler_disable_internal() {
if !FATAL_ERROR.enabled.swap(false, Ordering::Relaxed) {
return;
}
unsafe {
for handler in FAULTHANDLER_HANDLERS.iter_mut() {
faulthandler_disable_fatal_handler(handler);
}
}
#[cfg(windows)]
{
use windows_sys::Win32::System::Diagnostics::Debug::RemoveVectoredExceptionHandler;
let h = EXC_HANDLER.swap(0, Ordering::Relaxed);
if h != 0 {
unsafe {
RemoveVectoredExceptionHandler(h as *mut core::ffi::c_void);
}
}
}
}
#[cfg(not(any(unix, windows)))]
fn faulthandler_enable_internal() -> bool {
FATAL_ERROR.enabled.store(true, Ordering::Relaxed);
true
}
#[cfg(not(any(unix, windows)))]
fn faulthandler_disable_internal() {
FATAL_ERROR.enabled.store(false, Ordering::Relaxed);
}
#[pyfunction]
fn disable() -> bool {
let was_enabled = FATAL_ERROR.enabled.load(Ordering::Relaxed);
faulthandler_disable_internal();
was_enabled
}
#[pyfunction]
fn is_enabled() -> bool {
FATAL_ERROR.enabled.load(Ordering::Relaxed)
}
fn format_timeout(timeout_us: u64) -> String {
let sec = timeout_us / 1_000_000;
let us = timeout_us % 1_000_000;
let min = sec / 60;
let sec = sec % 60;
let hour = min / 60;
let min = min % 60;
if us != 0 {
format!("Timeout ({}:{:02}:{:02}.{:06})!\n", hour, min, sec, us)
} else {
format!("Timeout ({}:{:02}:{:02})!\n", hour, min, sec)
}
}
fn get_fd_from_file_opt(file: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult<i32> {
match file {
OptionalArg::Present(f) if !vm.is_none(&f) => {
if let Ok(fd) = f.try_to_value::<i32>(vm) {
if fd < 0 {
return Err(vm.new_value_error("file is not a valid file descriptor"));
}
return Ok(fd);
}
let fileno = vm.call_method(&f, "fileno", ())?;
let fd: i32 = fileno.try_to_value(vm)?;
if fd < 0 {
return Err(vm.new_value_error("file is not a valid file descriptor"));
}
let _ = vm.call_method(&f, "flush", ());
Ok(fd)
}
_ => {
let stderr = vm.sys_module.get_attr("stderr", vm)?;
if vm.is_none(&stderr) {
return Err(vm.new_runtime_error("sys.stderr is None"));
}
let fileno = vm.call_method(&stderr, "fileno", ())?;
let fd: i32 = fileno.try_to_value(vm)?;
let _ = vm.call_method(&stderr, "flush", ());
Ok(fd)
}
}
}
fn watchdog_thread(state: WatchdogHandle) {
let (lock, cvar) = &*state;
loop {
let mut guard = lock.lock();
if guard.cancel {
return;
}
let timeout = Duration::from_micros(guard.timeout_us);
cvar.wait_for(&mut guard, timeout);
if guard.cancel {
return;
}
let repeat = guard.repeat;
let exit = guard.exit;
let fd = guard.fd;
let header = guard.header.clone();
#[cfg(feature = "threading")]
let thread_frame_slots = guard.thread_frame_slots.clone();
drop(guard);
#[cfg(target_arch = "wasm32")]
let _ = (exit, fd, &header);
#[cfg(not(target_arch = "wasm32"))]
{
puts_bytes(fd, header.as_bytes());
#[cfg(feature = "threading")]
{
for (tid, slot) in &thread_frame_slots {
let frames = slot.frames.lock();
dump_traceback_thread_frames(fd, *tid, false, &frames);
}
}
#[cfg(not(feature = "threading"))]
{
write_thread_id(fd, current_thread_id(), false);
dump_live_frames(fd);
}
if exit {
std::process::exit(1);
}
}
if !repeat {
return;
}
}
}
#[derive(FromArgs)]
#[allow(unused)]
struct DumpTracebackLaterArgs {
#[pyarg(positional, error_msg = "timeout must be a number (int or float)")]
timeout: ArgIntoFloat,
#[pyarg(any, default = false)]
repeat: bool,
#[pyarg(any, default)]
file: OptionalArg<PyObjectRef>,
#[pyarg(any, default = false)]
exit: bool,
}
#[pyfunction]
fn dump_traceback_later(args: DumpTracebackLaterArgs, vm: &VirtualMachine) -> PyResult<()> {
let timeout: f64 = args.timeout.into_float();
if timeout <= 0.0 {
return Err(vm.new_value_error("timeout must be greater than 0"));
}
let fd = get_fd_from_file_opt(args.file, vm)?;
let timeout_us = (timeout * 1_000_000.0) as u64;
if timeout_us == 0 {
return Err(vm.new_value_error("timeout must be greater than 0"));
}
let header = format_timeout(timeout_us);
#[cfg(feature = "threading")]
let thread_frame_slots: Vec<(u64, ThreadFrameSlot)> = {
let registry = vm.state.thread_frames.lock();
registry
.iter()
.map(|(&id, slot)| (id, Arc::clone(slot)))
.collect()
};
cancel_dump_traceback_later();
let state = Arc::new((
Mutex::new(WatchdogState {
cancel: false,
fd,
timeout_us,
repeat: args.repeat,
exit: args.exit,
header,
#[cfg(feature = "threading")]
thread_frame_slots,
}),
Condvar::new(),
));
{
let mut watchdog = WATCHDOG.lock();
*watchdog = Some(Arc::clone(&state));
}
thread::spawn(move || {
watchdog_thread(state);
});
Ok(())
}
#[pyfunction]
fn cancel_dump_traceback_later() {
let state = {
let mut watchdog = WATCHDOG.lock();
watchdog.take()
};
if let Some(state) = state {
let (lock, cvar) = &*state;
{
let mut guard = lock.lock();
guard.cancel = true;
}
cvar.notify_all();
}
}
#[cfg(unix)]
mod user_signals {
use parking_lot::Mutex;
const NSIG: usize = 64;
#[derive(Clone, Copy)]
pub struct UserSignal {
pub enabled: bool,
pub fd: i32,
pub all_threads: bool,
pub chain: bool,
pub previous: libc::sigaction,
}
impl Default for UserSignal {
fn default() -> Self {
Self {
enabled: false,
fd: 2, all_threads: true,
chain: false,
previous: unsafe { core::mem::zeroed() },
}
}
}
static USER_SIGNALS: Mutex<Option<Vec<UserSignal>>> = Mutex::new(None);
pub fn get_user_signal(signum: usize) -> Option<UserSignal> {
let guard = USER_SIGNALS.lock();
guard.as_ref().and_then(|v| v.get(signum).cloned())
}
pub fn set_user_signal(signum: usize, signal: UserSignal) {
let mut guard = USER_SIGNALS.lock();
if guard.is_none() {
*guard = Some(vec![UserSignal::default(); NSIG]);
}
if let Some(ref mut v) = *guard
&& signum < v.len()
{
v[signum] = signal;
}
}
pub fn clear_user_signal(signum: usize) -> Option<UserSignal> {
let mut guard = USER_SIGNALS.lock();
if let Some(ref mut v) = *guard
&& signum < v.len()
&& v[signum].enabled
{
let old = v[signum];
v[signum] = UserSignal::default();
return Some(old);
}
None
}
pub fn is_enabled(signum: usize) -> bool {
let guard = USER_SIGNALS.lock();
guard
.as_ref()
.and_then(|v| v.get(signum))
.is_some_and(|s| s.enabled)
}
}
#[cfg(unix)]
extern "C" fn faulthandler_user_signal(signum: libc::c_int) {
let save_errno = get_errno();
let user = match user_signals::get_user_signal(signum as usize) {
Some(u) if u.enabled => u,
_ => return,
};
faulthandler_dump_traceback(user.fd, user.all_threads);
if user.chain {
unsafe {
libc::sigaction(signum, &user.previous, core::ptr::null_mut());
}
set_errno(save_errno);
unsafe {
libc::raise(signum);
}
let save_errno2 = get_errno();
unsafe {
let mut action: libc::sigaction = core::mem::zeroed();
action.sa_sigaction = faulthandler_user_signal as *const () as libc::sighandler_t;
action.sa_flags = libc::SA_NODEFER;
libc::sigaction(signum, &action, core::ptr::null_mut());
}
set_errno(save_errno2);
}
}
#[cfg(unix)]
fn check_signum(signum: i32, vm: &VirtualMachine) -> PyResult<()> {
let is_fatal = unsafe { FAULTHANDLER_HANDLERS.iter().any(|h| h.signum == signum) };
if is_fatal {
return Err(vm.new_runtime_error(format!(
"signal {} cannot be registered, use enable() instead",
signum
)));
}
if !(1..64).contains(&signum) {
return Err(vm.new_value_error("signal number out of range"));
}
Ok(())
}
#[cfg(unix)]
#[derive(FromArgs)]
#[allow(unused)]
struct RegisterArgs {
#[pyarg(positional)]
signum: i32,
#[pyarg(any, default)]
file: OptionalArg<PyObjectRef>,
#[pyarg(any, default = true)]
all_threads: bool,
#[pyarg(any, default = false)]
chain: bool,
}
#[cfg(unix)]
#[pyfunction]
fn register(args: RegisterArgs, vm: &VirtualMachine) -> PyResult<()> {
check_signum(args.signum, vm)?;
let fd = get_fd_from_file_opt(args.file, vm)?;
let signum = args.signum as usize;
let previous = if !user_signals::is_enabled(signum) {
unsafe {
let mut action: libc::sigaction = core::mem::zeroed();
action.sa_sigaction = faulthandler_user_signal as *const () as libc::sighandler_t;
action.sa_flags = if args.chain {
libc::SA_NODEFER
} else {
libc::SA_RESTART
};
let mut prev: libc::sigaction = core::mem::zeroed();
if libc::sigaction(args.signum, &action, &mut prev) != 0 {
return Err(vm.new_os_error(format!(
"Failed to register signal handler for signal {}",
args.signum
)));
}
prev
}
} else {
user_signals::get_user_signal(signum)
.map(|u| u.previous)
.unwrap_or(unsafe { core::mem::zeroed() })
};
user_signals::set_user_signal(
signum,
user_signals::UserSignal {
enabled: true,
fd,
all_threads: args.all_threads,
chain: args.chain,
previous,
},
);
Ok(())
}
#[cfg(unix)]
#[pyfunction]
fn unregister(signum: i32, vm: &VirtualMachine) -> PyResult<bool> {
check_signum(signum, vm)?;
if let Some(old) = user_signals::clear_user_signal(signum as usize) {
unsafe {
libc::sigaction(signum, &old.previous, core::ptr::null_mut());
}
Ok(true)
} else {
Ok(false)
}
}
#[pyfunction]
fn _read_null(_vm: &VirtualMachine) {
#[cfg(not(target_arch = "wasm32"))]
{
suppress_crash_report();
unsafe {
let ptr: *const i32 = core::ptr::null();
core::ptr::read_volatile(ptr);
}
}
}
#[derive(FromArgs)]
#[allow(dead_code)]
struct SigsegvArgs {
#[pyarg(any, default = false)]
release_gil: bool,
}
#[pyfunction]
fn _sigsegv(_args: SigsegvArgs, _vm: &VirtualMachine) {
#[cfg(not(target_arch = "wasm32"))]
{
suppress_crash_report();
unsafe {
let ptr: *mut i32 = core::ptr::null_mut();
core::ptr::write_volatile(ptr, 0);
}
}
}
#[pyfunction]
fn _sigabrt(_vm: &VirtualMachine) {
#[cfg(not(target_arch = "wasm32"))]
{
suppress_crash_report();
unsafe {
libc::abort();
}
}
}
#[pyfunction]
fn _sigfpe(_vm: &VirtualMachine) {
#[cfg(not(target_arch = "wasm32"))]
{
suppress_crash_report();
unsafe {
libc::raise(libc::SIGFPE);
}
}
}
#[pyfunction]
fn _fatal_error_c_thread() {
#[cfg(not(target_arch = "wasm32"))]
{
suppress_crash_report();
std::thread::spawn(|| {
panic!("Fatal Python error: in new thread");
});
std::thread::sleep(core::time::Duration::from_secs(1));
}
}
#[cfg(not(target_arch = "wasm32"))]
fn suppress_crash_report() {
#[cfg(windows)]
{
use windows_sys::Win32::System::Diagnostics::Debug::{
SEM_NOGPFAULTERRORBOX, SetErrorMode,
};
unsafe {
let mode = SetErrorMode(SEM_NOGPFAULTERRORBOX);
SetErrorMode(mode | SEM_NOGPFAULTERRORBOX);
}
}
#[cfg(unix)]
{
#[cfg(not(any(target_os = "redox", target_os = "wasi")))]
{
use libc::{RLIMIT_CORE, rlimit, setrlimit};
let rl = rlimit {
rlim_cur: 0,
rlim_max: 0,
};
unsafe {
let _ = setrlimit(RLIMIT_CORE, &rl);
}
}
}
}
#[cfg(windows)]
#[pyattr]
const _EXCEPTION_ACCESS_VIOLATION: u32 = 0xC0000005;
#[cfg(windows)]
#[pyattr]
const _EXCEPTION_INT_DIVIDE_BY_ZERO: u32 = 0xC0000094;
#[cfg(windows)]
#[pyattr]
const _EXCEPTION_STACK_OVERFLOW: u32 = 0xC00000FD;
#[cfg(windows)]
#[pyattr]
const _EXCEPTION_NONCONTINUABLE: u32 = 0x00000001;
#[cfg(windows)]
#[pyattr]
const _EXCEPTION_NONCONTINUABLE_EXCEPTION: u32 = 0xC0000025;
#[cfg(windows)]
#[derive(FromArgs)]
struct RaiseExceptionArgs {
#[pyarg(positional)]
code: u32,
#[pyarg(positional, default = 0)]
flags: u32,
}
#[cfg(windows)]
#[pyfunction]
fn _raise_exception(args: RaiseExceptionArgs, _vm: &VirtualMachine) {
use windows_sys::Win32::System::Diagnostics::Debug::RaiseException;
suppress_crash_report();
unsafe {
RaiseException(args.code, args.flags, 0, core::ptr::null());
}
}
}