Home | History | Annotate | Line # | Download | only in gdb.threads
slow-waitpid.c revision 1.1
      1 /* This testcase is part of GDB, the GNU debugger.
      2 
      3    Copyright 2018-2019 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 }
    343