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

Message ID 541C98BA.2040904@codesourcery.com
State New, archived
Headers

Commit Message

Don Breazeal Sept. 19, 2014, 8:57 p.m. UTC
  This is a small update to this patch so that it will work on targets
where hardware single step is not supported.  I had noted previously
that I had not tested inheritance of hardware watchpoints across a
fork.  I have now completed that testing, which exposed a couple of
problems, one of which is fixed by this update.  The only change from
the previous version of this patch is to initialize the variable
'status' in linux-low.c:linux_follow_fork.

Thanks,
--Don

On 8/20/2014 5:29 PM, Don Breazeal wrote:
> This patch implements basic support for follow-fork and detach-on-fork on
> 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
>  
>    - this adds the ptrace option to trace fork events to the new functions
>      from patch 4 that set up ptrace options.
> 
>  * 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
>      mimics a similar mechanism used in the native implementation.  Here is
>      it used in several ways:
>      - in remote_detach_1 to distinguish between a process that is being
>        detached-on-fork vs. just detached.  In the fork case we don't want
>        to mourn the process because we want to keep the inferior around in
>        case the user decides to run the inferior again.
>      - to record the child pid for the expected follow_fork request.
>      - to find the parent in follow_fork (and later, elsewhere).
> 
>    - we also store the waitstatus in a new lwp_info member, 'waitstatus',
>      which is used in controlling the reporting of the event in
>      linux_wait_1.  We cannot re-use pending_follow for this because we
>      need to mark this one with 'ignored' status as part of the
>      linux_wait_1 procedure to stop event processing, and we need to keep
>      pending_follow intact for use later on.  We will also need this later
>      on for exec event handling, where pending_follow makes no sense.
> 
>    - 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 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.
> 
>  * 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).
> 
>    - extended_remote target routine extended_remote_follow_fork
> 
>    - RSP packet vFollowFork
> 
>    - in gdbserver struct target_ops, add functions linux_supports_follow_fork
>      and linux_follow_fork.  The linux_follow_fork routine mimics the
>      implementation of linux-nat.c:linux_child_follow_fork, but the data
>      structures in use prevented turning this into a common function for now.
> 
> Tested on x64 Ubuntu Lucid, native, remote, extended-remote.
> 
> Thanks
> --Don
> 
> gdb/
> 2014-08-20  Don Breazeal  <donb@codesourcery.com>
> 
> 	* remote.c (remote_detach_1): Add target_ops argument, handle
> 	detach-on-fork.
> 	(remote_detach, extended_remote_detach): Call remote_detach_1
> 	with target_ops argument.
> 	(remote_parse_stop_reply): Handle new RSP stop reason "fork" in
> 	'T' stop reply packet.
> 	(extended_remote_follow_fork): Implement follow-fork support.
> 	(remote_pid_to_str): Print process.
> 	(_initialize_remote): Call add_packet_config_cmd for new RSP packet.
> 
> gdb/gdbserver/
> 2014-08-20  Don Breazeal  <donb@codesourcery.com>
> 
> 	* gdbthread.h (struct thread_info) <pending_follow>: New member.
> 	* linux-low.c (handle_extended_wait): Change function type from
> 	void to int, handle PTRACE_EVENT_FORK, call internal_error.
> 	(is_parent_callback): New function.
> 	(linux_follow_fork): New function.
> 	(linux_low_filter_event): Handle return value from
> 	handle_extended_wait.
> 	(extended_event_reported): New function.
> 	(linux_write_memory): Add pid to debug print.
> 	(linux_target_ops) <follow_fork>: Initialize new member.
> 	(initialize_low): Add PTRACE_O_TRACEFORK option.
> 	* linux-low.h (struct lwp_info) <waitstatus>: New member.
> 	* lynx-low.c (lynx_target_ops) <follow_fork>: Initialize new member.
> 	* nto-low.c (nto_target_ops) <follow_fork>: Initialize new member.
> 	* remote-utils.c (prepare_resume_reply): New RSP stop reason "fork"
> 	for 'T' stop reply.
> 	* server.c (handle_v_follow_fork): New function.
> 	(handle_v_requests): Handle vFollowFork packet, call
> 	handle_v_follow_fork.
> 	* spu-low.c (spu_target_ops) <follow_fork>: Initialize new member.
> 	* target.h (struct target_ops) <follow_fork>: New member.
> 	(target_follow_fork): Define macro.
> 	* win32-low.c (win32_target_ops) <follow_fork>: Initialize new member.
> 

---
 gdb/gdbserver/gdbthread.h    |    5 +
 gdb/gdbserver/linux-low.c    |  207
+++++++++++++++++++++++++++++++++++++++---
 gdb/gdbserver/linux-low.h    |    5 +
 gdb/gdbserver/lynx-low.c     |    1 +
 gdb/gdbserver/nto-low.c      |    1 +
 gdb/gdbserver/remote-utils.c |   14 +++-
 gdb/gdbserver/server.c       |   41 ++++++++
 gdb/gdbserver/spu-low.c      |    1 +
 gdb/gdbserver/target.h       |   13 +++
 gdb/gdbserver/win32-low.c    |    1 +
 gdb/remote.c                 |   80 ++++++++++++----
 11 files changed, 334 insertions(+), 35 deletions(-)

     PACKET_qXfer_siginfo_read },
@@ -4333,10 +4336,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."));
@@ -4373,19 +4378,28 @@ remote_detach_1 (const char *args, int from_tty,
int extended)
   if (from_tty && !extended)
     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);
 }

 /* Same as remote_detach, but don't send the "D" packet; just
disconnect.  */
@@ -5481,7 +5495,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);
@@ -5533,6 +5548,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.  */
@@ -7820,6 +7840,32 @@ extended_remote_kill (struct target_ops *ops)
   target_mourn_inferior ();
 }

+/* Target routine for follow-fork.  */
+
+static int
+extended_remote_follow_fork (struct target_ops *ops, int follow_child,
+			     int detach_fork)
+{
+  struct remote_state *rs = get_remote_state ();
+
+  if (extended_remote_feature_supported (FORK_EVENT))
+    {
+      char *p = rs->buf;
+      char *endbuf = rs->buf + get_remote_packet_size ();
+
+      xsnprintf (rs->buf, get_remote_packet_size (), "vFollowFork;%d;%d",
+		 follow_child, detach_fork);
+
+      putpkt (rs->buf);
+      getpkt (&rs->buf, &rs->buf_size, 0);
+
+      if (rs->buf[0] == 'E')
+	return 1;
+    }
+
+  return 0;
+}
+
 static void
 remote_mourn (struct target_ops *ops)
 {
@@ -7836,20 +7882,6 @@ remote_mourn_1 (struct target_ops *target)
   generic_mourn_inferior ();
 }

-/* Target follow-fork function for extended-remote targets.  */
-
-static int
-extended_remote_follow_fork (struct target_ops *target, int follow_child,
-			     int detach_fork)
-{
-  if (extended_remote_feature_supported (FORK_EVENT))
-    {
-      /* FIXME: Implement follow-fork here.  */
-      return -1;
-    }
-  return 0;
-}
-
 static void
 extended_remote_mourn_1 (struct target_ops *target)
 {
@@ -9356,8 +9388,11 @@ 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));
+	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));
@@ -12184,6 +12219,9 @@ Show the maximum size of the address (in bits)
in a memory packet."), NULL,
   add_packet_config_cmd (&remote_protocol_packets[PACKET_vKill],
 			 "vKill", "kill", 0);

+  add_packet_config_cmd (&remote_protocol_packets[PACKET_vFollowFork],
+			 "vFollowFork", "follow-fork", 0);
+
   add_packet_config_cmd (&remote_protocol_packets[PACKET_qAttached],
 			 "qAttached", "query-attached", 0);
  

Patch

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

+  /* This is used to remember when a fork or vfork event was caught by
+     a catchpoint, and thus the event is to be followed at the next
+     resume of the thread, and not immediately.  */
+  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 045d507..56993bd 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"
@@ -368,17 +369,17 @@  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
+static int
 handle_extended_wait (struct lwp_info *event_child, int wstat)
 {
   int event = linux_ptrace_get_extended_event (wstat);
   struct thread_info *event_thr = get_lwp_thread (event_child);
   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;
@@ -403,6 +404,56 @@  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 (current_inferior);
+	  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 for target processing.  */
+	  current_inferior->pending_follow.kind = TARGET_WAITKIND_FORKED;
+	  current_inferior->pending_follow.value.related_pid = ptid;
+
+	  /* Save fork info for reporting to GDB.  */
+	  event_child->waitstatus.kind = TARGET_WAITKIND_FORKED;
+	  event_child->waitstatus.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",
@@ -452,7 +503,12 @@  handle_extended_wait (struct lwp_info *event_child,
int wstat)
 	 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);
+
+      /* 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
@@ -1177,6 +1233,97 @@  linux_detach (int pid)
   return 0;
 }

+/* Callback used to find the parent process of a fork.  */
+
+static int
+is_parent_callback (struct inferior_list_entry *entry, void *ignore)
+{
+  struct thread_info *thread = (struct thread_info *) entry;
+
+  if (thread->pending_follow.kind == TARGET_WAITKIND_FORKED
+      || thread->pending_follow.kind == TARGET_WAITKIND_VFORKED)
+    return 1;
+
+  return 0;
+}
+
+/* Handle a fork in the inferior process.  Mainly this consists of
+   handling the case where we are detaching the new child process by
+   cleaning up its state so it can proceed.  Note that if we are
+   detaching the parent process, GDB has already done that via
+   target_detach.  */
+
+static int
+linux_follow_fork (int follow_child, int detach_fork)
+{
+  struct inferior_list_entry *parent_inf;
+  struct thread_info *parent_thread;
+
+  parent_inf = find_inferior (&all_threads, is_parent_callback, NULL);
+
+  /* If we can't find the parent, we are following the child and the
+     parent has already been detached.  Nothing to do, so return OK.  */
+  if (parent_inf == NULL)
+    return 0;
+
+  parent_thread = (struct thread_info *)parent_inf;
+  parent_thread->pending_follow.kind = TARGET_WAITKIND_IGNORE;
+
+  if (!follow_child)
+    {
+      if (detach_fork)
+	{
+	  int status = W_STOPCODE (0);
+	  ptid_t child_ptid = parent_thread->pending_follow.value.related_pid;
+	  pid_t child_pid = ptid_get_pid (child_ptid);
+	  struct lwp_info *child_lwp = find_lwp_pid (child_ptid);
+
+	  if (the_low_target.prepare_to_resume != NULL)
+	    the_low_target.prepare_to_resume (child_lwp);
+
+	  /* 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_ptrace_disable_options (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 process_info *child_proc;
+
+	      signo = WSTOPSIG (status);
+	      if (signo == SIGSTOP
+		  || (signo != 0
+		      && !pass_signals[gdb_signal_from_host (signo)]))
+		signo = 0;
+
+	      ptrace (PTRACE_DETACH, child_pid, 0, signo);
+
+	      /* Deallocate all process-related storage.  */
+	      child_proc = find_process_pid (child_pid);
+	      if (child_proc != NULL)
+		the_target->mourn (child_proc);
+
+	      current_inferior = NULL;
+	    }
+	}
+    }
+
+  return 0;
+}
+
 /* Remove all LWPs that belong to process PROC from the lwp list.  */

 static int
@@ -1875,8 +2022,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
@@ -2477,6 +2626,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
@@ -2840,7 +3002,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 (&event_child->waitstatus));

   run_breakpoint_commands (event_child->stop_pc);

@@ -2862,6 +3025,13 @@  retry:
 			  paddress (event_child->stop_pc),
 			  paddress (event_child->step_range_start),
 			  paddress (event_child->step_range_end));
+	  if (extended_event_reported (&event_child->waitstatus))
+	    {
+	      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
@@ -2960,7 +3130,17 @@  retry:
 	unstop_all_lwps (1, event_child);
     }

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

   if (current_thread->last_resume_kind == resume_stop
       && WSTOPSIG (w) == SIGSTOP)
@@ -2977,7 +3157,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));
     }
@@ -4769,8 +4949,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.  */
@@ -6065,6 +6245,7 @@  static struct target_ops linux_target_ops = {
   linux_supports_follow_fork,
   linux_supports_follow_exec,
   linux_enable_extended_features,
+  linux_follow_fork,
   linux_mourn,
   linux_join,
   linux_thread_alive,
@@ -6180,7 +6361,7 @@  initialize_low (void)

   initialize_low_arch ();

-  /* Placeholder to enable extended events.  */
-  linux_ptrace_set_requested_options (0);
+  /* Enable extended events.  */
+  linux_ptrace_set_requested_options (PTRACE_O_TRACEFORK);
   linux_ptrace_check_options ();
 }
diff --git a/gdb/gdbserver/linux-low.h b/gdb/gdbserver/linux-low.h
index 4820929..a903430 100644
--- a/gdb/gdbserver/linux-low.h
+++ b/gdb/gdbserver/linux-low.h
@@ -266,6 +266,11 @@  struct lwp_info
      status_pending).  */
   int dead;

+  /* If WAITSTATUS->KIND != TARGET_WAITKIND_IGNORE, the waitstatus for
+     this LWP's last event.  This is used to maintain the current status
+     during event processing.  */
+  struct target_waitstatus waitstatus;
+
   /* When stopped is set, the last wait status recorded for this lwp.  */
   int last_status;

diff --git a/gdb/gdbserver/lynx-low.c b/gdb/gdbserver/lynx-low.c
index d399e67..902302a 100644
--- a/gdb/gdbserver/lynx-low.c
+++ b/gdb/gdbserver/lynx-low.c
@@ -725,6 +725,7 @@  static struct target_ops lynx_target_ops = {
   NULL,  /* supports_follow_fork */
   NULL,  /* supports_follow_exec */
   NULL,  /* enable_extended_features */
+  NULL,  /* follow_fork */
   lynx_mourn,
   lynx_join,
   lynx_thread_alive,
diff --git a/gdb/gdbserver/nto-low.c b/gdb/gdbserver/nto-low.c
index 17f2a14..934657f 100644
--- a/gdb/gdbserver/nto-low.c
+++ b/gdb/gdbserver/nto-low.c
@@ -931,6 +931,7 @@  static struct target_ops nto_target_ops = {
   NULL, /* supports_follow_fork */
   NULL, /* supports_follow_exec */
   NULL, /* enable_extended_features */
+  NULL, /* follow_fork */
   nto_mourn,
   NULL, /* nto_join */
   nto_thread_alive,
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 a13d2d5..d756c94 100644
--- a/gdb/gdbserver/server.c
+++ b/gdb/gdbserver/server.c
@@ -2558,6 +2558,29 @@  handle_v_kill (char *own_buf)
     }
 }

+/* Handle forked process.  */
+
+static void
+handle_v_follow_fork (char *own_buf)
+{
+  int follow_child;
+  int detach_fork;
+  char *p = &own_buf[12];
+  int ret;
+
+  gdb_assert (extended_protocol);
+
+  follow_child = strtol (p, NULL, 16);
+  p = strchr (p, ';') + 1;
+  detach_fork = strtol (p, NULL, 16);
+
+  ret = target_follow_fork (follow_child, detach_fork);
+  if (ret == 0)
+    write_ok (own_buf);
+  else
+    write_enn (own_buf);
+}
+
 /* Handle all of the extended 'v' packets.  */
 void
 handle_v_requests (char *own_buf, int packet_len, int *new_packet_len)
@@ -2623,6 +2646,24 @@  handle_v_requests (char *own_buf, int packet_len,
int *new_packet_len)
       return;
     }

+  if (strncmp (own_buf, "vFollowFork;", 6) == 0)
+    {
+      if (!target_running ())
+	{
+	  fprintf (stderr, "No process to follow\n");
+	  write_enn (own_buf);
+	  return;
+	}
+      if (!extended_protocol || !multi_process)
+	{
+	  fprintf (stderr, "Target doesn't support follow-fork\n");
+	  write_enn (own_buf);
+	  return;
+	}
+      handle_v_follow_fork (own_buf);
+      return;
+    }
+
   if (handle_notif_ack (own_buf, packet_len))
     return;

diff --git a/gdb/gdbserver/spu-low.c b/gdb/gdbserver/spu-low.c
index d4c5429..9becef6 100644
--- a/gdb/gdbserver/spu-low.c
+++ b/gdb/gdbserver/spu-low.c
@@ -644,6 +644,7 @@  static struct target_ops spu_target_ops = {
   NULL, /* supports_follow_fork */
   NULL, /* supports_follow_exec */
   NULL, /* enable_extended_features */
+  NULL, /* follow_fork */
   spu_mourn,
   spu_join,
   spu_thread_alive,
diff --git a/gdb/gdbserver/target.h b/gdb/gdbserver/target.h
index abce9b3..03bfe76 100644
--- a/gdb/gdbserver/target.h
+++ b/gdb/gdbserver/target.h
@@ -105,6 +105,11 @@  struct target_ops

   void (*enable_extended_features) (void);

+  /* Handle a call to fork as specified by follow-fork-mode and
+     detach-on-fork.  */
+
+  int (*follow_fork) (int follow_child, int detach_fork);
+
   /* The inferior process has died.  Do what is right.  */

   void (*mourn) (struct process_info *proc);
@@ -438,6 +443,14 @@  int kill_inferior (int);
   (the_target->supports_multi_process ? \
    (*the_target->supports_multi_process) () : 0)

+#define target_supports_follow_fork() \
+ (the_target->supports_follow_fork ? \
+  (*the_target->supports_follow_fork) () : 0)
+
+#define target_follow_fork(follow_child, detach_fork) \
+  (the_target->follow_fork ? \
+   (*the_target->follow_fork) (follow_child, detach_fork) : 0)
+
 #define target_process_qsupported(query)		\
   do							\
     {							\
diff --git a/gdb/gdbserver/win32-low.c b/gdb/gdbserver/win32-low.c
index 42cbf32..944d06b 100644
--- a/gdb/gdbserver/win32-low.c
+++ b/gdb/gdbserver/win32-low.c
@@ -1770,6 +1770,7 @@  static struct target_ops win32_target_ops = {
   NULL, /* supports_follow_fork */
   NULL, /* supports_follow_exec */
   NULL, /* enable_extended_features */
+  NULL, /* follow_fork */
   win32_mourn,
   win32_join,
   win32_thread_alive,
diff --git a/gdb/remote.c b/gdb/remote.c
index f3076b3..19f3efb 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 "exceptions.h"
@@ -3916,6 +3917,8 @@  static const struct protocol_feature
remote_protocol_features[] = {
     PACKET_QStartNoAckMode },
   { "multiprocess", PACKET_DISABLE, remote_supported_packet,
     PACKET_multiprocess_feature },
+  { "vFollowFork", PACKET_DISABLE, remote_supported_packet,
+    PACKET_vFollowFork },
   { "QNonStop", PACKET_DISABLE, remote_supported_packet, PACKET_QNonStop },
   { "qXfer:siginfo:read", PACKET_DISABLE, remote_supported_packet,