Fix failure to detach if process exits while detaching on Linux
authorPedro Alves <palves@redhat.com>
Fri, 1 Jul 2016 10:16:33 +0000 (11:16 +0100)
committerPedro Alves <palves@redhat.com>
Fri, 1 Jul 2016 10:27:06 +0000 (11:27 +0100)
This commit fixes detaching on Linux when some thread exits the whole
thread group (process) just while we're detaching.

On Linux, a ptracer must detach from each LWP individually, with
PTRACE_DETACH.  Since PTRACE_DETACH sets the thread running free, if
one of the already-detached threads causes the whole thread group to
exit (e.g., simply calls exit), the kernel force-kills the other
threads in the group, making them zombie, just as we're still
detaching them.  Since PTRACE_DETACH against a zombie thread fails
with ESRCH, and gdb/gdbserver are not expecting this, the detach fails
with an error like: "Can't detach process: No such process.".

This patch detects this detach failure as normal, and instead of
erroring out, reaps the now-dead thread.

New test included, that exercises several different scenarios that
cause GDB/GDBserver to error out when it should not.

Tested on x86-64 GNU/Linux with {unix, native-gdbserver,
native-extended-gdbserver}

Note: without the previous fix, the "single-process + continue"
variant of the new test would fail with:

 (gdb) PASS: gdb.threads/process-dies-while-detaching.exp: single-process: continue: watchpoint: switch to parent
 continue
 Continuing.
 Warning:
 Could not insert hardware watchpoint 3.
 Could not insert hardware breakpoints:
 You may have requested too many hardware breakpoints/watchpoints.

 Command aborted.
 (gdb) FAIL: gdb.threads/process-dies-while-detaching.exp: single-process: continue: watchpoint: continue

gdb/gdbserver/ChangeLog:
2016-07-01  Pedro Alves  <palves@redhat.com>
    Antoine Tremblay  <antoine.tremblay@ericsson.com>

* linux-low.c: Change interface to take the target lwp_info
pointer directly and return void.  Handle detaching from a zombie
thread.
(linux_detach_lwp_callback): New function.
(linux_detach): Detach from the leader thread after detaching from
the clone threads.

gdb/ChangeLog:
2016-07-01  Pedro Alves  <palves@redhat.com>
    Antoine Tremblay  <antoine.tremblay@ericsson.com>

* inf-ptrace.c (inf_ptrace_detach_success): New function, factored
out from ...
(inf_ptrace_detach): ... here.
* inf-ptrace.h (inf_ptrace_detach_success): New declaration.
* linux-nat.c (get_pending_status): Rename to ...
(get_detach_signal): ... this, and return a host signal instead of
filling in a wait status.
(detach_one_lwp): New function, factored out from detach_callback
and adjusted to handle detaching from a zombie thread.
(detach_callback): Skip the leader thread.
(linux_nat_detach): No longer defer to inf_ptrace_detach to detach
the leader thread, nor build a signal string to pass down.
Instead, use target_announce_detach, detach_one_lwp and
inf_ptrace_detach_success.

gdb/testsuite/ChangeLog:
2016-07-01  Pedro Alves  <palves@redhat.com>
    Antoine Tremblay  <antoine.tremblay@ericsson.com>

* gdb.threads/process-dies-while-detaching.c: New file.
* gdb.threads/process-dies-while-detaching.exp: New file.

gdb/ChangeLog
gdb/gdbserver/ChangeLog
gdb/gdbserver/linux-low.c
gdb/inf-ptrace.c
gdb/inf-ptrace.h
gdb/linux-nat.c
gdb/testsuite/ChangeLog
gdb/testsuite/gdb.threads/process-dies-while-detaching.c [new file with mode: 0644]
gdb/testsuite/gdb.threads/process-dies-while-detaching.exp [new file with mode: 0644]

index 978311a0b918981b1f679a9e6c63a4461b500ac8..d3caa1abf57509d716baa6a4f6241d5e7281027a 100644 (file)
@@ -1,3 +1,21 @@
+2016-07-01  Pedro Alves  <palves@redhat.com>
+           Antoine Tremblay  <antoine.tremblay@ericsson.com>
+
+       * inf-ptrace.c (inf_ptrace_detach_success): New function, factored
+       out from ...
+       (inf_ptrace_detach): ... here.
+       * inf-ptrace.h (inf_ptrace_detach_success): New declaration.
+       * linux-nat.c (get_pending_status): Rename to ...
+       (get_detach_signal): ... this, and return a host signal instead of
+       filling in a wait status.
+       (detach_one_lwp): New function, factored out from detach_callback
+       and adjusted to handle detaching from a zombie thread.
+       (detach_callback): Skip the leader thread.
+       (linux_nat_detach): No longer defer to inf_ptrace_detach to detach
+       the leader thread, nor build a signal string to pass down.
+       Instead, use target_announce_detach, detach_one_lwp and
+       inf_ptrace_detach_success.
+
 2016-07-01  Pedro Alves  <palves@redhat.com>
 
        * breakpoint.c (breakpoint_init_inferior): Discard watchpoint
index 30d04987ffd1afd67268023cf60f5646160b2984..29b8ba64c1cdd5a63549491a1e6ec397d68bc90e 100644 (file)
@@ -1,3 +1,13 @@
+2016-07-01  Pedro Alves  <palves@redhat.com>
+           Antoine Tremblay  <antoine.tremblay@ericsson.com>
+
+       * linux-low.c: Change interface to take the target lwp_info
+       pointer directly and return void.  Handle detaching from a zombie
+       thread.
+       (linux_detach_lwp_callback): New function.
+       (linux_detach): Detach from the leader thread after detaching from
+       the clone threads.
+
 2016-06-28  Yao Qi  <yao.qi@linaro.org>
 
        * linux-aarch64-low.c (aarch64_ftrace_insn_reloc_b): Use int64_t
index 0f4bb8709bb966302f89c25cf014917f8475fdad..46c5a38c1a3a89ee4a68d450dee406c8e3152e70 100644 (file)
@@ -267,6 +267,7 @@ static int kill_lwp (unsigned long lwpid, int signo);
 static void enqueue_pending_signal (struct lwp_info *lwp, int signal, siginfo_t *info);
 static void complete_ongoing_step_over (void);
 static int linux_low_ptrace_options (int attached);
+static int check_ptrace_stopped_lwp_gone (struct lwp_info *lp);
 
 /* When the event-loop is doing a step-over, this points at the thread
    being stepped.  */
@@ -1492,16 +1493,14 @@ get_detach_signal (struct thread_info *thread)
     }
 }
 
-static int
-linux_detach_one_lwp (struct inferior_list_entry *entry, void *args)
+/* Detach from LWP.  */
+
+static void
+linux_detach_one_lwp (struct lwp_info *lwp)
 {
-  struct thread_info *thread = (struct thread_info *) entry;
-  struct lwp_info *lwp = get_thread_lwp (thread);
-  int pid = * (int *) args;
+  struct thread_info *thread = get_lwp_thread (lwp);
   int sig;
-
-  if (ptid_get_pid (entry->id) != pid)
-    return 0;
+  int lwpid;
 
   /* If there is a pending SIGSTOP, get rid of it.  */
   if (lwp->stop_expected)
@@ -1514,22 +1513,94 @@ linux_detach_one_lwp (struct inferior_list_entry *entry, void *args)
       lwp->stop_expected = 0;
     }
 
-  /* Flush any pending changes to the process's registers.  */
-  regcache_invalidate_thread (thread);
-
   /* Pass on any pending signal for this thread.  */
   sig = get_detach_signal (thread);
 
-  /* Finally, let it resume.  */
-  if (the_low_target.prepare_to_resume != NULL)
-    the_low_target.prepare_to_resume (lwp);
-  if (ptrace (PTRACE_DETACH, lwpid_of (thread), (PTRACE_TYPE_ARG3) 0,
+  /* Preparing to resume may try to write registers, and fail if the
+     lwp is zombie.  If that happens, ignore the error.  We'll handle
+     it below, when detach fails with ESRCH.  */
+  TRY
+    {
+      /* Flush any pending changes to the process's registers.  */
+      regcache_invalidate_thread (thread);
+
+      /* Finally, let it resume.  */
+      if (the_low_target.prepare_to_resume != NULL)
+       the_low_target.prepare_to_resume (lwp);
+    }
+  CATCH (ex, RETURN_MASK_ERROR)
+    {
+      if (!check_ptrace_stopped_lwp_gone (lwp))
+       throw_exception (ex);
+    }
+  END_CATCH
+
+  lwpid = lwpid_of (thread);
+  if (ptrace (PTRACE_DETACH, lwpid, (PTRACE_TYPE_ARG3) 0,
              (PTRACE_TYPE_ARG4) (long) sig) < 0)
-    error (_("Can't detach %s: %s"),
-          target_pid_to_str (ptid_of (thread)),
-          strerror (errno));
+    {
+      int save_errno = errno;
+
+      /* We know the thread exists, so ESRCH must mean the lwp is
+        zombie.  This can happen if one of the already-detached
+        threads exits the whole thread group.  In that case we're
+        still attached, and must reap the lwp.  */
+      if (save_errno == ESRCH)
+       {
+         int ret, status;
+
+         ret = my_waitpid (lwpid, &status, __WALL);
+         if (ret == -1)
+           {
+             warning (_("Couldn't reap LWP %d while detaching: %s"),
+                      lwpid, strerror (errno));
+           }
+         else if (!WIFEXITED (status) && !WIFSIGNALED (status))
+           {
+             warning (_("Reaping LWP %d while detaching "
+                        "returned unexpected status 0x%x"),
+                      lwpid, status);
+           }
+       }
+      else
+       {
+         error (_("Can't detach %s: %s"),
+                target_pid_to_str (ptid_of (thread)),
+                strerror (save_errno));
+       }
+    }
+  else if (debug_threads)
+    {
+      debug_printf ("PTRACE_DETACH (%s, %s, 0) (OK)\n",
+                   target_pid_to_str (ptid_of (thread)),
+                   strsignal (sig));
+    }
 
   delete_lwp (lwp);
+}
+
+/* Callback for find_inferior.  Detaches from non-leader threads of a
+   given process.  */
+
+static int
+linux_detach_lwp_callback (struct inferior_list_entry *entry, void *args)
+{
+  struct thread_info *thread = (struct thread_info *) entry;
+  struct lwp_info *lwp = get_thread_lwp (thread);
+  int pid = *(int *) args;
+  int lwpid = lwpid_of (thread);
+
+  /* Skip other processes.  */
+  if (ptid_get_pid (entry->id) != pid)
+    return 0;
+
+  /* We don't actually detach from the thread group leader just yet.
+     If the thread group exits, we must reap the zombie clone lwps
+     before we're able to reap the leader.  */
+  if (ptid_get_pid (entry->id) == lwpid)
+    return 0;
+
+  linux_detach_one_lwp (lwp);
   return 0;
 }
 
@@ -1537,6 +1608,7 @@ static int
 linux_detach (int pid)
 {
   struct process_info *process;
+  struct lwp_info *main_lwp;
 
   process = find_process_pid (pid);
   if (process == NULL)
@@ -1560,7 +1632,13 @@ linux_detach (int pid)
   /* Stabilize threads (move out of jump pads).  */
   stabilize_threads ();
 
-  find_inferior (&all_threads, linux_detach_one_lwp, &pid);
+  /* Detach from the clone lwps first.  If the thread group exits just
+     while we're detaching, we must reap the clone lwps before we're
+     able to reap the leader.  */
+  find_inferior (&all_threads, linux_detach_lwp_callback, &pid);
+
+  main_lwp = find_lwp_pid (pid_to_ptid (pid));
+  linux_detach_one_lwp (main_lwp);
 
   the_target->mourn (process);
 
index dd11043a968ffc2a0f4564e3f09cebee6c584285..0896cff8cbabe8edb8a834e87184610e1aa641bb 100644 (file)
@@ -257,6 +257,16 @@ inf_ptrace_detach (struct target_ops *ops, const char *args, int from_tty)
   error (_("This system does not support detaching from a process"));
 #endif
 
+  inf_ptrace_detach_success (ops);
+}
+
+/* See inf-ptrace.h.  */
+
+void
+inf_ptrace_detach_success (struct target_ops *ops)
+{
+  pid_t pid = ptid_get_pid (inferior_ptid);
+
   inferior_ptid = null_ptid;
   detach_inferior (pid);
 
index 0a267202eccbe6140bc68d211b9fef2cd25b1146..f1fc11193ef020466de6630cf65d917c57a980d8 100644 (file)
@@ -38,4 +38,8 @@ extern struct target_ops *
 
 extern pid_t get_ptrace_pid (ptid_t);
 
+
+/* Cleanup the inferior after a successful ptrace detach.  */
+extern void inf_ptrace_detach_success (struct target_ops *ops);
+
 #endif
index fd2df5f8ca583634303f693716a6282185adcadf..5d5efa0af45fa8c36b143d6cc8b812f06c64e568 100644 (file)
@@ -820,6 +820,7 @@ linux_nat_pass_signals (struct target_ops *self,
 static int stop_wait_callback (struct lwp_info *lp, void *data);
 static char *linux_child_pid_to_exec_file (struct target_ops *self, int pid);
 static int resume_stopped_resumed_lwps (struct lwp_info *lp, void *data);
+static int check_ptrace_stopped_lwp_gone (struct lwp_info *lp);
 
 \f
 
@@ -1295,9 +1296,13 @@ linux_nat_attach (struct target_ops *ops, const char *args, int from_tty)
     target_async (1);
 }
 
-/* Get pending status of LP.  */
+/* Get pending signal of THREAD as a host signal number, for detaching
+   purposes.  This is the signal the thread last stopped for, which we
+   need to deliver to the thread when detaching, otherwise, it'd be
+   suppressed/lost.  */
+
 static int
-get_pending_status (struct lwp_info *lp, int *status)
+get_detach_signal (struct lwp_info *lp)
 {
   enum gdb_signal signo = GDB_SIGNAL_0;
 
@@ -1350,8 +1355,6 @@ get_pending_status (struct lwp_info *lp, int *status)
        }
     }
 
-  *status = 0;
-
   if (signo == GDB_SIGNAL_0)
     {
       if (debug_linux_nat)
@@ -1370,21 +1373,28 @@ get_pending_status (struct lwp_info *lp, int *status)
     }
   else
     {
-      *status = W_STOPCODE (gdb_signal_to_host (signo));
-
       if (debug_linux_nat)
        fprintf_unfiltered (gdb_stdlog,
                            "GPT: lwp %s has pending signal %s\n",
                            target_pid_to_str (lp->ptid),
                            gdb_signal_to_string (signo));
+
+      return gdb_signal_to_host (signo);
     }
 
   return 0;
 }
 
-static int
-detach_callback (struct lwp_info *lp, void *data)
+/* Detach from LP.  If SIGNO_P is non-NULL, then it points to the
+   signal number that should be passed to the LWP when detaching.
+   Otherwise pass any pending signal the LWP may have, if any.  */
+
+static void
+detach_one_lwp (struct lwp_info *lp, int *signo_p)
 {
+  int lwpid = ptid_get_lwp (lp->ptid);
+  int signo;
+
   gdb_assert (lp->status == 0 || WIFSTOPPED (lp->status));
 
   if (debug_linux_nat && lp->status)
@@ -1400,36 +1410,83 @@ detach_callback (struct lwp_info *lp, void *data)
                            "DC: Sending SIGCONT to %s\n",
                            target_pid_to_str (lp->ptid));
 
-      kill_lwp (ptid_get_lwp (lp->ptid), SIGCONT);
+      kill_lwp (lwpid, SIGCONT);
       lp->signalled = 0;
     }
 
-  /* We don't actually detach from the LWP that has an id equal to the
-     overall process id just yet.  */
-  if (ptid_get_lwp (lp->ptid) != ptid_get_pid (lp->ptid))
+  if (signo_p == NULL)
     {
-      int status = 0;
-
       /* Pass on any pending signal for this LWP.  */
-      get_pending_status (lp, &status);
+      signo = get_detach_signal (lp);
+    }
+  else
+    signo = *signo_p;
 
+  /* Preparing to resume may try to write registers, and fail if the
+     lwp is zombie.  If that happens, ignore the error.  We'll handle
+     it below, when detach fails with ESRCH.  */
+  TRY
+    {
       if (linux_nat_prepare_to_resume != NULL)
        linux_nat_prepare_to_resume (lp);
-      errno = 0;
-      if (ptrace (PTRACE_DETACH, ptid_get_lwp (lp->ptid), 0,
-                 WSTOPSIG (status)) < 0)
-       error (_("Can't detach %s: %s"), target_pid_to_str (lp->ptid),
-              safe_strerror (errno));
+    }
+  CATCH (ex, RETURN_MASK_ERROR)
+    {
+      if (!check_ptrace_stopped_lwp_gone (lp))
+       throw_exception (ex);
+    }
+  END_CATCH
 
-      if (debug_linux_nat)
-       fprintf_unfiltered (gdb_stdlog,
-                           "PTRACE_DETACH (%s, %s, 0) (OK)\n",
-                           target_pid_to_str (lp->ptid),
-                           strsignal (WSTOPSIG (status)));
+  if (ptrace (PTRACE_DETACH, lwpid, 0, signo) < 0)
+    {
+      int save_errno = errno;
+
+      /* We know the thread exists, so ESRCH must mean the lwp is
+        zombie.  This can happen if one of the already-detached
+        threads exits the whole thread group.  In that case we're
+        still attached, and must reap the lwp.  */
+      if (save_errno == ESRCH)
+       {
+         int ret, status;
 
-      delete_lwp (lp->ptid);
+         ret = my_waitpid (lwpid, &status, __WALL);
+         if (ret == -1)
+           {
+             warning (_("Couldn't reap LWP %d while detaching: %s"),
+                      lwpid, strerror (errno));
+           }
+         else if (!WIFEXITED (status) && !WIFSIGNALED (status))
+           {
+             warning (_("Reaping LWP %d while detaching "
+                        "returned unexpected status 0x%x"),
+                      lwpid, status);
+           }
+       }
+      else
+       {
+         error (_("Can't detach %s: %s"), target_pid_to_str (lp->ptid),
+                safe_strerror (save_errno));
+       }
+    }
+  else if (debug_linux_nat)
+    {
+      fprintf_unfiltered (gdb_stdlog,
+                         "PTRACE_DETACH (%s, %s, 0) (OK)\n",
+                         target_pid_to_str (lp->ptid),
+                         strsignal (signo));
     }
 
+  delete_lwp (lp->ptid);
+}
+
+static int
+detach_callback (struct lwp_info *lp, void *data)
+{
+  /* We don't actually detach from the thread group leader just yet.
+     If the thread group exits, we must reap the zombie clone lwps
+     before we're able to reap the leader.  */
+  if (ptid_get_lwp (lp->ptid) != ptid_get_pid (lp->ptid))
+    detach_one_lwp (lp, NULL);
   return 0;
 }
 
@@ -1437,7 +1494,6 @@ static void
 linux_nat_detach (struct target_ops *ops, const char *args, int from_tty)
 {
   int pid;
-  int status;
   struct lwp_info *main_lwp;
 
   pid = ptid_get_pid (inferior_ptid);
@@ -1459,29 +1515,6 @@ linux_nat_detach (struct target_ops *ops, const char *args, int from_tty)
 
   main_lwp = find_lwp_pid (pid_to_ptid (pid));
 
-  /* Pass on any pending signal for the last LWP.  */
-  if ((args == NULL || *args == '\0')
-      && get_pending_status (main_lwp, &status) != -1
-      && WIFSTOPPED (status))
-    {
-      char *tem;
-
-      /* Put the signal number in ARGS so that inf_ptrace_detach will
-        pass it along with PTRACE_DETACH.  */
-      tem = (char *) alloca (8);
-      xsnprintf (tem, 8, "%d", (int) WSTOPSIG (status));
-      args = tem;
-      if (debug_linux_nat)
-       fprintf_unfiltered (gdb_stdlog,
-                           "LND: Sending signal %s to %s\n",
-                           args,
-                           target_pid_to_str (main_lwp->ptid));
-    }
-
-  if (linux_nat_prepare_to_resume != NULL)
-    linux_nat_prepare_to_resume (main_lwp);
-  delete_lwp (main_lwp->ptid);
-
   if (forks_exist_p ())
     {
       /* Multi-fork case.  The current inferior_ptid is being detached
@@ -1491,7 +1524,24 @@ linux_nat_detach (struct target_ops *ops, const char *args, int from_tty)
       linux_fork_detach (args, from_tty);
     }
   else
-    linux_ops->to_detach (ops, args, from_tty);
+    {
+      int signo;
+
+      target_announce_detach (from_tty);
+
+      /* Pass on any pending signal for the last LWP, unless the user
+        requested detaching with a different signal (most likely 0,
+        meaning, discard the signal).  */
+      if (args != NULL)
+       signo = atoi (args);
+      else
+       signo = get_detach_signal (main_lwp);
+
+      detach_one_lwp (main_lwp, &signo);
+
+      inf_ptrace_detach_success (ops);
+    }
+  delete_lwp (main_lwp->ptid);
 }
 
 /* Resume execution of the inferior process.  If STEP is nonzero,
index 611de5231b492c9e9c9ee79feeb571786e463586..fd943790c533cedfa152a2bbe75c7872a7ecd7be 100644 (file)
@@ -1,3 +1,9 @@
+2016-07-01  Pedro Alves  <palves@redhat.com>
+           Antoine Tremblay  <antoine.tremblay@ericsson.com>
+
+       * gdb.threads/process-dies-while-detaching.c: New file.
+       * gdb.threads/process-dies-while-detaching.exp: New file.
+
 2016-07-01  Pedro Alves  <palves@redhat.com>
 
        * gdb.multi/watchpoint-multi-exit.c: New file.
diff --git a/gdb/testsuite/gdb.threads/process-dies-while-detaching.c b/gdb/testsuite/gdb.threads/process-dies-while-detaching.c
new file mode 100644 (file)
index 0000000..a28e804
--- /dev/null
@@ -0,0 +1,116 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2016 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+#include <pthread.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <assert.h>
+
+/* This barrier ensures we only reach the initial breakpoint after all
+   threads have started.  */
+pthread_barrier_t start_threads_barrier;
+
+/* Many threads in order to be fairly sure the process exits while GDB
+   is detaching from each thread in the process, on targets that need
+   to detach from each thread individually.  */
+#define NTHREADS 256
+
+/* GDB sets a watchpoint here.  */
+int globalvar = 1;
+
+/* GDB reads this.  */
+int mypid;
+
+/* Threads' entry point.  */
+
+void *
+thread_function (void *arg)
+{
+  pthread_barrier_wait (&start_threads_barrier);
+  _exit (0);
+}
+
+/* The fork child's entry point.  */
+
+void
+child_function (void)
+{
+  pthread_t threads[NTHREADS];
+  int i;
+
+  pthread_barrier_init (&start_threads_barrier, NULL, NTHREADS + 1);
+
+  for (i = 0; i < NTHREADS; i++)
+    pthread_create (&threads[i], NULL, thread_function, NULL);
+  pthread_barrier_wait (&start_threads_barrier);
+
+  exit (0);
+}
+
+/* This is defined by the .exp file if testing the multi-process
+   variant.  */
+#ifdef MULTIPROCESS
+
+/* The fork parent's entry point.  */
+
+void
+parent_function (pid_t child)
+{
+  int status, ret;
+
+  alarm (300);
+
+  ret = waitpid (child, &status, 0);
+  if (ret == -1)
+    exit (1);
+  else if (!WIFEXITED (status))
+    exit (2);
+  else
+    {
+      printf ("exited, status=%d\n", WEXITSTATUS (status));
+      exit (0);
+    }
+}
+
+#endif
+
+int
+main (void)
+{
+#ifdef MULTIPROCESS
+  pid_t child;
+
+  child = fork ();
+  if (child == -1)
+    return 1;
+#endif
+
+  mypid = getpid ();
+
+#ifdef MULTIPROCESS
+  if (child != 0)
+    parent_function (child);
+  else
+#endif
+    child_function ();
+
+  /* Not reached.  */
+  abort ();
+}
diff --git a/gdb/testsuite/gdb.threads/process-dies-while-detaching.exp b/gdb/testsuite/gdb.threads/process-dies-while-detaching.exp
new file mode 100644 (file)
index 0000000..8dc1aec
--- /dev/null
@@ -0,0 +1,327 @@
+# Copyright 2016 Free Software Foundation, Inc.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# This test spawns a few threads that immediately exit the whole
+# process.  On targets where the debugger needs to detach from each
+# thread individually (such as on the Linux kernel), the debugger must
+# handle the case of the process exiting while the detach is ongoing.
+#
+# Similarly, the process can also be killed from outside the debugger
+# (e.g., with SIGKILL), _before_ the user requests a detach.  The
+# debugger must likewise detach gracefully.
+#
+# The testcase actually builds two variants of the test program:
+# single-process, and multi-process.  In the multi-process variant,
+# the test program forks, and it's the fork child that spawns threads
+# that exit just while the process is being detached from.  The fork
+# parent waits for its child to exit, so if GDB fails to detach from
+# the child correctly, the parent hangs.  Because continuing the
+# parent can mask failure to detach from the child correctly (e.g.,
+# due to waitpid(-1,...) calls deep in the target layers managing to
+# reap the child), we try immediately detaching from the parent too,
+# and observing whether the parent exits via standard output.
+#
+# Normally, if testing with "target remote" against gdbserver, then
+# after detaching from all attached processes, gdbserver exits.
+# However, when gdbserver detaches from a process that is its own
+# direct child, gdbserver does not exit immediately.  Instead it
+# "joins" (waits for) the child, only exiting when the child itself
+# exits too.  Thus, on Linux, if gdbserver fails to detach from the
+# zombie child's threads correctly (or rather, reap them), it'll hang,
+# because the leader thread will only return an exit status after all
+# threads are reaped.  We test that as well.
+
+standard_testfile
+
+# Test that GDBserver exits.
+
+proc test_server_exit {} {
+    global server_spawn_id
+
+    set test "server exits"
+    gdb_expect {
+       -i $server_spawn_id
+       eof {
+           pass $test
+           wait -i $server_spawn_id
+           unset server_spawn_id
+       }
+       timeout {
+           fail "$test (timeout)"
+       }
+    }
+}
+
+# If RESULT is not zero, make the caller return.
+
+proc return_if_fail { result } {
+    if {$result != 0} {
+       return -code return
+    }
+}
+
+# Detach from a process, and ensure that it exits after detaching.
+# This relies on inferior I/O.
+
+proc detach_and_expect_exit {test} {
+    global decimal
+    global gdb_spawn_id
+    global inferior_spawn_id
+    global gdb_prompt
+
+    return_if_fail [gdb_test_multiple "detach" $test {
+       -re "Detaching from .*, process $decimal" {
+       }
+    }]
+
+    set saw_prompt 0
+    set saw_inf_exit 0
+    while { !$saw_prompt && ! $saw_inf_exit } {
+       # We don't know what order the interesting things will arrive in.
+       # Using a pattern of the form 'x|y|z' instead of -re x ... -re y
+       # ... -re z ensures that expect always chooses the match that
+       # occurs leftmost in the input, and not the pattern appearing
+       # first in the script that occurs anywhere in the input, so that
+       # we don't skip anything.
+       return_if_fail [gdb_test_multiple "" $test {
+           -i "$inferior_spawn_id $gdb_spawn_id"
+           -re "(exited, status=0)|($gdb_prompt )" {
+               if {[info exists expect_out(1,string)]} {
+                   verbose -log "saw inferior exit"
+                   set saw_inf_exit 1
+               } elseif {[info exists expect_out(2,string)]} {
+                   verbose -log "saw prompt"
+                   set saw_prompt 1
+               }
+               array unset expect_out
+           }
+       }]
+    }
+
+    pass $test
+}
+
+# Run to _exit in the child.
+
+proc continue_to_exit_bp {} {
+    gdb_breakpoint "_exit" temporary
+    gdb_continue_to_breakpoint "_exit" ".*_exit.*"
+}
+
+# If testing single-process, simply detach from the process.
+#
+# If testing multi-process, first detach from the child, then detach
+# from the parent and confirm that the parent exits, thus ensuring
+# we've detached from the child successfully, as the parent hangs in
+# its waitpid call otherwise.
+#
+# If connected with "target remote", make sure gdbserver exits.
+#
+# CMD indicates what to do with the parent after detaching the child.
+# Can be either "detach" to detach, or "continue", to continue to
+# exit.  If "continue", then CONTINUE_RE is the regexp to expect.
+# Defaults to normal exit output.
+#
+proc do_detach {multi_process cmd {continue_re ""}} {
+    global decimal
+    global server_spawn_id
+
+    if {$continue_re == ""} {
+       set continue_re "exited normally.*"
+    }
+
+    set is_remote [expr {[target_info exists gdb_protocol]
+                        && [target_info gdb_protocol] == "remote"}]
+
+    if {$multi_process} {
+       gdb_test "detach" "Detaching from .*, process $decimal" \
+           "detach child"
+
+       gdb_test "inferior 1" "\[Switching to inferior $decimal\].*" \
+           "switch to parent"
+
+       if {$cmd == "detach"} {
+           # Make sure that detach works and that the parent process
+           # exits cleanly.
+           detach_and_expect_exit "detach parent"
+       } elseif {$cmd == "continue"} {
+           # Make sure that continuing works and that the parent process
+           # exits cleanly.
+           gdb_test "continue" $continue_re
+       } else {
+           perror "unhandled command: $cmd"
+       }
+    } else {
+       if $is_remote {
+           set extra "\r\nEnding remote debugging\."
+       } else {
+           set extra ""
+       }
+       if {$cmd == "detach"} {
+           gdb_test "detach" "Detaching from .*, process $decimal$extra"
+       } elseif {$cmd == "continue"} {
+           gdb_test "continue" $continue_re
+       } else {
+           perror "unhandled command: $cmd"
+       }
+    }
+
+    # When connected in "target remote" mode, the server should exit
+    # when there are no processes left to debug.
+    if { $is_remote && [info exists server_spawn_id]} {
+       test_server_exit
+    }
+}
+
+# Test detaching from a process that dies just while GDB is detaching.
+
+proc test_detach {multi_process cmd} {
+    with_test_prefix "detach" {
+       global binfile
+
+       clean_restart ${binfile}
+
+       if ![runto_main] {
+           fail "Can't run to main"
+           return -1
+       }
+
+       if {$multi_process} {
+           gdb_test_no_output "set detach-on-fork off"
+           gdb_test_no_output "set follow-fork-mode child"
+       }
+
+       # Run to _exit in the child.
+       continue_to_exit_bp
+
+       do_detach $multi_process $cmd
+    }
+}
+
+# Same as test_detach, except set a watchpoint before detaching.
+
+proc test_detach_watch {multi_process cmd} {
+    with_test_prefix "watchpoint" {
+       global binfile decimal
+
+       clean_restart ${binfile}
+
+       if ![runto_main] {
+           fail "Can't run to main"
+           return -1
+       }
+
+       if {$multi_process} {
+           gdb_test_no_output "set detach-on-fork off"
+           gdb_test_no_output "set follow-fork-mode child"
+
+           gdb_breakpoint "child_function" temporary
+           gdb_continue_to_breakpoint "child_function" ".*"
+       }
+
+       # Set a watchpoint in the child.
+       gdb_test "watch globalvar" ".* watchpoint $decimal: globalvar"
+
+       # Continue to the _exit breakpoint.  This arms the watchpoint
+       # registers in all threads.  Detaching will thus need to clear
+       # them out, and handle the case of the thread disappearing
+       # while doing that (on targets that need to detach from each
+       # thread individually).
+       continue_to_exit_bp
+
+       do_detach $multi_process $cmd
+    }
+}
+
+# Test detaching from a process that dies _before_ GDB starts
+# detaching.
+
+proc test_detach_killed_outside {multi_process cmd} {
+    with_test_prefix "killed outside" {
+       global binfile
+
+       clean_restart ${binfile}
+
+       if ![runto_main] {
+           fail "Can't run to main"
+           return -1
+       }
+
+       gdb_test_no_output "set breakpoint always-inserted on"
+
+       if {$multi_process} {
+           gdb_test_no_output "set detach-on-fork off"
+           gdb_test_no_output "set follow-fork-mode child"
+       }
+
+       # Run to _exit in the child.
+       continue_to_exit_bp
+
+       set childpid [get_integer_valueof "mypid" -1]
+       if { $childpid == -1 } {
+           untested "failed to extract child pid"
+           return -1
+       }
+
+       remote_exec target "kill -9 ${childpid}"
+
+       # Give it some time to die.
+       sleep 2
+
+       if {$multi_process} {
+           set continue_re "exited with code 02.*"
+       } else {
+           set continue_re "terminated with signal SIGKILL.*"
+       }
+       do_detach $multi_process $cmd $continue_re
+    }
+}
+
+# The test proper.  MULTI_PROCESS is true if testing the multi-process
+# variant.
+
+proc do_test {multi_process cmd} {
+    global testfile srcfile binfile
+
+    if {$multi_process && $cmd == "detach"
+       && [target_info exists gdb,noinferiorio]} {
+       # This requires inferior I/O to tell whether both the parent
+       # and child exit successfully.
+       return
+    }
+
+    set binfile [standard_output_file ${testfile}-$multi_process-$cmd]
+    set options {debug pthreads}
+    if {$multi_process} {
+       lappend options "additional_flags=-DMULTIPROCESS"
+    }
+
+    if {[build_executable "failed to build" \
+            $testfile-$multi_process-$cmd $srcfile $options] == -1} {
+       return -1
+    }
+
+    test_detach $multi_process $cmd
+    test_detach_watch $multi_process $cmd
+    test_detach_killed_outside $multi_process $cmd
+}
+
+foreach multi_process {0 1} {
+    set mode [expr {$multi_process ? "single-process" : "multi-process"}]
+    foreach cmd {"detach" "continue"} {
+       with_test_prefix "$mode: $cmd" {
+           do_test $multi_process $cmd
+       }
+    }
+}
This page took 0.045386 seconds and 4 git commands to generate.