]>
Commit | Line | Data |
---|---|---|
7010835a AB |
1 | /* This testcase is part of GDB, the GNU debugger. |
2 | ||
3 | Copyright 2018 Free Software Foundation, Inc. | |
4 | ||
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. | |
9 | ||
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. | |
14 | ||
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/>. */ | |
17 | ||
18 | /* This file contains a library that can be preloaded into GDB on Linux | |
19 | using the LD_PRELOAD technique. | |
20 | ||
21 | The library intercepts calls to WAITPID and SIGSUSPEND in order to | |
22 | simulate the behaviour of a heavily loaded kernel. | |
23 | ||
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. | |
27 | ||
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. | |
32 | ||
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. | |
38 | ||
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 behaviour we see on a slow | |
43 | kernel. | |
44 | ||
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. */ | |
54 | ||
55 | #define _GNU_SOURCE | |
56 | ||
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> | |
69 | ||
70 | /* Logging. */ | |
71 | ||
72 | static void | |
73 | log_msg (const char *fmt, ...) | |
74 | { | |
75 | #ifdef LOGGING | |
76 | va_list ap; | |
77 | ||
78 | va_start (ap, fmt); | |
79 | vfprintf (stderr, fmt, ap); | |
80 | va_end (ap); | |
81 | #endif /* LOGGING */ | |
82 | } | |
83 | ||
84 | /* Error handling, message and exit. */ | |
85 | ||
86 | static void | |
87 | error (const char *fmt, ...) | |
88 | { | |
89 | va_list ap; | |
90 | ||
91 | va_start (ap, fmt); | |
92 | vfprintf (stderr, fmt, ap); | |
93 | va_end (ap); | |
94 | ||
95 | exit (EXIT_FAILURE); | |
96 | } | |
97 | ||
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. */ | |
102 | ||
103 | static struct | |
104 | { | |
105 | /* Flag to indicate when we have a result cached. */ | |
106 | int cached_p; | |
107 | ||
108 | /* The cached result fields from a waitpid call. */ | |
109 | pid_t pid; | |
110 | int wstatus; | |
111 | } cached_wait_status; | |
112 | ||
113 | /* Lock to hold when modifying SIGNAL_THREAD_ACTIVE_P. */ | |
114 | ||
115 | static pthread_mutex_t thread_creation_lock_obj = PTHREAD_MUTEX_INITIALIZER; | |
116 | #define thread_creation_lock (&thread_creation_lock_obj) | |
117 | ||
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. */ | |
121 | ||
122 | static int signal_thread_active_p; | |
123 | ||
124 | /* When we last allowed a waitpid to complete. */ | |
125 | ||
126 | static struct timeval last_waitpid_time = { 0, 0 }; | |
127 | ||
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. */ | |
131 | ||
132 | #define WAITPID_MIN_TIME (1) | |
133 | ||
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. */ | |
137 | ||
138 | static int | |
139 | should_skip_waitpid (void) | |
140 | { | |
141 | struct timeval *tv = &last_waitpid_time; | |
142 | if (tv->tv_sec == 0) | |
143 | { | |
144 | if (gettimeofday (tv, NULL) < 0) | |
145 | error ("error: gettimeofday failed\n"); | |
146 | return 0; /* Don't skip. */ | |
147 | } | |
148 | else | |
149 | { | |
150 | struct timeval new_tv; | |
151 | ||
152 | if (gettimeofday (&new_tv, NULL) < 0) | |
153 | error ("error: gettimeofday failed\n"); | |
154 | ||
155 | if ((new_tv.tv_sec - tv->tv_sec) < WAITPID_MIN_TIME) | |
156 | return 1; /* Skip. */ | |
157 | ||
158 | *tv = new_tv; | |
159 | } | |
160 | ||
161 | /* Don't skip. */ | |
162 | return 0; | |
163 | } | |
164 | ||
165 | /* Perform a real waitpid call. */ | |
166 | ||
167 | static pid_t | |
168 | real_waitpid (pid_t pid, int *wstatus, int options) | |
169 | { | |
170 | typedef pid_t (*fptr_t) (pid_t, int *, int); | |
171 | static fptr_t real_func = NULL; | |
172 | ||
173 | if (real_func == NULL) | |
174 | { | |
175 | real_func = dlsym (RTLD_NEXT, "waitpid"); | |
176 | if (real_func == NULL) | |
177 | error ("error: failed to find real waitpid\n"); | |
178 | } | |
179 | ||
180 | return (*real_func) (pid, wstatus, options); | |
181 | } | |
182 | ||
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. */ | |
187 | ||
188 | static void* | |
189 | send_sigchld_thread (void *arg) | |
190 | { | |
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); | |
195 | ||
196 | pthread_mutex_lock (thread_creation_lock); | |
197 | signal_thread_active_p = 0; | |
198 | ||
199 | if (cached_wait_status.cached_p) | |
200 | { | |
201 | log_msg ("signal-thread: sending SIGCHLD\n"); | |
202 | kill (getpid (), SIGCHLD); | |
203 | } | |
204 | ||
205 | pthread_mutex_unlock (thread_creation_lock); | |
206 | return NULL; | |
207 | } | |
208 | ||
209 | /* The waitpid entry point function. */ | |
210 | ||
211 | pid_t | |
212 | waitpid (pid_t pid, int *wstatus, int options) | |
213 | { | |
214 | log_msg ("waitpid: waitpid (%d, %p, 0x%x)\n", pid, wstatus, options); | |
215 | ||
216 | if ((options & WNOHANG) != 0 | |
217 | && pid == -1 | |
218 | && should_skip_waitpid ()) | |
219 | { | |
220 | if (!cached_wait_status.cached_p) | |
221 | { | |
222 | /* Do the waitpid call, but hold the result back. */ | |
223 | pid_t tmp_pid; | |
224 | int tmp_wstatus; | |
225 | ||
226 | tmp_pid = real_waitpid (-1, &tmp_wstatus, options); | |
227 | if (tmp_pid > 0) | |
228 | { | |
229 | log_msg ("waitpid: delaying waitpid result (pid = %d)\n", | |
230 | tmp_pid); | |
231 | ||
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; | |
236 | ||
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) | |
243 | { | |
244 | sigset_t old_ss, new_ss; | |
245 | pthread_t thread_id; | |
246 | pthread_attr_t attr; | |
247 | ||
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); | |
254 | ||
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); | |
260 | ||
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"); | |
265 | ||
266 | signal_thread_active_p = 1; | |
267 | sigprocmask (SIG_SETMASK, &old_ss, NULL); | |
268 | pthread_attr_destroy (&attr); | |
269 | } | |
270 | ||
271 | pthread_mutex_unlock (thread_creation_lock); | |
272 | } | |
273 | } | |
274 | ||
275 | log_msg ("waitpid: skipping\n"); | |
276 | return 0; | |
277 | } | |
278 | ||
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)) | |
283 | { | |
284 | pid_t pid; | |
285 | ||
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; | |
291 | } | |
292 | ||
293 | log_msg ("waitpid: real waitpid call\n"); | |
294 | return real_waitpid (pid, wstatus, options); | |
295 | } | |
296 | ||
297 | /* Perform a real sigsuspend call. */ | |
298 | ||
299 | static int | |
300 | real_sigsuspend (const sigset_t *mask) | |
301 | { | |
302 | typedef int (*fptr_t) (const sigset_t *); | |
303 | static fptr_t real_func = NULL; | |
304 | ||
305 | if (real_func == NULL) | |
306 | { | |
307 | real_func = dlsym (RTLD_NEXT, "sigsuspend"); | |
308 | if (real_func == NULL) | |
309 | error ("error: failed to find real sigsuspend\n"); | |
310 | } | |
311 | ||
312 | return (*real_func) (mask); | |
313 | } | |
314 | ||
315 | /* The sigsuspend entry point function. */ | |
316 | ||
317 | int | |
318 | sigsuspend (const sigset_t *mask) | |
319 | { | |
320 | log_msg ("sigsuspend: sigsuspend (0x%p)\n", ((void *) mask)); | |
321 | ||
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)) | |
329 | { | |
330 | if (cached_wait_status.cached_p) | |
331 | { | |
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; | |
337 | } | |
338 | } | |
339 | ||
340 | log_msg ("sigsuspend: real sigsuspend call\n"); | |
341 | return real_sigsuspend (mask); | |
342 | } |