/
mono-threads-posix-signals.c
297 lines (248 loc) · 8.14 KB
/
mono-threads-posix-signals.c
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
/**
* \file
* Shared facility for Posix signals support
*
* Author:
* Ludovic Henry (ludovic@gmail.com)
*
* (C) 2015 Xamarin, Inc
*/
#include <config.h>
#include <glib.h>
#include "mono-threads.h"
#if defined(USE_POSIX_BACKEND)
#include <errno.h>
#include <signal.h>
#ifdef HAVE_ANDROID_LEGACY_SIGNAL_INLINES_H
#include <android/legacy_signal_inlines.h>
#endif
#include "mono-threads-debug.h"
#include "mono-threads-coop.h"
gint
mono_threads_suspend_search_alternative_signal (void)
{
#if !defined (SIGRTMIN)
g_error ("signal search only works with RTMIN");
#else
int i;
/* we try to avoid SIGRTMIN and any one that might have been set already, see bug #75387 */
for (i = SIGRTMIN + 1; i < SIGRTMAX; ++i) {
struct sigaction sinfo;
sigaction (i, NULL, &sinfo);
if (sinfo.sa_handler == SIG_DFL && (void*)sinfo.sa_sigaction == (void*)SIG_DFL) {
return i;
}
}
g_error ("Could not find an available signal");
#endif
}
static int suspend_signal_num = -1;
static int restart_signal_num = -1;
static int abort_signal_num = -1;
static sigset_t suspend_signal_mask;
static sigset_t suspend_ack_signal_mask;
static void
signal_add_handler (int signo, void (*handler)(int, siginfo_t *, void *), int flags)
{
struct sigaction sa;
int ret;
sa.sa_sigaction = handler;
sigfillset (&sa.sa_mask);
sa.sa_flags = SA_SIGINFO | flags;
ret = sigaction (signo, &sa, NULL);
g_assert (ret != -1);
}
static int
abort_signal_get (void)
{
#if defined(HOST_ANDROID)
return SIGTTIN;
#elif defined (__OpenBSD__)
return SIGUSR1;
#elif defined (SIGRTMIN)
static int abort_signum = -1;
if (abort_signum == -1)
abort_signum = mono_threads_suspend_search_alternative_signal ();
return abort_signum;
#elif defined (SIGTTIN)
return SIGTTIN;
#else
g_error ("unable to get abort signal");
#endif
}
static int
suspend_signal_get (void)
{
#if defined(HOST_ANDROID)
return SIGPWR;
#elif defined (SIGRTMIN)
static int suspend_signum = -1;
if (suspend_signum == -1)
suspend_signum = mono_threads_suspend_search_alternative_signal ();
return suspend_signum;
#else
#if defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
return SIGXFSZ;
#else
return SIGPWR;
#endif
#endif
}
static int
restart_signal_get (void)
{
#if defined(HOST_ANDROID)
return SIGXCPU;
#elif defined (SIGRTMIN)
static int restart_signum = -1;
if (restart_signum == -1)
restart_signum = mono_threads_suspend_search_alternative_signal ();
return restart_signum;
#else
return SIGXCPU;
#endif
}
static void
restart_signal_handler (int _dummy, siginfo_t *_info, void *context)
{
MonoThreadInfo *info;
int old_errno = errno;
info = mono_thread_info_current ();
info->signal = restart_signal_num;
errno = old_errno;
}
static void
suspend_signal_handler (int _dummy, siginfo_t *info, void *context)
{
int old_errno = errno;
int hp_save_index = mono_hazard_pointer_save_for_signal_handler ();
MonoThreadInfo *current = mono_thread_info_current ();
THREADS_SUSPEND_DEBUG ("SIGNAL HANDLER FOR %p [%p]\n", mono_thread_info_get_tid (current), (void*)current->native_handle);
if (current->syscall_break_signal) {
current->syscall_break_signal = FALSE;
THREADS_SUSPEND_DEBUG ("\tsyscall break for %p\n", mono_thread_info_get_tid (current));
mono_threads_notify_initiator_of_abort (current);
goto done;
}
/* Have we raced with self suspend? */
if (!mono_threads_transition_finish_async_suspend (current)) {
current->suspend_can_continue = TRUE;
THREADS_SUSPEND_DEBUG ("\tlost race with self suspend %p\n", mono_thread_info_get_tid (current));
/* Under full preemptive suspend, there is no self suspension,
* so no race.
*
* Under full cooperative suspend, there is no signal, so no
* race.
*
* Under hybrid a blocking thread could race done/abort
* blocking with the signal handler running: if the done/abort
* blocking win, they will wait for a resume - the signal
* handler should notify the suspend initiator that the thread
* suspended, and then immediately return and let the thread
* continue waiting on the resume semaphore.
*/
g_assert (mono_threads_is_hybrid_suspension_enabled ());
mono_threads_notify_initiator_of_suspend (current);
goto done;
}
/*
* If the thread is starting, then thread_state_init_from_sigctx returns FALSE,
* as the thread might have been attached without the domain or lmf having been
* initialized yet.
*
* One way to fix that is to keep the thread suspended (wait for the restart
* signal), and make sgen aware that even if a thread might be suspended, there
* would be cases where you cannot scan its stack/registers. That would in fact
* consist in removing the async suspend compensation, and treat the case directly
* in sgen. That's also how it was done in the sgen specific suspend code.
*/
/* thread_state_init_from_sigctx return FALSE if the current thread is starting or detaching and suspend can't continue. */
current->suspend_can_continue = mono_threads_get_runtime_callbacks ()->thread_state_init_from_sigctx (¤t->thread_saved_state [ASYNC_SUSPEND_STATE_INDEX], context);
if (!current->suspend_can_continue)
THREADS_SUSPEND_DEBUG ("\tThread is starting or detaching, failed to capture state %p\n", mono_thread_info_get_tid (current));
/*
Block the restart signal.
We need to block the restart signal while posting to the suspend_ack semaphore or we race to sigsuspend,
which might miss the signal and get stuck.
*/
pthread_sigmask (SIG_BLOCK, &suspend_ack_signal_mask, NULL);
/* We're done suspending */
mono_threads_notify_initiator_of_suspend (current);
do {
current->signal = 0;
sigsuspend (&suspend_signal_mask);
} while (current->signal != restart_signal_num);
/* Unblock the restart signal. */
pthread_sigmask (SIG_UNBLOCK, &suspend_ack_signal_mask, NULL);
if (current->async_target) {
#if MONO_ARCH_HAS_MONO_CONTEXT
MonoContext tmp = current->thread_saved_state [ASYNC_SUSPEND_STATE_INDEX].ctx;
mono_threads_get_runtime_callbacks ()->setup_async_callback (&tmp, current->async_target, current->user_data);
current->user_data = NULL;
current->async_target = NULL;
mono_monoctx_to_sigctx (&tmp, context);
#else
g_error ("The new interruption machinery requires a working mono-context");
#endif
}
/* We're done resuming */
mono_threads_notify_initiator_of_resume (current);
done:
mono_hazard_pointer_restore_for_signal_handler (hp_save_index);
errno = old_errno;
}
void
mono_threads_suspend_init_signals (void)
{
sigset_t signal_set;
sigemptyset (&signal_set);
/* add suspend signal */
suspend_signal_num = suspend_signal_get ();
signal_add_handler (suspend_signal_num, suspend_signal_handler, SA_RESTART);
sigaddset (&signal_set, suspend_signal_num);
/* add restart signal */
restart_signal_num = restart_signal_get ();
sigfillset (&suspend_signal_mask);
sigdelset (&suspend_signal_mask, restart_signal_num);
sigemptyset (&suspend_ack_signal_mask);
sigaddset (&suspend_ack_signal_mask, restart_signal_num);
signal_add_handler (restart_signal_num, restart_signal_handler, SA_RESTART);
sigaddset (&signal_set, restart_signal_num);
/* add abort signal */
abort_signal_num = abort_signal_get ();
/* the difference between abort and suspend here is made by not
* passing SA_RESTART, meaning we won't restart the syscall when
* receiving a signal */
signal_add_handler (abort_signal_num, suspend_signal_handler, 0);
sigaddset (&signal_set, abort_signal_num);
/* ensure all the new signals are unblocked */
sigprocmask (SIG_UNBLOCK, &signal_set, NULL);
/*
On 32bits arm Android, signals with values >=32 are not usable as their headers ship a broken sigset_t.
See 5005c6f3fbc1da584c6a550281689cc23f59fe6d for more details.
*/
#ifdef HOST_ANDROID
g_assert (suspend_signal_num < 32);
g_assert (restart_signal_num < 32);
g_assert (abort_signal_num < 32);
#endif
}
gint
mono_threads_suspend_get_suspend_signal (void)
{
g_assert (suspend_signal_num != -1);
return suspend_signal_num;
}
gint
mono_threads_suspend_get_restart_signal (void)
{
g_assert (restart_signal_num != -1);
return restart_signal_num;
}
gint
mono_threads_suspend_get_abort_signal (void)
{
g_assert (abort_signal_num != -1);
return abort_signal_num;
}
#endif /* defined(USE_POSIX_BACKEND) */