Don't preempt WIN32 libraries

This change refactors our POSIX signals emulation for Windows so that it
performs some additional safety checks before calling SetThreadContext()
which needs to be locked and must never ever interrupt Microsoft's code.
Kudos to the the Go developers for figuring out how to do this properly.
This commit is contained in:
Justine Tunney 2023-10-13 13:56:14 -07:00
parent d1a283a588
commit aca2261cda
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
5 changed files with 61 additions and 30 deletions

View file

@ -48,8 +48,11 @@ static textwindows int _park_thread(uint32_t msdelay, sigset_t waitmask,
if ((rc = _check_cancel()) != -1 && (rc = _check_signal(restartable)) != -1) {
unassert((wi = WaitForSingleObject(sem, msdelay)) != -1u);
if (wi != kNtWaitTimeout) {
_check_signal(false);
rc = eintr();
_check_cancel();
} else if ((rc = _check_signal(restartable))) {
_check_cancel();
}
}
__sig_finishwait(om);

View file

@ -193,8 +193,9 @@ sys_readwrite_nt(int fd, void *data, size_t size, ssize_t offset,
// check and see if it was pthread_cancel() which committed the deed
// in which case _check_cancel() can acknowledge the cancelation now
// it's also fine to do nothing here; punt to next cancelation point
if (GetLastError() == kNtErrorOperationAborted && _check_cancel() == -1) {
return ecanceled();
if (GetLastError() == kNtErrorOperationAborted) {
if (_check_cancel() == -1) return ecanceled();
if (!eintered && _check_signal(false)) return eintr();
}
// if we chose to process a pending signal earlier then we preserve

View file

@ -128,8 +128,7 @@ static textwindows bool __sig_start(struct PosixThread *pt, int sig,
return false;
}
if (pt->tib->tib_sigmask & (1ull << (sig - 1))) {
STRACE("tid %d masked %G delivering to tib_sigpending", _pthread_tid(pt),
sig);
STRACE("enqueing %G on %d", sig, _pthread_tid(pt));
pt->tib->tib_sigpending |= 1ull << (sig - 1);
return false;
}
@ -137,10 +136,6 @@ static textwindows bool __sig_start(struct PosixThread *pt, int sig,
STRACE("terminating on %G due to no handler", sig);
__sig_terminate(sig);
}
if (*flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
return true;
}
@ -153,6 +148,10 @@ textwindows int __sig_raise(int sig, int sic) {
struct PosixThread *pt = _pthread_self();
ucontext_t ctx = {.uc_sigmask = pt->tib->tib_sigmask};
if (!__sig_start(pt, sig, &rva, &flags)) return 0;
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
siginfo_t si = {.si_signo = sig, .si_code = sic};
struct NtContext nc;
nc.ContextFlags = kNtContextFull;
@ -232,30 +231,53 @@ static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
__sig_restore(&sf->ctx);
}
static textwindows int __sig_killer(struct PosixThread *pt, int sig, int sic) {
uintptr_t th;
static int __sig_killer(struct PosixThread *pt, int sig, int sic) {
// prepare for signal
unsigned rva, flags;
if (!__sig_start(pt, sig, &rva, &flags)) return 0;
th = _pthread_syshand(pt);
uint32_t old_suspend_count;
old_suspend_count = SuspendThread(th);
if (old_suspend_count == -1u) {
STRACE("SuspendThread failed w/ %d", GetLastError());
return ESRCH;
}
if (old_suspend_count) {
STRACE("kill contention of %u on tid %d", old_suspend_count,
_pthread_tid(pt));
pt->tib->tib_sigpending |= 1ull << (sig - 1);
ResumeThread(th);
if (!__sig_start(pt, sig, &rva, &flags)) {
return 0;
}
// take control of thread
// suspending the thread happens asynchronously
// however getting the context blocks until it's frozen
static pthread_spinlock_t killer_lock;
pthread_spin_lock(&killer_lock);
uintptr_t th = _pthread_syshand(pt);
if (SuspendThread(th) == -1u) {
STRACE("SuspendThread failed w/ %d", GetLastError());
pthread_spin_unlock(&killer_lock);
return ESRCH;
}
struct NtContext nc;
nc.ContextFlags = kNtContextFull;
if (!GetThreadContext(th, &nc)) {
STRACE("GetThreadContext failed w/ %d", GetLastError());
ResumeThread(th);
pthread_spin_unlock(&killer_lock);
return ESRCH;
}
pthread_spin_unlock(&killer_lock);
// we can't preempt threads that are running in win32 code
if ((pt->tib->tib_sigmask & (1ull << (sig - 1))) ||
!((uintptr_t)__executable_start <= nc.Rip &&
nc.Rip < (uintptr_t)__privileged_start)) {
STRACE("enqueing %G on %d", sig, _pthread_tid(pt));
pt->tib->tib_sigpending |= 1ull << (sig - 1);
ResumeThread(th);
__sig_cancel(pt, sig, flags);
return 0;
}
// we're committed to delivering this signal now
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
// inject trampoline function call into thread
uintptr_t sp;
if (__sig_should_use_altstack(flags, pt->tib)) {
sp = (uintptr_t)pt->tib->tib_sigstack_addr + pt->tib->tib_sigstack_size;
@ -307,7 +329,8 @@ textwindows void __sig_generate(int sig, int sic) {
_pthread_lock();
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
pt = POSIXTHREAD_CONTAINER(e);
if (atomic_load_explicit(&pt->pt_status, memory_order_acquire) <
if (pt != _pthread_self() &&
atomic_load_explicit(&pt->pt_status, memory_order_acquire) <
kPosixThreadTerminated &&
!(pt->tib->tib_sigmask & (1ull << (sig - 1)))) {
mark = pt;
@ -315,14 +338,14 @@ textwindows void __sig_generate(int sig, int sic) {
}
}
_pthread_unlock();
ALLOW_SIGNALS;
if (mark) {
STRACE("generating %G by killing %d", sig, _pthread_tid(mark));
__sig_kill(mark, sig, sic);
__sig_killer(mark, sig, sic);
} else {
STRACE("all threads block %G so adding to pending signals of process", sig);
__sig.pending |= 1ull << (sig - 1);
}
ALLOW_SIGNALS;
}
static int __sig_crash_sig(struct NtExceptionPointers *ep, int *code) {

View file

@ -140,8 +140,9 @@ __winsock_block(int64_t handle, uint32_t flags, bool nonblock,
if (eagained) {
return eagain();
}
if (WSAGetLastError() == kNtErrorOperationAborted && _check_cancel()) {
return ecanceled();
if (GetLastError() == kNtErrorOperationAborted) {
if (_check_cancel() == -1) return ecanceled();
if (!eintered && _check_signal(false)) return eintr();
}
if (eintered) {
return eintr();

View file

@ -19,6 +19,7 @@
#include "libc/sysv/consts/sig.h"
#include "libc/atomic.h"
#include "libc/calls/calls.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/sigaction.h"
#include "libc/dce.h"
#include "libc/errno.h"
@ -26,6 +27,7 @@
#include "libc/nt/enum/context.h"
#include "libc/nt/struct/context.h"
#include "libc/nt/thread.h"
#include "libc/runtime/clktck.h"
#include "libc/sock/struct/pollfd.h"
#include "libc/sysv/consts/poll.h"
#include "libc/testlib/testlib.h"
@ -64,6 +66,7 @@ TEST(SetThreadContext, test) {
if (!IsWindows()) return;
ASSERT_EQ(0, pthread_create(&th, 0, Worker, 0));
while (!ready) donothing;
usleep(1000);
int64_t hand = _pthread_syshand((struct PosixThread *)th);
ASSERT_EQ(0, SuspendThread(hand));
struct NtContext nc;
@ -101,9 +104,9 @@ TEST(poll, interrupt) {
signal(SIGUSR1, OnSig);
ASSERT_SYS(0, 0, pipe(pfds));
ASSERT_EQ(0, pthread_create(&th, 0, Worker2, 0));
for (int i = 0; i < 100; ++i) {
for (int i = 0; i < 20; ++i) {
ASSERT_EQ(0, pthread_kill(th, SIGUSR1));
usleep(1000);
usleep(1e6 / CLK_TCK * 2);
}
isdone = true;
ASSERT_EQ(0, pthread_kill(th, SIGUSR1));