diff --git a/redox-rt/src/arch/aarch64.rs b/redox-rt/src/arch/aarch64.rs index 265ea88816..58d2972944 100644 --- a/redox-rt/src/arch/aarch64.rs +++ b/redox-rt/src/arch/aarch64.rs @@ -476,5 +476,10 @@ pub unsafe fn arch_pre(stack: &mut SigStack, os: &mut SigArea) -> PosixStackt { flags: 0, // TODO } } +pub fn arch_ret_to_sig(stack: &mut SigStack, control: &Sigcontrol) { + let orig_pc = core::mem::replace(&mut stack.regs.pc, __relibc_internal_sigentry as usize); + control.saved_ip.set(orig_pc); + control.saved_archdep_reg.set(stack.regs.x0); +} pub(crate) static PROC_FD: SyncUnsafeCell = SyncUnsafeCell::new(usize::MAX); static PROC_CALL: [usize; 1] = [ProcCall::Sigdeq as usize]; diff --git a/redox-rt/src/arch/i686.rs b/redox-rt/src/arch/i686.rs index cd71f604ad..64439fc9d6 100644 --- a/redox-rt/src/arch/i686.rs +++ b/redox-rt/src/arch/i686.rs @@ -371,6 +371,11 @@ pub unsafe fn arch_pre(stack: &mut SigStack, area: &mut SigArea) -> PosixStackt flags: 0, // TODO } } +pub fn arch_ret_to_sig(stack: &mut SigStack, control: &Sigcontrol) { + let orig_eip = core::mem::replace(&mut stack.regs.eip, __relibc_internal_sigentry as usize); + control.saved_ip.set(orig_eip); + control.saved_archdep_reg.set(stack.regs.eflags); +} #[unsafe(no_mangle)] pub unsafe fn manually_enter_trampoline() { let c = unsafe { &crate::Tcb::current().unwrap().os_specific.control }; diff --git a/redox-rt/src/arch/riscv64.rs b/redox-rt/src/arch/riscv64.rs index 88e93fb046..ca802438c9 100644 --- a/redox-rt/src/arch/riscv64.rs +++ b/redox-rt/src/arch/riscv64.rs @@ -643,5 +643,12 @@ pub unsafe fn arch_pre(stack: &mut SigStack, area: &mut SigArea) -> PosixStackt get_sigaltstack(area, stack.regs.int_regs[1] as usize).into() } +pub fn arch_ret_to_sig(stack: &mut SigStack, control: &Sigcontrol) { + let orig_pc = core::mem::replace(&mut stack.regs.pc, __relibc_internal_sigentry as u64); + control.saved_ip.set(orig_pc as usize); + control + .saved_archdep_reg + .set(stack.regs.int_regs[4] as usize); // t0 +} pub(crate) static PROC_FD: SyncUnsafeCell = SyncUnsafeCell::new(usize::MAX); static PROC_CALL: [usize; 1] = [ProcCall::Sigdeq as usize]; diff --git a/redox-rt/src/arch/x86_64.rs b/redox-rt/src/arch/x86_64.rs index dc24394193..516f93b2f0 100644 --- a/redox-rt/src/arch/x86_64.rs +++ b/redox-rt/src/arch/x86_64.rs @@ -482,6 +482,14 @@ pub unsafe fn arch_pre(stack: &mut SigStack, area: &mut SigArea) -> PosixStackt get_sigaltstack(area, stack.regs.rsp).into() } +/// Rearrange the restore-stack and sigarea in a way that makes it look like a new signal was +/// immediately delivered after restoring the allowset to what it was prior to the original signal delivery. +pub fn arch_ret_to_sig(stack: &mut SigStack, control: &Sigcontrol) { + let orig_rip = core::mem::replace(&mut stack.regs.rip, __relibc_internal_sigentry as usize); + control.saved_ip.set(orig_rip); + control.saved_archdep_reg.set(stack.regs.rflags); +} + pub(crate) static SUPPORTS_AVX: AtomicU8 = AtomicU8::new(0); // __relibc will be prepended to the name, so no_mangle is fine diff --git a/redox-rt/src/signal.rs b/redox-rt/src/signal.rs index 31a316069e..b29f9218e7 100644 --- a/redox-rt/src/signal.rs +++ b/redox-rt/src/signal.rs @@ -242,15 +242,20 @@ unsafe fn inner(stack: &mut SigStack) { ); core::sync::atomic::compiler_fence(Ordering::Acquire); - // Update allowset again. + // Update allowset again, and obtain the set of pending unblocked signals with this new + // allowset. If this is nonempty, we must deliver the signal here rather than wait for it + // asynchronously, as shown e.g. in https://gitlab.redox-os.org/redox-os/relibc/-/issues/239. + // We do this even if signals_were_disabled beforehand (where the caller hence explicitly + // must have jumped to this trampoline). let new_mask = stack.old_mask; let old_mask = get_allowset_raw(&os.control.word); - let _pending_when_restored_mask = set_allowset_raw(&os.control.word, old_mask, new_mask); - - // TODO: If resetting the sigmask caused signals to be unblocked, then should they be delivered - // here? And would it be possible to tail-call-optimize that? + let pending_unblocked = { + let thread_pending = set_allowset_raw(&os.control.word, old_mask, new_mask); + let proc_pending = PROC_CONTROL_STRUCT.pending.load(Ordering::Relaxed); + (thread_pending | proc_pending) & new_mask + }; unsafe { (*os.arch.get()).last_sig_was_restart = shall_restart }; @@ -259,8 +264,18 @@ unsafe fn inner(stack: &mut SigStack) { // TODO: Support restoring uc_stack? - // And re-enable them again - if !signals_were_disabled { + if pending_unblocked > 0 { + // If there were signals visible at the time we checked (otherwise it can be considered + // "asynchronous" and will be delivered the next time an EINTR is seen, or after the + // post-preemption check in the kernel), we delay clearing the INHIBIT_DELIVERY bit, and + // rearrange the saved instr ptr field on the stack and the sc_saved_rip field, so that it + // returns to the start of the trampoline in a way that makes it look like a new signal was + // immediately delivered. There's no need to worry about the __crit_* sections since + // INHIBIT_DELIVERY is still set. + arch_ret_to_sig(stack, &os.control); + } else if !signals_were_disabled { + // Re-enable signals again, except in the case where they were disabled and the trampoline + // was explicitly jumped to when handling EINTR. core::sync::atomic::compiler_fence(Ordering::Release); control_flags.store( control_flags.load(Ordering::Relaxed) & !SigcontrolFlags::INHIBIT_DELIVERY.bits(), @@ -747,7 +762,7 @@ pub fn currently_pending_blocked() -> u64 { let w0 = control.word[0].load(Ordering::Relaxed); let w1 = control.word[1].load(Ordering::Relaxed); let allow = (w0 >> 32) | ((w1 >> 32) << 32); - let thread_pending = (w0 & 0xffff_ffff) | ((w1 >> 32) & 0xffff_ffff); + let thread_pending = (w0 & 0xffff_ffff) | ((w1 & 0xffff_ffff) << 32); let proc_pending = PROC_CONTROL_STRUCT.pending.load(Ordering::Relaxed); core::sync::atomic::fence(Ordering::Acquire); // TODO: Correct ordering? diff --git a/src/platform/redox/signal.rs b/src/platform/redox/signal.rs index 5ae9e35f17..b0ff66b74d 100644 --- a/src/platform/redox/signal.rs +++ b/src/platform/redox/signal.rs @@ -223,13 +223,19 @@ impl PalSignal for Sys { set: Option<&sigset_t>, oset: Option<&mut sigset_t>, ) -> Result<(), Errno> { - Ok(match how { + match how { + _ if set.is_none() => { + if let Some(oset) = oset { + *oset = redox_rt::signal::get_sigmask()?; + } + } SIG_SETMASK => redox_rt::signal::set_sigmask(set.copied(), oset)?, SIG_BLOCK => redox_rt::signal::or_sigmask(set.copied(), oset)?, SIG_UNBLOCK => redox_rt::signal::andn_sigmask(set.copied(), oset)?, _ => return Err(Errno(EINVAL)), - }) + } + Ok(()) } fn sigsuspend(mask: &sigset_t) -> Errno {