I hereby relicense all my work on Sortix under the ISC license as below. All Sortix contributions by other people are already under this license, are not substantial enough to be copyrightable, or have been removed. All imported code from other projects is compatible with this license. All GPL licensed code from other projects had previously been removed. Copyright 2011-2016 Jonas 'Sortie' Termansen and contributors. Permission to use, copy, modify, and distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
855 lines
25 KiB
C++
855 lines
25 KiB
C++
/*
|
|
* Copyright (c) 2011, 2012, 2013, 2014, 2015 Jonas 'Sortie' Termansen.
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*
|
|
* signal.cpp
|
|
* Asynchronous user-space thread interruption.
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <signal.h>
|
|
|
|
#include <sortix/sigaction.h>
|
|
#include <sortix/signal.h>
|
|
#include <sortix/sigset.h>
|
|
#include <sortix/stack.h>
|
|
#include <sortix/ucontext.h>
|
|
|
|
#include <sortix/kernel/copy.h>
|
|
#include <sortix/kernel/interrupt.h>
|
|
#include <sortix/kernel/kernel.h>
|
|
#include <sortix/kernel/process.h>
|
|
#include <sortix/kernel/ptable.h>
|
|
#include <sortix/kernel/signal.h>
|
|
#include <sortix/kernel/syscall.h>
|
|
#include <sortix/kernel/thread.h>
|
|
|
|
namespace Sortix {
|
|
|
|
sigset_t default_ignored_signals;
|
|
sigset_t default_stop_signals;
|
|
sigset_t unblockable_signals;
|
|
|
|
// A per-cpu value whether a signal is pending in the running task.
|
|
extern "C" { volatile unsigned long asm_signal_is_pending = 0; }
|
|
|
|
static
|
|
void UpdatePendingSignals(Thread* thread) // thread->process->signal_lock held
|
|
{
|
|
struct sigaction* signal_actions = thread->process->signal_actions;
|
|
|
|
// Determine which signals wouldn't be ignored if received.
|
|
sigset_t handled_signals;
|
|
sigemptyset(&handled_signals);
|
|
for ( int i = 1; i < SIG_MAX_NUM; i++ )
|
|
{
|
|
if ( signal_actions[i].sa_handler == SIG_IGN )
|
|
continue;
|
|
if ( signal_actions[i].sa_handler == SIG_DFL &&
|
|
sigismember(&default_ignored_signals, i) )
|
|
continue;
|
|
// TODO: A process that is a member of an orphaned process group shall
|
|
// not be allowed to stop in response to the SIGTSTP, SIGTTIN, or
|
|
// SIGTTOU signals. In cases where delivery of one of these
|
|
// signals would stop such a process, the signal shall be
|
|
// discarded.
|
|
if ( /* is member of an orphaned process group */ false &&
|
|
signal_actions[i].sa_handler == SIG_DFL &&
|
|
sigismember(&default_stop_signals, i) )
|
|
continue;
|
|
sigaddset(&handled_signals, i);
|
|
}
|
|
|
|
// TODO: Handle that signals can be pending process-wide!
|
|
|
|
// Discard all requested signals that would be ignored if delivered.
|
|
sigandset(&thread->signal_pending, &thread->signal_pending, &handled_signals);
|
|
|
|
// Determine which signals are not blocked.
|
|
sigset_t permitted_signals;
|
|
signotset(&permitted_signals, &thread->signal_mask);
|
|
sigorset(&permitted_signals, &permitted_signals, &unblockable_signals);
|
|
|
|
// Determine which signals can currently be delivered to this thread.
|
|
sigset_t deliverable_signals;
|
|
sigandset(&deliverable_signals, &permitted_signals, &thread->signal_pending);
|
|
|
|
// Determine whether any signals can be delivered.
|
|
unsigned long is_pending = !sigisemptyset(&deliverable_signals) ? 1 : 0;
|
|
if ( thread->force_no_signals )
|
|
is_pending = 0;
|
|
|
|
// Store whether a signal is pending in the virtual register.
|
|
if ( thread == CurrentThread() )
|
|
asm_signal_is_pending = is_pending;
|
|
else
|
|
thread->registers.signal_pending = is_pending;
|
|
}
|
|
|
|
void Thread::DoUpdatePendingSignal()
|
|
{
|
|
ScopedLock lock(&process->signal_lock);
|
|
UpdatePendingSignals(this);
|
|
}
|
|
|
|
int sys_sigaction(int signum,
|
|
const struct sigaction* user_newact,
|
|
struct sigaction* user_oldact)
|
|
{
|
|
if ( signum < 0 || signum == 0 /* null signal */ || SIG_MAX_NUM <= signum )
|
|
return errno = EINVAL;
|
|
|
|
Process* process = CurrentProcess();
|
|
ScopedLock lock(&process->signal_lock);
|
|
|
|
struct sigaction* kact = &process->signal_actions[signum];
|
|
|
|
// Let the caller know the previous action.
|
|
if ( user_oldact )
|
|
{
|
|
if ( !CopyToUser(user_oldact, kact, sizeof(struct sigaction)) )
|
|
return -1;
|
|
}
|
|
|
|
// Retrieve and validate the new signal action.
|
|
if ( user_newact )
|
|
{
|
|
struct sigaction newact;
|
|
if ( !CopyFromUser(&newact, user_newact, sizeof(struct sigaction)) )
|
|
return -1;
|
|
|
|
if ( newact.sa_flags & ~__SA_SUPPORTED_FLAGS )
|
|
return errno = EINVAL, -1;
|
|
|
|
if ( newact.sa_handler == SIG_ERR )
|
|
return errno = EINVAL, -1;
|
|
|
|
memcpy(kact, &newact, sizeof(struct sigaction));
|
|
|
|
// Signals may become discarded because of the new handler.
|
|
ScopedLock threads_lock(&process->threadlock);
|
|
for ( Thread* t = process->firstthread; t; t = t->nextsibling )
|
|
UpdatePendingSignals(t);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sys_sigaltstack(const stack_t* user_newstack, stack_t* user_oldstack)
|
|
{
|
|
Thread* thread = CurrentThread();
|
|
|
|
if ( user_oldstack )
|
|
{
|
|
if ( !CopyToUser(user_oldstack, &thread->signal_stack, sizeof(stack_t)) )
|
|
return -1;
|
|
}
|
|
|
|
if ( user_newstack )
|
|
{
|
|
stack_t newstack;
|
|
if ( !CopyFromUser(&newstack, user_newstack, sizeof(stack_t)) )
|
|
return -1;
|
|
|
|
if ( newstack.ss_flags & ~__SS_SUPPORTED_FLAGS )
|
|
return errno = EINVAL, -1;
|
|
|
|
memcpy(&thread->signal_stack, &newstack, sizeof(stack_t));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sys_sigpending(sigset_t* set)
|
|
{
|
|
Process* process = CurrentProcess();
|
|
Thread* thread = CurrentThread();
|
|
|
|
ScopedLock lock(&process->signal_lock);
|
|
|
|
// TODO: What about process-wide signals?
|
|
|
|
return CopyToUser(set, &thread->signal_pending, sizeof(sigset_t)) ? 0 : -1;
|
|
}
|
|
|
|
int sys_sigprocmask(int how, const sigset_t* user_set, sigset_t* user_oldset)
|
|
{
|
|
Process* process = CurrentProcess();
|
|
Thread* thread = CurrentThread();
|
|
|
|
// TODO: Signal masks are a per-thread property, perhaps this should be
|
|
// locked in another manner?
|
|
ScopedLock lock(&process->signal_lock);
|
|
|
|
// Let the caller know the previous signal mask.
|
|
if ( user_oldset )
|
|
{
|
|
if ( !CopyToUser(user_oldset, &thread->signal_mask, sizeof(sigset_t)) )
|
|
return -1;
|
|
}
|
|
|
|
// Update the current signal mask according to how.
|
|
if ( user_set )
|
|
{
|
|
sigset_t set;
|
|
if ( !CopyFromUser(&set, user_set, sizeof(sigset_t)) )
|
|
return -1;
|
|
|
|
switch ( how )
|
|
{
|
|
case SIG_BLOCK:
|
|
sigorset(&thread->signal_mask, &thread->signal_mask, &set);
|
|
break;
|
|
case SIG_UNBLOCK:
|
|
signotset(&set, &set);
|
|
sigandset(&thread->signal_mask, &thread->signal_mask, &set);
|
|
break;
|
|
case SIG_SETMASK:
|
|
memcpy(&thread->signal_mask, &set, sizeof(sigset_t));
|
|
break;
|
|
default:
|
|
return errno = EINVAL, -1;
|
|
};
|
|
|
|
UpdatePendingSignals(thread);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sys_sigsuspend(const sigset_t* set)
|
|
{
|
|
Process* process = CurrentProcess();
|
|
Thread* thread = CurrentThread();
|
|
|
|
sigset_t old_signal_mask; sigemptyset(&old_signal_mask);
|
|
sigset_t new_signal_mask;
|
|
|
|
ScopedLock lock(&process->signal_lock);
|
|
|
|
// Only accept signals from the user-provided set if given.
|
|
if ( set )
|
|
{
|
|
if ( !CopyFromUser(&new_signal_mask, set, sizeof(sigset_t)) )
|
|
return -1;
|
|
memcpy(&old_signal_mask, &thread->signal_mask, sizeof(sigset_t));
|
|
memcpy(&thread->signal_mask, &new_signal_mask, sizeof(sigset_t));
|
|
UpdatePendingSignals(thread);
|
|
}
|
|
|
|
// Wait for a signal to happen or otherwise never halt.
|
|
kthread_cond_t never_triggered = KTHREAD_COND_INITIALIZER;
|
|
while ( !Signal::IsPending() )
|
|
kthread_cond_wait_signal(&never_triggered, &process->signal_lock);
|
|
|
|
// Restore the previous signal mask if the user gave its own set to wait on.
|
|
if ( set )
|
|
{
|
|
memcpy(&thread->signal_mask, &old_signal_mask, sizeof(sigset_t));
|
|
UpdatePendingSignals(thread);
|
|
}
|
|
|
|
// The system call never halts or it halts because a signal interrupted it.
|
|
return errno = EINTR, -1;
|
|
}
|
|
|
|
int sys_kill(pid_t pid, int signum)
|
|
{
|
|
// Protect the kernel process.
|
|
if ( !pid )
|
|
return errno = EPERM, -1;
|
|
|
|
// TODO: Implement that pid == -1 means all processes!
|
|
bool process_group = pid < 0 ? (pid = -pid, true) : false;
|
|
|
|
// TODO: Race condition: The process could be deleted while we use it.
|
|
Process* process = CurrentProcess()->GetPTable()->Get(pid);
|
|
if ( !process )
|
|
return errno = ESRCH, -1;
|
|
|
|
// TODO: Protect init?
|
|
// TODO: Check for permission.
|
|
// TODO: Check for zombies.
|
|
|
|
if ( process_group )
|
|
{
|
|
if ( !process->DeliverGroupSignal(signum) && errno != ESIGPENDING )
|
|
return -1;
|
|
return errno = 0, 0;
|
|
}
|
|
|
|
if ( !process->DeliverSignal(signum) && errno != ESIGPENDING )
|
|
return -1;
|
|
return errno = 0, 0;
|
|
}
|
|
|
|
bool Process::DeliverGroupSignal(int signum)
|
|
{
|
|
ScopedLock lock(&groupparentlock);
|
|
if ( !groupfirst )
|
|
return errno = ESRCH, false;
|
|
for ( Process* iter = groupfirst; iter; iter = iter->groupnext )
|
|
{
|
|
int saved_errno = errno;
|
|
if ( !iter->DeliverSignal(signum) && errno != ESIGPENDING )
|
|
{
|
|
// This is not currently an error condition.
|
|
}
|
|
errno = saved_errno;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Process::DeliverSignal(int signum)
|
|
{
|
|
ScopedLock lock(&threadlock);
|
|
|
|
if ( !firstthread )
|
|
return errno = EINIT, false;
|
|
|
|
// Broadcast particular signals to all the threads in the process.
|
|
if ( signum == SIGCONT || signum == SIGSTOP || signum == SIGKILL )
|
|
{
|
|
int saved_errno = errno;
|
|
for ( Thread* t = firstthread; t; t = t->nextsibling )
|
|
{
|
|
if ( !t->DeliverSignal(signum) && errno != ESIGPENDING )
|
|
{
|
|
// This is not currently an error condition.
|
|
}
|
|
}
|
|
errno = saved_errno;
|
|
return true;
|
|
}
|
|
|
|
// Route the signal to a suitable thread that accepts it.
|
|
// TODO: This isn't how signals should be routed to a particular thread.
|
|
if ( CurrentThread()->process == this )
|
|
return CurrentThread()->DeliverSignal(signum);
|
|
return firstthread->DeliverSignal(signum);
|
|
}
|
|
|
|
int sys_raise(int signum)
|
|
{
|
|
if ( !CurrentThread()->DeliverSignal(signum) && errno != ESIGPENDING )
|
|
return -1;
|
|
return errno = 0, 0;
|
|
}
|
|
|
|
bool Thread::DeliverSignal(int signum)
|
|
{
|
|
ScopedLock lock(&process->signal_lock);
|
|
return DeliverSignalUnlocked(signum);
|
|
}
|
|
|
|
bool Thread::DeliverSignalUnlocked(int signum) // thread->process->signal_lock held
|
|
{
|
|
if ( signum <= 0 || SIG_MAX_NUM <= signum )
|
|
return errno = EINVAL, false;
|
|
|
|
// Discard the null signal, which does error checking, but doesn't actually
|
|
// deliver a signal to the process or thread.
|
|
if ( signum == 0 )
|
|
return true;
|
|
|
|
if ( sigismember(&signal_pending, signum) )
|
|
return errno = ESIGPENDING, false;
|
|
|
|
sigaddset(&signal_pending, signum);
|
|
if ( signum == SIGSTOP || signum == SIGTSTP ||
|
|
signum == SIGTTIN || signum == SIGTTOU )
|
|
sigdelset(&signal_pending, SIGCONT);
|
|
if ( signum == SIGCONT )
|
|
{
|
|
sigdelset(&signal_pending, SIGSTOP);
|
|
sigdelset(&signal_pending, SIGTSTP);
|
|
sigdelset(&signal_pending, SIGTTIN);
|
|
sigdelset(&signal_pending, SIGTTOU);
|
|
}
|
|
UpdatePendingSignals(this);
|
|
|
|
return true;
|
|
}
|
|
|
|
static int PickImportantSignal(const sigset_t* set)
|
|
{
|
|
if ( sigismember(set, SIGKILL) )
|
|
return SIGKILL;
|
|
if ( sigismember(set, SIGSTOP) )
|
|
return SIGSTOP;
|
|
for ( int i = 1; i < SIG_MAX_NUM; i++ )
|
|
if ( sigismember(set, i) )
|
|
return i;
|
|
return 0;
|
|
}
|
|
|
|
static void EncodeMachineContext(mcontext_t* mctx,
|
|
const struct thread_registers* regs,
|
|
const struct interrupt_context* intctx)
|
|
{
|
|
memset(mctx, 0, sizeof(*mctx));
|
|
#if defined(__i386__)
|
|
// TODO: REG_GS
|
|
// TODO: REG_FS
|
|
// TODO: REG_ES
|
|
// TODO: REG_DS
|
|
mctx->gregs[REG_EDI] = regs->edi;
|
|
mctx->gregs[REG_ESI] = regs->esi;
|
|
mctx->gregs[REG_EBP] = regs->ebp;
|
|
mctx->gregs[REG_ESP] = regs->esp;
|
|
mctx->gregs[REG_EBX] = regs->ebx;
|
|
mctx->gregs[REG_EDX] = regs->edx;
|
|
mctx->gregs[REG_ECX] = regs->ecx;
|
|
mctx->gregs[REG_EAX] = regs->eax;
|
|
mctx->gregs[REG_EIP] = regs->eip;
|
|
// TODO: REG_CS
|
|
mctx->gregs[REG_EFL] = regs->eflags & 0x0000FFFF;
|
|
mctx->gregs[REG_CR2] = intctx->cr2;
|
|
// TODO: REG_SS
|
|
memcpy(mctx->fpuenv, regs->fpuenv, 512);
|
|
#elif defined(__x86_64__)
|
|
mctx->gregs[REG_R8] = regs->r8;
|
|
mctx->gregs[REG_R9] = regs->r9;
|
|
mctx->gregs[REG_R10] = regs->r10;
|
|
mctx->gregs[REG_R11] = regs->r11;
|
|
mctx->gregs[REG_R12] = regs->r12;
|
|
mctx->gregs[REG_R13] = regs->r13;
|
|
mctx->gregs[REG_R14] = regs->r14;
|
|
mctx->gregs[REG_R15] = regs->r15;
|
|
mctx->gregs[REG_RDI] = regs->rdi;
|
|
mctx->gregs[REG_RSI] = regs->rsi;
|
|
mctx->gregs[REG_RBP] = regs->rbp;
|
|
mctx->gregs[REG_RBX] = regs->rbx;
|
|
mctx->gregs[REG_RDX] = regs->rdx;
|
|
mctx->gregs[REG_RAX] = regs->rax;
|
|
mctx->gregs[REG_RCX] = regs->rcx;
|
|
mctx->gregs[REG_RSP] = regs->rsp;
|
|
mctx->gregs[REG_RIP] = regs->rip;
|
|
mctx->gregs[REG_EFL] = regs->rflags & 0x000000000000FFFF;
|
|
// TODO: REG_CSGSFS.
|
|
mctx->gregs[REG_CR2] = intctx->cr2;
|
|
mctx->gregs[REG_FSBASE] = 0x0;
|
|
mctx->gregs[REG_GSBASE] = 0x0;
|
|
memcpy(mctx->fpuenv, regs->fpuenv, 512);
|
|
#else
|
|
#error "You need to implement conversion to mcontext"
|
|
#endif
|
|
}
|
|
|
|
static void DecodeMachineContext(const mcontext_t* mctx,
|
|
struct thread_registers* regs)
|
|
{
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
unsigned long user_flags = FLAGS_CARRY | FLAGS_PARITY | FLAGS_AUX
|
|
| FLAGS_ZERO | FLAGS_SIGN | FLAGS_DIRECTION
|
|
| FLAGS_OVERFLOW;
|
|
#endif
|
|
#if defined(__i386__)
|
|
regs->edi = mctx->gregs[REG_EDI];
|
|
regs->esi = mctx->gregs[REG_ESI];
|
|
regs->ebp = mctx->gregs[REG_EBP];
|
|
regs->esp = mctx->gregs[REG_ESP];
|
|
regs->ebx = mctx->gregs[REG_EBX];
|
|
regs->edx = mctx->gregs[REG_EDX];
|
|
regs->ecx = mctx->gregs[REG_ECX];
|
|
regs->eax = mctx->gregs[REG_EAX];
|
|
regs->eip = mctx->gregs[REG_EIP];
|
|
regs->eflags &= ~user_flags;
|
|
regs->eflags |= mctx->gregs[REG_EFL] & user_flags;
|
|
memcpy(regs->fpuenv, mctx->fpuenv, 512);
|
|
#elif defined(__x86_64__)
|
|
regs->r8 = mctx->gregs[REG_R8];
|
|
regs->r9 = mctx->gregs[REG_R9];
|
|
regs->r10 = mctx->gregs[REG_R10];
|
|
regs->r11 = mctx->gregs[REG_R11];
|
|
regs->r12 = mctx->gregs[REG_R12];
|
|
regs->r13 = mctx->gregs[REG_R13];
|
|
regs->r14 = mctx->gregs[REG_R14];
|
|
regs->r15 = mctx->gregs[REG_R15];
|
|
regs->rdi = mctx->gregs[REG_RDI];
|
|
regs->rsi = mctx->gregs[REG_RSI];
|
|
regs->rbp = mctx->gregs[REG_RBP];
|
|
regs->rbx = mctx->gregs[REG_RBX];
|
|
regs->rdx = mctx->gregs[REG_RDX];
|
|
regs->rax = mctx->gregs[REG_RAX];
|
|
regs->rcx = mctx->gregs[REG_RCX];
|
|
regs->rsp = mctx->gregs[REG_RSP];
|
|
regs->rip = mctx->gregs[REG_RIP];
|
|
regs->rflags &= ~user_flags;
|
|
regs->rflags |= mctx->gregs[REG_EFL] & user_flags;
|
|
memcpy(regs->fpuenv, mctx->fpuenv, 512);
|
|
#else
|
|
#error "You need to implement conversion to mcontext"
|
|
#endif
|
|
}
|
|
|
|
#if defined(__i386__)
|
|
struct stack_frame
|
|
{
|
|
unsigned long sigreturn;
|
|
int signum_param;
|
|
siginfo_t* siginfo_param;
|
|
ucontext_t* ucontext_param;
|
|
void* cookie_param;
|
|
siginfo_t siginfo;
|
|
ucontext_t ucontext;
|
|
};
|
|
#elif defined(__x86_64__)
|
|
struct stack_frame
|
|
{
|
|
unsigned long sigreturn;
|
|
siginfo_t siginfo;
|
|
ucontext_t ucontext;
|
|
};
|
|
#else
|
|
#error "You need to implement struct stack_frame"
|
|
#endif
|
|
|
|
void Thread::HandleSignal(struct interrupt_context* intctx)
|
|
{
|
|
assert(Interrupt::IsEnabled());
|
|
assert(this == CurrentThread());
|
|
|
|
ScopedLock lock(&process->signal_lock);
|
|
|
|
assert(process->sigreturn);
|
|
|
|
retry_another_signal:
|
|
|
|
// Determine which signals are not blocked.
|
|
sigset_t permitted_signals;
|
|
signotset(&permitted_signals, &signal_mask);
|
|
sigorset(&permitted_signals, &permitted_signals, &unblockable_signals);
|
|
|
|
// Determine which signals can currently be delivered to this thread.
|
|
sigset_t deliverable_signals;
|
|
sigandset(&deliverable_signals, &permitted_signals, &signal_pending);
|
|
|
|
// Decide which signal to deliver to the thread.
|
|
int signum = PickImportantSignal(&deliverable_signals);
|
|
if ( !signum )
|
|
return;
|
|
|
|
// Unmark the selected signal as pending.
|
|
sigdelset(&signal_pending, signum);
|
|
UpdatePendingSignals(this);
|
|
intctx->signal_pending = asm_signal_is_pending;
|
|
|
|
// Destroy the current thread if the signal is critical.
|
|
if ( signum == SIGKILL )
|
|
{
|
|
lock.Reset();
|
|
kthread_exit();
|
|
}
|
|
|
|
struct sigaction* action = &process->signal_actions[signum];
|
|
|
|
// Stop the current thread upon receipt of a stop signal that isn't handled
|
|
// or cannot be handled (SIGSTOP).
|
|
if ( (action->sa_handler == SIG_DFL &&
|
|
sigismember(&default_stop_signals, signum) ) ||
|
|
signum == SIGSTOP )
|
|
{
|
|
Log::PrintF("%s:%u: `%s' FIXME SIGSTOP\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
|
|
// TODO: Stop the current process.
|
|
// TODO: Deliver SIGCHLD to the parent except if SA_NOCLDSTOP is set in
|
|
// the parent's SIGCHLD sigaction.
|
|
// TODO: SIGCHLD should not be delivered until all the threads in the
|
|
// process has received SIGSTOP and stopped?
|
|
// TODO: SIGKILL must still be deliverable to a stopped process.
|
|
}
|
|
|
|
// Resume the current thread upon receipt of SIGCONT.
|
|
if ( signum == SIGCONT )
|
|
{
|
|
Log::PrintF("%s:%u: `%s' FIXME SIGCONT\n", __FILE__, __LINE__, __PRETTY_FUNCTION__);
|
|
// TODO: Resume the current process.
|
|
// TODO: Can SIGCONT be masked?
|
|
// TODO: Can SIGCONT be handled?
|
|
// TODO: Can SIGCONT be ignored?
|
|
// TODO: Deliver SIGCHLD to the parent except if SA_NOCLDSTOP is set in
|
|
// the parent's SIGCHLD sigaction.
|
|
}
|
|
|
|
// Signals that would be ignored are already filtered away at this point.
|
|
assert(action->sa_handler != SIG_IGN);
|
|
assert(action->sa_handler != SIG_DFL || !sigismember(&default_ignored_signals, signum));
|
|
|
|
// The default action must be to terminate the process. Signals that are
|
|
// ignored by default got discarded earlier.
|
|
if ( action->sa_handler == SIG_DFL )
|
|
{
|
|
kthread_mutex_unlock(&process->signal_lock);
|
|
process->ExitThroughSignal(signum);
|
|
kthread_mutex_lock(&process->signal_lock);
|
|
goto retry_another_signal;
|
|
}
|
|
|
|
// At this point we have to attempt to invoke the user-space signal handler,
|
|
// which will then return control to us through sigreturn. However, we can't
|
|
// save the kernel state because 1) we can't trust the user-space stack 2)
|
|
// we can't rely on the kernel stack being intact as the signal handler may
|
|
// invoke system calls. For those reasons, we'll have to modify the saved
|
|
// registers so they restore a user-space state. We can do this because
|
|
// threads in the kernel cannot be delivered signals except when returning
|
|
// from a system call, so we'll simply save the state that would have been
|
|
// returned to user-space had no signal occured.
|
|
if ( !InUserspace(intctx) )
|
|
{
|
|
#if defined(__i386__)
|
|
uint32_t* params = (uint32_t*) intctx->ebx;
|
|
intctx->eip = params[0];
|
|
intctx->eflags = params[2];
|
|
intctx->esp = params[3];
|
|
intctx->cs = UCS | URPL;
|
|
intctx->ds = UDS | URPL;
|
|
intctx->ss = UDS | URPL;
|
|
intctx->ebx = 0;
|
|
#elif defined(__x86_64__)
|
|
intctx->rip = intctx->rdi;
|
|
intctx->rflags = intctx->rsi;
|
|
intctx->rsp = intctx->r8;
|
|
intctx->cs = UCS | URPL;
|
|
intctx->ds = UDS | URPL;
|
|
intctx->ss = UDS | URPL;
|
|
intctx->rdi = 0;
|
|
intctx->rsi = 0;
|
|
intctx->r8 = 0;
|
|
#else
|
|
#error "You may need to fix the registers"
|
|
#endif
|
|
}
|
|
|
|
struct thread_registers stopped_regs;
|
|
Scheduler::SaveInterruptedContext(intctx, &stopped_regs);
|
|
|
|
sigset_t new_signal_mask;
|
|
memcpy(&new_signal_mask, &action->sa_mask, sizeof(sigset_t));
|
|
sigorset(&new_signal_mask, &new_signal_mask, &signal_mask);
|
|
|
|
// Prevent signals from interrupting themselves by default.
|
|
if ( !(action->sa_flags & SA_NODEFER) )
|
|
sigaddset(&new_signal_mask, signum);
|
|
|
|
// Determine whether we use an alternate signal stack.
|
|
bool signal_uses_altstack = action->sa_flags & SA_ONSTACK;
|
|
bool usable_altstack = !(signal_stack.ss_flags & (SS_DISABLE | SS_ONSTACK));
|
|
bool use_altstack = signal_uses_altstack && usable_altstack;
|
|
|
|
// Determine which signal stack to use and what to save.
|
|
stack_t old_signal_stack, new_signal_stack;
|
|
uintptr_t stack_location;
|
|
if ( use_altstack )
|
|
{
|
|
old_signal_stack = signal_stack;
|
|
new_signal_stack = signal_stack;
|
|
new_signal_stack.ss_flags |= SS_ONSTACK;
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
stack_location = (uintptr_t) signal_stack.ss_sp + signal_stack.ss_size;
|
|
#else
|
|
#error "You need to implement getting the alternate stack pointer"
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
old_signal_stack.ss_sp = NULL;
|
|
old_signal_stack.ss_flags = SS_DISABLE;
|
|
old_signal_stack.ss_size = 0;
|
|
new_signal_stack = signal_stack;
|
|
#if defined(__i386__)
|
|
stack_location = (uintptr_t) stopped_regs.esp;
|
|
#elif defined(__x86_64__)
|
|
stack_location = (uintptr_t) stopped_regs.rsp;
|
|
#else
|
|
#error "You need to implement getting the user-space stack pointer"
|
|
#endif
|
|
}
|
|
|
|
struct thread_registers handler_regs;
|
|
memcpy(&handler_regs, &stopped_regs, sizeof(handler_regs));
|
|
|
|
struct stack_frame stack_frame;
|
|
memset(&stack_frame, 0, sizeof(stack_frame));
|
|
|
|
void* handler_ptr = action->sa_flags & SA_COOKIE ?
|
|
(void*) action->sa_sigaction_cookie :
|
|
action->sa_flags & SA_SIGINFO ?
|
|
(void*) action->sa_sigaction :
|
|
(void*) action->sa_handler;
|
|
|
|
#if defined(__i386__)
|
|
stack_location -= sizeof(stack_frame);
|
|
stack_location &= ~(4UL-1UL);
|
|
struct stack_frame* stack = (struct stack_frame*) stack_location;
|
|
|
|
stack_frame.sigreturn = (unsigned long) process->sigreturn;
|
|
stack_frame.signum_param = signum;
|
|
stack_frame.siginfo_param = &stack->siginfo;
|
|
stack_frame.ucontext_param = &stack->ucontext;
|
|
stack_frame.cookie_param = action->sa_cookie;
|
|
|
|
handler_regs.esp = (unsigned long) stack;
|
|
handler_regs.eip = (unsigned long) handler_ptr;
|
|
handler_regs.eflags &= ~FLAGS_DIRECTION;
|
|
#elif defined(__x86_64__)
|
|
stack_location -= 128; /* Red zone. */
|
|
stack_location -= sizeof(stack_frame);
|
|
stack_location = ((stack_location - 8) & ~(16UL-1UL)) + 8;
|
|
struct stack_frame* stack = (struct stack_frame*) stack_location;
|
|
|
|
stack_frame.sigreturn = (unsigned long) process->sigreturn;
|
|
handler_regs.rdi = (unsigned long) signum;
|
|
handler_regs.rsi = (unsigned long) &stack->siginfo;
|
|
handler_regs.rdx = (unsigned long) &stack->ucontext;
|
|
handler_regs.rcx = (unsigned long) action->sa_cookie;
|
|
|
|
handler_regs.rsp = (unsigned long) stack;
|
|
handler_regs.rip = (unsigned long) handler_ptr;
|
|
handler_regs.rflags &= ~FLAGS_DIRECTION;
|
|
#else
|
|
#error "You need to format the stack frame"
|
|
#endif
|
|
|
|
// Format the siginfo into the stack frame.
|
|
stack_frame.siginfo.si_signo = signum;
|
|
#if defined(__i386__) || defined(__x86_64__)
|
|
// TODO: Is this cr2 value trustworthy? I don't think it is.
|
|
if ( signum == SIGSEGV )
|
|
stack_frame.siginfo.si_addr = (void*) intctx->cr2;
|
|
#else
|
|
#warning "You need to tell user-space where it crashed"
|
|
#endif
|
|
|
|
// Format the ucontext into the stack frame.
|
|
stack_frame.ucontext.uc_link = NULL;
|
|
memcpy(&stack_frame.ucontext.uc_sigmask, &signal_mask, sizeof(signal_mask));
|
|
memcpy(&stack_frame.ucontext.uc_stack, &signal_stack, sizeof(signal_stack));
|
|
EncodeMachineContext(&stack_frame.ucontext.uc_mcontext, &stopped_regs, intctx);
|
|
|
|
if ( !CopyToUser(stack, &stack_frame, sizeof(stack_frame)) )
|
|
{
|
|
// Self-destruct if we crashed during delivering the crash signal.
|
|
if ( signum == SIGSEGV )
|
|
{
|
|
kthread_mutex_unlock(&process->signal_lock);
|
|
process->ExitThroughSignal(signum);
|
|
kthread_mutex_lock(&process->signal_lock);
|
|
goto retry_another_signal;
|
|
}
|
|
|
|
// Deliver SIGSEGV if we could not deliver the signal on the stack.
|
|
// TODO: Is it possible to block SIGSEGV here?
|
|
kthread_mutex_unlock(&process->signal_lock);
|
|
DeliverSignal(SIGSEGV);
|
|
kthread_mutex_lock(&process->signal_lock);
|
|
goto retry_another_signal;
|
|
}
|
|
|
|
// Update the current signal mask.
|
|
memcpy(&signal_mask, &new_signal_mask, sizeof(sigset_t));
|
|
|
|
// Update the current alternate signal stack.
|
|
signal_stack = new_signal_stack;
|
|
|
|
// Update the current registers.
|
|
Scheduler::LoadInterruptedContext(intctx, &handler_regs);
|
|
|
|
// Reset the signal handler if this signal handler is once only.
|
|
if ( action->sa_flags & SA_RESETHAND )
|
|
{
|
|
// POSIX mandates SA_RESETHAND is silently ignored for these signals.
|
|
if ( signum != SIGILL && signum != SIGTRAP )
|
|
{
|
|
action->sa_flags &= ~SA_RESETHAND;
|
|
action->sa_handler = SIG_DFL;
|
|
}
|
|
}
|
|
|
|
// Run the signal handler by returning to user-space.
|
|
return;
|
|
}
|
|
|
|
void Thread::HandleSigreturn(struct interrupt_context* intctx)
|
|
{
|
|
assert(Interrupt::IsEnabled());
|
|
assert(this == CurrentThread());
|
|
|
|
ScopedLock lock(&process->signal_lock);
|
|
|
|
struct stack_frame stack_frame;
|
|
const struct stack_frame* user_stack_frame;
|
|
#if defined(__i386__)
|
|
user_stack_frame = (const struct stack_frame*) (intctx->esp - 4);
|
|
#elif defined(__x86_64__)
|
|
user_stack_frame = (const struct stack_frame*) (intctx->rsp - 8);
|
|
#else
|
|
#error "You need to locate the stack we passed the signal handler"
|
|
#endif
|
|
|
|
if ( CopyFromUser(&stack_frame, user_stack_frame, sizeof(stack_frame)) )
|
|
{
|
|
memcpy(&signal_mask, &stack_frame.ucontext.uc_sigmask, sizeof(signal_mask));
|
|
memcpy(&signal_stack, &stack_frame.ucontext.uc_stack, sizeof(signal_stack));
|
|
signal_stack.ss_flags &= __SS_SUPPORTED_FLAGS;
|
|
struct thread_registers resume_regs;
|
|
Scheduler::SaveInterruptedContext(intctx, &resume_regs);
|
|
DecodeMachineContext(&stack_frame.ucontext.uc_mcontext, &resume_regs);
|
|
Scheduler::LoadInterruptedContext(intctx, &resume_regs);
|
|
}
|
|
|
|
UpdatePendingSignals(this);
|
|
intctx->signal_pending = asm_signal_is_pending;
|
|
|
|
lock.Reset();
|
|
|
|
HandleSignal(intctx);
|
|
}
|
|
|
|
namespace Signal {
|
|
|
|
void DispatchHandler(struct interrupt_context* intctx, void* /*user*/)
|
|
{
|
|
return CurrentThread()->HandleSignal(intctx);
|
|
}
|
|
|
|
void ReturnHandler(struct interrupt_context* intctx, void* /*user*/)
|
|
{
|
|
return CurrentThread()->HandleSigreturn(intctx);
|
|
}
|
|
|
|
void Init()
|
|
{
|
|
sigemptyset(&default_ignored_signals);
|
|
sigaddset(&default_ignored_signals, SIGCHLD);
|
|
sigaddset(&default_ignored_signals, SIGURG);
|
|
sigaddset(&default_ignored_signals, SIGPWR);
|
|
sigaddset(&default_ignored_signals, SIGWINCH);
|
|
sigemptyset(&default_stop_signals);
|
|
sigaddset(&default_stop_signals, SIGTSTP);
|
|
sigaddset(&default_stop_signals, SIGTTIN);
|
|
sigaddset(&default_stop_signals, SIGTTOU);
|
|
sigemptyset(&unblockable_signals);
|
|
sigaddset(&unblockable_signals, SIGKILL);
|
|
sigaddset(&unblockable_signals, SIGSTOP);
|
|
}
|
|
|
|
} // namespace Signal
|
|
|
|
} // namespace Sortix
|