5f762c83abe784c9b66e1457287e3e34ea1a2ac1
[mono.git] / mono / utils / mono-threads-posix-signals.c
1 /**
2  * \file
3  * Shared facility for Posix signals support
4  *
5  * Author:
6  *      Ludovic Henry (ludovic@gmail.com)
7  *
8  * (C) 2015 Xamarin, Inc
9  */
10
11 #include <config.h>
12 #include <glib.h>
13
14 #include "mono-threads.h"
15
16 #if defined(USE_POSIX_BACKEND)
17
18 #include <errno.h>
19 #include <signal.h>
20
21 #include "mono-threads-debug.h"
22
23 gint
24 mono_threads_suspend_search_alternative_signal (void)
25 {
26 #if !defined (SIGRTMIN)
27         g_error ("signal search only works with RTMIN");
28 #else
29         int i;
30         /* we try to avoid SIGRTMIN and any one that might have been set already, see bug #75387 */
31         for (i = SIGRTMIN + 1; i < SIGRTMAX; ++i) {
32                 struct sigaction sinfo;
33                 sigaction (i, NULL, &sinfo);
34                 if (sinfo.sa_handler == SIG_DFL && (void*)sinfo.sa_sigaction == (void*)SIG_DFL) {
35                         return i;
36                 }
37         }
38         g_error ("Could not find an available signal");
39 #endif
40 }
41
42 static int suspend_signal_num = -1;
43 static int restart_signal_num = -1;
44 static int abort_signal_num = -1;
45
46 static sigset_t suspend_signal_mask;
47 static sigset_t suspend_ack_signal_mask;
48
49 static void
50 signal_add_handler (int signo, void (*handler)(int, siginfo_t *, void *), int flags)
51 {
52         struct sigaction sa;
53         int ret;
54
55         sa.sa_sigaction = handler;
56         sigfillset (&sa.sa_mask);
57         sa.sa_flags = SA_SIGINFO | flags;
58         ret = sigaction (signo, &sa, NULL);
59         g_assert (ret != -1);
60 }
61
62 static int
63 abort_signal_get (void)
64 {
65 #if defined(HOST_ANDROID)
66         return SIGTTIN;
67 #elif defined (SIGRTMIN)
68         static int abort_signum = -1;
69         if (abort_signum == -1)
70                 abort_signum = mono_threads_suspend_search_alternative_signal ();
71         return abort_signum;
72 #elif defined (SIGTTIN)
73         return SIGTTIN;
74 #else
75         g_error ("unable to get abort signal");
76 #endif
77 }
78
79 static int
80 suspend_signal_get (void)
81 {
82 #if defined(HOST_ANDROID)
83         return SIGPWR;
84 #elif defined (SIGRTMIN)
85         static int suspend_signum = -1;
86         if (suspend_signum == -1)
87                 suspend_signum = mono_threads_suspend_search_alternative_signal ();
88         return suspend_signum;
89 #else
90 #if defined(__APPLE__) || defined(__OpenBSD__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
91         return SIGXFSZ;
92 #else
93         return SIGPWR;
94 #endif
95 #endif
96 }
97
98 static int
99 restart_signal_get (void)
100 {
101 #if defined(HOST_ANDROID)
102         return SIGXCPU;
103 #elif defined (SIGRTMIN)
104         static int restart_signum = -1;
105         if (restart_signum == -1)
106                 restart_signum = mono_threads_suspend_search_alternative_signal ();
107         return restart_signum;
108 #else
109         return SIGXCPU;
110 #endif
111 }
112
113 static void
114 restart_signal_handler (int _dummy, siginfo_t *_info, void *context)
115 {
116         MonoThreadInfo *info;
117         int old_errno = errno;
118
119         info = mono_thread_info_current ();
120         info->signal = restart_signal_num;
121         errno = old_errno;
122 }
123
124 static void
125 suspend_signal_handler (int _dummy, siginfo_t *info, void *context)
126 {
127         int old_errno = errno;
128         int hp_save_index = mono_hazard_pointer_save_for_signal_handler ();
129
130         MonoThreadInfo *current = mono_thread_info_current ();
131
132         THREADS_SUSPEND_DEBUG ("SIGNAL HANDLER FOR %p [%p]\n", mono_thread_info_get_tid (current), (void*)current->native_handle);
133         if (current->syscall_break_signal) {
134                 current->syscall_break_signal = FALSE;
135                 THREADS_SUSPEND_DEBUG ("\tsyscall break for %p\n", mono_thread_info_get_tid (current));
136                 mono_threads_notify_initiator_of_abort (current);
137                 goto done;
138         }
139
140         /* Have we raced with self suspend? */
141         if (!mono_threads_transition_finish_async_suspend (current)) {
142                 current->suspend_can_continue = TRUE;
143                 THREADS_SUSPEND_DEBUG ("\tlost race with self suspend %p\n", mono_thread_info_get_tid (current));
144                 goto done;
145         }
146
147         /*
148          * If the thread is starting, then thread_state_init_from_sigctx returns FALSE,
149          * as the thread might have been attached without the domain or lmf having been
150          * initialized yet.
151          *
152          * One way to fix that is to keep the thread suspended (wait for the restart
153          * signal), and make sgen aware that even if a thread might be suspended, there
154          * would be cases where you cannot scan its stack/registers. That would in fact
155          * consist in removing the async suspend compensation, and treat the case directly
156          * in sgen. That's also how it was done in the sgen specific suspend code.
157          */
158
159         /* thread_state_init_from_sigctx return FALSE if the current thread is starting or detaching and suspend can't continue. */
160         current->suspend_can_continue = mono_threads_get_runtime_callbacks ()->thread_state_init_from_sigctx (&current->thread_saved_state [ASYNC_SUSPEND_STATE_INDEX], context);
161
162         if (!current->suspend_can_continue)
163                 THREADS_SUSPEND_DEBUG ("\tThread is starting or detaching, failed to capture state %p\n", mono_thread_info_get_tid (current));
164
165         /*
166         Block the restart signal.
167         We need to block the restart signal while posting to the suspend_ack semaphore or we race to sigsuspend,
168         which might miss the signal and get stuck.
169         */
170         pthread_sigmask (SIG_BLOCK, &suspend_ack_signal_mask, NULL);
171
172         /* We're done suspending */
173         mono_threads_notify_initiator_of_suspend (current);
174
175         do {
176                 current->signal = 0;
177                 sigsuspend (&suspend_signal_mask);
178         } while (current->signal != restart_signal_num);
179
180         /* Unblock the restart signal. */
181         pthread_sigmask (SIG_UNBLOCK, &suspend_ack_signal_mask, NULL);
182
183         if (current->async_target) {
184 #if MONO_ARCH_HAS_MONO_CONTEXT
185                 MonoContext tmp = current->thread_saved_state [ASYNC_SUSPEND_STATE_INDEX].ctx;
186                 mono_threads_get_runtime_callbacks ()->setup_async_callback (&tmp, current->async_target, current->user_data);
187                 current->user_data = NULL;
188                 current->async_target = NULL;
189                 mono_monoctx_to_sigctx (&tmp, context);
190 #else
191                 g_error ("The new interruption machinery requires a working mono-context");
192 #endif
193         }
194
195         /* We're done resuming */
196         mono_threads_notify_initiator_of_resume (current);
197
198 done:
199         mono_hazard_pointer_restore_for_signal_handler (hp_save_index);
200         errno = old_errno;
201 }
202
203 void
204 mono_threads_suspend_init_signals (void)
205 {
206         sigset_t signal_set;
207
208         sigemptyset (&signal_set);
209
210         /* add suspend signal */
211         suspend_signal_num = suspend_signal_get ();
212
213         signal_add_handler (suspend_signal_num, suspend_signal_handler, SA_RESTART);
214
215         sigaddset (&signal_set, suspend_signal_num);
216
217         /* add restart signal */
218         restart_signal_num = restart_signal_get ();
219
220         sigfillset (&suspend_signal_mask);
221         sigdelset (&suspend_signal_mask, restart_signal_num);
222
223         sigemptyset (&suspend_ack_signal_mask);
224         sigaddset (&suspend_ack_signal_mask, restart_signal_num);
225
226         signal_add_handler (restart_signal_num, restart_signal_handler, SA_RESTART);
227
228         sigaddset (&signal_set, restart_signal_num);
229
230         /* add abort signal */
231         abort_signal_num = abort_signal_get ();
232
233         /* the difference between abort and suspend here is made by not
234          * passing SA_RESTART, meaning we won't restart the syscall when
235          * receiving a signal */
236         signal_add_handler (abort_signal_num, suspend_signal_handler, 0);
237
238         sigaddset (&signal_set, abort_signal_num);
239
240         /* ensure all the new signals are unblocked */
241         sigprocmask (SIG_UNBLOCK, &signal_set, NULL);
242
243         /*
244         On 32bits arm Android, signals with values >=32 are not usable as their headers ship a broken sigset_t.
245         See 5005c6f3fbc1da584c6a550281689cc23f59fe6d for more details.
246         */
247 #ifdef HOST_ANDROID
248         g_assert (suspend_signal_num < 32);
249         g_assert (restart_signal_num < 32);
250         g_assert (abort_signal_num < 32);
251 #endif
252 }
253
254 gint
255 mono_threads_suspend_get_suspend_signal (void)
256 {
257         g_assert (suspend_signal_num != -1);
258         return suspend_signal_num;
259 }
260
261 gint
262 mono_threads_suspend_get_restart_signal (void)
263 {
264         g_assert (restart_signal_num != -1);
265         return restart_signal_num;
266 }
267
268 gint
269 mono_threads_suspend_get_abort_signal (void)
270 {
271         g_assert (abort_signal_num != -1);
272         return abort_signal_num;
273 }
274
275 #endif /* defined(USE_POSIX_BACKEND) */