slow-waitpid.c revision 1.1.1.5 1 1.1 christos /* This testcase is part of GDB, the GNU debugger.
2 1.1 christos
3 1.1.1.4 christos Copyright 2018-2024 Free Software Foundation, Inc.
4 1.1 christos
5 1.1 christos This program is free software; you can redistribute it and/or modify
6 1.1 christos it under the terms of the GNU General Public License as published by
7 1.1 christos the Free Software Foundation; either version 3 of the License, or
8 1.1 christos (at your option) any later version.
9 1.1 christos
10 1.1 christos This program is distributed in the hope that it will be useful,
11 1.1 christos but WITHOUT ANY WARRANTY; without even the implied warranty of
12 1.1 christos MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 1.1 christos GNU General Public License for more details.
14 1.1 christos
15 1.1 christos You should have received a copy of the GNU General Public License
16 1.1 christos along with this program. If not, see <http://www.gnu.org/licenses/>. */
17 1.1 christos
18 1.1 christos /* This file contains a library that can be preloaded into GDB on Linux
19 1.1 christos using the LD_PRELOAD technique.
20 1.1 christos
21 1.1 christos The library intercepts calls to WAITPID and SIGSUSPEND in order to
22 1.1.1.5 christos simulate the behavior of a heavily loaded kernel.
23 1.1 christos
24 1.1 christos When GDB wants to stop all threads in an inferior each thread is sent a
25 1.1 christos SIGSTOP, GDB will then wait for the signal to be received by the thread
26 1.1 christos with a waitpid call.
27 1.1 christos
28 1.1 christos If the kernel is slow in either delivering the signal, or making the
29 1.1 christos result available to the waitpid call then GDB will enter a sigsuspend
30 1.1 christos call in order to wait for the inferior threads to change state, this is
31 1.1 christos signalled to GDB with a SIGCHLD.
32 1.1 christos
33 1.1 christos A bug in GDB meant that in some cases we would deadlock during this
34 1.1 christos process. This was rarely seen as the kernel is usually quick at
35 1.1 christos delivering signals and making the results available to waitpid, so quick
36 1.1 christos that GDB would gather the statuses from all inferior threads in the
37 1.1 christos original pass.
38 1.1 christos
39 1.1 christos The idea in this library is to rate limit calls to waitpid (where pid is
40 1.1 christos -1 and the WNOHANG option is set) so that only 1 per second can return
41 1.1 christos an answer. Any additional calls will report that no threads are
42 1.1.1.5 christos currently ready. This should match the behavior we see on a slow
43 1.1 christos kernel.
44 1.1 christos
45 1.1 christos However, given that usually when using this library, the kernel does
46 1.1 christos have the waitpid result ready this means that the kernel will never send
47 1.1 christos GDB a SIGCHLD. This means that when GDB enters sigsuspend it will block
48 1.1 christos forever. Alternatively, if GDB enters its polling loop the lack of
49 1.1 christos SIGCHLD means that we will never see an event on the child threads. To
50 1.1 christos resolve these problems the library intercepts calls to sigsuspend and
51 1.1 christos forces the call to exit if there is a pending waitpid result. Also,
52 1.1 christos when we know that there's a waitpid result that we've ignored, we create
53 1.1 christos a new thread which, after a short delay, will send GDB a SIGCHLD. */
54 1.1 christos
55 1.1 christos #define _GNU_SOURCE
56 1.1 christos
57 1.1 christos #include <sys/types.h>
58 1.1 christos #include <sys/wait.h>
59 1.1 christos #include <sys/time.h>
60 1.1 christos #include <stdlib.h>
61 1.1 christos #include <stdio.h>
62 1.1 christos #include <dlfcn.h>
63 1.1 christos #include <string.h>
64 1.1 christos #include <stdarg.h>
65 1.1 christos #include <signal.h>
66 1.1 christos #include <errno.h>
67 1.1 christos #include <pthread.h>
68 1.1 christos #include <unistd.h>
69 1.1 christos
70 1.1 christos /* Logging. */
71 1.1 christos
72 1.1 christos static void
73 1.1 christos log_msg (const char *fmt, ...)
74 1.1 christos {
75 1.1 christos #ifdef LOGGING
76 1.1 christos va_list ap;
77 1.1 christos
78 1.1 christos va_start (ap, fmt);
79 1.1 christos vfprintf (stderr, fmt, ap);
80 1.1 christos va_end (ap);
81 1.1 christos #endif /* LOGGING */
82 1.1 christos }
83 1.1 christos
84 1.1 christos /* Error handling, message and exit. */
85 1.1 christos
86 1.1 christos static void
87 1.1 christos error (const char *fmt, ...)
88 1.1 christos {
89 1.1 christos va_list ap;
90 1.1 christos
91 1.1 christos va_start (ap, fmt);
92 1.1 christos vfprintf (stderr, fmt, ap);
93 1.1 christos va_end (ap);
94 1.1 christos
95 1.1 christos exit (EXIT_FAILURE);
96 1.1 christos }
97 1.1 christos
98 1.1 christos /* Cache the result of a waitpid call that has not been reported back to
99 1.1 christos GDB yet. We only ever cache a single result. Once we have a result
100 1.1 christos cached then later calls to waitpid with the WNOHANG option will return a
101 1.1 christos result of 0. */
102 1.1 christos
103 1.1 christos static struct
104 1.1 christos {
105 1.1 christos /* Flag to indicate when we have a result cached. */
106 1.1 christos int cached_p;
107 1.1 christos
108 1.1 christos /* The cached result fields from a waitpid call. */
109 1.1 christos pid_t pid;
110 1.1 christos int wstatus;
111 1.1 christos } cached_wait_status;
112 1.1 christos
113 1.1 christos /* Lock to hold when modifying SIGNAL_THREAD_ACTIVE_P. */
114 1.1 christos
115 1.1 christos static pthread_mutex_t thread_creation_lock_obj = PTHREAD_MUTEX_INITIALIZER;
116 1.1 christos #define thread_creation_lock (&thread_creation_lock_obj)
117 1.1 christos
118 1.1 christos /* This flag is only modified while holding the THREAD_CREATION_LOCK mutex.
119 1.1 christos When this flag is true then there is a signal thread alive that will be
120 1.1 christos sending a SIGCHLD at some point in the future. */
121 1.1 christos
122 1.1 christos static int signal_thread_active_p;
123 1.1 christos
124 1.1 christos /* When we last allowed a waitpid to complete. */
125 1.1 christos
126 1.1 christos static struct timeval last_waitpid_time = { 0, 0 };
127 1.1 christos
128 1.1 christos /* The number of seconds that must elapse between calls to waitpid where
129 1.1 christos the pid is -1 and the WNOHANG option is set. If calls occur faster than
130 1.1 christos this then we force a result of 0 to be returned from waitpid. */
131 1.1 christos
132 1.1 christos #define WAITPID_MIN_TIME (1)
133 1.1 christos
134 1.1 christos /* Return true (non-zero) if we should skip this call to waitpid, or false
135 1.1 christos (zero) if this waitpid call should be handled with a call to the "real"
136 1.1 christos waitpid function. Allows 1 waitpid call per second. */
137 1.1 christos
138 1.1 christos static int
139 1.1 christos should_skip_waitpid (void)
140 1.1 christos {
141 1.1 christos struct timeval *tv = &last_waitpid_time;
142 1.1 christos if (tv->tv_sec == 0)
143 1.1 christos {
144 1.1 christos if (gettimeofday (tv, NULL) < 0)
145 1.1 christos error ("error: gettimeofday failed\n");
146 1.1 christos return 0; /* Don't skip. */
147 1.1 christos }
148 1.1 christos else
149 1.1 christos {
150 1.1 christos struct timeval new_tv;
151 1.1 christos
152 1.1 christos if (gettimeofday (&new_tv, NULL) < 0)
153 1.1 christos error ("error: gettimeofday failed\n");
154 1.1 christos
155 1.1 christos if ((new_tv.tv_sec - tv->tv_sec) < WAITPID_MIN_TIME)
156 1.1 christos return 1; /* Skip. */
157 1.1 christos
158 1.1 christos *tv = new_tv;
159 1.1 christos }
160 1.1 christos
161 1.1 christos /* Don't skip. */
162 1.1 christos return 0;
163 1.1 christos }
164 1.1 christos
165 1.1 christos /* Perform a real waitpid call. */
166 1.1 christos
167 1.1 christos static pid_t
168 1.1 christos real_waitpid (pid_t pid, int *wstatus, int options)
169 1.1 christos {
170 1.1 christos typedef pid_t (*fptr_t) (pid_t, int *, int);
171 1.1 christos static fptr_t real_func = NULL;
172 1.1 christos
173 1.1 christos if (real_func == NULL)
174 1.1 christos {
175 1.1 christos real_func = dlsym (RTLD_NEXT, "waitpid");
176 1.1 christos if (real_func == NULL)
177 1.1 christos error ("error: failed to find real waitpid\n");
178 1.1 christos }
179 1.1 christos
180 1.1 christos return (*real_func) (pid, wstatus, options);
181 1.1 christos }
182 1.1 christos
183 1.1 christos /* Thread worker created when we cache a waitpid result. Delays for a
184 1.1 christos short period of time and then sends SIGCHLD to the GDB process. This
185 1.1 christos should trigger GDB to call waitpid again, at which point we will make
186 1.1 christos the cached waitpid result available. */
187 1.1 christos
188 1.1 christos static void*
189 1.1 christos send_sigchld_thread (void *arg)
190 1.1 christos {
191 1.1 christos /* Delay one second longer than WAITPID_MIN_TIME so that there can be no
192 1.1 christos chance that a call to SHOULD_SKIP_WAITPID will return true once the
193 1.1 christos SIGCHLD is delivered and handled. */
194 1.1 christos sleep (WAITPID_MIN_TIME + 1);
195 1.1 christos
196 1.1 christos pthread_mutex_lock (thread_creation_lock);
197 1.1 christos signal_thread_active_p = 0;
198 1.1 christos
199 1.1 christos if (cached_wait_status.cached_p)
200 1.1 christos {
201 1.1 christos log_msg ("signal-thread: sending SIGCHLD\n");
202 1.1 christos kill (getpid (), SIGCHLD);
203 1.1 christos }
204 1.1 christos
205 1.1 christos pthread_mutex_unlock (thread_creation_lock);
206 1.1 christos return NULL;
207 1.1 christos }
208 1.1 christos
209 1.1 christos /* The waitpid entry point function. */
210 1.1 christos
211 1.1 christos pid_t
212 1.1 christos waitpid (pid_t pid, int *wstatus, int options)
213 1.1 christos {
214 1.1 christos log_msg ("waitpid: waitpid (%d, %p, 0x%x)\n", pid, wstatus, options);
215 1.1 christos
216 1.1 christos if ((options & WNOHANG) != 0
217 1.1 christos && pid == -1
218 1.1 christos && should_skip_waitpid ())
219 1.1 christos {
220 1.1 christos if (!cached_wait_status.cached_p)
221 1.1 christos {
222 1.1 christos /* Do the waitpid call, but hold the result back. */
223 1.1 christos pid_t tmp_pid;
224 1.1 christos int tmp_wstatus;
225 1.1 christos
226 1.1 christos tmp_pid = real_waitpid (-1, &tmp_wstatus, options);
227 1.1 christos if (tmp_pid > 0)
228 1.1 christos {
229 1.1 christos log_msg ("waitpid: delaying waitpid result (pid = %d)\n",
230 1.1 christos tmp_pid);
231 1.1 christos
232 1.1 christos /* Cache the result. */
233 1.1 christos cached_wait_status.pid = tmp_pid;
234 1.1 christos cached_wait_status.wstatus = tmp_wstatus;
235 1.1 christos cached_wait_status.cached_p = 1;
236 1.1 christos
237 1.1 christos /* Is there a thread around that will be sending a signal in
238 1.1 christos the near future? The prevents us from creating one
239 1.1 christos thread per call to waitpid when the calls occur in a
240 1.1 christos sequence. */
241 1.1 christos pthread_mutex_lock (thread_creation_lock);
242 1.1 christos if (!signal_thread_active_p)
243 1.1 christos {
244 1.1 christos sigset_t old_ss, new_ss;
245 1.1 christos pthread_t thread_id;
246 1.1 christos pthread_attr_t attr;
247 1.1 christos
248 1.1 christos /* Create the new signal sending thread in detached
249 1.1 christos state. This means that the thread doesn't need to be
250 1.1 christos pthread_join'ed. Which is fine as there's no result
251 1.1 christos we care about. */
252 1.1 christos pthread_attr_init (&attr);
253 1.1 christos pthread_attr_setdetachstate (&attr, PTHREAD_CREATE_DETACHED);
254 1.1 christos
255 1.1 christos /* Ensure the signal sending thread has all signals
256 1.1 christos blocked. We don't want any signals to GDB to be
257 1.1 christos handled in that thread. */
258 1.1 christos sigfillset (&new_ss);
259 1.1 christos sigprocmask (SIG_BLOCK, &new_ss, &old_ss);
260 1.1 christos
261 1.1 christos log_msg ("waitpid: spawn thread to signal us\n");
262 1.1 christos if (pthread_create (&thread_id, &attr,
263 1.1 christos send_sigchld_thread, NULL) != 0)
264 1.1 christos error ("error: pthread_create failed\n");
265 1.1 christos
266 1.1 christos signal_thread_active_p = 1;
267 1.1 christos sigprocmask (SIG_SETMASK, &old_ss, NULL);
268 1.1 christos pthread_attr_destroy (&attr);
269 1.1 christos }
270 1.1 christos
271 1.1 christos pthread_mutex_unlock (thread_creation_lock);
272 1.1 christos }
273 1.1 christos }
274 1.1 christos
275 1.1 christos log_msg ("waitpid: skipping\n");
276 1.1 christos return 0;
277 1.1 christos }
278 1.1 christos
279 1.1 christos /* If we have a cached result that is a suitable reply for this call to
280 1.1 christos waitpid then send that cached result back now. */
281 1.1 christos if (cached_wait_status.cached_p
282 1.1 christos && (pid == -1 || pid == cached_wait_status.pid))
283 1.1 christos {
284 1.1 christos pid_t pid;
285 1.1 christos
286 1.1 christos pid = cached_wait_status.pid;
287 1.1 christos log_msg ("waitpid: return cached result (%d)\n", pid);
288 1.1 christos *wstatus = cached_wait_status.wstatus;
289 1.1 christos cached_wait_status.cached_p = 0;
290 1.1 christos return pid;
291 1.1 christos }
292 1.1 christos
293 1.1 christos log_msg ("waitpid: real waitpid call\n");
294 1.1 christos return real_waitpid (pid, wstatus, options);
295 1.1 christos }
296 1.1 christos
297 1.1 christos /* Perform a real sigsuspend call. */
298 1.1 christos
299 1.1 christos static int
300 1.1 christos real_sigsuspend (const sigset_t *mask)
301 1.1 christos {
302 1.1 christos typedef int (*fptr_t) (const sigset_t *);
303 1.1 christos static fptr_t real_func = NULL;
304 1.1 christos
305 1.1 christos if (real_func == NULL)
306 1.1 christos {
307 1.1 christos real_func = dlsym (RTLD_NEXT, "sigsuspend");
308 1.1 christos if (real_func == NULL)
309 1.1 christos error ("error: failed to find real sigsuspend\n");
310 1.1 christos }
311 1.1 christos
312 1.1 christos return (*real_func) (mask);
313 1.1 christos }
314 1.1 christos
315 1.1 christos /* The sigsuspend entry point function. */
316 1.1 christos
317 1.1 christos int
318 1.1 christos sigsuspend (const sigset_t *mask)
319 1.1 christos {
320 1.1 christos log_msg ("sigsuspend: sigsuspend (0x%p)\n", ((void *) mask));
321 1.1 christos
322 1.1 christos /* If SIGCHLD is _not_ in MASK, and is therefore deliverable, then if we
323 1.1 christos have a pending wait status pretend that a signal arrived. We will
324 1.1 christos have a thread alive that is going to deliver a signal but doing this
325 1.1 christos will boost the speed as we don't have to wait for a signal. If the
326 1.1 christos signal ends up being delivered then it should be harmless, we'll just
327 1.1 christos perform an additional waitpid call. */
328 1.1 christos if (!sigismember (mask, SIGCHLD))
329 1.1 christos {
330 1.1 christos if (cached_wait_status.cached_p)
331 1.1 christos {
332 1.1 christos log_msg ("sigsuspend: interrupt for cached waitstatus\n");
333 1.1 christos last_waitpid_time.tv_sec = 0;
334 1.1 christos last_waitpid_time.tv_usec = 0;
335 1.1 christos errno = EINTR;
336 1.1 christos return -1;
337 1.1 christos }
338 1.1 christos }
339 1.1 christos
340 1.1 christos log_msg ("sigsuspend: real sigsuspend call\n");
341 1.1 christos return real_sigsuspend (mask);
342 1.1 christos }
343