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