Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions expected/wasm32-wasi-eh/defined-symbols.txt
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,8 @@ __set_thread_area
__shgetc
__shlim
__shm_mapname
__sig_deliver_pending
__sig_register_callback
__sigaction
__sigaction_external_default
__signgam
Expand Down
2 changes: 2 additions & 0 deletions expected/wasm32-wasi-ehpic/defined-symbols.txt
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ __set_thread_area
__shgetc
__shlim
__shm_mapname
__sig_deliver_pending
__sig_register_callback
__sigaction
__sigaction_external_default
__signgam
Expand Down
2 changes: 2 additions & 0 deletions expected/wasm32-wasi/defined-symbols.txt
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ __set_thread_area
__shgetc
__shlim
__shm_mapname
__sig_deliver_pending
__sig_register_callback
__sigaction
__sigaction_external_default
__signgam
Expand Down
8 changes: 8 additions & 0 deletions libc-top-half/musl/src/env/__init_tls.c
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ int __init_tp(void *p)
td->robust_list.head = &td->robust_list.head;
td->sysinfo = __sysinfo;
td->next = td->prev = td;
#ifndef __wasilibc_unmodified_upstream
/* __wasi_init_tp() allocates this structure with aligned_alloc(), which
* does not zero, so every field the signal code reads has to be set here
* explicitly. Leaving the mask as heap garbage makes arbitrary signals
* look blocked. */
memset(&td->sigmask, 0, sizeof td->sigmask);
memset(&td->sigpending, 0, sizeof td->sigpending);
#endif
return 0;
}

Expand Down
9 changes: 9 additions & 0 deletions libc-top-half/musl/src/include/signal.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,13 @@ hidden void __restore_sigs(void *);

hidden void __get_handler_set(sigset_t *);

/* Dispatches signals that are pending on the calling thread and no longer
* blocked. WASIX delivers signals only at syscall boundaries, so whoever
* unblocks a signal has to flush it: nothing will interrupt us to do it. */
hidden void __sig_deliver_pending(void);

/* (Re-)registers the guest signal callback with the host. Needed after fork:
* the host's registration is per instance and does not survive it. */
hidden void __sig_register_callback(void);

#endif
13 changes: 13 additions & 0 deletions libc-top-half/musl/src/internal/pthread_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@
#include <pthread.h>
#ifdef __wasilibc_unmodified_upstream
#include <signal.h>
#else
/* The signal mask below needs sigset_t as a complete type. Pulling in all of
* <signal.h> here would be circular, so take just the typedef. */
#define __NEED_sigset_t
#include <bits/alltypes.h>
#endif
#include <errno.h>
#include <limits.h>
Expand Down Expand Up @@ -67,6 +72,14 @@ struct pthread {
volatile int killlock[1];
char *dlerror_buf;
void *stdio_locks;
#ifndef __wasilibc_unmodified_upstream
/* Signals are masked in the guest: WASIX has no kernel to hold a
* per-thread mask for us. `sigpending` holds deliveries that arrived
* while blocked, which __sig_deliver_pending() flushes on unblock.
* Only ever touched by the owning thread, so no lock is needed. */
sigset_t sigmask;
sigset_t sigpending;
#endif

/* Part 3 -- the positions of these fields relative to
* the end of the structure is external and internal ABI. */
Expand Down
54 changes: 54 additions & 0 deletions libc-top-half/musl/src/internal/sigmask.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#ifndef SIGMASK_H
#define SIGMASK_H

#include <signal.h>

/* Meaningful extent of a sigset_t. The structure is padded well past the
* number of signals that exist, and k_sigaction stores only this much, so all
* mask arithmetic is done over this many bytes. */
#define __SIGSET_BYTES (_NSIG/8)

static inline void __sigset_or(sigset_t *dst, const void *bits)
{
unsigned char *d = (unsigned char *)dst;
const unsigned char *s = (const unsigned char *)bits;
for (unsigned i = 0; i < __SIGSET_BYTES; i++) d[i] |= s[i];
}

static inline void __sigset_andnot(sigset_t *dst, const void *bits)
{
unsigned char *d = (unsigned char *)dst;
const unsigned char *s = (const unsigned char *)bits;
for (unsigned i = 0; i < __SIGSET_BYTES; i++) d[i] &= ~s[i];
}

static inline void __sigset_copy(sigset_t *dst, const void *bits)
{
unsigned char *d = (unsigned char *)dst;
const unsigned char *s = (const unsigned char *)bits;
for (unsigned i = 0; i < __SIGSET_BYTES; i++) d[i] = s[i];
}

/* Unlike sigaddset()/sigismember(), these neither validate `sig` nor touch
* errno, so they are safe on the signal delivery path -- clobbering errno
* there would corrupt the error reporting of whatever syscall the delivery
* interrupted. Callers must keep `sig` within 1.._NSIG-1. */
static inline int __sigset_test(const sigset_t *set, int sig)
{
unsigned s = sig-1;
return !!(set->__bits[s/8/sizeof *set->__bits] & 1UL<<(s&8*sizeof *set->__bits-1));
}

static inline void __sigset_add(sigset_t *set, int sig)
{
unsigned s = sig-1;
set->__bits[s/8/sizeof *set->__bits] |= 1UL<<(s&8*sizeof *set->__bits-1);
}

static inline void __sigset_del(sigset_t *set, int sig)
{
unsigned s = sig-1;
set->__bits[s/8/sizeof *set->__bits] &= ~(1UL<<(s&8*sizeof *set->__bits-1));
}

#endif
10 changes: 10 additions & 0 deletions libc-top-half/musl/src/process/_Fork.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include "syscall.h"
#ifdef __wasilibc_unmodified_upstream
#else
Expand Down Expand Up @@ -56,6 +57,15 @@ pid_t _Fork(int copy_mem)
__thread_list_lock = 0;
libc.threads_minus_1 = 0;
if (libc.need_locks) libc.need_locks = -1;
#ifndef __wasilibc_unmodified_upstream
/* The child is a fresh instance as far as the host is concerned, so
* it has no signal callback registered even though the guest-side
* bookkeeping was inherited and claims otherwise. Without this the
* host discards every signal sent to the child. */
__sig_register_callback();
/* POSIX: the child's set of pending signals starts out empty. */
memset(&self->sigpending, 0, sizeof self->sigpending);
#endif
}
UNLOCK(__abort_lock);
__aio_atfork(!ret);
Expand Down
19 changes: 12 additions & 7 deletions libc-top-half/musl/src/signal/block.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
#include <wasi/api.h>
#endif
#include <signal.h>

#ifdef __wasilibc_unmodified_upstream
#else
extern volatile int __eintr_handler_lock[1];
#include "sigmask.h"
#endif

static const unsigned long all_mask[] = {
Expand Down Expand Up @@ -42,8 +41,9 @@ void __block_all_sigs(void *set)
#ifdef __wasilibc_unmodified_upstream
__syscall(SYS_rt_sigprocmask, SIG_BLOCK, &all_mask, set, _NSIG/8);
#else
a_store(__eintr_handler_lock, 1);
__wasi_callback_signal("__wasm_signal_blocked");
pthread_t self = __pthread_self();
if (set) __sigset_copy((sigset_t *)set, &self->sigmask);
__sigset_or(&self->sigmask, all_mask);
#endif
}

Expand All @@ -52,8 +52,9 @@ void __block_app_sigs(void *set)
#ifdef __wasilibc_unmodified_upstream
__syscall(SYS_rt_sigprocmask, SIG_BLOCK, &app_mask, set, _NSIG/8);
#else
a_store(__eintr_handler_lock, 1);
__wasi_callback_signal("__wasm_signal_blocked");
pthread_t self = __pthread_self();
if (set) __sigset_copy((sigset_t *)set, &self->sigmask);
__sigset_or(&self->sigmask, app_mask);
#endif
}

Expand All @@ -62,6 +63,10 @@ void __restore_sigs(void *set)
#ifdef __wasilibc_unmodified_upstream
__syscall(SYS_rt_sigprocmask, SIG_SETMASK, set, 0, _NSIG/8);
#else
__wasi_callback_signal("__wasm_signal");
pthread_t self = __pthread_self();
if (set) __sigset_copy(&self->sigmask, (const sigset_t *)set);
/* Restoring can unblock, and nothing else will come along to deliver:
* WASIX only delivers signals at syscall boundaries. */
__sig_deliver_pending();
#endif
}
115 changes: 106 additions & 9 deletions libc-top-half/musl/src/signal/sigaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "libc.h"
#include "lock.h"
#include "ksigaction.h"
#include "sigmask.h"

static int unmask_done;
static unsigned long handler_set[_NSIG/(8*sizeof(long))];
Expand Down Expand Up @@ -178,23 +179,119 @@ volatile int __eintr_valid_flag;

#ifdef __wasilibc_unmodified_upstream
#else
__attribute__((export_name("__wasm_signal")))
void __wasm_signal(int sig) {
if (sig-32U < 3 || sig-1U >= _NSIG-1) {
return;
}
/* Runs one delivery of `sig`, with the handler's mask installed for the
* duration per POSIX: sa_mask, plus `sig` itself unless SA_NODEFER. */
static void dispatch_signal(int sig) {
LOCK(__eintr_handler_lock);
struct k_sigaction ksa = __eintr_handler_callbacks[sig];
if (ksa.handler != 0 && (ksa.flags & SA_RESETHAND)) {
/* POSIX resets the disposition to SIG_DFL on entry to a handler
* installed with SA_RESETHAND, so a signal re-raised from inside
* the handler takes the default action instead of re-entering the
* handler and recursing until the stack is exhausted. */
struct k_sigaction reset;
memset(&reset, 0, sizeof reset);
__eintr_handler_callbacks[sig] = reset;
}
UNLOCK(__eintr_handler_lock);

pthread_t self = __pthread_self();
sigset_t saved = self->sigmask;

if (ksa.handler != 0) {
ksa.handler(sig);
__sigset_or(&self->sigmask, ksa.mask);
if (!(ksa.flags & SA_NODEFER)) {
__sigset_add(&self->sigmask, sig);
}

if (ksa.flags & SA_SIGINFO) {
/* A handler installed through `sa_sigaction` takes three
* arguments. On wasm the argument count is part of the
* function type, so calling it through the one-argument
* `sa_handler` signature traps with "indirect call type
* mismatch" instead of silently ignoring the extra
* arguments the way native ABIs do. Dispatch through a
* matching signature instead.
*
* The signal is delivered by the host rather than raised
* from a faulting instruction, so there is no machine
* context to hand over; `ucontext` is null and `siginfo_t`
* carries just the fields the host can attest to. */
union {
void (*plain)(int);
void (*with_info)(int, siginfo_t *, void *);
} dispatch = { .plain = ksa.handler };
siginfo_t si;
memset(&si, 0, sizeof si);
si.si_signo = sig;
si.si_code = SI_USER;
dispatch.with_info(sig, &si, NULL);
} else {
ksa.handler(sig);
}
} else {
unsigned long set[_NSIG/(8*sizeof(long))];
__block_all_sigs(&set);
/* The default actions are not interruptible. sigfillset() covers
* every application signal and deliberately leaves the libc
* internal ones (SIGTIMER/SIGCANCEL/SIGSYNCCALL) alone. */
sigset_t all;
sigfillset(&all);
__sigset_or(&self->sigmask, &all);
default_handler(sig);
__restore_sigs(&set);
}

self->sigmask = saved;
}

void __sig_deliver_pending(void) {
pthread_t self = __pthread_self();
for (;;) {
int sig = 0;
for (int i = 1; i < _NSIG; i++) {
if (i-32U < 3) {
continue;
}
if (__sigset_test(&self->sigpending, i) &&
!__sigset_test(&self->sigmask, i)) {
sig = i;
break;
}
}
if (!sig) {
return;
}
__sigset_del(&self->sigpending, sig);
/* Iterative, not recursive: a handler that re-raises its own signal
* has that delivery deferred and picked up by the next turn of this
* loop, so the handler runs again sequentially rather than nesting. */
dispatch_signal(sig);
}
}

void __sig_register_callback(void) {
/* Unconditional, unlike the a_cas() guards elsewhere: the host tracks the
* registration per instance while __eintr_callback_registered is ordinary
* memory, so after a fork the child looks registered but the host has no
* callback for it and silently eats every signal. */
a_store(&__eintr_callback_registered, 1);
__wasi_callback_signal("__wasm_signal");
}

__attribute__((export_name("__wasm_signal")))
void __wasm_signal(int sig) {
if (sig-32U < 3 || sig-1U >= _NSIG-1) {
return;
}

pthread_t self = __pthread_self();
if (__sigset_test(&self->sigmask, sig)) {
/* Blocked: hold it until something unblocks it. Standard signals do
* not queue, so a repeat while blocked collapses into this one. */
__sigset_add(&self->sigpending, sig);
return;
}

dispatch_signal(sig);
__sig_deliver_pending();
}
#endif

Expand Down
12 changes: 11 additions & 1 deletion libc-top-half/musl/src/signal/sigpending.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,23 @@
#include <errno.h>
#ifdef __wasilibc_unmodified_upstream
#include "syscall.h"
#else
#include <wasi/api.h>
#include "pthread_impl.h"
#include "sigmask.h"
#endif

int sigpending(sigset_t *set)
{
#ifdef __wasilibc_unmodified_upstream
return syscall(SYS_rt_sigpending, set, _NSIG/8);
#else
return EINVAL;
/* A signal the host has queued but not yet handed over is not in our
* pending set yet. Delivery happens at syscall boundaries, so yielding
* flushes the queue into it (blocked ones land in sigpending, deliverable
* ones run their handlers) and makes the answer current. */
(void)__wasi_sched_yield();
__sigset_copy(set, &__pthread_self()->sigpending);
return 0;
#endif
}
7 changes: 7 additions & 0 deletions libc-top-half/musl/src/thread/pthread_create.c
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,13 @@ int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict att
new->robust_list.head = &new->robust_list.head;
new->canary = self->canary;
new->sysinfo = self->sysinfo;
#ifndef __wasilibc_unmodified_upstream
/* POSIX: a new thread inherits the creator's signal mask and starts with
* nothing pending. Upstream gets this from clone(); the mask lives in the
* thread structure here, so copy it across explicitly. */
new->sigmask = self->sigmask;
memset(&new->sigpending, 0, sizeof new->sigpending);
#endif

/* Setup argument structure for the new thread on its stack.
* It's safe to access from the caller only until the thread
Expand Down
Loading
Loading