Sixty-five headers sat side by side in include/, so `#include "irq.h"` said nothing about where irq.h was or what it belonged to, and errno.h, stat.h and fcntl.h at the top shadowed the system's for any host tool built with -I./include. They are in folders now -- arch/ core/ fs/ dev/ gfx/ net/ uapi/ (and ldls/ for the Linux driver layer) -- and every include names the folder, as Linux's do: "core/proc.h". Done mechanically and checked the hard way: the kernel built from this tree disassembles identically to the one built from the tree before it. The full image boots, and userland is the same set of programs. Paths in docs and comments follow; docs/guides/layout.md has a table of what is where, and tools/fix-includes.py rewrites old-style includes from the folders in the tree, for code written before this. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
318 lines
13 KiB
C
318 lines
13 KiB
C
#include "core/types.h"
|
|
#include "core/defs.h"
|
|
#include "core/param.h"
|
|
#include "core/proc.h"
|
|
#include "arch/x86.h"
|
|
#include "core/syscall.h"
|
|
#include "core/uaccess.h"
|
|
#include "uapi/errno.h"
|
|
#include "uapi/posix.h" /* SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK, signal numbers */
|
|
|
|
// Linux sigset_t puts signal N at bit N-1; kernel-internal masks
|
|
// (pending_signals / signal_mask / deliver_signals) put signal N at bit N.
|
|
// Convert at the user boundary — storing user sets raw silently shifted
|
|
// every mask by one signal (blocking SIGCHLD actually blocked SIGCONT),
|
|
// which let mksh's SIGCHLD handler run inside the very critical sections
|
|
// sigprocmask() was guarding, wrecking its job table.
|
|
static uint64 sigset_from_user(uint64 uset) { return uset << 1; }
|
|
static uint64 sigset_to_user(uint64 kset) { return kset >> 1; }
|
|
|
|
addr_t
|
|
sys_sigfillset(void)
|
|
{
|
|
uint64 *set;
|
|
if (argptr(0, (char**)&set, sizeof(uint64)) < 0)
|
|
return -1;
|
|
if (set) *set = ~0ULL;
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_sigprocmask(void)
|
|
{
|
|
int how;
|
|
addr_t setp, oldsetp;
|
|
argint(0, &how);
|
|
argaddr(1, &setp);
|
|
argaddr(2, &oldsetp);
|
|
|
|
// oldset is optional (NULL = don't return previous mask)
|
|
if (oldsetp) {
|
|
uint64 old = sigset_to_user(myproc()->signal_mask);
|
|
put_user(old, oldsetp);
|
|
}
|
|
// set is optional (NULL = just query)
|
|
if (setp) {
|
|
uint64 uset;
|
|
if (copy_from_user(&uset, setp, sizeof(uset)) == 0) {
|
|
uint64 set = sigset_from_user(uset);
|
|
if (how == SIG_BLOCK) myproc()->signal_mask |= set;
|
|
else if (how == SIG_UNBLOCK) myproc()->signal_mask &= ~set;
|
|
else myproc()->signal_mask = set;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
addr_t
|
|
sys_sigwait(void)
|
|
{
|
|
uint64 *set_ptr;
|
|
int *sig_ptr;
|
|
if (argptr(0, (char**)&set_ptr, sizeof(uint64)) < 0 ||
|
|
argptr(1, (char**)&sig_ptr, sizeof(int)) < 0)
|
|
return -1;
|
|
uint64 set = sigset_from_user(*set_ptr);
|
|
acquire(&tickslock);
|
|
while (!(myproc()->pending_signals & set)) {
|
|
if (myproc()->killed) { release(&tickslock); return -1; }
|
|
sleep(myproc(), &tickslock);
|
|
}
|
|
for (int i = 0; i < 64; i++) {
|
|
if ((myproc()->pending_signals & set) & (1ULL << i)) {
|
|
myproc()->pending_signals &= ~(1ULL << i);
|
|
*sig_ptr = i;
|
|
break;
|
|
}
|
|
}
|
|
release(&tickslock);
|
|
return 0;
|
|
}
|
|
|
|
// musl's struct sigaction (musl 1.2.5 signal.h) for this target:
|
|
// sa_handler (8 bytes) at offset 0, then a 128-byte sigset_t (musl uses
|
|
// 128/sizeof(long) = 16 longs regardless of NSIG), sa_flags (4 bytes) +
|
|
// padding, then sa_restorer (8 bytes) at offset 144. We only care about
|
|
// those two 8-byte fields.
|
|
#define SA_HANDLER_OFF 0
|
|
#define SA_RESTORER_OFF 144
|
|
|
|
// rt_sigaction: store the handler + restorer so deliver_signals() (called
|
|
// from syscall()'s tail, kernel/sys/syscall.c) can actually jump to user
|
|
// code instead of silently doing nothing. sig 0 is invalid (matches
|
|
// sigaction(2)); we don't validate against NSIG beyond our own array size.
|
|
//
|
|
// User accesses go through copy_from_user/put_user (uaccess.h), which are
|
|
// page-safe. An earlier version bulk-copied the ~152-byte struct through a
|
|
// single uva2ka() translation of its start address; when the user's
|
|
// act/oldact pointer landed close enough to a page boundary, the write ran
|
|
// past the mapped page into an unrelated physical page, silently crashing
|
|
// every freshly exec'd /bin/sh (mksh calls sigaction() during startup).
|
|
addr_t
|
|
sys_rt_sigaction(void)
|
|
{
|
|
int sig;
|
|
addr_t actp, oldactp;
|
|
argint(0, &sig);
|
|
argaddr(1, &actp);
|
|
argaddr(2, &oldactp);
|
|
if (sig <= 0 || sig >= 32) return -1;
|
|
|
|
if (oldactp) {
|
|
put_user(myproc()->sig_handler[sig], oldactp + SA_HANDLER_OFF);
|
|
put_user(myproc()->sig_restorer[sig], oldactp + SA_RESTORER_OFF);
|
|
}
|
|
if (actp) {
|
|
copy_from_user(&myproc()->sig_handler[sig], actp + SA_HANDLER_OFF,
|
|
sizeof(myproc()->sig_handler[sig]));
|
|
copy_from_user(&myproc()->sig_restorer[sig], actp + SA_RESTORER_OFF,
|
|
sizeof(myproc()->sig_restorer[sig]));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// rt_sigreturn: entered via `syscall` (rax=15) from musl's __restore_rt
|
|
// trampoline, which is exactly what we set as the manufactured return
|
|
// address in deliver_signals() below — the handler function's own `ret`
|
|
// lands here once it's done. Restore the trapframe deliver_signals()
|
|
// stashed before redirecting to the handler, so syscall()'s normal
|
|
// sysretq path resumes the interrupted code exactly where it left off.
|
|
addr_t
|
|
sys_rt_sigreturn(void)
|
|
{
|
|
addr_t saved_rax = myproc()->saved_sigtf.rax;
|
|
*myproc()->tf = myproc()->saved_sigtf;
|
|
fxrstor(myproc()->sigfpu); // what the interrupted code had
|
|
myproc()->in_signal = 0;
|
|
// If this handler interrupted a sigsuspend(), put its pre-suspend
|
|
// signal mask back now that the handler has finished.
|
|
if (myproc()->sigsusp_active) {
|
|
myproc()->signal_mask = myproc()->sigsusp_oldmask;
|
|
myproc()->sigsusp_active = 0;
|
|
}
|
|
// syscall() does `tf->rax = <our return value>` right after this
|
|
// returns — hand back the already-restored rax so that assignment is
|
|
// a harmless no-op instead of clobbering what we just restored.
|
|
return saved_rax;
|
|
}
|
|
|
|
// Called from syscall()'s tail (kernel/sys/syscall.c), right before
|
|
// returning to user mode, for every syscall a process makes (this is the
|
|
// only "check point" we have without also hooking the interrupt-return
|
|
// path in trap.c — sufficient for what actually needed this: a process
|
|
// blocked in a syscall, like sigsuspend(), waiting for its own next
|
|
// syscall return to notice a signal arrived).
|
|
//
|
|
// If there's a pending, unblocked signal with a registered handler,
|
|
// redirect tf to it: rdi = signal number (SysV ABI first arg), rip =
|
|
// handler.
|
|
//
|
|
// The return address (what the handler's own `ret` lands on) can't be
|
|
// sig_restorer[sig] — this musl build has no SA_RESTORER defined at all
|
|
// (checked: not defined anywhere in musl 1.2.5), so sigaction() never
|
|
// fills it in; it's always 0. Pushing 0 as a return address is exactly
|
|
// what crashed every mksh instance the first time a signal was ever
|
|
// actually delivered (handler ran, `ret` jumped to address 0). Instead,
|
|
// inject a tiny trampoline directly onto the user's own stack — 7 bytes
|
|
// of machine code for `mov eax, 15; syscall` (rt_sigreturn) — below the
|
|
// current stack pointer (clear of the SysV red zone) and point the
|
|
// return address at that, the standard way a kernel supplies sigreturn
|
|
// when the C library doesn't hand it one itself.
|
|
void
|
|
deliver_signals(struct trapframe *tf)
|
|
{
|
|
if (myproc()->in_signal) return; // no nested delivery
|
|
uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask;
|
|
if (!deliverable) return;
|
|
|
|
for (int sig = 1; sig < 32; sig++) {
|
|
if (!(deliverable & (1ULL << sig))) continue;
|
|
|
|
// SIG_DFL is 0 and SIG_IGN is 1 -- neither is an address to jump to.
|
|
//
|
|
// Only the 0 case was checked here. A process that had asked to
|
|
// ignore a signal got its handler "called" at address 1, which is a
|
|
// page fault at rip=0x1 and a dead process. Nothing sent SIGPIPE
|
|
// until pipes learned to, at which point every shell that ignores
|
|
// SIGPIPE -- which is every shell -- died the first time a pipeline
|
|
// ended early. `cat big | head -c 5` was enough.
|
|
//
|
|
// The default action for a signal with no handler has already been
|
|
// applied by sigsend(); all that is left here is to stop it being
|
|
// pending, which the old `continue` never did, so an ignored signal
|
|
// was re-examined on the way out of every syscall forever after.
|
|
if (myproc()->sig_handler[sig] <= 1) {
|
|
myproc()->pending_signals &= ~(1ULL << sig);
|
|
continue;
|
|
}
|
|
|
|
uint64 tramp_addr = (tf->rsp - 256) & ~0xFULL;
|
|
uint8 *tramp = (uint8*)uva2ka(myproc()->pgdir, (char*)tramp_addr);
|
|
if (!tramp) continue; // can't deliver safely; try another pending signal
|
|
|
|
uint64 sp = tramp_addr - 8;
|
|
uint64 *retaddr = (uint64*)uva2ka(myproc()->pgdir, (char*)sp);
|
|
if (!retaddr) continue;
|
|
|
|
// The return trampoline: `mov eax, <rt_sigreturn> ; syscall`.
|
|
//
|
|
// **Which number depends on the process's ABI**, and it used to be
|
|
// hardcoded to 15. Fifteen is Linux's rt_sigreturn, which is right for
|
|
// anything musl-linked -- every port, and the shells. It is *native*
|
|
// syscall 15 for a first-party binary, and native 15 is `open`. So a
|
|
// handler in any cmd/ program ran, returned into the trampoline, called
|
|
// open() with the handler's leftover registers as arguments, got
|
|
// -ENOENT, and carried on executing the bytes after the trampoline --
|
|
// which are stack, not code.
|
|
//
|
|
// See table.inc: SYSCALL(97, 15, rt_sigreturn). syscall_map is the
|
|
// thing that says which numbering this process speaks.
|
|
uint32 sret = myproc()->syscall_map ? 15 : SYS_rt_sigreturn;
|
|
tramp[0] = 0xb8; // mov eax, imm32
|
|
tramp[1] = (uint8)(sret );
|
|
tramp[2] = (uint8)(sret >> 8);
|
|
tramp[3] = (uint8)(sret >> 16);
|
|
tramp[4] = (uint8)(sret >> 24);
|
|
tramp[5] = 0x0f; tramp[6] = 0x05; // syscall
|
|
*retaddr = tramp_addr;
|
|
|
|
myproc()->pending_signals &= ~(1ULL << sig);
|
|
myproc()->saved_sigtf = *tf;
|
|
// The handler is free to use %xmm; the code it interrupted had values
|
|
// there. Put back by rt_sigreturn.
|
|
fxsave(myproc()->sigfpu);
|
|
myproc()->in_signal = 1;
|
|
|
|
tf->rsp = sp;
|
|
tf->rdi = (uint64)sig;
|
|
// Both return paths must point at the handler: iretq-based returns
|
|
// use tf->rip, but syscall_trapret exits via sysretq, which takes
|
|
// the user RIP from RCX and *discards* tf->rip (trapasm.S pops rcx
|
|
// from the frame, then `addq $40` throws rip/cs/rflags away). With
|
|
// only tf->rip set, the "delivered" handler never actually ran on
|
|
// the syscall path: user code resumed at the old rcx with the
|
|
// shifted rsp, and its ret fell into the sigreturn trampoline,
|
|
// silently undoing everything. exec() handles this same quirk by
|
|
// setting tf->rcx = elf.entry.
|
|
tf->rip = myproc()->sig_handler[sig];
|
|
tf->rcx = myproc()->sig_handler[sig];
|
|
return;
|
|
}
|
|
}
|
|
|
|
// sigaltstack: set/get alternate signal stack.
|
|
// We don't deliver signals, so just store/return zeros.
|
|
addr_t sys_sigaltstack(void) { return 0; }
|
|
|
|
// rt_sigpending: write the set of pending signals to user buffer.
|
|
addr_t sys_rt_sigpending(void)
|
|
{
|
|
addr_t setp;
|
|
argaddr(0, &setp);
|
|
if (setp) {
|
|
uint64 pend = sigset_to_user(myproc()->pending_signals);
|
|
put_user(pend, setp);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// rt_sigtimedwait: wait for a signal from the set; returns -EAGAIN immediately.
|
|
addr_t sys_rt_sigtimedwait(void) { return -EAGAIN; }
|
|
|
|
// rt_sigsuspend: atomically replace the signal mask and suspend until a
|
|
// signal not blocked by the *temporary* mask is pending, then return -EINTR.
|
|
//
|
|
// The atomic mask swap is the entire point of sigsuspend(2): mksh blocks
|
|
// SIGCHLD while it inspects its job table, then calls sigsuspend() with the
|
|
// old (SIGCHLD-unblocked) mask to wait for a child. The previous stub
|
|
// ignored the mask argument and just slept one tick — SIGCHLD stayed
|
|
// blocked, deliver_signals() could never fire the handler, and the shell
|
|
// hung forever after running any external command (unreaped zombie child,
|
|
// shell parked in sigsuspend: the "can't type after a command" hang).
|
|
//
|
|
// The temporary mask must still be in effect when deliver_signals() runs at
|
|
// syscall()'s tail (that's what allows the delivery), so the old mask can't
|
|
// be restored here — sys_rt_sigreturn() restores it once the handler
|
|
// finishes, via sigsusp_oldmask/sigsusp_active.
|
|
addr_t sys_rt_sigsuspend(void)
|
|
{
|
|
addr_t maskp;
|
|
uint64 newmask;
|
|
argaddr(0, &maskp);
|
|
|
|
uint64 oldmask = myproc()->signal_mask;
|
|
if (maskp && copy_from_user(&newmask, maskp, sizeof(newmask)) == 0)
|
|
myproc()->signal_mask = sigset_from_user(newmask);
|
|
|
|
acquire(&tickslock);
|
|
while (!(myproc()->pending_signals & ~myproc()->signal_mask) && !myproc()->killed)
|
|
sleep(&ticks, &tickslock);
|
|
release(&tickslock);
|
|
|
|
// Will any pending signal actually reach a handler? If so, the mask is
|
|
// restored by sigreturn after the handler runs; otherwise (no handler
|
|
// installed, or we were killed) restore it right now.
|
|
uint64 deliverable = myproc()->pending_signals & ~myproc()->signal_mask;
|
|
int will_deliver = 0;
|
|
for (int sig = 1; sig < 32; sig++)
|
|
if ((deliverable & (1ULL << sig)) && myproc()->sig_handler[sig])
|
|
{ will_deliver = 1; break; }
|
|
|
|
if (will_deliver && !myproc()->in_signal) {
|
|
myproc()->sigsusp_oldmask = oldmask;
|
|
myproc()->sigsusp_active = 1;
|
|
} else {
|
|
myproc()->signal_mask = oldmask;
|
|
}
|
|
return -EINTR;
|
|
}
|