diff --git a/libc/calls/__sig2.c b/libc/calls/__sig2.c index f59dbfecb..245786e20 100644 --- a/libc/calls/__sig2.c +++ b/libc/calls/__sig2.c @@ -25,10 +25,17 @@ #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/siginfo.h" #include "libc/calls/struct/sigset.h" +#include "libc/calls/struct/ucontext.internal.h" +#include "libc/calls/ucontext.h" #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" +#include "libc/log/libfatal.internal.h" #include "libc/macros.internal.h" +#include "libc/nt/console.h" +#include "libc/nt/enum/context.h" #include "libc/nt/runtime.h" +#include "libc/nt/struct/context.h" +#include "libc/nt/thread.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" @@ -39,6 +46,28 @@ #ifdef __x86_64__ +/** + * Returns true if signal default action is to end process. + */ +textwindows bool __sig_is_fatal(int sig) { + return !(sig == SIGURG || // + sig == SIGCHLD || // + sig == SIGWINCH); +} + +/** + * Returns true if signal is so fatal it should dump core. + */ +textwindows bool __sig_is_core(int sig) { + return sig == SIGSYS || // + sig == SIGBUS || // + sig == SIGSEGV || // + sig == SIGQUIT || // + sig == SIGTRAP || // + sig == SIGXCPU || // + sig == SIGXFSZ; +} + /** * Allocates piece of memory for storing pending signal. * @assume lock is held @@ -105,49 +134,66 @@ static textwindows struct Signal *__sig_remove(int sigops) { /** * Delivers signal to callback. - * @note called from main thread - * @return true if EINTR should be returned by caller + * + * @return true if `EINTR` should be raised */ -static bool __sig_deliver(int sigops, int sig, int si_code, ucontext_t *ctx) { - unsigned rva, flags; - siginfo_t info, *infop; - STRACE("delivering %G", sig); +bool __sig_deliver(int sigops, int sig, int sic, ucontext_t *ctx) { + unsigned rva = __sighandrvas[sig]; + unsigned flags = __sighandflags[sig]; - // enter the signal - rva = __sighandrvas[sig]; - flags = __sighandflags[sig]; - if ((~flags & SA_NODEFER) || (flags & SA_RESETHAND)) { - // by default we try to avoid reentering a signal handler. for - // example, if a sigsegv handler segfaults, then we'd want the - // second signal to just kill the process. doing this means we - // track state. that's bad if you want to longjmp() out of the - // signal handler. in that case you must use SA_NODEFER. - __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; - } - - // setup the somewhat expensive information args - // only if they're requested by the user in sigaction() + // generate expensive data if needed + ucontext_t uc; + siginfo_t info; + siginfo_t *infop; if (flags & SA_SIGINFO) { - bzero(&info, sizeof(info)); + __repstosb(&info, 0, sizeof(info)); info.si_signo = sig; - info.si_code = si_code; + info.si_code = sic; infop = &info; + if (!ctx) { + struct NtContext nc = {.ContextFlags = kNtContextAll}; + __repstosb(&uc, 0, sizeof(uc)); + GetThreadContext(GetCurrentThread(), &nc); + _ntcontext2linux(&uc, &nc); + ctx = &uc; + } } else { infop = 0; - ctx = 0; } - // handover control to user + // save the thread's signal mask + uint64_t oldmask; + if (__tls_enabled) { + oldmask = __get_tls()->tib_sigmask; + } else { + oldmask = __sig.sigmask; + } + if (ctx) { + ctx->uc_sigmask = (sigset_t){{oldmask}}; + } + + // mask the signal that's being handled whilst handling + if (!(flags & SA_NODEFER)) { + if (__tls_enabled) { + __get_tls()->tib_sigmask |= 1ull << (sig - 1); + } else { + __sig.sigmask |= 1ull << (sig - 1); + } + } + + STRACE("delivering %G", sig); ((sigaction_f)(__executable_start + rva))(sig, infop, ctx); - if ((~flags & SA_NODEFER) && (~flags & SA_RESETHAND)) { - // it's now safe to reenter the signal so we need to restore it. - // since sigaction() is @asyncsignalsafe we only restore it if the - // user didn't change it during the signal handler. we also don't - // need to do anything if this was a oneshot signal or nodefer. - if (__sighandrvas[sig] == (int32_t)(intptr_t)SIG_DFL) { - __sighandrvas[sig] = rva; - } + if (ctx) { + oldmask = ctx->uc_sigmask.__bits[0]; + } + if (__tls_enabled) { + __get_tls()->tib_sigmask = oldmask; + } else { + __sig.sigmask = oldmask; + } + if (flags & SA_RESETHAND) { + __sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL; } if (!(sigops & kSigOpRestartable)) { @@ -160,38 +206,23 @@ static bool __sig_deliver(int sigops, int sig, int si_code, ucontext_t *ctx) { } } -/** - * Returns true if signal default action is to end process. - */ -static textwindows bool __sig_is_fatal(int sig) { - return !(sig == SIGURG || // - sig == SIGCHLD || // - sig == SIGWINCH); -} - -/** - * Returns true if signal is so fatal it should dump core. - */ -static textwindows bool __sig_is_core(int sig) { - return sig == SIGSYS || // - sig == SIGBUS || // - sig == SIGSEGV || // - sig == SIGQUIT || // - sig == SIGTRAP || // - sig == SIGXCPU || // - sig == SIGXFSZ; -} - /** * Handles signal. - * @return true if signal was delivered + * @return true if `EINTR` should be raised */ -textwindows bool __sig_handle(int sigops, int sig, int si_code, - ucontext_t *ctx) { - bool delivered; +textwindows bool __sig_handle(int sigops, int sig, int sic, ucontext_t *ctx) { + if (__sig_is_masked(sig)) { + if (sigops & kSigOpUnmaskable) { + goto DefaultAction; + } + __sig_add(0, sig, sic); + return false; + } switch (__sighandrvas[sig]) { case (intptr_t)SIG_DFL: + DefaultAction: if (__sig_is_fatal(sig)) { + uint32_t cmode; intptr_t hStderr; const char *signame; char *end, sigbuf[21], output[22]; @@ -199,45 +230,18 @@ textwindows bool __sig_handle(int sigops, int sig, int si_code, STRACE("terminating due to uncaught %s", signame); if (__sig_is_core(sig)) { hStderr = GetStdHandle(kNtStdErrorHandle); - end = stpcpy(stpcpy(output, signame), "\n"); - WriteFile(hStderr, output, end - output, 0, 0); + if (GetConsoleMode(hStderr, &cmode)) { + end = stpcpy(stpcpy(output, signame), "\n"); + WriteFile(hStderr, output, end - output, 0, 0); + } } ExitProcess(sig); } // fallthrough case (intptr_t)SIG_IGN: - STRACE("ignoring %G", sig); - delivered = false; - break; + return false; default: - delivered = __sig_deliver(sigops, sig, si_code, ctx); - break; - } - return delivered; -} - -/** - * Handles signal immediately if not blocked. - * - * @param restartable is for functions like read() but not poll() - * @return true if EINTR should be returned by caller - * @return 1 if delivered, 0 if enqueued, otherwise -1 w/ errno - * @note called from main thread - * @threadsafe - */ -textwindows int __sig_raise(int sig, int si_code) { - if (1 <= sig && sig <= 64) { - if (!__sig_is_masked(sig)) { - ++__sig_count; - // TODO(jart): ucontext_t support - __sig_handle(false, sig, si_code, 0); - return 0; - } else { - STRACE("%G is masked", sig); - return __sig_add(gettid(), sig, si_code); - } - } else { - return einval(); + return __sig_deliver(sigops, sig, sic, ctx); } } @@ -319,7 +323,7 @@ textwindows void __sig_check_ignore(const int sig, const unsigned rva) { } else if (prev) { prev->next = cur->next; } - __sig_handle(false, cur->sig, cur->si_code, 0); + __sig_handle(0, cur->sig, cur->si_code, 0); __sig_free(cur); } else { prev = cur; diff --git a/libc/calls/bo.internal.h b/libc/calls/bo.internal.h new file mode 100644 index 000000000..ffff39573 --- /dev/null +++ b/libc/calls/bo.internal.h @@ -0,0 +1,26 @@ +#ifndef COSMOPOLITAN_LIBC_CALLS_BO_INTERNAL_H_ +#define COSMOPOLITAN_LIBC_CALLS_BO_INTERNAL_H_ +#include "libc/dce.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +int begin_blocking_operation(void); +void end_blocking_operation(int); + +#if SupportsWindows() +#define BEGIN_BLOCKING_OPERATION \ + do { \ + int _Flags; \ + _Flags = begin_blocking_operation() +#define END_BLOCKING_OPERATION \ + end_blocking_operation(_Flags); \ + } \ + while (0) +#else +#define BEGIN_BLOCKING_OPERATION (void)0 +#define END_BLOCKING_OPERATION (void)0 +#endif + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_CALLS_BO_INTERNAL_H_ */ diff --git a/libc/calls/calls.mk b/libc/calls/calls.mk index 2b49d09c2..fe59edd02 100644 --- a/libc/calls/calls.mk +++ b/libc/calls/calls.mk @@ -67,10 +67,13 @@ $(LIBC_CALLS_A).pkg: \ # we can't use sanitizers because: # we're on a stack owned by win32 without tls -o/$(MODE)/libc/calls/onntconsoleevent.o: private \ +o/$(MODE)/libc/calls/foist.o \ +o/$(MODE)/libc/calls/__sig2.o \ +o/$(MODE)/libc/calls/onntconsoleevent.o \ +o/$(MODE)/libc/calls/wincrash.o \ +o/$(MODE)/libc/calls/ntcontext2linux.o: private \ COPTS += \ - -ffreestanding \ - -fno-sanitize=all + $(NO_MAGIC) # we can't use asan because: # siginfo_t memory is owned by kernels @@ -108,14 +111,6 @@ o/$(MODE)/libc/calls/mkntenvblock.o: private \ -ffreestanding \ -fno-sanitize=address -# we can't use sanitizers because: -# windows owns the data structure -o/$(MODE)/libc/calls/wincrash.o \ -o/$(MODE)/libc/calls/ntcontext2linux.o: private \ - COPTS += \ - -fno-sanitize=all \ - -fpatchable-function-entry=0,0 - ifneq ($(ARCH), aarch64) # we always want -O3 because: # it makes the code size smaller too diff --git a/libc/calls/clock_nanosleep-nt.c b/libc/calls/clock_nanosleep-nt.c index 4679a49a9..a61483c25 100644 --- a/libc/calls/clock_nanosleep-nt.c +++ b/libc/calls/clock_nanosleep-nt.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/bo.internal.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" #include "libc/calls/struct/timespec.h" @@ -25,9 +26,9 @@ #include "libc/sysv/consts/timer.h" #include "libc/sysv/errfuns.h" -textwindows int sys_clock_nanosleep_nt(int clock, int flags, - const struct timespec *req, - struct timespec *rem) { +static textwindows int sys_clock_nanosleep_nt_impl(int clock, int flags, + const struct timespec *req, + struct timespec *rem) { struct timespec now, abs; if (flags & TIMER_ABSTIME) { abs = *req; @@ -55,3 +56,13 @@ textwindows int sys_clock_nanosleep_nt(int clock, int flags, } } } + +textwindows int sys_clock_nanosleep_nt(int clock, int flags, + const struct timespec *req, + struct timespec *rem) { + int rc; + BEGIN_BLOCKING_OPERATION; + rc = sys_clock_nanosleep_nt_impl(clock, flags, req, rem); + END_BLOCKING_OPERATION; + return rc; +} diff --git a/libc/calls/foist.c b/libc/calls/foist.c new file mode 100644 index 000000000..7626d66d9 --- /dev/null +++ b/libc/calls/foist.c @@ -0,0 +1,115 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +╞══════════════════════════════════════════════════════════════════════════════╡ +│ Copyright 2023 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/calls/sig.internal.h" +#include "libc/calls/struct/ucontext.internal.h" +#include "libc/calls/ucontext.h" +#include "libc/dce.h" +#include "libc/fmt/itoa.h" +#include "libc/intrin/atomic.h" +#include "libc/macros.internal.h" +#include "libc/nt/enum/context.h" +#include "libc/nt/enum/ctrlevent.h" +#include "libc/nt/enum/threadaccess.h" +#include "libc/nt/runtime.h" +#include "libc/nt/struct/context.h" +#include "libc/nt/thread.h" +#include "libc/nt/thunk/msabi.h" +#include "libc/sysv/consts/sicode.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/tls2.internal.h" + +#ifdef __x86_64__ + +// WIN32 doesn't have the System V red-zone. Microsoft says they reserve +// the right to trample all over it. so we technically don't need to use +// this value. it's just not clear how common it is for WIN32 to clobber +// the red zone, which means broken code could seem to mostly work which +// means it's better that we're not the ones responsible for breaking it +#define kRedzoneSize 128 + +// Both Microsoft and the Fifth Bell System agree on this one. +#define kStackAlign 16 + +__msabi extern typeof(CloseHandle) *const __imp_CloseHandle; +__msabi extern typeof(GetLastError) *const __imp_GetLastError; +__msabi extern typeof(GetStdHandle) *const __imp_GetStdHandle; +__msabi extern typeof(GetThreadContext) *const __imp_GetThreadContext; +__msabi extern typeof(OpenThread) *const __imp_OpenThread; +__msabi extern typeof(ResumeThread) *const __imp_ResumeThread; +__msabi extern typeof(SetThreadContext) *const __imp_SetThreadContext; +__msabi extern typeof(SuspendThread) *const __imp_SuspendThread; +__msabi extern typeof(WriteFile) *const __imp_WriteFile; + +int WinThreadLaunch(struct Delivery *, long, int (*)(struct Delivery *), long); + +static textwindows unsigned long StrLen(const char *s) { + unsigned long n = 0; + while (*s++) ++n; + return n; +} + +static textwindows void Log(const char *s) { +#if IsModeDbg() + __imp_WriteFile(__imp_GetStdHandle(kNtStdErrorHandle), s, StrLen(s), 0, 0); +#endif +} + +/** + * Executes signal handler asynchronously inside other thread. + * + * @return 0 on success, or -1 on error + */ +textwindows int _pthread_signal(struct PosixThread *pt, int sig, int sic) { + int rc = -1; + intptr_t th; + if ((th = __imp_OpenThread( + kNtThreadSuspendResume | kNtThreadGetContext, false, + atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire)))) { + uint32_t old_suspend_count; + if ((old_suspend_count = __imp_SuspendThread(th)) != -1u) { + if (!old_suspend_count && + atomic_load_explicit(&pt->status, memory_order_acquire) < + kPosixThreadTerminated) { + struct NtContext nc = {.ContextFlags = kNtContextAll}; + struct Delivery pkg = {0, sig, sic, &nc}; + if (__imp_GetThreadContext(th, &nc)) { + struct CosmoTib *mytls; + mytls = __get_tls(); + __set_tls_win32(pt->tib); + rc = WinThreadLaunch( + &pkg, 0, __sig_tramp, + ROUNDDOWN(nc.Rsp - kRedzoneSize, kStackAlign) - 8); + __imp_SetThreadContext(th, &nc); + __set_tls_win32(mytls); + } else { + Log("GetThreadContext failed\n"); + } + } + __imp_ResumeThread(th); + } else { + Log("SuspendThread failed\n"); + } + __imp_CloseHandle(th); + } else { + Log("OpenThread failed\n"); + } + return rc; +} + +#endif /* __x86_64__ */ diff --git a/libc/calls/internal.h b/libc/calls/internal.h index 41dabb0f3..0b8af5aea 100644 --- a/libc/calls/internal.h +++ b/libc/calls/internal.h @@ -10,6 +10,7 @@ #define kSigOpRestartable 1 #define kSigOpNochld 2 +#define kSigOpUnmaskable 4 #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ diff --git a/libc/calls/ntcontext2linux.c b/libc/calls/ntcontext2linux.c index b0d1d91c4..6c76c591f 100644 --- a/libc/calls/ntcontext2linux.c +++ b/libc/calls/ntcontext2linux.c @@ -23,9 +23,7 @@ #ifdef __x86_64__ -// TODO(jart): uc_sigmask support - -privileged void _ntcontext2linux(ucontext_t *ctx, const struct NtContext *cr) { +textwindows void _ntcontext2linux(ucontext_t *ctx, const struct NtContext *cr) { if (!cr) return; ctx->uc_mcontext.eflags = cr->EFlags; ctx->uc_mcontext.rax = cr->Rax; @@ -52,7 +50,7 @@ privileged void _ntcontext2linux(ucontext_t *ctx, const struct NtContext *cr) { __repmovsb(&ctx->__fpustate, &cr->FltSave, sizeof(ctx->__fpustate)); } -privileged void _ntlinux2context(struct NtContext *cr, const ucontext_t *ctx) { +textwindows void _ntlinux2context(struct NtContext *cr, const ucontext_t *ctx) { if (!cr) return; cr->EFlags = ctx->uc_mcontext.eflags; cr->Rax = ctx->uc_mcontext.rax; diff --git a/libc/calls/onntconsoleevent.c b/libc/calls/onntconsoleevent.c index 489413db0..934f62326 100644 --- a/libc/calls/onntconsoleevent.c +++ b/libc/calls/onntconsoleevent.c @@ -16,64 +16,37 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/calls.h" #include "libc/calls/sig.internal.h" -#include "libc/dce.h" -#include "libc/errno.h" +#include "libc/calls/state.internal.h" #include "libc/intrin/atomic.h" -#include "libc/intrin/dll.h" -#include "libc/log/libfatal.internal.h" -#include "libc/nexgen32e/nt2sysv.h" -#include "libc/nt/enum/context.h" #include "libc/nt/enum/ctrlevent.h" -#include "libc/nt/enum/threadaccess.h" #include "libc/nt/runtime.h" -#include "libc/nt/struct/context.h" -#include "libc/nt/thread.h" #include "libc/nt/thunk/msabi.h" -#include "libc/str/str.h" #include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sig.h" #include "libc/thread/posixthread.internal.h" #include "libc/thread/thread.h" #include "libc/thread/tls.h" -#include "libc/thread/tls2.internal.h" #ifdef __x86_64__ -// WIN32 doesn't have the System V red-zone. Microsoft says they reserve -// the right to trample all over it. so we technically don't need to use -// this value. it's just not clear how common it is for WIN32 to clobber -// the red zone, which means broken code could seem to mostly work which -// means it's better that we're not the ones responsible for breaking it -#define kRedzoneSize 128 - -// Both Microsoft and the Fifth Bell System agree on this one. -#define kStackAlign 16 - -__msabi extern typeof(CloseHandle) *const __imp_CloseHandle; -__msabi extern typeof(GetLastError) *const __imp_GetLastError; __msabi extern typeof(GetStdHandle) *const __imp_GetStdHandle; -__msabi extern typeof(GetThreadContext) *const __imp_GetThreadContext; -__msabi extern typeof(OpenThread) *const __imp_OpenThread; -__msabi extern typeof(ResumeThread) *const __imp_ResumeThread; -__msabi extern typeof(SuspendThread) *const __imp_SuspendThread; __msabi extern typeof(WriteFile) *const __imp_WriteFile; -int WinThreadLaunch(int, int, int (*)(int, int), intptr_t); - -static unsigned long StrLen(const char *s) { +static textwindows unsigned long StrLen(const char *s) { unsigned long n = 0; while (*s++) ++n; return n; } -static void Log(const char *s) { +static textwindows void Log(const char *s) { #ifndef NDEBUG __imp_WriteFile(__imp_GetStdHandle(kNtStdErrorHandle), s, StrLen(s), 0, 0); #endif } -static int GetSig(uint32_t dwCtrlType) { +static textwindows int GetSig(uint32_t dwCtrlType) { switch (dwCtrlType) { case kNtCtrlCEvent: return SIGINT; @@ -88,71 +61,63 @@ static int GetSig(uint32_t dwCtrlType) { } } -__msabi textwindows dontinstrument dontasan dontubsan bool32 -__onntconsoleevent(uint32_t dwCtrlType) { +__msabi textwindows bool32 __onntconsoleevent(uint32_t dwCtrlType) { - // the signal to be delivered + // we're on a stack that's owned by win32. to make matters worse, + // win32 spawns a totally new thread just to invoke this handler. int sig = GetSig(dwCtrlType); - int sic = SI_KERNEL; + if (__sighandrvas[sig] == (uintptr_t)SIG_IGN) { + return true; + } // if we don't have tls, then we can't hijack a safe stack from a // thread so just try our luck punting the signal to the next i/o if (!__tls_enabled) { - goto PuntSignal; + __sig_add(0, sig, SI_KERNEL); + return true; } - // we're on a stack that's owned by win32. to make matters worse, - // win32 spawns a totally new thread just to invoke this handler. - // that means most of the cosmo runtime is broken right now which + pthread_spin_lock(&_pthread_lock); + + // before we get asynchronous, let's try to find a thread that is + // currently blocked on io which we can interrupt with our signal + // this is important, because if we we asynchronously interrupt a + // thread that's calling ReadFile() by suspending / resuming then + // the io operation will report end of file (why) upon resumation + struct Dll *e; + for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) { + struct PosixThread *pt = POSIXTHREAD_CONTAINER(e); + int tid = atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire); + if (tid <= 0) continue; // -1 means spawning, 0 means terminated + if (pt->tib->tib_sigmask & (1ull << (sig - 1))) continue; // masked + if (pt->flags & PT_BLOCKED) { + pthread_spin_unlock(&_pthread_lock); + __sig_add(0, sig, SI_KERNEL); + return true; + } + } + + // limbo means most of the cosmo runtime is totally broken, which // means we can't call the user signal handler safely. what we'll // do instead is pick a posix thread at random to hijack, pretend // to be that thread, use its stack, and then deliver this signal // asynchronously if it isn't blocked. hopefully it won't longjmp // because once the handler returns, we'll restore the old thread - bool gotsome = false; - pthread_spin_lock(&_pthread_lock); - for (struct Dll *e = dll_first(_pthread_list); e && !gotsome; - e = dll_next(_pthread_list, e)) { + // going asynchronous is heavy but it's the only way to stop code + // that does stuff like scientific computing, which are cpu-bound + for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) { struct PosixThread *pt = POSIXTHREAD_CONTAINER(e); int tid = atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire); if (tid <= 0) continue; // -1 means spawning, 0 means terminated - if (pt->tib->tib_sigmask & (1ull << (sig - 1))) continue; // blocked - intptr_t th; - if ((th = __imp_OpenThread(kNtThreadSuspendResume | kNtThreadGetContext, - false, tid))) { - uint32_t old_suspend_count = __imp_SuspendThread(th); - if (old_suspend_count != -1u) { - if (!old_suspend_count && - atomic_load_explicit(&pt->status, memory_order_acquire) < - kPosixThreadTerminated) { - struct NtContext ctx; - __repstosb(&ctx, 0, sizeof(ctx)); - ctx.ContextFlags = kNtContextControl; - if (__imp_GetThreadContext(th, &ctx)) { - gotsome = true; - pthread_spin_unlock(&_pthread_lock); - __set_tls_win32(pt->tib); - WinThreadLaunch(sig, sic, __sig_raise, - ROUNDDOWN(ctx.Rsp - kRedzoneSize, kStackAlign) - 8); - } else { - Log("GetThreadContext failed\n"); - } - } - __imp_ResumeThread(th); - } else { - Log("SuspendThread failed\n"); - } - __imp_CloseHandle(th); - } else { - Log("OpenThread failed\n"); + if (pt->tib->tib_sigmask & (1ull << (sig - 1))) continue; // masked + pthread_spin_unlock(&_pthread_lock); + if (_pthread_signal(pt, sig, SI_KERNEL) == -1) { + __sig_add(0, sig, SI_KERNEL); } + return true; } - if (!gotsome) { - pthread_spin_unlock(&_pthread_lock); - PuntSignal: - __sig_add(0, sig, sic); - } + pthread_spin_unlock(&_pthread_lock); return true; } diff --git a/libc/calls/pause-nt.c b/libc/calls/pause-nt.c index 7b8c2f0c6..8801b5e47 100644 --- a/libc/calls/pause-nt.c +++ b/libc/calls/pause-nt.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/bo.internal.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" #include "libc/calls/syscall_support-nt.internal.h" @@ -26,6 +27,7 @@ #include "libc/sysv/errfuns.h" textwindows int sys_pause_nt(void) { + BEGIN_BLOCKING_OPERATION; for (;;) { if (_check_interrupts(0)) { @@ -46,4 +48,5 @@ textwindows int sys_pause_nt(void) { } #endif } + END_BLOCKING_OPERATION; } diff --git a/libc/calls/poll.c b/libc/calls/poll.c index 5dd26bd3c..8007533b4 100644 --- a/libc/calls/poll.c +++ b/libc/calls/poll.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/bo.internal.h" #include "libc/calls/cp.internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" @@ -81,7 +82,9 @@ int poll(struct pollfd *fds, size_t nfds, int timeout_ms) { } } else { millis = timeout_ms; + BEGIN_BLOCKING_OPERATION; rc = sys_poll_nt(fds, nfds, &millis, 0); + END_BLOCKING_OPERATION; } END_CANCELLATION_POINT; diff --git a/libc/calls/ppoll.c b/libc/calls/ppoll.c index aa5ce858f..cd0f93f94 100644 --- a/libc/calls/ppoll.c +++ b/libc/calls/ppoll.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/bo.internal.h" #include "libc/calls/cp.internal.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" @@ -97,7 +98,9 @@ int ppoll(struct pollfd *fds, size_t nfds, const struct timespec *timeout, ckd_add(&millis, timeout->tv_sec, timeout->tv_nsec / 1000000)) { millis = -1; } + BEGIN_BLOCKING_OPERATION; rc = sys_poll_nt(fds, nfds, &millis, sigmask); + END_BLOCKING_OPERATION; } END_CANCELLATION_POINT; diff --git a/libc/calls/raise.c b/libc/calls/raise.c index a5d66d38b..8012c8740 100644 --- a/libc/calls/raise.c +++ b/libc/calls/raise.c @@ -64,16 +64,8 @@ int raise(int sig) { RaiseSigFpe(); rc = 0; #endif - } else if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd() || IsNetbsd()) { - rc = sys_tkill(gettid(), sig, 0); - } else if (IsWindows() || IsMetal()) { - if (IsWindows() && sig == SIGKILL) { - ExitProcess(sig); - } else { - rc = __sig_raise(sig, SI_TKILL); - } } else { - __builtin_unreachable(); + rc = tkill(gettid(), sig); } STRACE("...raise(%G) → %d% m", sig, rc); return rc; diff --git a/libc/calls/read-nt.c b/libc/calls/read-nt.c index 8d57784e8..f64735651 100644 --- a/libc/calls/read-nt.c +++ b/libc/calls/read-nt.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/bo.internal.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" @@ -113,6 +114,7 @@ StartOver: // since for overlapped i/o, we always use GetOverlappedResult ok = ReadFile(handle, targetdata, targetsize, 0, &overlap); if (!ok && GetLastError() == kNtErrorIoPending) { + BEGIN_BLOCKING_OPERATION; // the i/o operation is in flight; blocking is unavoidable // if we're in a non-blocking mode, then immediately abort // if an interrupt is pending then we abort before waiting @@ -141,6 +143,7 @@ StartOver: } } ok = true; + END_BLOCKING_OPERATION; } if (ok) { // overlapped is allocated on stack, so it's important we wait @@ -219,7 +222,6 @@ textwindows ssize_t sys_read_nt(int fd, const struct iovec *iov, size_t iovlen, ssize_t rc; size_t i, total; if (opt_offset < -1) return einval(); - if (_check_interrupts(kSigOpRestartable)) return -1; while (iovlen && !iov[0].iov_len) iov++, iovlen--; if (iovlen) { for (total = i = 0; i < iovlen; ++i) { diff --git a/libc/calls/sig.internal.h b/libc/calls/sig.internal.h index 758f00a87..77b4f948e 100644 --- a/libc/calls/sig.internal.h +++ b/libc/calls/sig.internal.h @@ -3,6 +3,7 @@ #include "libc/atomic.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/ucontext.h" +#include "libc/nt/struct/context.h" #define __SIG_QUEUE_LENGTH 32 #define __SIG_POLLING_INTERVAL_MS 20 @@ -11,6 +12,13 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ +struct Delivery { + int ops; + int sig; + int sic; + struct NtContext *nc; +}; + struct Signal { struct Signal *next; bool used; @@ -29,13 +37,16 @@ extern struct Signals __sig; extern atomic_long __sig_count; bool __sig_check(int); +bool __sig_is_core(int); +bool __sig_is_fatal(int); bool __sig_handle(int, int, int, ucontext_t *); int __sig_add(int, int, int); int __sig_mask(int, const sigset_t *, sigset_t *); -int __sig_raise(int, int); void __sig_check_ignore(const int, const unsigned); void __sig_pending(sigset_t *); int __sig_is_applicable(struct Signal *); +bool __sig_deliver(int, int, int, ucontext_t *); +int __sig_tramp(struct Delivery *); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/calls/sigblockall.c b/libc/calls/sigblockall.c index 7c0efd50c..a0a516bfc 100644 --- a/libc/calls/sigblockall.c +++ b/libc/calls/sigblockall.c @@ -17,10 +17,11 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/sigset.h" +#include "libc/log/libfatal.internal.h" #include "libc/str/str.h" -sigset_t _sigblockall(void) { +dontasan sigset_t _sigblockall(void) { sigset_t ss; - memset(&ss, -1, sizeof(ss)); + __repstosb(&ss, -1, sizeof(ss)); return _sigsetmask(ss); } diff --git a/libc/calls/sigsetmask.c b/libc/calls/sigsetmask.c index c43e11180..1ee6af22a 100644 --- a/libc/calls/sigsetmask.c +++ b/libc/calls/sigsetmask.c @@ -23,7 +23,7 @@ #include "libc/dce.h" #include "libc/sysv/consts/sig.h" -sigset_t _sigsetmask(sigset_t neu) { +dontasan sigset_t _sigsetmask(sigset_t neu) { sigset_t res; if (IsMetal() || IsWindows()) { __sig_mask(SIG_SETMASK, &neu, &res); diff --git a/libc/calls/sigsuspend.c b/libc/calls/sigsuspend.c index 5f3d07e3b..3f8f60510 100644 --- a/libc/calls/sigsuspend.c +++ b/libc/calls/sigsuspend.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/bo.internal.h" #include "libc/calls/calls.h" #include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" @@ -77,6 +78,7 @@ int sigsuspend(const sigset_t *ignore) { long ms = 0; long totoms = 0; #endif + BEGIN_BLOCKING_OPERATION; do { if ((rc = _check_interrupts(0))) { break; @@ -93,6 +95,7 @@ int sigsuspend(const sigset_t *ignore) { } #endif } while (1); + END_BLOCKING_OPERATION; __sig_mask(SIG_SETMASK, &save, 0); } } else { diff --git a/libc/thread/tkill.c b/libc/calls/tkill.c similarity index 82% rename from libc/thread/tkill.c rename to libc/calls/tkill.c index 85067bb78..4795a5f3a 100644 --- a/libc/thread/tkill.c +++ b/libc/calls/tkill.c @@ -16,8 +16,10 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/calls.h" #include "libc/calls/sig.internal.h" +#include "libc/calls/state.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall_support-nt.internal.h" #include "libc/dce.h" @@ -25,8 +27,10 @@ #include "libc/intrin/atomic.h" #include "libc/intrin/dll.h" #include "libc/intrin/strace.internal.h" +#include "libc/nt/enum/context.h" #include "libc/nt/enum/threadaccess.h" #include "libc/nt/runtime.h" +#include "libc/nt/struct/context.h" #include "libc/nt/thread.h" #include "libc/sysv/consts/sicode.h" #include "libc/sysv/consts/sig.h" @@ -38,42 +42,44 @@ static dontinline textwindows int __tkill_nt(int tid, int sig, struct CosmoTib *tib) { + // validate api usage + if (tid <= 0 || !(1 <= sig && sig <= 64)) { + return einval(); + } + + // check if caller is killing themself + if (tid == gettid() && __tls_enabled && (!tib || tib == __get_tls())) { + struct NtContext nc = {.ContextFlags = kNtContextAll}; + struct Delivery pkg = {0, sig, SI_TKILL, &nc}; + unassert(GetThreadContext(GetCurrentThread(), &nc)); + __sig_tramp(&pkg); + return 0; + } + // check to see if this is a cosmo posix thread - int rc = 0; struct Dll *e; - bool found = false; pthread_spin_lock(&_pthread_lock); for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) { enum PosixThreadStatus status; struct PosixThread *pt = POSIXTHREAD_CONTAINER(e); + int rhs = atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire); + if (rhs <= 0 || tid != rhs) continue; if (tib && tib != pt->tib) continue; - int other = atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire); - if (!other || tid != other) continue; status = atomic_load_explicit(&pt->status, memory_order_acquire); - found = true; + pthread_spin_unlock(&_pthread_lock); if (status < kPosixThreadTerminated) { - if (sig == SIGKILL) { - intptr_t h; - if ((h = OpenThread(kNtThreadTerminate, false, tid))) { - TerminateThread(h, sig); - CloseHandle(h); - } - atomic_store_explicit(&pt->status, kPosixThreadTerminated, - memory_order_release); + if (pt->flags & PT_BLOCKED) { + return __sig_add(tid, sig, SI_TKILL); } else { - rc = __sig_add(tid, sig, SI_TKILL); + return _pthread_signal(pt, sig, SI_TKILL); } } else { - // already dead but not joined + return 0; } - break; } pthread_spin_unlock(&_pthread_lock); - if (found) { - return rc; - } - // otherwise try our luck sigkilling a manually made thread + // otherwise try our luck hunting a win32 thread if (!tib) { intptr_t h; if ((h = OpenThread(kNtThreadTerminate, false, tid))) { diff --git a/libc/calls/onwincrash.S b/libc/calls/tramp.c similarity index 73% rename from libc/calls/onwincrash.S rename to libc/calls/tramp.c index ad35d439c..56736dfac 100644 --- a/libc/calls/onwincrash.S +++ b/libc/calls/tramp.c @@ -1,7 +1,7 @@ -/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ -│vi: set et ft=asm ts=8 tw=8 fenc=utf-8 :vi│ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ +│ Copyright 2023 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 │ @@ -16,10 +16,17 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/macros.internal.h" -.text.windows +#include "libc/calls/sig.internal.h" +#include "libc/calls/struct/ucontext.internal.h" +#include "libc/calls/ucontext.h" +#ifdef __x86_64__ -__wincrash_nt: - ezlea __wincrash,ax - jmp __nt2sysv - .endfn __wincrash_nt,globl,hidden +textwindows int __sig_tramp(struct Delivery *pkg) { + ucontext_t ctx = {0}; + _ntcontext2linux(&ctx, pkg->nc); + __sig_handle(pkg->ops, pkg->sig, pkg->sic, &ctx); + _ntlinux2context(pkg->nc, &ctx); + return 0; +} + +#endif /* __x86_64__ */ diff --git a/libc/calls/wait4-nt.c b/libc/calls/wait4-nt.c index 432744be9..a08047bf4 100644 --- a/libc/calls/wait4-nt.c +++ b/libc/calls/wait4-nt.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/bo.internal.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" @@ -162,6 +163,7 @@ textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options, sigset_t oldmask, mask = {0}; sigaddset(&mask, SIGCHLD); __sig_mask(SIG_BLOCK, &mask, &oldmask); + BEGIN_BLOCKING_OPERATION; do { rc = _check_interrupts(kSigOpRestartable | kSigOpNochld); if (rc == -1) break; @@ -169,6 +171,7 @@ textwindows int sys_wait4_nt(int pid, int *opt_out_wstatus, int options, rc = sys_wait4_nt_impl(&pid, opt_out_wstatus, options, opt_out_rusage); __fds_unlock(); } while (rc == -2); + END_BLOCKING_OPERATION; __sig_mask(SIG_SETMASK, &oldmask, 0); return rc; } diff --git a/libc/calls/wincrash.c b/libc/calls/wincrash.c index f76303c79..75a4a954e 100644 --- a/libc/calls/wincrash.c +++ b/libc/calls/wincrash.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/struct/ucontext.internal.h" @@ -36,18 +37,14 @@ #ifdef __x86_64__ -unsigned __wincrash(struct NtExceptionPointers *ep) { - int64_t rip; +// win32 calls this; we're running inside the thread that crashed +__msabi unsigned __wincrash(struct NtExceptionPointers *ep) { int sig, code; - ucontext_t ctx; struct CosmoTib *tib; static bool noreentry; noreentry = true; - STRACE("wincrash rip %x bt %s", ep->ContextRecord->Rip, - DescribeBacktrace((struct StackFrame *)ep->ContextRecord->Rbp)); - - if ((tib = __tls_enabled ? __get_tls_privileged() : 0)) { + if ((tib = __tls_enabled ? __get_tls() : 0)) { if (~tib->tib_flags & TIB_FLAG_WINCRASHING) { tib->tib_flags |= TIB_FLAG_WINCRASHING; } else { @@ -61,12 +58,14 @@ unsigned __wincrash(struct NtExceptionPointers *ep) { } } - STRACE("__wincrash"); - switch (ep->ExceptionRecord->ExceptionCode) { case kNtSignalBreakpoint: code = TRAP_BRKPT; sig = SIGTRAP; + // Windows seems to be the only operating system that traps INT3 at + // addressof(INT3) rather than addressof(INT3)+1. So we must adjust + // RIP to prevent the same INT3 from being trapped forevermore. + ep->ContextRecord->Rip++; break; case kNtSignalIllegalInstruction: code = ILL_ILLOPC; @@ -133,21 +132,15 @@ unsigned __wincrash(struct NtExceptionPointers *ep) { sig = SIGSEGV; break; } - rip = ep->ContextRecord->Rip; + + STRACE("wincrash %G rip %x bt %s", sig, ep->ContextRecord->Rip, + DescribeBacktrace((struct StackFrame *)ep->ContextRecord->Rbp)); if (__sighandflags[sig] & SA_SIGINFO) { - _ntcontext2linux(&ctx, ep->ContextRecord); - __sig_handle(false, sig, code, &ctx); - _ntlinux2context(ep->ContextRecord, &ctx); + struct Delivery pkg = {kSigOpUnmaskable, sig, code, ep->ContextRecord}; + __sig_tramp(&pkg); } else { - __sig_handle(false, sig, code, 0); - } - - // Windows seems to be the only operating system that traps INT3 at - // addressof(INT3) rather than addressof(INT3)+1. So we must adjust - // RIP to prevent the same INT3 from being trapped forevermore. - if (sig == SIGTRAP && rip == ep->ContextRecord->Rip) { - ep->ContextRecord->Rip++; + __sig_handle(kSigOpUnmaskable, sig, code, 0); } noreentry = false; diff --git a/libc/calls/wincrash.internal.h b/libc/calls/wincrash.internal.h index 9f3ceb638..15ce3e3b2 100644 --- a/libc/calls/wincrash.internal.h +++ b/libc/calls/wincrash.internal.h @@ -4,7 +4,7 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -unsigned __wincrash_nt(struct NtExceptionPointers *); +unsigned __wincrash(struct NtExceptionPointers *); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/libc/calls/wincrash_init.S b/libc/calls/wincrash_init.S index a4ab04618..bbf659738 100644 --- a/libc/calls/wincrash_init.S +++ b/libc/calls/wincrash_init.S @@ -27,6 +27,6 @@ ntcall __imp_RemoveVectoredExceptionHandler #endif pushpop 1,%rcx - ezlea __wincrash_nt,dx + ezlea __wincrash,dx ntcall __imp_AddVectoredExceptionHandler 1: .init.end 300,_init_wincrash,globl,hidden diff --git a/libc/calls/write-nt.c b/libc/calls/write-nt.c index 59f9d4885..681885552 100644 --- a/libc/calls/write-nt.c +++ b/libc/calls/write-nt.c @@ -89,12 +89,12 @@ static textwindows ssize_t sys_write_nt_impl(int fd, void *data, size_t size, // return edquot(); /* handled by consts.sh */ case kNtErrorBrokenPipe: // broken pipe case kNtErrorNoData: // closing named pipe - if (_weaken(__sig_raise)) { - _weaken(__sig_raise)(SIGPIPE, SI_KERNEL); + if (_weaken(__sig_handle)) { + _weaken(__sig_handle)(0, SIGPIPE, SI_KERNEL, 0); return epipe(); } else { STRACE("broken pipe"); - ExitProcess(EPIPE); + ExitProcess(SIGPIPE); } case kNtErrorAccessDenied: // write doesn't return EACCESS return ebadf(); diff --git a/libc/intrin/bo.c b/libc/intrin/bo.c new file mode 100644 index 000000000..806e88f78 --- /dev/null +++ b/libc/intrin/bo.c @@ -0,0 +1,47 @@ +/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ +│vi: set net 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/calls/bo.internal.h" +#include "libc/thread/posixthread.internal.h" +#include "libc/thread/tls.h" + +int begin_blocking_operation(void) { + int state = 0; + struct CosmoTib *tib; + struct PosixThread *pt; + if (__tls_enabled) { + tib = __get_tls(); + if ((pt = (struct PosixThread *)tib->tib_pthread)) { + state = pt->flags & PT_BLOCKED; + pt->flags |= PT_BLOCKED; + } + } + return state; +} + +void end_blocking_operation(int state) { + struct CosmoTib *tib; + struct PosixThread *pt; + if (__tls_enabled) { + tib = __get_tls(); + if ((pt = (struct PosixThread *)tib->tib_pthread)) { + pt->flags &= ~PT_BLOCKED; + pt->flags |= state; + } + } +} diff --git a/libc/log/oncrash_arm64.c b/libc/log/oncrash_arm64.c index 8b5f0d56e..fe216a3dc 100644 --- a/libc/log/oncrash_arm64.c +++ b/libc/log/oncrash_arm64.c @@ -273,8 +273,11 @@ relegated void __oncrash_arm64(int sig, struct siginfo *si, void *arg) { if (pc && st && (symbol = __get_symbol(st, pc))) { addend = pc - st->addr_base; addend -= st->symbols[symbol].x; - Append(b, " %s", GetSymbolName(st, symbol, &mem, &memsz)); - if (addend) Append(b, "%+d", addend); + Append(b, " "); + if (!AppendFileLine(b, addr2line, debugbin, pc)) { + Append(b, "%s", GetSymbolName(st, symbol, &mem, &memsz)); + if (addend) Append(b, "%+d", addend); + } } Append(b, "\n"); diff --git a/libc/nt/kernel32/SetThreadContext.S b/libc/nt/kernel32/SetThreadContext.S new file mode 100644 index 000000000..c6a0582be --- /dev/null +++ b/libc/nt/kernel32/SetThreadContext.S @@ -0,0 +1,18 @@ +#include "libc/nt/codegen.h" +.imp kernel32,__imp_SetThreadContext,SetThreadContext + + .text.windows + .ftrace1 +SetThreadContext: + .ftrace2 +#ifdef __x86_64__ + push %rbp + mov %rsp,%rbp + mov __imp_SetThreadContext(%rip),%rax + jmp __sysv2nt +#elif defined(__aarch64__) + mov x0,#0 + ret +#endif + .endfn SetThreadContext,globl + .previous diff --git a/libc/nt/master.sh b/libc/nt/master.sh index 9dd105b9d..089ce1110 100755 --- a/libc/nt/master.sh +++ b/libc/nt/master.sh @@ -260,6 +260,7 @@ imp 'SetProcessWorkingSetSize' SetProcessWorkingSetSize kernel32 3 imp 'SetProcessWorkingSetSizeEx' SetProcessWorkingSetSizeEx kernel32 4 imp 'SetStdHandle' SetStdHandle kernel32 2 imp 'SetThreadAffinityMask' SetThreadAffinityMask kernel32 2 +imp 'SetThreadContext' SetThreadContext kernel32 2 imp 'SetThreadPriority' SetThreadPriority kernel32 2 imp 'SetThreadPriorityBoost' SetThreadPriorityBoost kernel32 2 imp 'SetUnhandledExceptionFilter' SetUnhandledExceptionFilter kernel32 1 diff --git a/libc/nt/struct/context.h b/libc/nt/struct/context.h index b6b911ed3..6bb5e58e6 100644 --- a/libc/nt/struct/context.h +++ b/libc/nt/struct/context.h @@ -53,7 +53,7 @@ struct NtContext { uint64_t LastBranchFromRip; uint64_t LastExceptionToRip; uint64_t LastExceptionFromRip; -}; +} forcealign(16); #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_NT_STRUCT_CONTEXT_H_ */ diff --git a/libc/nt/thread.h b/libc/nt/thread.h index fe33e2c3b..2ede7f3b3 100644 --- a/libc/nt/thread.h +++ b/libc/nt/thread.h @@ -64,6 +64,7 @@ void *TlsGetValue(uint32_t); uint32_t SuspendThread(int64_t hThread); uint32_t ResumeThread(int64_t hThread); bool32 GetThreadContext(int64_t hThread, struct NtContext *in_out_lpContext); +bool32 SetThreadContext(int64_t hThread, const struct NtContext *lpContext); #if ShouldUseMsabiAttribute() #include "libc/nt/thunk/thread.inc" diff --git a/libc/runtime/fork-nt.c b/libc/runtime/fork-nt.c index c50d12a90..945257dad 100644 --- a/libc/runtime/fork-nt.c +++ b/libc/runtime/fork-nt.c @@ -302,11 +302,11 @@ textwindows void WinMainForked(void) { #ifdef SYSDEBUG RemoveVectoredExceptionHandler(oncrash); #endif - if (_weaken(__wincrash_nt)) { + if (_weaken(__wincrash)) { if (!IsTiny()) { RemoveVectoredExceptionHandler(__wincrashearly); } - AddVectoredExceptionHandler(1, (void *)_weaken(__wincrash_nt)); + AddVectoredExceptionHandler(1, (void *)_weaken(__wincrash)); } if (_weaken(__onntconsoleevent)) { SetConsoleCtrlHandler(_weaken(__onntconsoleevent), 1); diff --git a/libc/sock/select-nt.c b/libc/sock/select-nt.c index 795427b63..72bf4f100 100644 --- a/libc/sock/select-nt.c +++ b/libc/sock/select-nt.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/bo.internal.h" #include "libc/calls/internal.h" #include "libc/calls/state.internal.h" #include "libc/calls/struct/timeval.h" @@ -63,7 +64,9 @@ int sys_select_nt(int nfds, fd_set *readfds, fd_set *writefds, } // call our nt poll implementation + BEGIN_BLOCKING_OPERATION; fdcount = sys_poll_nt(fds, pfds, &millis, sigmask); + END_BLOCKING_OPERATION; if (fdcount == -1) return -1; // convert pollfd back to bitsets diff --git a/libc/sock/sendfile.c b/libc/sock/sendfile.c index 22e9a903d..567b61770 100644 --- a/libc/sock/sendfile.c +++ b/libc/sock/sendfile.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/bo.internal.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" @@ -104,7 +105,9 @@ static dontinline textwindows ssize_t sys_sendfile_nt( if (TransmitFile(oh, ih, uptobytes, 0, &ov, 0, 0)) { rc = uptobytes; } else { + BEGIN_BLOCKING_OPERATION; rc = SendfileBlock(oh, &ov); + END_BLOCKING_OPERATION; } if (rc != -1) { if (opt_in_out_inoffset) { diff --git a/libc/sock/wsablock.c b/libc/sock/wsablock.c index d8b5bc584..df86e577f 100644 --- a/libc/sock/wsablock.c +++ b/libc/sock/wsablock.c @@ -17,6 +17,7 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/calls/bo.internal.h" #include "libc/calls/internal.h" #include "libc/calls/sig.internal.h" #include "libc/errno.h" @@ -43,13 +44,13 @@ static textwindows void __wsablock_abort(int64_t handle, textwindows int __wsablock(struct Fd *fd, struct NtOverlapped *overlapped, uint32_t *flags, int sigops, uint32_t timeout) { - int abort_errno; uint32_t i, got; + int rc, abort_errno; if (WSAGetLastError() != kNtErrorIoPending) { // our i/o operation never happened because it failed return __winsockerr(); } -TryAgain: + BEGIN_BLOCKING_OPERATION; // our i/o operation is in flight and it needs to block abort_errno = EAGAIN; if (fd->flags & O_NONBLOCK) { @@ -87,16 +88,13 @@ TryAgain: // overlapped is allocated on stack by caller, so it's important that // we wait for win32 to acknowledge that it's done using that memory. if (WSAGetOverlappedResult(fd->handle, overlapped, &got, true, flags)) { - return got; - } - switch (WSAGetLastError()) { - case kNtErrorIoIncomplete: - goto TryAgain; - case kNtErrorOperationAborted: + rc = got; + } else { + rc = -1; + if (WSAGetLastError() == kNtErrorOperationAborted) { errno = abort_errno; - break; - default: - break; + } } - return -1; + END_BLOCKING_OPERATION; + return rc; } diff --git a/libc/stdio/getrandom.c b/libc/stdio/getrandom.c index f981f7831..028cea922 100644 --- a/libc/stdio/getrandom.c +++ b/libc/stdio/getrandom.c @@ -176,9 +176,6 @@ static ssize_t GetDevUrandom(char *p, size_t n) { ssize_t __getrandom(void *p, size_t n, unsigned f) { ssize_t rc; if (IsWindows()) { - if (_check_interrupts(kSigOpRestartable)) { - return -1; - } rc = RtlGenRandom(p, n) ? n : __winerr(); } else if (have_getrandom) { if (IsXnu() || IsOpenbsd()) { diff --git a/libc/thread/posixthread.internal.h b/libc/thread/posixthread.internal.h index 17f9f17d8..c891d2602 100644 --- a/libc/thread/posixthread.internal.h +++ b/libc/thread/posixthread.internal.h @@ -13,8 +13,9 @@ #define PT_NOCANCEL 8 #define PT_MASKED 16 #define PT_INCANCEL 32 -#define PT_OPENBSD_KLUDGE 64 +#define PT_BLOCKED 64 #define PT_EXITING 128 +#define PT_OPENBSD_KLUDGE 256 #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -92,6 +93,7 @@ extern _Atomic(pthread_key_dtor) _pthread_key_dtor[PTHREAD_KEYS_MAX]; int _pthread_atfork(atfork_f, atfork_f, atfork_f); int _pthread_reschedule(struct PosixThread *); int _pthread_setschedparam_freebsd(int, int, const struct sched_param *); +int _pthread_signal(struct PosixThread *, int, int); void _pthread_zombify(struct PosixThread *); void _pthread_free(struct PosixThread *); void _pthread_onfork_prepare(void); diff --git a/test/libc/calls/sigaction_test.c b/test/libc/calls/sigaction_test.c index 75a54320f..4e01b02f4 100644 --- a/test/libc/calls/sigaction_test.c +++ b/test/libc/calls/sigaction_test.c @@ -18,19 +18,23 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/struct/sigaction.h" #include "libc/calls/calls.h" +#include "libc/calls/pledge.h" #include "libc/calls/struct/rusage.h" #include "libc/calls/struct/sigaction.h" #include "libc/calls/struct/siginfo.h" #include "libc/calls/struct/sigset.h" #include "libc/calls/struct/sigset.internal.h" +#include "libc/calls/struct/ucontext.internal.h" #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/calls/ucontext.h" #include "libc/dce.h" #include "libc/errno.h" +#include "libc/intrin/kprintf.h" #include "libc/nexgen32e/nexgen32e.h" #include "libc/nexgen32e/vendor.internal.h" #include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" +#include "libc/runtime/syslib.internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/sig.h" @@ -42,6 +46,7 @@ struct sigaction oldsa; volatile bool gotsigint; void SetUpOnce(void) { + __enable_threads(); ASSERT_SYS(0, 0, pledge("stdio rpath proc", 0)); } @@ -54,6 +59,48 @@ void SetUp(void) { gotsigint = false; } +//////////////////////////////////////////////////////////////////////////////// +// test that signal handlers expose cpu state, and let it be changed arbitrarily + +void *Gateway(void *arg) { + __builtin_trap(); +} + +void PromisedLand(void *arg) { + sigset_t ss; + CheckStackIsAligned(); + sigprocmask(SIG_SETMASK, 0, &ss); + ASSERT_TRUE(sigismember(&ss, SIGUSR1)); + pthread_exit(arg); +} + +void Teleporter(int sig, struct siginfo *si, void *ctx) { + ucontext_t *uc = ctx; + sigaddset(&uc->uc_sigmask, SIGUSR1); + uc->uc_mcontext.PC = (uintptr_t)PromisedLand; + uc->uc_mcontext.SP &= -16; +#ifdef __x86_64__ + uc->uc_mcontext.SP -= 8; +#endif +} + +TEST(sigaction, handlersCanMutateMachineState) { + void *rc; + sigset_t ss; + pthread_t t; + struct sigaction oldill, oldtrap; + struct sigaction sa = {.sa_sigaction = Teleporter, .sa_flags = SA_SIGINFO}; + sigprocmask(SIG_SETMASK, 0, &ss); + ASSERT_FALSE(sigismember(&ss, SIGUSR1)); + ASSERT_SYS(0, 0, sigaction(SIGILL, &sa, &oldill)); + ASSERT_SYS(0, 0, sigaction(SIGTRAP, &sa, &oldtrap)); + ASSERT_EQ(0, pthread_create(&t, 0, Gateway, (void *)42L)); + ASSERT_EQ(0, pthread_join(t, &rc)); + ASSERT_EQ(42, (uintptr_t)rc); + ASSERT_SYS(0, 0, sigaction(SIGILL, &oldill, 0)); + ASSERT_SYS(0, 0, sigaction(SIGTRAP, &oldtrap, 0)); +} + //////////////////////////////////////////////////////////////////////////////// // test raise() @@ -226,12 +273,14 @@ sig_atomic_t gotusr1; void OnSigMask(int sig, struct siginfo *si, void *ctx) { ucontext_t *uc = ctx; +#ifdef __x86_64__ + ASSERT_EQ(123, uc->uc_mcontext.r15); +#endif sigaddset(&uc->uc_sigmask, sig); gotusr1 = true; } TEST(uc_sigmask, signalHandlerCanChangeSignalMaskOfTrappedThread) { - if (IsWindows()) return; // TODO(jart): uc_sigmask support on windows sigset_t want, got; struct sigaction oldsa; struct sigaction sa = {.sa_sigaction = OnSigMask, .sa_flags = SA_SIGINFO}; @@ -239,6 +288,9 @@ TEST(uc_sigmask, signalHandlerCanChangeSignalMaskOfTrappedThread) { ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &want, &got)); ASSERT_FALSE(sigismember(&got, SIGUSR1)); ASSERT_SYS(0, 0, sigaction(SIGUSR1, &sa, &oldsa)); +#ifdef __x86_64__ + asm volatile("mov\t%0,%%r15" : : "i"(123) : "r15", "memory"); +#endif ASSERT_SYS(0, 0, raise(SIGUSR1)); ASSERT_TRUE(gotusr1); ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, 0, &got)); @@ -265,3 +317,40 @@ TEST(sig_ign, discardsPendingSignalsEvenIfBlocked) { ASSERT_SYS(0, 0, sigaction(SIGUSR1, &oldsa, 0)); ASSERT_SYS(0, 0, sigprocmask(SIG_SETMASK, &oldmask, 0)); } + +void AutoMask(int sig, struct siginfo *si, void *ctx) { + sigset_t ss; + ucontext_t *uc = ctx; + sigprocmask(SIG_SETMASK, 0, &ss); + EXPECT_FALSE(sigismember(&uc->uc_sigmask, SIGUSR2)); // original mask + EXPECT_TRUE(sigismember(&ss, SIGUSR2)); // temporary mask +} + +TEST(sigaction, signalBeingDeliveredGetsAutoMasked) { + sigset_t ss; + struct sigaction os, sa = {.sa_sigaction = AutoMask, .sa_flags = SA_SIGINFO}; + ASSERT_SYS(0, 0, sigaction(SIGUSR2, &sa, &os)); + raise(SIGUSR2); + ASSERT_SYS(0, 0, sigaction(SIGUSR2, &os, 0)); + sigprocmask(SIG_SETMASK, 0, &ss); + EXPECT_FALSE(sigismember(&ss, SIGUSR2)); // original mask +} + +void NoDefer(int sig, struct siginfo *si, void *ctx) { + sigset_t ss; + ucontext_t *uc = ctx; + sigprocmask(SIG_SETMASK, 0, &ss); + EXPECT_FALSE(sigismember(&uc->uc_sigmask, SIGUSR2)); + EXPECT_FALSE(sigismember(&ss, SIGUSR2)); +} + +TEST(sigaction, NoDefer) { + struct sigaction os; + struct sigaction sa = { + .sa_sigaction = NoDefer, + .sa_flags = SA_SIGINFO | SA_NODEFER, + }; + ASSERT_SYS(0, 0, sigaction(SIGUSR2, &sa, &os)); + raise(SIGUSR2); + ASSERT_SYS(0, 0, sigaction(SIGUSR2, &os, 0)); +} diff --git a/test/libc/thread/pthread_kill_test.c b/test/libc/thread/pthread_kill_test.c index 24bcb7025..a355f53b8 100644 --- a/test/libc/thread/pthread_kill_test.c +++ b/test/libc/thread/pthread_kill_test.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/atomic.h" #include "libc/calls/calls.h" #include "libc/calls/struct/sigaction.h" #include "libc/dce.h" @@ -23,6 +24,7 @@ #include "libc/runtime/runtime.h" #include "libc/stdio/stdio.h" #include "libc/sysv/consts/sig.h" +#include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" @@ -62,3 +64,53 @@ TEST(pthread_kill, canCancelReadOperation) { ASSERT_SYS(0, 0, close(fds[1])); ASSERT_SYS(0, 0, sigaction(SIGUSR1, &oldsa, 0)); } + +volatile unsigned got_sig_async; +volatile pthread_t cpu_worker_th; +volatile unsigned is_wasting_cpu; + +void OnSigAsync(int sig) { + ASSERT_TRUE(pthread_equal(cpu_worker_th, pthread_self())); + got_sig_async = 1; +} + +void *CpuWorker(void *arg) { + cpu_worker_th = pthread_self(); + while (!got_sig_async) { + is_wasting_cpu = 1; + } + return 0; +} + +TEST(pthread_kill, canAsynchronouslyRunHandlerInsideTargetThread) { + pthread_t t; + struct sigaction oldsa; + struct sigaction sa = {.sa_handler = OnSigAsync}; + ASSERT_SYS(0, 0, sigaction(SIGUSR1, &sa, &oldsa)); + ASSERT_EQ(0, pthread_create(&t, 0, CpuWorker, 0)); + while (!is_wasting_cpu) donothing; + ASSERT_EQ(0, pthread_kill(t, SIGUSR1)); + ASSERT_EQ(0, pthread_join(t, 0)); + ASSERT_TRUE(got_sig_async); + ASSERT_SYS(0, 0, sigaction(SIGUSR1, &oldsa, 0)); +} + +volatile int is_having_fun; + +void *FunWorker(void *arg) { + for (;;) { + is_having_fun = 1; + sched_yield(); + } + return 0; +} + +TEST(pthread_kill, defaultThreadSignalHandlerWillKillWholeProcess) { + SPAWN(fork); + pthread_t t; + ASSERT_EQ(0, pthread_create(&t, 0, FunWorker, 0)); + while (!is_having_fun) sched_yield(); + ASSERT_SYS(0, 0, pthread_kill(t, SIGKILL)); + for (;;) sched_yield(); + TERMS(SIGKILL); +}