diff --git a/src/arch/x86_64/interrupt/handler.rs b/src/arch/x86_64/interrupt/handler.rs index 035850a5a1..1e4f02e257 100644 --- a/src/arch/x86_64/interrupt/handler.rs +++ b/src/arch/x86_64/interrupt/handler.rs @@ -110,6 +110,12 @@ impl InterruptStack { pub fn stack_pointer(&self) -> usize { self.iret.rsp } + pub fn instr_pointer(&self) -> usize { + self.iret.rip + } + pub fn flags(&self) -> usize { + self.iret.rflags + } pub fn set_instr_pointer(&mut self, rip: usize) { self.iret.rip = rip; } @@ -193,6 +199,14 @@ impl InterruptStack { self.iret.rflags & FLAG_SINGLESTEP == FLAG_SINGLESTEP } } +impl ScratchRegisters { + pub fn a(&self) -> usize { + self.rax + } + pub fn b(&self) -> usize { + self.rdx + } +} #[macro_export] macro_rules! push_scratch { diff --git a/src/context/context.rs b/src/context/context.rs index e012d507e4..5a8edb3ed4 100644 --- a/src/context/context.rs +++ b/src/context/context.rs @@ -452,8 +452,9 @@ impl Context { Some(unsafe { &mut *kstack.initial_top().sub(size_of::()).cast() }) } pub fn sigcontrol(&mut self) -> Option<(&Sigcontrol, &SigProcControl, &mut SignalState)> { - let sig = self.sig.as_mut()?; - + Some(Self::sigcontrol_raw(self.sig.as_mut()?)) + } + pub fn sigcontrol_raw(sig: &mut SignalState) -> (&Sigcontrol, &SigProcControl, &mut SignalState) { let check = |off| { assert_eq!(usize::from(off) % mem::align_of::(), 0); assert!(usize::from(off).saturating_add(mem::size_of::()) < PAGE_SIZE); @@ -470,7 +471,7 @@ impl Context { .data() as *const SigProcControl).byte_add(usize::from(sig.procctl_off)) }; - Some((for_thread, for_proc, sig)) + (for_thread, for_proc, sig) } } diff --git a/src/context/signal.rs b/src/context/signal.rs index d528b9b803..e9b68e1065 100644 --- a/src/context/signal.rs +++ b/src/context/signal.rs @@ -2,10 +2,9 @@ use alloc::sync::Arc; use core::mem::size_of; use syscall::{ flag::{ - PTRACE_FLAG_IGNORE, PTRACE_STOP_SIGNAL, SIGCHLD, SIGCONT, SIGKILL, SIGSTOP, SIGTSTP, - SIGTTIN, SIGTTOU, SIGTERM, + PTRACE_FLAG_IGNORE, PTRACE_STOP_SIGNAL, SIGCHLD, SIGCONT, SIGKILL, SIGSTOP, SIGTERM, SIGTSTP, SIGTTIN, SIGTTOU }, - ptrace_event, IntRegisters, + ptrace_event, IntRegisters, SigcontrolFlags, }; use crate::{ @@ -41,16 +40,60 @@ pub fn kmain_signal_handler() { } pub fn signal_handler() { - /*let thumbs_down = ptrace::breakpoint_callback( + let context_lock = context::current().expect("running signal handler outside of context"); + let mut context = context_lock.write(); + let context = &mut *context; + + if context.being_sigkilled { + crate::syscall::process::exit(SIGKILL << 8); + } + + let thumbs_down = ptrace::breakpoint_callback( PTRACE_STOP_SIGNAL, - Some(ptrace_event!(PTRACE_STOP_SIGNAL, sig, handler)), + Some(ptrace_event!(PTRACE_STOP_SIGNAL)), ) - .and_then(|_| ptrace::next_breakpoint().map(|f| f.contains(PTRACE_FLAG_IGNORE)));*/ + .and_then(|_| ptrace::next_breakpoint().map(|f| f.contains(PTRACE_FLAG_IGNORE))); + + // TODO: thumbs_down + let Some((thread_ctl, proc_ctl, st)) = context.sigcontrol() else { + // Discard signal if sigcontrol is unset. + return; + }; + if unsafe { thread_ctl.control_flags.get().read() }.contains(SigcontrolFlags::INHIBIT_DELIVERY) { + // Signals are inhibited to protect critical sections inside libc, but this code will run + // every time the context is switched to. + return; + } + + if !core::mem::take(&mut st.is_pending) { + return; + } + + let Some(regs) = context.regs_mut() else { + // TODO: is this even reachable? + return; + }; + + let ip = regs.instr_pointer(); + let sp = regs.stack_pointer(); + let fl = regs.flags(); + let scratch_a = regs.scratch.a(); + let scratch_b = regs.scratch.b(); + + let (thread_ctl, _, _) = context.sigcontrol() + .expect("cannot have been unset while holding the lock"); + + unsafe { + thread_ctl.saved_ip.get().write_volatile(ip); + thread_ctl.saved_sp.get().write_volatile(sp); + thread_ctl.saved_flags.get().write_volatile(fl); + thread_ctl.saved_scratch_a.get().write_volatile(scratch_a); + thread_ctl.saved_scratch_b.get().write_volatile(scratch_b); + (*thread_ctl.control_flags.get()) |= SigcontrolFlags::INHIBIT_DELIVERY; + } } pub fn excp_handler(signal: usize) { - let Ok(current) = context::current() else { - panic!("CPU exception but not inside of context! Trying to switch..."); - }; + let current = context::current().expect("CPU exception but not inside of context!"); let mut context = current.write(); let Some(eh) = context.sig.as_ref().and_then(|s| s.excp_handler) else { diff --git a/src/syscall/process.rs b/src/syscall/process.rs index 23e64c02bb..883c788c89 100644 --- a/src/syscall/process.rs +++ b/src/syscall/process.rs @@ -162,11 +162,18 @@ pub fn kill(pid: ContextId, sig: usize) -> Result { return true; } - // Convert stopped processes to blocked if sending SIGCONT if sig == SIGCONT && let context::Status::Stopped(_sig) = context.status { + // Convert stopped processes to blocked if sending SIGCONT, regardless of whether + // SIGCONT is blocked or ignored. It can however be controlled whether the process + // will additionally ignore, defer, or handle that signal. context.status = context::Status::Blocked; - if let Some((ctl, _, _)) = context.sigcontrol() { + + if let Some((ctl, _, st)) = context.sigcontrol() { ctl.word[0].fetch_and(!(sig_bit(SIGTTIN) | sig_bit(SIGTTOU) | sig_bit(SIGTSTP)), Ordering::Relaxed); + ctl.word[0].fetch_or(sig_bit(SIGCONT), Ordering::Relaxed); + if (ctl.word[0].load(Ordering::Relaxed) >> 32) & sig_bit(SIGCONT) == 0 { + st.is_pending = true; + } } } else if sig == SIGSTOP || (matches!(sig, SIGTTIN | SIGTTOU | SIGTSTP) && context.sigcontrol().map_or(false, |(_, proc, _)| proc.signal_will_stop(sig))) { context.status = context::Status::Stopped(sig); @@ -181,7 +188,11 @@ pub fn kill(pid: ContextId, sig: usize) -> Result { st.is_pending = true; } } else { - context.being_sigkilled = true; + // Discard signals if sighandler is unset. This includes both special contexts such + // as bootstrap, and child processes or threads that have not yet been started. + // This is semantically equivalent to having all signals except SIGSTOP and SIGKILL + // blocked/ignored (SIGCONT can be ignored and masked, but will always continue + // stopped processes first). } true