Implement signal handler tail recursion

GNU Make on Windows now appears to be working reliably. This change also
fixes a bug where, after fork the Windows thread handle wasn't reset and
that caused undefined behavior using SetThreadContext() with our signals
This commit is contained in:
Justine Tunney 2023-10-14 10:29:26 -07:00
parent a657f3e878
commit cdf556e7d2
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
15 changed files with 627 additions and 77 deletions

View file

@ -97,6 +97,32 @@ textwindows void __sig_delete(int sig) {
ALLOW_SIGNALS;
}
static textwindows int __sig_getter(struct CosmoTib *tib, atomic_ulong *sigs) {
int sig;
sigset_t bit, pending, masked, deliverable;
for (;;) {
pending = atomic_load_explicit(sigs, memory_order_acquire);
masked = atomic_load_explicit(&tib->tib_sigmask, memory_order_acquire);
if ((deliverable = pending & ~masked)) {
sig = _bsf(deliverable) + 1;
bit = 1ull << (sig - 1);
if (atomic_fetch_and_explicit(sigs, ~bit, memory_order_acq_rel) & bit) {
return sig;
}
} else {
return 0;
}
}
}
static textwindows int __sig_get(struct CosmoTib *tib) {
int sig;
if (!(sig = __sig_getter(tib, &tib->tib_sigpending))) {
sig = __sig_getter(tib, &__sig.pending);
}
return sig;
}
static textwindows bool __sig_should_use_altstack(unsigned flags,
struct CosmoTib *tib) {
if (!(flags & SA_ONSTACK)) {
@ -146,34 +172,49 @@ static textwindows sigaction_f __sig_handler(unsigned rva) {
}
textwindows int __sig_raise(int sig, int sic) {
unsigned rva, flags;
// create machine context object
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;
GetThreadContext(GetCurrentThread(), &nc);
_ntcontext2linux(&ctx, &nc);
pt->tib->tib_sigmask |= __sighandmask[sig];
if (!(flags & SA_NODEFER)) {
pt->tib->tib_sigmask |= 1ull << (sig - 1);
}
NTTRACE("entering raise(%G) signal handler %t with mask %s → %s", sig,
__sig_handler(rva), DescribeSigset(0, &ctx.uc_sigmask),
DescribeSigset(0, (sigset_t *)&pt->tib->tib_sigmask));
__sig_handler(rva)(sig, &si, &ctx);
NTTRACE("leaving raise(%G) signal handler %t with mask %s → %s", sig,
__sig_handler(rva),
DescribeSigset(0, (sigset_t *)&pt->tib->tib_sigmask),
DescribeSigset(0, &ctx.uc_sigmask));
atomic_store_explicit(&pt->tib->tib_sigmask, ctx.uc_sigmask,
memory_order_release);
return (flags & SA_RESTART) ? 2 : 1;
// process signal(s)
int handler_was_called = 0;
do {
// start the signal
unsigned rva, flags;
if (__sig_start(pt, sig, &rva, &flags)) {
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
// update the signal mask in preparation for signal handller
sigset_t blocksigs = __sighandmask[sig];
if (!(flags & SA_NODEFER)) blocksigs |= 1ull << (sig - 1);
ctx.uc_sigmask = atomic_fetch_or_explicit(
&pt->tib->tib_sigmask, blocksigs, memory_order_acq_rel);
// call the user's signal handler
char ssbuf[2][128];
siginfo_t si = {.si_signo = sig, .si_code = sic};
STRACE("__sig_raise(%G, %t) mask %s → %s", sig, __sig_handler(rva),
(DescribeSigset)(ssbuf[0], 0, &ctx.uc_sigmask),
(DescribeSigset)(ssbuf[1], 0, (sigset_t *)&pt->tib->tib_sigmask));
__sig_handler(rva)(sig, &si, &ctx);
(void)ssbuf;
// restore the original signal mask
atomic_store_explicit(&pt->tib->tib_sigmask, ctx.uc_sigmask,
memory_order_release);
handler_was_called |= (flags & SA_RESTART) ? 2 : 1;
}
sic = SI_KERNEL;
} while ((sig = __sig_get(pt->tib)));
return handler_was_called;
}
// cancels blocking operations being performed by signaled thread
@ -220,17 +261,48 @@ textwindows void __sig_cancel(struct PosixThread *pt, int sig, unsigned flags) {
WakeByAddressSingle(blocker);
}
// the user's signal handler callback is composed with this trampoline
static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed);
int sig = sf->si.si_signo;
sigset_t blocksigs = __sighandmask[sig];
if (!(sf->flags & SA_NODEFER)) blocksigs |= 1ull << (sig - 1);
sf->ctx.uc_sigmask = atomic_fetch_or_explicit(
&__get_tls()->tib_sigmask, blocksigs, memory_order_acq_rel);
__sig_handler(sf->rva)(sig, &sf->si, &sf->ctx);
atomic_store_explicit(&__get_tls()->tib_sigmask, sf->ctx.uc_sigmask,
memory_order_release);
__sig_restore(&sf->ctx);
struct CosmoTib *tib = __get_tls();
struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread;
for (;;) {
atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed);
// update the signal mask in preparation for signal handller
sigset_t blocksigs = __sighandmask[sig];
if (!(sf->flags & SA_NODEFER)) blocksigs |= 1ull << (sig - 1);
sf->ctx.uc_sigmask = atomic_fetch_or_explicit(&tib->tib_sigmask, blocksigs,
memory_order_acq_rel);
// call the user's signal handler
char ssbuf[2][128];
STRACE("__sig_tramp(%G, %t) mask %s → %s", sig, __sig_handler(sf->rva),
(DescribeSigset)(ssbuf[0], 0, &sf->ctx.uc_sigmask),
(DescribeSigset)(ssbuf[1], 0, (sigset_t *)&tib->tib_sigmask));
__sig_handler(sf->rva)(sig, &sf->si, &sf->ctx);
(void)ssbuf;
// restore the signal mask that was used by the interrupted code
// this may have been modified by the signal handler in the callback
atomic_store_explicit(&tib->tib_sigmask, sf->ctx.uc_sigmask,
memory_order_release);
// jump back into original code if there aren't any pending signals
do {
if (!(sig = __sig_get(tib))) {
__sig_restore(&sf->ctx);
}
} while (!__sig_start(pt, sig, &sf->rva, &sf->flags));
// tail recurse into another signal handler
sf->si.si_signo = sig;
sf->si.si_code = SI_KERNEL;
if (sf->flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
}
}
static int __sig_killer(struct PosixThread *pt, int sig, int sic) {
@ -266,7 +338,7 @@ static int __sig_killer(struct PosixThread *pt, int sig, int sic) {
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));
STRACE("enqueing %G on %d rip %p", sig, _pthread_tid(pt), nc.Rip);
pt->tib->tib_sigpending |= 1ull << (sig - 1);
ResumeThread(th);
__sig_cancel(pt, sig, flags);
@ -345,11 +417,11 @@ textwindows void __sig_generate(int sig, int sic) {
if (mark) {
STRACE("generating %G by killing %d", sig, _pthread_tid(mark));
__sig_killer(mark, sig, sic);
_pthread_unref(mark);
} else {
STRACE("all threads block %G so adding to pending signals of process", sig);
__sig.pending |= 1ull << (sig - 1);
}
_pthread_unref(mark);
ALLOW_SIGNALS;
}
@ -539,34 +611,17 @@ __msabi textwindows dontinstrument bool32 __sig_console(uint32_t dwCtrlType) {
return true;
}
static textwindows int __sig_checker(atomic_ulong *sigs, struct CosmoTib *tib) {
int sig, handler_was_called = 0;
sigset_t bit, pending, masked, deliverable;
pending = atomic_load_explicit(sigs, memory_order_acquire);
masked = atomic_load_explicit(&tib->tib_sigmask, memory_order_acquire);
if ((deliverable = pending & ~masked)) {
do {
sig = _bsf(deliverable) + 1;
bit = 1ull << (sig - 1);
if (atomic_fetch_and_explicit(sigs, ~bit, memory_order_acq_rel) & bit) {
STRACE("found pending %G we can raise now", sig);
handler_was_called |= __sig_raise(sig, SI_KERNEL);
}
} while ((deliverable &= ~bit));
}
return handler_was_called;
}
// returns 0 if no signal handlers were called, otherwise a bitmask
// consisting of `1` which means a signal handler was invoked which
// didn't have the SA_RESTART flag, and `2`, which means SA_RESTART
// handlers were called (or `3` if both were the case).
textwindows int __sig_check(void) {
int handler_was_called = false;
struct CosmoTib *tib = __get_tls();
handler_was_called |= __sig_checker(&tib->tib_sigpending, tib);
handler_was_called |= __sig_checker(&__sig.pending, tib);
return handler_was_called;
int sig;
if ((sig = __sig_get(__get_tls()))) {
return __sig_raise(sig, SI_KERNEL);
} else {
return 0;
}
}
textstartup void __sig_init(void) {

View file

@ -29,7 +29,7 @@ struct Signals __sig;
sigset_t __sig_block(void) {
if (IsWindows()) {
return atomic_exchange_explicit(&__get_tls()->tib_sigmask, -1,
memory_order_acq_rel);
memory_order_acquire);
} else {
sigset_t res, neu = -1;
sys_sigprocmask(SIG_SETMASK, &neu, &res);
@ -55,7 +55,7 @@ textwindows int __sig_enqueue(int sig) {
textwindows sigset_t __sig_beginwait(sigset_t waitmask) {
return atomic_exchange_explicit(&__get_tls()->tib_sigmask, waitmask,
memory_order_acq_rel);
memory_order_acquire);
}
textwindows void __sig_finishwait(sigset_t m) {

View file

@ -598,6 +598,10 @@ imp 'PdhOpenQuery' PdhOpenQueryW pdh 3 # Creates a new query that is
# PSAPI.DLL
#
# Name Actual DLL Arity
imp 'EnumProcessModules' EnumProcessModules psapi 4
imp 'EnumProcessModulesEx' EnumProcessModulesEx psapi 5
imp 'EnumProcesses' EnumProcesses psapi 3
imp 'GetModuleBaseName' GetModuleBaseNameW psapi 4
imp 'GetProcessImageFileName' GetProcessImageFileNameW psapi 3
imp 'GetProcessMemoryInfo' GetProcessMemoryInfo psapi 3

View file

@ -78,6 +78,17 @@ int64_t CreateToolhelp32Snapshot(uint32_t dwFlags, uint32_t th32ProcessID);
bool32 Process32First(int64_t hSnapshot, struct NtProcessEntry32 *in_out_lppe);
bool32 Process32Next(int64_t hSnapshot, struct NtProcessEntry32 *out_lppe);
bool32 EnumProcesses(uint32_t *out_lpidProcess, uint32_t cb,
uint32_t *out_lpcbNeeded) paramsnonnull();
bool32 EnumProcessModules(int64_t hProcess, int64_t *out_lphModule, uint32_t cb,
uint32_t *out_lpcbNeeded) paramsnonnull();
bool32 EnumProcessModulesEx(int64_t hProcess, int64_t *out_lphModule,
uint32_t cb, uint32_t *out_lpcbNeeded,
uint32_t dwFilterFlag) paramsnonnull();
uint32_t GetModuleBaseName(int64_t hProcess, int64_t opt_hModule,
char16_t *out_lpBaseName, uint32_t nSize)
paramsnonnull();
#if ShouldUseMsabiAttribute()
#include "libc/nt/thunk/process.inc"
#endif /* ShouldUseMsabiAttribute() */

View file

@ -0,0 +1,18 @@
#include "libc/nt/codegen.h"
.imp psapi,__imp_EnumProcessModules,EnumProcessModules
.text.windows
.ftrace1
EnumProcessModules:
.ftrace2
#ifdef __x86_64__
push %rbp
mov %rsp,%rbp
mov __imp_EnumProcessModules(%rip),%rax
jmp __sysv2nt
#elif defined(__aarch64__)
mov x0,#0
ret
#endif
.endfn EnumProcessModules,globl
.previous

View file

@ -0,0 +1,18 @@
#include "libc/nt/codegen.h"
.imp psapi,__imp_EnumProcessModulesEx,EnumProcessModulesEx
.text.windows
.ftrace1
EnumProcessModulesEx:
.ftrace2
#ifdef __x86_64__
push %rbp
mov %rsp,%rbp
mov __imp_EnumProcessModulesEx(%rip),%rax
jmp __sysv2nt6
#elif defined(__aarch64__)
mov x0,#0
ret
#endif
.endfn EnumProcessModulesEx,globl
.previous

View file

@ -0,0 +1,18 @@
#include "libc/nt/codegen.h"
.imp psapi,__imp_EnumProcesses,EnumProcesses
.text.windows
.ftrace1
EnumProcesses:
.ftrace2
#ifdef __x86_64__
push %rbp
mov %rsp,%rbp
mov __imp_EnumProcesses(%rip),%rax
jmp __sysv2nt
#elif defined(__aarch64__)
mov x0,#0
ret
#endif
.endfn EnumProcesses,globl
.previous

View file

@ -0,0 +1,18 @@
#include "libc/nt/codegen.h"
.imp psapi,__imp_GetModuleBaseNameW,GetModuleBaseNameW
.text.windows
.ftrace1
GetModuleBaseName:
.ftrace2
#ifdef __x86_64__
push %rbp
mov %rsp,%rbp
mov __imp_GetModuleBaseNameW(%rip),%rax
jmp __sysv2nt
#elif defined(__aarch64__)
mov x0,#0
ret
#endif
.endfn GetModuleBaseName,globl
.previous

View file

@ -395,6 +395,7 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) {
__set_tls(tib);
__morph_tls();
__tls_enabled_set(true);
// get new main thread handle
// clear pending signals
tib->tib_sigpending = 0;
atomic_store_explicit(&__sig.pending, 0, memory_order_relaxed);
@ -404,9 +405,9 @@ textwindows int sys_fork_nt(uint32_t dwCreationFlags) {
if (ftrace_stackdigs) {
_weaken(__hook)(_weaken(ftrace_hook), _weaken(GetSymbolTable)());
}
// reset console
// reset core runtime services
__proc_wipe();
__keystroke_wipe();
// reset alarms
if (_weaken(__itimer_wipe)) {
_weaken(__itimer_wipe)();
}

View file

@ -27,21 +27,22 @@
#include "libc/intrin/dll.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/nt/files.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
#include "libc/proc/proc.internal.h"
#include "libc/runtime/internal.h"
#include "libc/runtime/runtime.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/sysv/consts/sig.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/tls.h"
int _fork(uint32_t dwCreationFlags) {
struct Dll *e;
struct CosmoTib *tib;
int ax, dx, tid, parent;
struct PosixThread *me, *other;
parent = __pid;
(void)parent;
BLOCK_SIGNALS;
@ -55,33 +56,55 @@ int _fork(uint32_t dwCreationFlags) {
ax = sys_fork_nt(dwCreationFlags);
}
if (!ax) {
// get new process id
if (!IsWindows()) {
dx = sys_getpid().ax;
} else {
dx = GetCurrentProcessId();
}
__pid = dx;
tib = __get_tls();
me = (struct PosixThread *)tib->tib_pthread;
dll_remove(&_pthread_list, &me->list);
// turn other threads into zombies
// we can't free() them since we're monopolizing all locks
// we assume the operating system already reclaimed system handles
struct CosmoTib *tib = __get_tls();
struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread;
dll_remove(&_pthread_list, &pt->list);
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
other = POSIXTHREAD_CONTAINER(e);
atomic_store_explicit(&other->pt_status, kPosixThreadZombie,
atomic_store_explicit(&POSIXTHREAD_CONTAINER(e)->pt_status,
kPosixThreadZombie, memory_order_relaxed);
atomic_store_explicit(&POSIXTHREAD_CONTAINER(e)->tib->tib_syshand, 0,
memory_order_relaxed);
}
dll_make_first(&_pthread_list, &me->list);
dll_make_first(&_pthread_list, &pt->list);
// get new main thread id
tid = IsLinux() || IsXnuSilicon() ? dx : sys_gettid();
atomic_store_explicit(&tib->tib_tid, tid, memory_order_relaxed);
atomic_store_explicit(&me->ptid, tid, memory_order_relaxed);
atomic_store_explicit(&me->pt_canceled, false, memory_order_relaxed);
atomic_store_explicit(&pt->ptid, tid, memory_order_relaxed);
// get new system thread handle
intptr_t syshand = 0;
if (IsXnuSilicon()) {
syshand = __syslib->__pthread_self();
} else if (IsWindows()) {
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &syshand, 0, false,
kNtDuplicateSameAccess);
}
atomic_store_explicit(&tib->tib_syshand, syshand, memory_order_relaxed);
// we can't be canceled if the canceler no longer exists
atomic_store_explicit(&pt->pt_canceled, false, memory_order_relaxed);
// run user fork callbacks
if (__threaded && _weaken(_pthread_onfork_child)) {
_weaken(_pthread_onfork_child)();
}
if (IsWindows()) {
__proc_wipe();
}
STRACE("fork() → 0 (child of %d)", parent);
} else {
// this is the parent process
if (__threaded && _weaken(_pthread_onfork_parent)) {
_weaken(_pthread_onfork_parent)();
}

View file

@ -27,6 +27,7 @@
#include "libc/intrin/weaken.h"
#include "libc/macros.internal.h"
#include "libc/nt/files.h"
#include "libc/nt/process.h"
#include "libc/nt/runtime.h"
#include "libc/nt/synchronization.h"
#include "libc/nt/thread.h"
@ -191,10 +192,11 @@ textstartup void __enable_tls(void) {
tib->tib_locale = (intptr_t)&__c_dot_utf8_locale;
tib->tib_pthread = (pthread_t)&_pthread_static;
if (IsWindows()) {
intptr_t threadhand, pseudo = GetCurrentThread();
DuplicateHandle(GetCurrentProcess(), pseudo, GetCurrentProcess(),
&threadhand, 0, true, kNtDuplicateSameAccess);
atomic_store_explicit(&tib->tib_syshand, threadhand, memory_order_relaxed);
intptr_t hThread;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(),
GetCurrentProcess(), &hThread, 0, false,
kNtDuplicateSameAccess);
atomic_store_explicit(&tib->tib_syshand, hThread, memory_order_relaxed);
} else if (IsXnuSilicon()) {
tib->tib_syshand = __syslib->__pthread_self();
}