diff mbox

[06/16,v3] Extended-remote Linux follow fork

Message ID 54824E47.7020907@codesourcery.com
State New
Headers show

Commit Message

Don Breazeal Dec. 6, 2014, 12:31 a.m. UTC
On 11/13/2014 11:06 AM, Breazeal, Don wrote:
> On 11/13/2014 10:59 AM, Pedro Alves wrote:
>> On 11/13/2014 06:53 PM, Breazeal, Don wrote:
>>> My initial approach was to do just that, but I ended up with
>>> linux-specific code in remote.c (the code that lives in linux-nat.c
>>> for the native implementation).  I guess the direction of recent
>>> changes would be to put that code into a common file in gdb/nat,
>>> if possible.  Would that be the approach you would recommend?
>>
>> I'm not seeing what would be linux-specific?  On remote_follow_fork
>> fork, we switch the current remote thread to gdb's current
>> thread (either parent or child), by
>> calling 'set_general_thread (inferior_ptid);'
>> And then if we need to detach parent or child, we detach it with
>> the D;PID packet.
>>
>> Thanks,
>> Pedro Alves
>>
> I don't recall the details at this point.  I'll proceed
> with your recommendation, assuming I don't need any
> common code, and if I run into a problem I'll post a
> question about it.
> 
> Thanks
> --Don
> 
> 
Hi Pedro,

I have finally had a chance to spend a chunk of time on this.  I have
removed the vFollowFork packet and enabled fork events for remote as
well as extended-remote.  When gdbserver detaches an inferior, it
only exits if it's not in extended mode *and* there are no more inferiors.

This version of the patch is *mostly working*, but should still be
considered WIP.  I wanted to get it posted to make sure that we are
aligned on the direction that the implementation is taking before
going too much farther with it.

BTW this has made it back to the top of my priority list, so I should be
more responsive to reviews for the next month or so.

One WIP issue: extended-remote targets are working as well as before,
but there are still a few failures with 'target remote': an assertion in
multi-forks.exp and a few failures in gdb.threads/linux-dp.exp and
gdb.trace/report.exp.

Another WIP issue: I have a question about how to deal with the
workaround for the hardware single-step kernel bug, as in
linux-nat.c:linux_child_follow_fork where it manually steps the inferior
before detaching.  In the remote implementation I put the workaround in
a function linux-low.c:linux_detach_fork and called it from
linux_detach_one_lwp.  I experimented with keeping some state in the
thread_info structure flagging the "pending follow", but since gdbserver
doesn't know which inferiors are being followed, there was no way to
clean up the threads that weren't detached.  (I hope that makes sense).

In the current implementation it just executes linux_detach_fork for all
detach requests, whether they have anything to do with a fork or not.
That seems to work, but one might consider it a questionable solution.
Another option could be to just not work around the kernel bug in the
remote case.  It looks like the kernel fix may be as old as December of
2009 -- is that old enough so that for a new feature we can just skip
it?  Or are there other options I should be considering?

The revised patch is below, including an updated commit message.
Thanks!
--Don

This patch implements basic support for follow-fork and detach-on-fork on
remote and extended-remote Linux targets.  Only 'fork' is supported in this
patch; 'vfork' support is added n a subsequent patch.  Sufficient
extended-remote functionality has been implemented here to pass
gdb.base/foll-fork.exp with the catchpoint tests commented out.

The implementation follows the same general structure as for the native
implementation as much as possible.

This implementation included:
 * enabling fork events in linux-low.c in initialize_low and
   linux_enable_extended_features

 * handling fork events in gdbserver/linux-low.c:handle_extended_wait

   - when a fork event occurs in gdbserver, we must do the full creation
     of the new process, thread, lwp, and breakpoint lists.  This is
     required whether or not the new child is destined to be
     detached-on-fork, because GDB will make target calls that require all
     the structures.  In particular we need the breakpoint lists in order
     to remove the breakpoints from a detaching child.  If we are not
     detaching the child we will need all these structures anyway.

   - as part of this event handling we store the target_waitstatus in a new
     member of the parent thread_info structure, 'pending_follow'.  This
     is used to store extended event information for reporting to GDB.

   - handle_extended_wait is given a return value, denoting whether the
     handled event should be reported to GDB.  Previously it had only
     handled clone events, which were never reported.

 * using a new predicate in gdbserver to control handling of the fork event
   (and eventually all extended events) in linux_wait_1.  The predicate,
   extended_event_reported, checks a target_waitstatus.kind for an
   extended ptrace event.

 * adds a function linux_detach_fork to the gdbserver detach procedure.
   This function implements the workaround for the kernel bug related
   to inheritance of hardware singlestep settings.

 * implementing a new RSP 'T' Stop Reply Packet stop reason: "fork", in
   gdbserver/remote-utils.c and remote.c.

 * implementing new target and RSP support for target_follow_fork with
   target extended-remote.  (The RSP components were actually defined in
   patch 4, but they see their first use here).

   - remote target routine remote_follow_fork, which just sends the 'D;pid'
     detach packet to detach the new fork child cleanly.  We can't just
     call target_detach because the fork child data structures have not
     been allocated on the host side.

Tested on x64 Ubuntu Lucid, native, remote, extended-remote.

gdb/gdbserver/
2014-12-05  Don Breazeal  <donb@codesourcery.com>

	* gdbthread.h (struct thread_info) <pending_follow>: New member.
	* linux-low.c (handle_extended_wait): Implement return value,
	rename event_child to event_lwp, handle PTRACE_EVENT_FORK.
	(linux_detach_fork): New function.
	(linux_detach_one_lwp): Call linux_detach_fork.
	(linux_low_filter_event): Use return value from
	handle_extended_wait.
	(extended_event_reported): New function.
	(linux_write_memory): Add 'process <pid>' to debug message.
	* remote-utils.c (prepare_resume_reply): Implement stop reason
	'fork' for 'T' stop message.
	* server.c (process_serial_event): Don't exit if there is still
	one inferior.

gdb/
2014-12-05  Don Breazeal  <donb@codesourcery.com>

	* remote.c (remote_fork_event_p): Move out of #ifdef.
	(remote_follow_fork): New function.
	(set_general_process): Remove extended-mode restriction.
	(remote_detach_1): Add struct target_ops argument, don't
	mourn detached process if it was detach-on-fork.
	(remote_detach, extended_remote_detach): Pass struct target_ops
	argument to remote_detach_1.
	(remote_parse_stop_reply): Handle new 'T' stop reason 'fork'.
	(extended_remote_kill): Remove extended-mode restriction.
	(remote_pid_to_str): Print 'process' strings for pid/0/0 ptids.
	(remote_supports_multi_process): Remove extended-mode restriction.

---
 gdb/gdbserver/gdbthread.h    |   4 +
 gdb/gdbserver/linux-low.c    | 172
+++++++++++++++++++++++++++++++++++++++----
 gdb/gdbserver/remote-utils.c |  14 +++-
 gdb/gdbserver/server.c       |   7 +-
 gdb/remote.c                 |  97 +++++++++++++++++++-----
 5 files changed, 259 insertions(+), 35 deletions(-)


 /* Same as remote_detach, but don't send the "D" packet; just
disconnect.  */
@@ -5561,7 +5618,8 @@ remote_parse_stop_reply (char *buf, struct
stop_reply *event)
 	     as a register number.  */

 	  if (strncmp (p, "awatch", strlen("awatch")) != 0
-	      && strncmp (p, "core", strlen ("core") != 0))
+	      && strncmp (p, "core", strlen ("core") != 0)
+	      && strncmp (p, "fork", strlen ("fork") != 0))
 	    {
 	      /* Read the ``P'' register number.  */
 	      pnum = strtol (p, &p_temp, 16);
@@ -5613,6 +5671,11 @@ Packet: '%s'\n"),
 		  p = unpack_varlen_hex (++p1, &c);
 		  event->core = c;
 		}
+	      else if (strncmp (p, "fork", p1 - p) == 0)
+		{
+		  event->ws.value.related_pid = read_ptid (++p1, &p);
+		  event->ws.kind = TARGET_WAITKIND_FORKED;
+		}
 	      else
 		{
 		  /* Silently skip unknown optional info.  */
@@ -7877,7 +7940,7 @@ extended_remote_kill (struct target_ops *ops)
   struct remote_state *rs = get_remote_state ();

   res = remote_vkill (pid, rs);
-  if (res == -1 && !(rs->extended && remote_multi_process_p (rs)))
+  if (res == -1 && !(remote_multi_process_p (rs)))
     {
       /* Don't try 'k' on a multi-process aware stub -- it has no way
 	 to specify the pid.  */
@@ -9423,9 +9486,12 @@ remote_pid_to_str (struct target_ops *ops, ptid_t
ptid)
     {
       if (ptid_equal (magic_null_ptid, ptid))
 	xsnprintf (buf, sizeof buf, "Thread <main>");
-      else if (rs->extended && remote_multi_process_p (rs))
-	xsnprintf (buf, sizeof buf, "Thread %d.%ld",
-		   ptid_get_pid (ptid), ptid_get_lwp (ptid));
+      else if (remote_multi_process_p (rs))
+	if (ptid_get_lwp (ptid) == 0)
+	  return normal_pid_to_str (ptid);
+	else
+	  xsnprintf (buf, sizeof buf, "Thread %d.%ld",
+		     ptid_get_pid (ptid), ptid_get_lwp (ptid));
       else
 	xsnprintf (buf, sizeof buf, "Thread %ld",
 		   ptid_get_lwp (ptid));
@@ -10422,11 +10488,7 @@ remote_supports_multi_process (struct
target_ops *self)
 {
   struct remote_state *rs = get_remote_state ();

-  /* Only extended-remote handles being attached to multiple
-     processes, even though plain remote can use the multi-process
-     thread id extensions, so that GDB knows the target process's
-     PID.  */
-  return rs->extended && remote_multi_process_p (rs);
+  return remote_multi_process_p (rs);
 }

 static int
@@ -11559,6 +11621,7 @@ Specify the serial device it is connected to\n\
   remote_ops.to_remove_watchpoint = remote_remove_watchpoint;
   remote_ops.to_kill = remote_kill;
   remote_ops.to_load = remote_load;
+  remote_ops.to_follow_fork = remote_follow_fork;
   remote_ops.to_mourn_inferior = remote_mourn;
   remote_ops.to_pass_signals = remote_pass_signals;
   remote_ops.to_program_signals = remote_program_signals;

Comments

Pedro Alves Jan. 23, 2015, 11:55 a.m. UTC | #1
Hi Don,

I need to play with this in order to review it, and so
I'm trying to reconstruct the current series in my sandbox, but
I'm having troubling figuring out the pieces.  Do you keep
the updated series in a branch somewhere?

Thanks,
Pedro Alves
Don Breazeal Jan. 23, 2015, 4:42 p.m. UTC | #2
On 1/23/2015 3:55 AM, Pedro Alves wrote:
> Hi Don,
> 
> I need to play with this in order to review it, and so
> I'm trying to reconstruct the current series in my sandbox, but
> I'm having troubling figuring out the pieces.  Do you keep
> the updated series in a branch somewhere?
> 
> Thanks,
> Pedro Alves
> 
Hi Pedro,
Sorry the currently posted stuff isn't working for you.  My most recent
merge from upstream, yesterday, resulted in some conflicts and broke
some things like follow-vfork.  You would probably run into the same
conflicts if I posted an older (working) version.  I will post a new
version of the follow-fork part of the patch series as soon as I work
through the merge issue(s).
Thanks,
--Don
diff mbox

Patch

diff --git a/gdb/gdbserver/gdbthread.h b/gdb/gdbserver/gdbthread.h
index 8290ec1..aa77cd9 100644
--- a/gdb/gdbserver/gdbthread.h
+++ b/gdb/gdbserver/gdbthread.h
@@ -41,6 +41,10 @@  struct thread_info
   /* True if LAST_STATUS hasn't been reported to GDB yet.  */
   int status_pending_p;

+  /* This is used to store fork and exec event information until
+     it is reported to GDB.  */
+  struct target_waitstatus pending_follow;
+
   /* Given `while-stepping', a thread may be collecting data for more
      than one tracepoint simultaneously.  E.g.:

diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index a13631a..a18e8ee 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -20,6 +20,7 @@ 
 #include "linux-low.h"
 #include "nat/linux-osdata.h"
 #include "agent.h"
+#include "tdesc.h"

 #include "nat/linux-nat.h"
 #include "nat/linux-waitpid.h"
@@ -364,22 +365,23 @@  linux_add_process (int pid, int attached)
 }

 /* Handle a GNU/Linux extended wait response.  If we see a clone
-   event, we need to add the new LWP to our list (and not report the
-   trap to higher layers).  */
+   event, we need to add the new LWP to our list (and return 0 so as
+   not to report the trap to higher layers).  */

-static void
-handle_extended_wait (struct lwp_info *event_child, int wstat)
+static int
+handle_extended_wait (struct lwp_info *event_lwp, int wstat)
 {
   int event = linux_ptrace_get_extended_event (wstat);
-  struct thread_info *event_thr = get_lwp_thread (event_child);
+  struct thread_info *event_thr = get_lwp_thread (event_lwp);
   struct lwp_info *new_lwp;

-  if (event == PTRACE_EVENT_CLONE)
+  if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_CLONE)
     {
       ptid_t ptid;
       unsigned long new_pid;
       int ret, status;

+      /* Get the pid of the new lwp.  */
       ptrace (PTRACE_GETEVENTMSG, lwpid_of (event_thr),
(PTRACE_TYPE_ARG3) 0,
 	      &new_pid);

@@ -399,6 +401,52 @@  handle_extended_wait (struct lwp_info *event_child,
int wstat)
 	    warning ("wait returned unexpected status 0x%x", status);
 	}

+      if (event == PTRACE_EVENT_FORK)
+	{
+	  struct process_info *parent_proc;
+	  struct process_info *child_proc;
+	  struct lwp_info *child_lwp;
+	  struct target_desc *tdesc;
+
+	  ptid = ptid_build (new_pid, new_pid, 0);
+
+	  if (debug_threads)
+	    {
+	      debug_printf ("HEW: Got fork event "
+			    "from LWP %ld, new child is %d\n",
+			    ptid_get_lwp (ptid_of (event_thr)),
+			    ptid_get_pid (ptid));
+	    }
+
+	  /* Add the new process to the tables and clone the breakpoint
+	     lists of the parent.  We need to do this even if the new process
+	     will be detached, since we will need the process object and the
+	     breakpoints to remove any breakpoints from memory when we
+	     detach, and the host side will access registers.  */
+	  child_proc = linux_add_process (new_pid, 0);
+	  gdb_assert (child_proc != NULL);
+	  child_lwp = add_lwp (ptid);
+	  gdb_assert (child_lwp != NULL);
+	  child_lwp->stopped = 1;
+	  parent_proc = get_thread_process (event_thr);
+	  child_proc->attached = parent_proc->attached;
+	  clone_all_breakpoints (&child_proc->breakpoints,
+				 &child_proc->raw_breakpoints,
+				 parent_proc->breakpoints);
+
+	  tdesc = xmalloc (sizeof (struct target_desc));
+	  copy_target_description (tdesc, parent_proc->tdesc);
+	  child_proc->tdesc = tdesc;
+	  child_lwp->must_set_ptrace_flags = 1;
+
+	  /* Save fork info in the parent thread.  */
+	  event_thr->pending_follow.kind = TARGET_WAITKIND_FORKED;
+	  event_thr->pending_follow.value.related_pid = ptid;
+
+	  /* Report the event.  */
+	  return 0;
+	}
+
       if (debug_threads)
 	debug_printf ("HEW: Got clone event "
 		      "from LWP %ld, new child is LWP %ld\n",
@@ -447,8 +495,13 @@  handle_extended_wait (struct lwp_info *event_child,
int wstat)
       /* Always resume the current thread.  If we are stopping
 	 threads, it will have a pending SIGSTOP; we may as well
 	 collect it now.  */
-      linux_resume_one_lwp (event_child, event_child->stepping, 0, NULL);
+      linux_resume_one_lwp (event_lwp, event_lwp->stepping, 0, NULL);
+
+      /* Don't report the event.  */
+      return 1;
     }
+
+  internal_error (__FILE__, __LINE__, _("unknown ptrace event %d"), event);
 }

 /* Return the PC as read from the regcache of LWP, without any
@@ -1100,6 +1153,55 @@  get_detach_signal (struct thread_info *thread)
     }
 }

+/* Prepare a fork child to be detached, if necessary.  If THREAD is
+   not a fork child, just return OK.  */
+
+static int
+linux_detach_fork (struct thread_info *thread)
+{
+  ptid_t child_ptid;
+  pid_t child_pid;
+  int status = W_STOPCODE (0);
+
+  /* Here we know that we are in a follow-fork, that we are following
+     the parent, and that we are going to detach the child.  */
+  child_ptid = thread->entry.id;
+  child_pid = ptid_get_pid (child_ptid);
+
+  /* When debugging an inferior in an architecture that supports
+     hardware single stepping on a kernel without commit
+     6580807da14c423f0d0a708108e6df6ebc8bc83d, the vfork child
+     process starts with the TIF_SINGLESTEP/X86_EFLAGS_TF bits
+     set if the parent process had them set.
+     To work around this, single step the child process
+     once before detaching to clear the flags.  */
+
+  if (can_hardware_single_step ())
+    {
+      linux_disable_event_reporting (child_pid);
+      if (ptrace (PTRACE_SINGLESTEP, child_pid, 0, 0) < 0)
+	perror_with_name (_("Couldn't do single step"));
+      if (my_waitpid (child_pid, &status, 0) < 0)
+	perror_with_name (_("Couldn't wait vfork process"));
+    }
+
+  if (WIFSTOPPED (status))
+    {
+      int signo;
+      struct lwp_info *child_lwp = find_lwp_pid (child_ptid);
+
+      signo = WSTOPSIG (status);
+      if (signo == SIGSTOP
+	  || (signo != 0 && !pass_signals[gdb_signal_from_host (signo)]))
+	{
+	  /* Clear the SIGSTOP.  */
+	  WSETSTOP (child_lwp->status_pending, 0);
+	}
+    }
+
+  return 0;
+}
+
 static int
 linux_detach_one_lwp (struct inferior_list_entry *entry, void *args)
 {
@@ -1125,6 +1227,9 @@  linux_detach_one_lwp (struct inferior_list_entry
*entry, void *args)
   /* Flush any pending changes to the process's registers.  */
   regcache_invalidate_thread (thread);

+  /* Prepare fork child for detach, if necessary.  */
+  linux_detach_fork (thread);
+
   /* Pass on any pending signal for this thread.  */
   sig = get_detach_signal (thread);

@@ -1885,8 +1990,10 @@  linux_low_filter_event (ptid_t filter_ptid, int
lwpid, int wstat)
   if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGTRAP
       && linux_is_extended_waitstatus (wstat))
     {
-      handle_extended_wait (child, wstat);
-      return NULL;
+      if (handle_extended_wait (child, wstat))
+	return NULL;
+      else
+	return child;
     }

   if (WIFSTOPPED (wstat) && WSTOPSIG (wstat) == SIGSTOP
@@ -2484,6 +2591,19 @@  linux_stabilize_threads (void)
     }
 }

+/* Return non-zero if WAITSTATUS reflects an extended linux
+   event that gdbserver supports.  Otherwise, return zero.  */
+
+static int
+extended_event_reported (const struct target_waitstatus *waitstatus)
+{
+
+  if (waitstatus == NULL)
+    return 0;
+
+  return (waitstatus->kind == TARGET_WAITKIND_FORKED);
+}
+
 /* Wait for process, returns status.  */

 static ptid_t
@@ -2819,7 +2939,8 @@  retry:
 		       && !bp_explains_trap && !trace_event)
 		   || (gdb_breakpoint_here (event_child->stop_pc)
 		       && gdb_condition_true_at_breakpoint (event_child->stop_pc)
-		       && gdb_no_commands_at_breakpoint (event_child->stop_pc)));
+		       && gdb_no_commands_at_breakpoint (event_child->stop_pc))
+		   || extended_event_reported (&current_thread->pending_follow));

   run_breakpoint_commands (event_child->stop_pc);

@@ -2841,6 +2962,13 @@  retry:
 			  paddress (event_child->stop_pc),
 			  paddress (event_child->step_range_start),
 			  paddress (event_child->step_range_end));
+	  if (extended_event_reported (&current_thread->pending_follow))
+	    {
+	      char *str = target_waitstatus_to_string (ourstatus);
+	      debug_printf ("LWP %ld: extended event with waitstatus %s\n",
+			    lwpid_of (get_lwp_thread (event_child)), str);
+	      xfree (str);
+	    }
 	}

       /* We're not reporting this breakpoint to GDB, so apply the
@@ -2939,7 +3067,17 @@  retry:
 	unstop_all_lwps (1, event_child);
     }

-  ourstatus->kind = TARGET_WAITKIND_STOPPED;
+  if (extended_event_reported (&current_thread->pending_follow))
+    {
+      /* If the reported event is a fork, vfork or exec, let GDB know.  */
+      ourstatus->kind = current_thread->pending_follow.kind;
+      ourstatus->value = current_thread->pending_follow.value;
+
+      /* Reset the event lwp's waitstatus since we handled it already.  */
+      current_thread->pending_follow.kind = TARGET_WAITKIND_SPURIOUS;
+    }
+  else
+    ourstatus->kind = TARGET_WAITKIND_STOPPED;

   if (current_thread->last_resume_kind == resume_stop
       && WSTOPSIG (w) == SIGSTOP)
@@ -2956,7 +3094,7 @@  retry:
 	 but, it stopped for other reasons.  */
       ourstatus->value.sig = gdb_signal_from_host (WSTOPSIG (w));
     }
-  else
+  else if (ourstatus->kind == TARGET_WAITKIND_STOPPED)
     {
       ourstatus->value.sig = gdb_signal_from_host (WSTOPSIG (w));
     }
@@ -4748,8 +4886,8 @@  linux_write_memory (CORE_ADDR memaddr, const
unsigned char *myaddr, int len)
 	val = val & 0xffff;
       else if (len == 3)
 	val = val & 0xffffff;
-      debug_printf ("Writing %0*x to 0x%08lx\n", 2 * ((len < 4) ? len : 4),
-		    val, (long)memaddr);
+      debug_printf ("Writing %0*x to 0x%08lx in process %d\n",
+		    2 * ((len < 4) ? len : 4), val, (long)memaddr, pid);
     }

   /* Fill start and end extra bytes of buffer with existing memory
data.  */
@@ -6147,13 +6285,17 @@  initialize_low (void)

   initialize_low_arch ();

-#if PTRACE_EVENT_SUPPORT
+#if FULL_PTRACE_EVENT_SUPPORT
   /* Always try to enable ptrace event extensions.  Leave
      PTRACE_O_TRACESYSGOOD out until it is supported.  */
   linux_ptrace_set_additional_flags (PTRACE_O_TRACEVFORKDONE
 				     | PTRACE_O_TRACEVFORK
 				     | PTRACE_O_TRACEFORK
 				     | PTRACE_O_TRACEEXEC);
+#else
+  /* Enable fork events.  */
+  linux_ptrace_set_additional_flags (PTRACE_O_TRACEFORK);
 #endif
   linux_test_for_event_reporting ();
+  /* Enable extended events.  */
 }
diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
index 373fc15..e62b4b8 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -1105,12 +1105,24 @@  prepare_resume_reply (char *buf, ptid_t ptid,
   switch (status->kind)
     {
     case TARGET_WAITKIND_STOPPED:
+    case TARGET_WAITKIND_FORKED:
       {
 	struct thread_info *saved_thread;
 	const char **regp;
 	struct regcache *regcache;

-	sprintf (buf, "T%02x", status->value.sig);
+	if (status->kind == TARGET_WAITKIND_FORKED && multi_process)
+	  {
+	    enum gdb_signal signal = GDB_SIGNAL_TRAP;
+	    const char *event = "fork";
+
+	    sprintf (buf, "T%02x%s:p%x.%lx;", signal, event,
+		      ptid_get_pid (status->value.related_pid),
+		      ptid_get_lwp (status->value.related_pid));
+	  }
+	else
+	  sprintf (buf, "T%02x", status->value.sig);
+
 	buf += strlen (buf);

 	saved_thread = current_thread;
diff --git a/gdb/gdbserver/server.c b/gdb/gdbserver/server.c
index 71c954f..39c9fbd 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -3546,9 +3546,12 @@  process_serial_event (void)
 	  discard_queued_stop_replies (pid);
 	  write_ok (own_buf);

-	  if (extended_protocol)
+	  if ((get_first_inferior (&all_threads) != NULL)
+	      || extended_protocol)
 	    {
-	      /* Treat this like a normal program exit.  */
+	      /* There is still at least one inferior remaining, or we
+		 are in extended mode, so don't terminate gdbserver and
+		 treat this like a normal program exit.  */
 	      last_status.kind = TARGET_WAITKIND_EXITED;
 	      last_status.value.integer = 0;
 	      last_ptid = pid_to_ptid (pid);
diff --git a/gdb/remote.c b/gdb/remote.c
index cb40955..d809fcd 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -24,6 +24,7 @@ 
 #include <fcntl.h>
 #include "inferior.h"
 #include "infrun.h"
+#include "inf-child.h"
 #include "bfd.h"
 #include "symfile.h"
 #include "target.h"
@@ -1441,7 +1442,6 @@  remote_multi_process_p (struct remote_state *rs)
   return packet_support (PACKET_multiprocess_feature) == PACKET_ENABLE;
 }

-#if PTRACE_EVENT_SUPPORT
 /* Returns true if fork events are supported.  */

 static int
@@ -1450,6 +1450,7 @@  remote_fork_event_p (struct remote_state *rs)
   return packet_support (PACKET_fork_event_feature) == PACKET_ENABLE;
 }

+#if PTRACE_EVENT_SUPPORT
 /* Returns true if vfork events are supported.  */

 static int
@@ -1467,6 +1468,51 @@  remote_exec_event_p (struct remote_state *rs)
 }
 #endif

+/* Target follow-fork function for remote targets.  On entry, and
+   at return, the current inferior is the fork parent.  */
+
+static int
+remote_follow_fork (struct target_ops *target, int follow_child,
+		    int detach_fork)
+{
+  struct remote_state *rs = get_remote_state ();
+
+  /* Checking the fork event is sufficient for both fork and vfork.  */
+  if (remote_fork_event_p (rs))
+    {
+      if (detach_fork && !follow_child)
+	{
+	  ptid_t child_ptid;
+	  pid_t child_pid;
+
+	  gdb_assert ((inferior_thread ()->pending_follow.kind
+		       == TARGET_WAITKIND_FORKED)
+		      || (inferior_thread ()->pending_follow.kind
+			  == TARGET_WAITKIND_VFORKED));
+	  child_ptid = inferior_thread ()->pending_follow.value.related_pid;
+	  child_pid = ptid_get_pid (child_ptid);
+
+	  /* Tell the remote target to detach.  */
+	  xsnprintf (rs->buf, get_remote_packet_size (), "D;%x", child_pid);
+
+	  putpkt (rs->buf);
+	  getpkt (&rs->buf, &rs->buf_size, 0);
+
+	  if (rs->buf[0] == 'O' && rs->buf[1] == 'K')
+	    ;
+	  else if (rs->buf[0] == '\0')
+	    error (_("Remote doesn't know how to detach"));
+	  else
+	    error (_("Can't detach process."));
+
+	  inferior_ptid = null_ptid;
+	  detach_inferior (child_pid);
+	  inf_child_maybe_unpush_target (target);
+	}
+    }
+  return 0;
+}
+
 /* Tokens for use by the asynchronous signal handlers for SIGINT.  */
 static struct async_signal_handler *async_sigint_remote_twice_token;
 static struct async_signal_handler *async_sigint_remote_token;
@@ -1867,7 +1913,7 @@  set_general_process (void)
   struct remote_state *rs = get_remote_state ();

   /* If the remote can't handle multiple processes, don't bother.  */
-  if (!rs->extended || !remote_multi_process_p (rs))
+  if (!remote_multi_process_p (rs))
     return;

   /* We only need to change the remote current thread if it's pointing
@@ -4413,10 +4459,12 @@  remote_open_1 (const char *name, int from_tty,
    die when it hits one.  */

 static void
-remote_detach_1 (const char *args, int from_tty, int extended)
+remote_detach_1 (struct target_ops *ops, const char *args,
+		 int from_tty, int extended)
 {
   int pid = ptid_get_pid (inferior_ptid);
   struct remote_state *rs = get_remote_state ();
+  struct thread_info *tp = first_thread_of_process (pid);

   if (args)
     error (_("Argument given to \"detach\" when remotely debugging."));
@@ -4450,22 +4498,31 @@  remote_detach_1 (const char *args, int from_tty,
int extended)
   else
     error (_("Can't detach process."));

-  if (from_tty && !extended)
+  if (from_tty && !extended && (number_of_inferiors () > 0))
     puts_filtered (_("Ending remote debugging.\n"));

-  target_mourn_inferior ();
+  /* If doing detach-on-fork, we don't mourn, because that will delete
+     breakpoints that should be available for the child.  */
+  if (tp->pending_follow.kind != TARGET_WAITKIND_FORKED)
+    target_mourn_inferior ();
+  else
+    {
+      inferior_ptid = null_ptid;
+      detach_inferior (pid);
+      inf_child_maybe_unpush_target (ops);
+    }
 }

 static void
 remote_detach (struct target_ops *ops, const char *args, int from_tty)
 {
-  remote_detach_1 (args, from_tty, 0);
+  remote_detach_1 (ops, args, from_tty, 0);
 }

 static void
 extended_remote_detach (struct target_ops *ops, const char *args, int
from_tty)
 {
-  remote_detach_1 (args, from_tty, 1);
+  remote_detach_1 (ops, args, from_tty, 1);
 }