1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
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);
}
|