[v4,3/3] Fix failure to detach if process exits while detaching on Linux

Message ID 1466119968-15171-4-git-send-email-palves@redhat.com
State New, archived
Headers

Commit Message

Pedro Alves June 16, 2016, 11:32 p.m. UTC
  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:
yyyy-mm-dd  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:
yyyy-mm-dd  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:
yyyy-mm-dd  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/gdbserver/linux-low.c                          | 116 ++++++--
 gdb/inf-ptrace.c                                   |  10 +
 gdb/inf-ptrace.h                                   |   4 +
 gdb/linux-nat.c                                    | 152 ++++++----
 .../gdb.threads/process-dies-while-detaching.c     | 116 ++++++++
 .../gdb.threads/process-dies-while-detaching.exp   | 327 +++++++++++++++++++++
 6 files changed, 655 insertions(+), 70 deletions(-)
 create mode 100644 gdb/testsuite/gdb.threads/process-dies-while-detaching.c
 create mode 100644 gdb/testsuite/gdb.threads/process-dies-while-detaching.exp
  

Comments

Antoine Tremblay June 17, 2016, 1:03 p.m. UTC | #1
Pedro Alves writes:

> +# 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 unsuring

Typo: ensuring...

Otherwise, wow really nice work and quite well commented, I learned a
lot about the testsuite reading that.

Thanks!

Antoine
  
Yao Qi June 17, 2016, 3:07 p.m. UTC | #2
Pedro Alves <palves@redhat.com> writes:

> +# 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 unsuring

s/unsuring/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: $mode: $cmd"

no variable "mode".

> +	}
> +    } 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: $mode: $cmd"
> +	}
> +    }
> +
  
Pedro Alves June 24, 2016, 7:28 p.m. UTC | #3
On 06/17/2016 04:07 PM, Yao Qi wrote:
> Pedro Alves <palves@redhat.com> writes:
> 
>> +# 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 unsuring
> 
> s/unsuring/ensuring/ ?

Indeed.

>> +	} else {
>> +	    perror "unhandled command: $mode: $cmd"
> 
> no variable "mode".

Whoops, there used to be one.  Fixed now (the two instances):

-           perror "unhandled command: $mode: $cmd"
+           perror "unhandled command: $cmd"

Thanks,
Pedro Alves
  
Pedro Alves June 24, 2016, 7:29 p.m. UTC | #4
On 06/17/2016 02:03 PM, Antoine Tremblay wrote:
> 
> Pedro Alves writes:
> 
>> +# 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 unsuring
> 
> Typo: ensuring...

I fixed this locally.

> Otherwise, wow really nice work and quite well commented, I learned a
> lot about the testsuite reading that.

Thanks!  Glad to hear that.
  

Patch

diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 81134b0..7ce37d9 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -266,6 +266,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.  */
@@ -1447,16 +1448,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)
@@ -1469,22 +1468,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;
 }
 
@@ -1492,6 +1563,7 @@  static int
 linux_detach (int pid)
 {
   struct process_info *process;
+  struct lwp_info *main_lwp;
 
   process = find_process_pid (pid);
   if (process == NULL)
@@ -1515,7 +1587,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);
 
diff --git a/gdb/inf-ptrace.c b/gdb/inf-ptrace.c
index dd11043..0896cff 100644
--- a/gdb/inf-ptrace.c
+++ b/gdb/inf-ptrace.c
@@ -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);
 
diff --git a/gdb/inf-ptrace.h b/gdb/inf-ptrace.h
index 0a26720..f1fc111 100644
--- a/gdb/inf-ptrace.h
+++ b/gdb/inf-ptrace.h
@@ -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
diff --git a/gdb/linux-nat.c b/gdb/linux-nat.c
index e6d525f..7ab0eee 100644
--- a/gdb/linux-nat.c
+++ b/gdb/linux-nat.c
@@ -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);
 
 
 
@@ -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,
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
index 0000000..a28e804
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/process-dies-while-detaching.c
@@ -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
index 0000000..1ab3820
--- /dev/null
+++ b/gdb/testsuite/gdb.threads/process-dies-while-detaching.exp
@@ -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 unsuring
+# 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: $mode: $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: $mode: $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
+	}
+    }
+}