From 99dc1281f555293dde942fe81d166486c531baa7 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Fri, 8 Sep 2023 01:49:41 -0700 Subject: [PATCH] Overhaul Windows signal handling The new asynchronous signal delivery technique is now also being used for tkill(), raise(), etc. Many subtle issues have been addresesd. We now signal handling on Windows that's remarkably similar to the POSIX behaviors. However that's just across threads. We're lacking a way to have the signal semantics work well, across multiple WIN32 processes. --- libc/calls/__sig2.c | 188 ++++++++++++++------------- libc/calls/bo.internal.h | 26 ++++ libc/calls/calls.mk | 17 +-- libc/calls/clock_nanosleep-nt.c | 17 ++- libc/calls/foist.c | 115 ++++++++++++++++ libc/calls/internal.h | 1 + libc/calls/ntcontext2linux.c | 6 +- libc/calls/onntconsoleevent.c | 121 ++++++----------- libc/calls/pause-nt.c | 3 + libc/calls/poll.c | 3 + libc/calls/ppoll.c | 3 + libc/calls/raise.c | 10 +- libc/calls/read-nt.c | 4 +- libc/calls/sig.internal.h | 13 +- libc/calls/sigblockall.c | 5 +- libc/calls/sigsetmask.c | 2 +- libc/calls/sigsuspend.c | 3 + libc/{thread => calls}/tkill.c | 46 ++++--- libc/calls/{onwincrash.S => tramp.c} | 25 ++-- libc/calls/wait4-nt.c | 3 + libc/calls/wincrash.c | 35 ++--- libc/calls/wincrash.internal.h | 2 +- libc/calls/wincrash_init.S | 2 +- libc/calls/write-nt.c | 6 +- libc/intrin/bo.c | 47 +++++++ libc/log/oncrash_arm64.c | 7 +- libc/nt/kernel32/SetThreadContext.S | 18 +++ libc/nt/master.sh | 1 + libc/nt/struct/context.h | 2 +- libc/nt/thread.h | 1 + libc/runtime/fork-nt.c | 4 +- libc/sock/select-nt.c | 3 + libc/sock/sendfile.c | 3 + libc/sock/wsablock.c | 22 ++-- libc/stdio/getrandom.c | 3 - libc/thread/posixthread.internal.h | 4 +- test/libc/calls/sigaction_test.c | 91 ++++++++++++- test/libc/thread/pthread_kill_test.c | 52 ++++++++ 38 files changed, 635 insertions(+), 279 deletions(-) create mode 100644 libc/calls/bo.internal.h create mode 100644 libc/calls/foist.c rename libc/{thread => calls}/tkill.c (82%) rename libc/calls/{onwincrash.S => tramp.c} (73%) create mode 100644 libc/intrin/bo.c create mode 100644 libc/nt/kernel32/SetThreadContext.S 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); +}