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.
This commit is contained in:
Justine Tunney 2023-09-08 01:49:41 -07:00
parent 8bdaddd81d
commit 99dc1281f5
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
38 changed files with 635 additions and 279 deletions

View file

@ -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;

26
libc/calls/bo.internal.h Normal file
View file

@ -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_ */

View file

@ -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

View file

@ -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;
}

115
libc/calls/foist.c Normal file
View file

@ -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__ */

View file

@ -10,6 +10,7 @@
#define kSigOpRestartable 1
#define kSigOpNochld 2
#define kSigOpUnmaskable 4
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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) {

View file

@ -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) */

View file

@ -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);
}

View file

@ -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);

View file

@ -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 {

View file

@ -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))) {

View file

@ -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__ */

View file

@ -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;
}

View file

@ -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;

View file

@ -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) */

View file

@ -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

View file

@ -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();

47
libc/intrin/bo.c Normal file
View file

@ -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;
}
}
}

View file

@ -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");

View file

@ -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

View file

@ -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

View file

@ -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_ */

View file

@ -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"

View file

@ -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);

View file

@ -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

View file

@ -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) {

View file

@ -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;
}

View file

@ -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()) {

View file

@ -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);

View file

@ -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));
}

View file

@ -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);
}