mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
624573207e
This change doubles the performance of thread spawning. That's thanks to our new stack manager, which allows us to avoid zeroing stacks. It gives us 15µs spawns rather than 30µs spawns on Linux. Also, pthread_exit() is faster now, since it doesn't need to acquire the pthread GIL. On NetBSD, that helps us avoid allocating too many semaphores. Even if that happens we're now able to survive semaphores running out and even memory running out, when allocating *NSYNC waiter objects. I found a lot more rare bugs in the POSIX threads runtime that could cause things to crash, if you've got dozens of threads all spawning and joining dozens of threads. I want cosmo to be world class production worthy for 2025 so happy holidays all
749 lines
27 KiB
C
749 lines
27 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ Permission to use, copy, modify, and/or 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. │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/sysv/consts/sig.h"
|
|
#include "ape/sections.internal.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/calls/internal.h"
|
|
#include "libc/calls/sig.internal.h"
|
|
#include "libc/calls/state.internal.h"
|
|
#include "libc/calls/struct/sigaction.h"
|
|
#include "libc/calls/struct/siginfo.h"
|
|
#include "libc/calls/struct/sigset.internal.h"
|
|
#include "libc/calls/struct/ucontext.internal.h"
|
|
#include "libc/calls/syscall_support-nt.internal.h"
|
|
#include "libc/calls/ucontext.h"
|
|
#include "libc/dce.h"
|
|
#include "libc/errno.h"
|
|
#include "libc/intrin/atomic.h"
|
|
#include "libc/intrin/bsf.h"
|
|
#include "libc/intrin/describebacktrace.h"
|
|
#include "libc/intrin/dll.h"
|
|
#include "libc/intrin/maps.h"
|
|
#include "libc/intrin/nomultics.h"
|
|
#include "libc/intrin/strace.h"
|
|
#include "libc/intrin/weaken.h"
|
|
#include "libc/nt/console.h"
|
|
#include "libc/nt/enum/context.h"
|
|
#include "libc/nt/enum/exceptionhandleractions.h"
|
|
#include "libc/nt/enum/processcreationflags.h"
|
|
#include "libc/nt/enum/signal.h"
|
|
#include "libc/nt/enum/status.h"
|
|
#include "libc/nt/events.h"
|
|
#include "libc/nt/runtime.h"
|
|
#include "libc/nt/signals.h"
|
|
#include "libc/nt/struct/ntexceptionpointers.h"
|
|
#include "libc/nt/synchronization.h"
|
|
#include "libc/nt/thread.h"
|
|
#include "libc/runtime/internal.h"
|
|
#include "libc/runtime/symbols.internal.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/sa.h"
|
|
#include "libc/sysv/consts/sicode.h"
|
|
#include "libc/sysv/consts/ss.h"
|
|
#include "libc/sysv/consts/termios.h"
|
|
#include "libc/thread/posixthread.internal.h"
|
|
#ifdef __x86_64__
|
|
|
|
/**
|
|
* @fileoverview Cosmopolitan Signals for Windows.
|
|
*/
|
|
|
|
#define STKSZ 65536
|
|
|
|
struct SignalFrame {
|
|
unsigned rva;
|
|
unsigned flags;
|
|
siginfo_t si;
|
|
ucontext_t ctx;
|
|
};
|
|
|
|
extern pthread_mutex_t __sig_worker_lock;
|
|
|
|
static textwindows bool __sig_ignored_by_default(int sig) {
|
|
return sig == SIGURG || //
|
|
sig == SIGCONT || //
|
|
sig == SIGCHLD || //
|
|
sig == SIGWINCH;
|
|
}
|
|
|
|
textwindows bool __sig_ignored(int sig) {
|
|
return __sighandrvas[sig] == (intptr_t)SIG_IGN ||
|
|
(__sighandrvas[sig] == (intptr_t)SIG_DFL &&
|
|
__sig_ignored_by_default(sig));
|
|
}
|
|
|
|
textwindows void __sig_delete(int sig) {
|
|
struct Dll *e;
|
|
atomic_fetch_and_explicit(__sig.process, ~(1ull << (sig - 1)),
|
|
memory_order_relaxed);
|
|
_pthread_lock();
|
|
for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e))
|
|
atomic_fetch_and_explicit(&POSIXTHREAD_CONTAINER(e)->tib->tib_sigpending,
|
|
~(1ull << (sig - 1)), memory_order_relaxed);
|
|
_pthread_unlock();
|
|
}
|
|
|
|
static textwindows int __sig_getter(atomic_ulong *sigs, sigset_t masked) {
|
|
int sig;
|
|
sigset_t bit, pending, deliverable;
|
|
for (;;) {
|
|
pending = atomic_load_explicit(sigs, memory_order_acquire);
|
|
if ((deliverable = pending & ~masked)) {
|
|
sig = bsfl(deliverable) + 1;
|
|
bit = 1ull << (sig - 1);
|
|
if (atomic_fetch_and_explicit(sigs, ~bit, memory_order_acq_rel) & bit)
|
|
return sig;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
textwindows int __sig_get(sigset_t masked) {
|
|
int sig;
|
|
if (!(sig = __sig_getter(&__get_tls()->tib_sigpending, masked)))
|
|
sig = __sig_getter(__sig.process, masked);
|
|
return sig;
|
|
}
|
|
|
|
static textwindows bool __sig_should_use_altstack(unsigned flags,
|
|
struct CosmoTib *tib) {
|
|
if (!(flags & SA_ONSTACK))
|
|
return false; // signal handler didn't enable it
|
|
if (!tib->tib_sigstack_size)
|
|
return false; // sigaltstack() wasn't installed on this thread
|
|
if (tib->tib_sigstack_flags & SS_DISABLE)
|
|
return false; // sigaltstack() on this thread was disabled by user
|
|
char *bp = __builtin_frame_address(0);
|
|
if (tib->tib_sigstack_addr <= bp &&
|
|
bp <= tib->tib_sigstack_addr + tib->tib_sigstack_size)
|
|
return false; // we're already on the alternate stack
|
|
return true;
|
|
}
|
|
|
|
static textwindows wontreturn void __sig_terminate(int sig) {
|
|
TerminateThisProcess(sig);
|
|
}
|
|
|
|
textwindows static bool __sig_wake(struct PosixThread *pt, int sig) {
|
|
atomic_int *blocker;
|
|
blocker = atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
|
|
if (!blocker)
|
|
return false;
|
|
// threads can create semaphores on an as-needed basis
|
|
if (blocker == PT_BLOCKER_EVENT) {
|
|
STRACE("%G set %d's event object", sig, _pthread_tid(pt));
|
|
SetEvent(pt->pt_event);
|
|
return !!atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
|
|
}
|
|
// all other blocking ops that aren't overlap should use futexes
|
|
// we force restartable futexes to churn by waking w/o releasing
|
|
STRACE("%G waking %d's futex", sig, _pthread_tid(pt));
|
|
WakeByAddressSingle(blocker);
|
|
return !!atomic_load_explicit(&pt->pt_blocker, memory_order_acquire);
|
|
}
|
|
|
|
textwindows static bool __sig_start(struct PosixThread *pt, int sig,
|
|
unsigned *rva, unsigned *flags) {
|
|
*rva = __sighandrvas[sig];
|
|
*flags = __sighandflags[sig];
|
|
if (*rva == (intptr_t)SIG_IGN ||
|
|
(*rva == (intptr_t)SIG_DFL && __sig_ignored_by_default(sig))) {
|
|
STRACE("ignoring %G", sig);
|
|
return false;
|
|
}
|
|
if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
|
|
(1ull << (sig - 1))) {
|
|
STRACE("enqueing %G on %d", sig, _pthread_tid(pt));
|
|
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
|
|
memory_order_relaxed);
|
|
__sig_wake(pt, sig);
|
|
return false;
|
|
}
|
|
if (*rva == (intptr_t)SIG_DFL) {
|
|
STRACE("terminating on %G due to no handler", sig);
|
|
__sig_terminate(sig);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
textwindows static sigaction_f __sig_handler(unsigned rva) {
|
|
atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed);
|
|
return (sigaction_f)(__executable_start + rva);
|
|
}
|
|
|
|
textwindows int __sig_raise(volatile int sig, int sic) {
|
|
|
|
// bitset of kinds of handlers called
|
|
volatile int handler_was_called = 0;
|
|
|
|
// loop over pending signals
|
|
ucontext_t ctx;
|
|
getcontext(&ctx);
|
|
if (!sig) {
|
|
if ((sig = __sig_get(ctx.uc_sigmask))) {
|
|
sic = SI_KERNEL;
|
|
} else {
|
|
return handler_was_called;
|
|
}
|
|
}
|
|
|
|
// process signal(s)
|
|
unsigned rva, flags;
|
|
struct PosixThread *pt = _pthread_self();
|
|
if (__sig_start(pt, sig, &rva, &flags)) {
|
|
|
|
if (flags & SA_RESETHAND) {
|
|
STRACE("resetting %G handler", sig);
|
|
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
|
|
}
|
|
|
|
// update the signal mask in preparation for signal handller
|
|
sigset_t blocksigs = __sighandmask[sig];
|
|
if (!(flags & SA_NODEFER))
|
|
blocksigs |= 1ull << (sig - 1);
|
|
ctx.uc_sigmask = atomic_fetch_or_explicit(&pt->tib->tib_sigmask, blocksigs,
|
|
memory_order_acquire);
|
|
|
|
// call the user's signal handler
|
|
char ssbuf[128];
|
|
siginfo_t si = {.si_signo = sig, .si_code = sic};
|
|
STRACE("__sig_raise(%G, %t) mask %s", sig, __sig_handler(rva),
|
|
_DescribeSigset(ssbuf, 0, (sigset_t *)&pt->tib->tib_sigmask));
|
|
__sig_handler(rva)(sig, &si, &ctx);
|
|
|
|
// record this handler
|
|
if (flags & SA_RESTART) {
|
|
handler_was_called |= SIG_HANDLED_SA_RESTART;
|
|
} else {
|
|
handler_was_called |= SIG_HANDLED_NO_RESTART;
|
|
}
|
|
}
|
|
|
|
// restore sigmask
|
|
// loop back to top
|
|
// jump where handler says
|
|
sig = 0;
|
|
return setcontext(&ctx);
|
|
}
|
|
|
|
textwindows int __sig_relay(int sig, int sic, sigset_t waitmask) {
|
|
sigset_t m;
|
|
int handler_was_called;
|
|
m = atomic_exchange_explicit(&__get_tls()->tib_sigmask, waitmask,
|
|
memory_order_acquire);
|
|
handler_was_called = __sig_raise(sig, SI_KERNEL);
|
|
atomic_store_explicit(&__get_tls()->tib_sigmask, m, memory_order_release);
|
|
return handler_was_called;
|
|
}
|
|
|
|
// the user's signal handler callback is wrapped with this trampoline
|
|
static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
|
|
int sig = sf->si.si_signo;
|
|
struct CosmoTib *tib = __get_tls();
|
|
struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread;
|
|
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
|
|
for (;;) {
|
|
|
|
// update the signal mask in preparation for signal handler
|
|
sigset_t blocksigs = __sighandmask[sig];
|
|
if (!(sf->flags & SA_NODEFER))
|
|
blocksigs |= 1ull << (sig - 1);
|
|
sf->ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs,
|
|
memory_order_acquire);
|
|
|
|
// call the user's signal handler
|
|
char ssbuf[2][128];
|
|
STRACE("__sig_tramp(%G, %t) mask %s → %s", sig, __sig_handler(sf->rva),
|
|
_DescribeSigset(ssbuf[0], 0, &sf->ctx.uc_sigmask),
|
|
_DescribeSigset(ssbuf[1], 0, (sigset_t *)&tib->tib_sigmask));
|
|
__sig_handler(sf->rva)(sig, &sf->si, &sf->ctx);
|
|
|
|
// restore the signal mask that was used by the interrupted code
|
|
// this may have been modified by the signal handler in the callback
|
|
atomic_store_explicit(&tib->tib_sigmask, sf->ctx.uc_sigmask,
|
|
memory_order_release);
|
|
|
|
// jump back into original code if there aren't any pending signals
|
|
do {
|
|
if (!(sig = __sig_get(sf->ctx.uc_sigmask)))
|
|
__sig_restore(&sf->ctx);
|
|
} while (!__sig_start(pt, sig, &sf->rva, &sf->flags));
|
|
|
|
// tail recurse into another signal handler
|
|
sf->si.si_signo = sig;
|
|
sf->si.si_code = SI_KERNEL;
|
|
if (sf->flags & SA_RESETHAND) {
|
|
STRACE("resetting %G handler", sig);
|
|
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// sends signal to another specific thread which is ref'd
|
|
static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
|
|
unsigned rva = __sighandrvas[sig];
|
|
unsigned flags = __sighandflags[sig];
|
|
|
|
// do nothing if signal is ignored
|
|
if (rva == (intptr_t)SIG_IGN ||
|
|
(rva == (intptr_t)SIG_DFL && __sig_ignored_by_default(sig))) {
|
|
STRACE("ignoring %G", sig);
|
|
return 0;
|
|
}
|
|
|
|
// we can't preempt threads that masked sigs or are blocked on i/o
|
|
while ((atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
|
|
(1ull << (sig - 1)))) {
|
|
if (atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
|
|
memory_order_acq_rel) &
|
|
(1ull << (sig - 1)))
|
|
// we believe signal was already enqueued
|
|
return 0;
|
|
if (__sig_wake(pt, sig))
|
|
// we believe i/o routine will handle signal
|
|
return 0;
|
|
if (atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
|
|
(1ull << (sig - 1)))
|
|
// we believe ALLOW_SIGNALS will handle signal
|
|
return 0;
|
|
if (!(atomic_fetch_and_explicit(&pt->tib->tib_sigpending,
|
|
~(1ull << (sig - 1)),
|
|
memory_order_acq_rel) &
|
|
(1ull << (sig - 1))))
|
|
// we believe another thread sniped our signal
|
|
return 0;
|
|
break;
|
|
}
|
|
|
|
// avoid race conditions and deadlocks with thread suspend process
|
|
if (atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire)) {
|
|
// we believe another thread is asynchronously waking the mark
|
|
if (atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
|
|
memory_order_acq_rel) &
|
|
(1ull << (sig - 1)))
|
|
// we believe our signal is already being delivered
|
|
return 0;
|
|
if (atomic_load_explicit(&pt->pt_intoff, memory_order_acquire) ||
|
|
atomic_exchange_explicit(&pt->pt_intoff, 1, memory_order_acquire))
|
|
// we believe __sig_tramp will deliver our signal
|
|
return 0;
|
|
if (!(atomic_fetch_and_explicit(&pt->tib->tib_sigpending,
|
|
~(1ull << (sig - 1)),
|
|
memory_order_acq_rel) &
|
|
(1ull << (sig - 1))))
|
|
// we believe another thread sniped our signal
|
|
return 0;
|
|
}
|
|
|
|
// if there's no handler then killing a thread kills the process
|
|
if (rva == (intptr_t)SIG_DFL) {
|
|
STRACE("terminating on %G due to no handler", sig);
|
|
__sig_terminate(sig);
|
|
}
|
|
|
|
// take control of thread
|
|
// suspending the thread happens asynchronously
|
|
// however getting the context blocks until it's frozen
|
|
uintptr_t th = _pthread_syshand(pt);
|
|
if (SuspendThread(th) == -1u) {
|
|
STRACE("SuspendThread failed w/ %d", GetLastError());
|
|
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
|
|
return ESRCH;
|
|
}
|
|
struct NtContext nc;
|
|
nc.ContextFlags = kNtContextFull;
|
|
if (!GetThreadContext(th, &nc)) {
|
|
STRACE("GetThreadContext failed w/ %d", GetLastError());
|
|
ResumeThread(th);
|
|
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
|
|
return ESRCH;
|
|
}
|
|
|
|
// we can't preempt threads that masked sig or are blocked
|
|
// we can't preempt threads that are running in win32 code
|
|
// so we shall unblock the thread and let it signal itself
|
|
if (!((uintptr_t)__executable_start <= nc.Rip &&
|
|
nc.Rip < (uintptr_t)__privileged_start)) {
|
|
atomic_fetch_or_explicit(&pt->tib->tib_sigpending, 1ull << (sig - 1),
|
|
memory_order_relaxed);
|
|
ResumeThread(th);
|
|
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
|
|
__sig_wake(pt, sig);
|
|
return 0;
|
|
}
|
|
|
|
// preferring to live dangerously
|
|
// the thread will be signaled asynchronously
|
|
if (flags & SA_RESETHAND) {
|
|
STRACE("resetting %G handler", sig);
|
|
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
|
|
}
|
|
|
|
// inject call to trampoline function into thread
|
|
uintptr_t sp;
|
|
if (__sig_should_use_altstack(flags, pt->tib)) {
|
|
sp = (uintptr_t)pt->tib->tib_sigstack_addr + pt->tib->tib_sigstack_size;
|
|
} else {
|
|
sp = nc.Rsp;
|
|
}
|
|
sp -= sizeof(struct SignalFrame);
|
|
sp &= -16;
|
|
struct SignalFrame *sf = (struct SignalFrame *)sp;
|
|
_ntcontext2linux(&sf->ctx, &nc);
|
|
bzero(&sf->si, sizeof(sf->si));
|
|
sf->rva = rva;
|
|
sf->flags = flags;
|
|
sf->si.si_code = sic;
|
|
sf->si.si_signo = sig;
|
|
*(uintptr_t *)(sp -= sizeof(uintptr_t)) = nc.Rip;
|
|
nc.Rip = (intptr_t)__sig_tramp;
|
|
nc.Rdi = (intptr_t)sf;
|
|
nc.Rsp = sp;
|
|
if (!SetThreadContext(th, &nc)) {
|
|
STRACE("SetThreadContext failed w/ %d", GetLastError());
|
|
atomic_store_explicit(&pt->pt_intoff, 0, memory_order_release);
|
|
return ESRCH;
|
|
}
|
|
ResumeThread(th);
|
|
__sig_wake(pt, sig);
|
|
return 0;
|
|
}
|
|
|
|
// sends signal to another specific thread
|
|
textwindows int __sig_kill(struct PosixThread *pt, int sig, int sic) {
|
|
int rc;
|
|
BLOCK_SIGNALS;
|
|
rc = __sig_killer(pt, sig, sic);
|
|
ALLOW_SIGNALS;
|
|
return rc;
|
|
}
|
|
|
|
// sends signal to any other thread
|
|
// this should only be called by non-posix threads
|
|
textwindows void __sig_generate(int sig, int sic) {
|
|
struct Dll *e;
|
|
struct PosixThread *pt, *mark = 0;
|
|
if (__sig_ignored(sig)) {
|
|
STRACE("ignoring %G", sig);
|
|
return;
|
|
}
|
|
if (__sighandrvas[sig] == (intptr_t)SIG_DFL) {
|
|
STRACE("terminating on %G due to no handler", sig);
|
|
__sig_terminate(sig);
|
|
}
|
|
if (atomic_load_explicit(__sig.process, memory_order_acquire) &
|
|
(1ull << (sig - 1))) {
|
|
return;
|
|
}
|
|
_pthread_lock();
|
|
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
|
|
pt = POSIXTHREAD_CONTAINER(e);
|
|
// we don't want to signal ourself
|
|
if (pt == _pthread_self())
|
|
continue;
|
|
// we don't want to signal a thread that isn't running
|
|
if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >=
|
|
kPosixThreadTerminated) {
|
|
continue;
|
|
}
|
|
// choose this thread if it isn't masking sig
|
|
if (!(atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire) &
|
|
(1ull << (sig - 1)))) {
|
|
_pthread_ref(pt);
|
|
mark = pt;
|
|
break;
|
|
}
|
|
// if a thread is blocking then we check to see if it's planning
|
|
// to unblock our sig once the wait operation is completed; when
|
|
// that's the case we can cancel the thread's i/o to deliver sig
|
|
if (atomic_load_explicit(&pt->pt_blocker, memory_order_acquire) &&
|
|
!(pt->pt_blkmask & (1ull << (sig - 1)))) {
|
|
_pthread_ref(pt);
|
|
mark = pt;
|
|
break;
|
|
}
|
|
}
|
|
_pthread_unlock();
|
|
if (mark) {
|
|
// no lock needed since current thread is nameless and formless
|
|
__sig_killer(mark, sig, sic);
|
|
_pthread_unref(mark);
|
|
} else {
|
|
atomic_fetch_or_explicit(__sig.process, 1ull << (sig - 1),
|
|
memory_order_relaxed);
|
|
}
|
|
}
|
|
|
|
static textwindows char *__sig_stpcpy(char *d, const char *s) {
|
|
size_t i;
|
|
for (i = 0;; ++i)
|
|
if (!(d[i] = s[i]))
|
|
return d + i;
|
|
}
|
|
|
|
static textwindows wontreturn void __sig_death(int sig, const char *thing) {
|
|
#ifndef TINY
|
|
intptr_t hStderr;
|
|
char sigbuf[21], s[128], *p;
|
|
hStderr = GetStdHandle(kNtStdErrorHandle);
|
|
p = __sig_stpcpy(s, "Terminating on ");
|
|
p = __sig_stpcpy(p, thing);
|
|
p = __sig_stpcpy(p, strsignal_r(sig, sigbuf));
|
|
p = __sig_stpcpy(p,
|
|
". Pass --strace and/or ShowCrashReports() for details.\n");
|
|
WriteFile(hStderr, s, p - s, 0, 0);
|
|
#endif
|
|
__sig_terminate(sig);
|
|
}
|
|
|
|
static textwindows void __sig_unmaskable(struct NtExceptionPointers *ep,
|
|
int code, int sig,
|
|
struct CosmoTib *tib) {
|
|
|
|
// log vital crash information reliably for --strace before doing much
|
|
// we don't print this without the flag since raw numbers scare people
|
|
// this needs at least one page of stack memory in order to get logged
|
|
// otherwise it'll print a warning message about the lack of stack mem
|
|
STRACE("win32 vectored exception 0x%08Xu raising %G "
|
|
"cosmoaddr2line %s %lx %s",
|
|
ep->ExceptionRecord->ExceptionCode, sig,
|
|
_weaken(FindDebugBinary) ? _weaken(FindDebugBinary)()
|
|
: program_invocation_name,
|
|
ep->ContextRecord->Rip,
|
|
DescribeBacktrace((struct StackFrame *)ep->ContextRecord->Rbp));
|
|
|
|
// if the user didn't install a signal handler for this unmaskable
|
|
// exception, then print a friendly helpful hint message to stderr
|
|
unsigned rva = __sighandrvas[sig];
|
|
if (rva == (intptr_t)SIG_DFL || rva == (intptr_t)SIG_IGN)
|
|
__sig_death(sig, "uncaught ");
|
|
|
|
// if this signal handler is configured to auto-reset to the default
|
|
// then that reset needs to happen before the user handler is called
|
|
unsigned flags = __sighandflags[sig];
|
|
if (flags & SA_RESETHAND) {
|
|
STRACE("resetting %G handler", sig);
|
|
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
|
|
}
|
|
|
|
// determine the true memory address at which fault occurred
|
|
// if this is a stack overflow then reapply guard protection
|
|
void *si_addr;
|
|
if (ep->ExceptionRecord->ExceptionCode == kNtSignalGuardPage) {
|
|
si_addr = (void *)ep->ExceptionRecord->ExceptionInformation[1];
|
|
} else {
|
|
si_addr = ep->ExceptionRecord->ExceptionAddress;
|
|
}
|
|
|
|
// call the user signal handler
|
|
// and a modifiable view of the faulting code's cpu state
|
|
// temporarily replace signal mask while calling crash handler
|
|
// abort process if sig is already blocked to avoid crash loop
|
|
// note ucontext_t is a hefty data structures on top of NtContext
|
|
ucontext_t ctx = {0};
|
|
siginfo_t si = {.si_signo = sig, .si_code = code, .si_addr = si_addr};
|
|
_ntcontext2linux(&ctx, ep->ContextRecord);
|
|
sigset_t blocksigs = __sighandmask[sig];
|
|
if (!(flags & SA_NODEFER))
|
|
blocksigs |= 1ull << (sig - 1);
|
|
ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs,
|
|
memory_order_acquire);
|
|
if (ctx.uc_sigmask & (1ull << (sig - 1))) {
|
|
__sig_death(sig, "masked ");
|
|
__sig_terminate(sig);
|
|
}
|
|
__sig_handler(rva)(sig, &si, &ctx);
|
|
atomic_store_explicit(&tib->tib_sigmask, ctx.uc_sigmask,
|
|
memory_order_release);
|
|
_ntlinux2context(ep->ContextRecord, &ctx);
|
|
}
|
|
|
|
void __stack_call(struct NtExceptionPointers *, int, int, struct CosmoTib *,
|
|
void (*)(struct NtExceptionPointers *, int, int,
|
|
struct CosmoTib *),
|
|
void *);
|
|
|
|
// abashed the devil stood
|
|
// and felt how awful goodness is
|
|
__msabi dontinstrument unsigned __sig_crash(struct NtExceptionPointers *ep) {
|
|
|
|
// translate win32 to unix si_signo and si_code
|
|
int code, sig = __sig_crash_sig(ep->ExceptionRecord->ExceptionCode, &code);
|
|
|
|
// advance the instruction pointer to skip over debugger breakpoints
|
|
// this behavior is consistent with how unix kernels are implemented
|
|
if (sig == SIGTRAP) {
|
|
ep->ContextRecord->Rip++;
|
|
if (__sig_ignored(sig))
|
|
return kNtExceptionContinueExecution;
|
|
}
|
|
|
|
// win32 stack overflow detection executes INSIDE the guard page
|
|
// thus switch to the alternate signal stack as soon as possible
|
|
struct CosmoTib *tib = __get_tls();
|
|
unsigned flags = __sighandflags[sig];
|
|
if (__sig_should_use_altstack(flags, tib)) {
|
|
__stack_call(ep, code, sig, tib, __sig_unmaskable,
|
|
tib->tib_sigstack_addr + tib->tib_sigstack_size);
|
|
} else {
|
|
__sig_unmaskable(ep, code, sig, tib);
|
|
}
|
|
|
|
// resume running user program
|
|
// hopefully the user fixed the cpu state
|
|
// otherwise the crash will keep happening
|
|
return kNtExceptionContinueExecution;
|
|
}
|
|
|
|
static textwindows int __sig_console_sig(uint32_t dwCtrlType) {
|
|
switch (dwCtrlType) {
|
|
case kNtCtrlCEvent:
|
|
return SIGINT;
|
|
case kNtCtrlBreakEvent:
|
|
return SIGQUIT;
|
|
case kNtCtrlCloseEvent:
|
|
case kNtCtrlLogoffEvent: // only received by services
|
|
case kNtCtrlShutdownEvent: // only received by services
|
|
return SIGHUP;
|
|
default:
|
|
return SIGSTKFLT;
|
|
}
|
|
}
|
|
|
|
static textwindows int __sig_console_char(uint32_t dwCtrlType) {
|
|
switch (dwCtrlType) {
|
|
case kNtCtrlCEvent:
|
|
return __ttyconf.vintr;
|
|
case kNtCtrlBreakEvent:
|
|
return __ttyconf.vquit;
|
|
default:
|
|
return _POSIX_VDISABLE;
|
|
}
|
|
}
|
|
|
|
__msabi textwindows dontinstrument bool32 __sig_console(uint32_t dwCtrlType) {
|
|
// win32 launches a thread to deliver ctrl-c and ctrl-break when typed
|
|
// it only happens when kNtEnableProcessedInput is in play on console.
|
|
// otherwise we need to wait until read-nt.c discovers that keystroke.
|
|
struct CosmoTib tls;
|
|
__bootstrap_tls(&tls, __builtin_frame_address(0));
|
|
|
|
// ensure that ^C or ^\ gets printed to console appropriately
|
|
if (_weaken(EchoConsoleNt)) {
|
|
char c;
|
|
if ((c = __sig_console_char(dwCtrlType)) != _POSIX_VDISABLE)
|
|
_weaken(EchoConsoleNt)(&c, sizeof(c), false);
|
|
}
|
|
|
|
// take control of random thread and inject call to signal handler
|
|
__sig_generate(__sig_console_sig(dwCtrlType), SI_KERNEL);
|
|
return true;
|
|
}
|
|
|
|
// returns 0 if no signal handlers were called, otherwise a bitmask
|
|
// consisting of `1` which means a signal handler was invoked which
|
|
// didn't have the SA_RESTART flag, and `2`, which means SA_RESTART
|
|
// handlers were called (or `3` if both were the case).
|
|
textwindows int __sig_check(void) {
|
|
int sig, res = 0;
|
|
while ((sig = __sig_get(atomic_load_explicit(&__get_tls()->tib_sigmask,
|
|
memory_order_acquire))))
|
|
res |= __sig_raise(sig, SI_KERNEL);
|
|
return res;
|
|
}
|
|
|
|
// background thread for delivering inter-process signals asynchronously
|
|
// this checks for undelivered process-wide signals, once per scheduling
|
|
// quantum, which on windows should be every ~15ms or so, unless somehow
|
|
// the process was tuned to have more fine-grained event timing. we want
|
|
// signals to happen faster when possible; that happens when cancelation
|
|
// points, e.g. read need to wait on i/o; they too check for new signals
|
|
textwindows dontinstrument static uint32_t __sig_worker(void *arg) {
|
|
struct CosmoTib tls;
|
|
__bootstrap_tls(&tls, __builtin_frame_address(0));
|
|
char *sp = __builtin_frame_address(0);
|
|
__maps_track((char *)(((uintptr_t)sp + __pagesize - 1) & -__pagesize) - STKSZ,
|
|
STKSZ);
|
|
for (;;) {
|
|
pthread_mutex_lock(&__sig_worker_lock);
|
|
|
|
// dequeue all pending signals and fire them off. if there's no
|
|
// thread that can handle them then __sig_generate will requeue
|
|
// those signals back to __sig.process; hence the need for xchg
|
|
unsigned long sigs =
|
|
atomic_exchange_explicit(__sig.process, 0, memory_order_acq_rel);
|
|
while (sigs) {
|
|
int sig = bsfl(sigs) + 1;
|
|
sigs &= ~(1ull << (sig - 1));
|
|
__sig_generate(sig, SI_KERNEL);
|
|
}
|
|
|
|
// unblock stalled asynchronous signals in threads
|
|
struct PosixThread *mark;
|
|
for (;;) {
|
|
sigset_t pending, mask;
|
|
mark = 0;
|
|
_pthread_lock();
|
|
for (struct Dll *e = dll_first(_pthread_list); e;
|
|
e = dll_next(_pthread_list, e)) {
|
|
struct PosixThread *pt = POSIXTHREAD_CONTAINER(e);
|
|
if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) >=
|
|
kPosixThreadTerminated)
|
|
break;
|
|
pending = atomic_load_explicit(&pt->tib->tib_sigpending,
|
|
memory_order_acquire);
|
|
mask =
|
|
atomic_load_explicit(&pt->tib->tib_sigmask, memory_order_acquire);
|
|
if (pending & ~mask) {
|
|
_pthread_ref(pt);
|
|
mark = pt;
|
|
break;
|
|
}
|
|
}
|
|
_pthread_unlock();
|
|
if (!mark)
|
|
break;
|
|
while (!atomic_compare_exchange_weak_explicit(
|
|
&mark->tib->tib_sigpending, &pending, pending & ~mask,
|
|
memory_order_acq_rel, memory_order_relaxed)) {
|
|
}
|
|
while ((pending = pending & ~mask)) {
|
|
int sig = bsfl(pending) + 1;
|
|
pending &= ~(1ull << (sig - 1));
|
|
__sig_killer(mark, sig, SI_KERNEL);
|
|
}
|
|
}
|
|
|
|
// wait until next scheduler quantum
|
|
pthread_mutex_unlock(&__sig_worker_lock);
|
|
Sleep(POLL_INTERVAL_MS);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
__attribute__((__constructor__(10))) textstartup void __sig_init(void) {
|
|
if (!IsWindows())
|
|
return;
|
|
AddVectoredExceptionHandler(true, (void *)__sig_crash);
|
|
SetConsoleCtrlHandler((void *)__sig_console, true);
|
|
CreateThread(0, STKSZ, __sig_worker, 0, kNtStackSizeParamIsAReservation, 0);
|
|
}
|
|
|
|
#endif /* __x86_64__ */
|