diff options
Diffstat (limited to 'vere/pkg/noun/platform/windows')
-rw-r--r-- | vere/pkg/noun/platform/windows/rsignal.c | 167 | ||||
-rw-r--r-- | vere/pkg/noun/platform/windows/rsignal.h | 21 | ||||
-rw-r--r-- | vere/pkg/noun/platform/windows/veh_handler.c | 26 | ||||
-rw-r--r-- | vere/pkg/noun/platform/windows/veh_handler.h | 1 |
4 files changed, 215 insertions, 0 deletions
diff --git a/vere/pkg/noun/platform/windows/rsignal.c b/vere/pkg/noun/platform/windows/rsignal.c new file mode 100644 index 0000000..d927835 --- /dev/null +++ b/vere/pkg/noun/platform/windows/rsignal.c @@ -0,0 +1,167 @@ +#include "noun.h" +#include "rsignal.h" +#include <windows.h> + +int err_win_to_posix(DWORD winerr); + +// The current implementation of rsignal_ is single-threaded, +// but it can be extended to multi-threaded by replacing these +// static variables with a thread id-based hash map. +// +static __p_sig_fn_t _fns[SIG_COUNT]; +static volatile DWORD _tid; +static HANDLE _hvt; + +void rsignal_install_handler(int sig, __p_sig_fn_t fn) +{ + if (sig < 0 || sig >= SIG_COUNT) + return; + + DWORD newtid = GetCurrentThreadId(); + DWORD oldtid = InterlockedExchange((volatile LONG*)&_tid, newtid); + if (oldtid != 0 && oldtid != newtid) { + fprintf(stderr, "\r\nrsignal_install_handler: %lu -> %lu\r\n", oldtid, newtid); + return; + } + + __p_sig_fn_t oldfn = InterlockedExchangePointer((PVOID*)&_fns[sig], fn); + if (fn != 0 && oldfn != 0 && oldfn != fn) { + fprintf(stderr, "\r\nrsignal_install_handler: %p -> %p\r\n", oldfn, fn); + } +} + +void rsignal_deinstall_handler(int sig) +{ + rsignal_install_handler(sig, 0); +} + +void rsignal_raise(int sig) +{ + if (sig < 0 || sig >= SIG_COUNT) + return; + + __p_sig_fn_t oldfn = InterlockedExchangePointer((PVOID*)&_fns[sig], 0); + if (oldfn == 0) + return; + + if (_tid == GetCurrentThreadId()) { + oldfn(sig); + return; + } + + HANDLE hthread = OpenThread(THREAD_ALL_ACCESS, FALSE, _tid); + if (!hthread) { + fprintf(stderr, "\r\nrsignal_raise: OpenThread(%lu): %lu\r\n", _tid, GetLastError()); + return; + } + + if (SuspendThread(hthread) < 0) { + fprintf(stderr, "\r\nrsignal_raise: SuspendThread(%lu): %lu\r\n", _tid, GetLastError()); + goto cleanup; + } + + oldfn(sig); + + if (!ResumeThread(hthread)) { + fprintf(stderr, "\r\nrsignal_raise: ResumeThread(%lu): %lu\r\n", _tid, GetLastError()); + + // abort because the main thread is stuck + abort(); + } + +cleanup: + CloseHandle(hthread); +} + +static void _rsignal_vt_cb(PVOID param, BOOLEAN timedOut) +{ + rsignal_raise(SIGVTALRM); +} + +int rsignal_setitimer(int type, struct itimerval *in, struct itimerval *out) +{ + if (in == 0) { + errno = EFAULT; + return -1; + } + + if (type != ITIMER_VIRTUAL || out != 0) { + errno = ENOTSUP; + return -1; + } + + if (_hvt != NULL) { + DeleteTimerQueueTimer(NULL, _hvt, NULL); + _hvt = NULL; + } + + if (timerisset(&in->it_value) && !CreateTimerQueueTimer(&_hvt, NULL, _rsignal_vt_cb, NULL, + in->it_value.tv_sec * 1000 + in->it_value.tv_usec / 1000, + in->it_interval.tv_sec * 1000 + in->it_interval.tv_usec / 1000, 0)) + { + errno = err_win_to_posix(GetLastError()); + return -1; + } else { + return 0; + } +} + +// direct import from ntdll.dll +extern DWORD64 __imp_KiUserExceptionDispatcher; + +static void _rsignal_longjmp(intptr_t* builtin_jb) +{ + __builtin_longjmp((void**)builtin_jb, 1); +} + +void rsignal_post_longjmp(DWORD tid, intptr_t* builtin_jb) +{ + HANDLE hthread = OpenThread(THREAD_ALL_ACCESS, FALSE, tid); + if (!hthread) { + fprintf(stderr, "\r\nrsignal: OpenThread(%lu): %lu\r\n", tid, GetLastError()); + return; + } + + CONTEXT context; + context.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; + if (!GetThreadContext(hthread, &context)) { + fprintf(stderr, "\r\nrsignal: GetThreadContext(%lu): %lu\r\n", tid, GetLastError()); + goto cleanup; + } + + // see if the thread is currently handling a structured exception + // if so, let the handler (usually the libsigsegv handler) finish + // and set up the the signal to run at the exception resume point + // otherwise, passing a parameter to fn is completely unreliable + // + DWORD64 kibase; + PRUNTIME_FUNCTION ki = RtlLookupFunctionEntry(__imp_KiUserExceptionDispatcher, &kibase, NULL); + CONTEXT c = context; + while (1) + { + DWORD64 base, frame; + PRUNTIME_FUNCTION f = RtlLookupFunctionEntry(c.Rip, &base, NULL); + if (!f) break; + if (f == ki) + { + // KiUserExceptionDispatcher has a "bare" frame + // with $rsp pointing to the CONTEXT structure + // + ((PCONTEXT)c.Rsp)->Rip = (DWORD64)_rsignal_longjmp; + ((PCONTEXT)c.Rsp)->Rcx = (DWORD64)builtin_jb; + goto cleanup; + } + PVOID handler_data; + RtlVirtualUnwind(0, base, c.Rip, f, &c, &handler_data, &frame, NULL); + } + + context.Rip = (DWORD64)_rsignal_longjmp; + context.Rcx = (DWORD64)builtin_jb; + if (!SetThreadContext(hthread, &context)) { + fprintf(stderr, "\r\nrsignal: SetThreadContext(%lu): %lu\r\n", tid, GetLastError()); + goto cleanup; + } + +cleanup: + CloseHandle(hthread); +} diff --git a/vere/pkg/noun/platform/windows/rsignal.h b/vere/pkg/noun/platform/windows/rsignal.h new file mode 100644 index 0000000..998652c --- /dev/null +++ b/vere/pkg/noun/platform/windows/rsignal.h @@ -0,0 +1,21 @@ +#include "windows.h" + +#ifndef _RSIGNAL_H +#define _RSIGNAL_H + +#define rsignal_setjmp setjmp +#define rsignal_longjmp longjmp + +void rsignal_raise(int sig); +void rsignal_install_handler(int sig, __p_sig_fn_t fn); +void rsignal_deinstall_handler(int sig); +void rsignal_post_longjmp(unsigned long tid, intptr_t* builtin_jb); + +#define ITIMER_VIRTUAL 1 +struct itimerval { + struct timeval it_value, it_interval; +}; + +int rsignal_setitimer(int type, struct itimerval *in, struct itimerval *out); + +#endif//_RSIGNAL_H diff --git a/vere/pkg/noun/platform/windows/veh_handler.c b/vere/pkg/noun/platform/windows/veh_handler.c new file mode 100644 index 0000000..d2a035c --- /dev/null +++ b/vere/pkg/noun/platform/windows/veh_handler.c @@ -0,0 +1,26 @@ +#include "noun.h" +#include "rsignal.h" + +c3_i +u3m_fault(void* adr_v, c3_i ser_i); + +/* _windows_exception_filter: replaces libsigsegv on windows + using vectored exception handling + */ +LONG WINAPI +_windows_exception_filter(struct _EXCEPTION_POINTERS *ExceptionInfo) +{ + EXCEPTION_RECORD ExceptionRecord = *ExceptionInfo->ExceptionRecord; + if (ExceptionRecord.ExceptionCode == EXCEPTION_ACCESS_VIOLATION && + ExceptionRecord.ExceptionInformation[0] == 1 && + u3m_fault((void*)ExceptionRecord.ExceptionInformation[1], 1)) + { + return EXCEPTION_CONTINUE_EXECUTION; + } + + if (ExceptionRecord.ExceptionCode == EXCEPTION_STACK_OVERFLOW) { + rsignal_raise(SIGSTK); + } + + return EXCEPTION_CONTINUE_SEARCH; +} diff --git a/vere/pkg/noun/platform/windows/veh_handler.h b/vere/pkg/noun/platform/windows/veh_handler.h new file mode 100644 index 0000000..dfa4b69 --- /dev/null +++ b/vere/pkg/noun/platform/windows/veh_handler.h @@ -0,0 +1 @@ +LONG WINAPI _windows_exception_filter(struct _EXCEPTION_POINTERS *ExceptionInfo); |