[v6,5/6] Remote follow vfork

Message ID 1426625788-4469-6-git-send-email-donb@codesourcery.com
State New, archived
Headers

Commit Message

Don Breazeal March 17, 2015, 8:56 p.m. UTC
  Hi Pedro,

This version of this patch is unchanged except possibly for merges from
the mainline.

Thanks,
--Don

This patch implements follow-fork for vfork on remote and extended-remote
Linux targets.

The implementation follows the native implementation as much as possible.
Most of the work is done on the GDB side in the existing code now in
infrun.c.  GDBserver just has to report the events and do a little
bookkeeping.

Implementation was almost entirely in gdbserver, excepting changes to
gdb/remote.c, and included:

 * enabling VFORK events by adding ptrace options for VFORK and VFORK_DONE
   as 'additional options' in linux-low.c:initialize_low.

 * handling VFORK and VFORK_DONE events in linux-low.c:handle_extended_wait
   and reporting them to GDB.
 
 * including VFORK and VFORK_DONE events in the predicate
   linux-low.c:extended_event_reported.

 * adding support for VFORK and VFORK_DONE events in RSP by adding stop
   reasons "vfork" and "vforkdone" to the 'T' Stop Reply Packet in both
   gdbserver/remote-utils.c and gdb/remote.c.

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

Thanks
--Don

gdb/doc/
2015-03-17  Don Breazeal  <donb@codesourcery.com>

	* gdb.texinfo (Stop Reply Packets): List new stop reasons
	"vfork" and "vforkdone".

gdb/gdbserver/
2015-03-17  Don Breazeal  <donb@codesourcery.com>

	* linux-low.c (handle_extended_wait): Handle PTRACE_EVENT_FORK and
	PTRACE_EVENT_VFORK_DONE.
	(linux_low_enable_events): Prevent enablement of VFORK events if
	GDB has not requested them.
	(extended_event_reported): Add vfork and vfork-done to the list
	of extended events.
	(initialize_low): Enable PTRACE_EVENT_FORK and PTRACE_EVENT_VFORK_DONE.
	* remote-utils.c (prepare_resume_reply): New stop reasons "vfork"
	and "vforkdone" for RSP 'T' Stop Reply Packet.
	* server.h (report_vfork_events): Declare
	global variable.

gdb/
2015-03-17  Don Breazeal  <donb@codesourcery.com>

	* NEWS: Add vfork and vforkdone to the announcements about fork
	event support and RSP support for fork events.
	* remote.c (remote_follow_fork): Add vfork event type to assert.
	(remote_parse_stop_reply): New stop reasons "vfork" and
	"vforkdone" for RSP 'T' Stop Reply Packet.

---
 gdb/NEWS                     | 11 +++++++----
 gdb/doc/gdb.texinfo          | 14 ++++++++++++++
 gdb/gdbserver/linux-low.c    | 30 +++++++++++++++++++++++++-----
 gdb/gdbserver/remote-utils.c | 17 +++++++++++++++--
 gdb/gdbserver/server.h       |  1 +
 gdb/remote.c                 | 26 ++++++++++++++++++++++++--
 6 files changed, 86 insertions(+), 13 deletions(-)
  

Comments

Pedro Alves March 24, 2015, 12:28 p.m. UTC | #1
On 03/17/2015 08:56 PM, Don Breazeal wrote:

> diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
> index dc43e38..42c3ec5 100644
> --- a/gdb/gdbserver/remote-utils.c
> +++ b/gdb/gdbserver/remote-utils.c
> @@ -1115,15 +1115,19 @@ prepare_resume_reply (char *buf, ptid_t ptid,
>      {
>      case TARGET_WAITKIND_STOPPED:
>      case TARGET_WAITKIND_FORKED:
> +    case TARGET_WAITKIND_VFORKED:
>        {
>  	struct thread_info *saved_thread;
>  	const char **regp;
>  	struct regcache *regcache;
>  
> -	if (status->kind == TARGET_WAITKIND_FORKED && report_fork_events)
> +	if ((status->kind == TARGET_WAITKIND_FORKED && report_fork_events)
> +	     || (status->kind == TARGET_WAITKIND_VFORKED
> +		 && report_vfork_events))
>  	  {
>  	    enum gdb_signal signal = GDB_SIGNAL_TRAP;
> -	    const char *event = "xfork";
> +	    const char *event = (status->kind == TARGET_WAITKIND_FORKED
> +				 ? "xfork" : "vfork");
>  
>  	    sprintf (buf, "T%02x%s:", signal, event);
>  	    buf += strlen (buf);
> @@ -1245,6 +1249,15 @@ prepare_resume_reply (char *buf, ptid_t ptid,
>        else
>  	sprintf (buf, "X%02x", status->value.sig);
>        break;
> +    case TARGET_WAITKIND_VFORK_DONE:
> +      if (multi_process)
> +	{
> +	  enum gdb_signal signal = GDB_SIGNAL_TRAP;
> +	  const char *event = "vforkdone";
> +

Should only include vforkdone if report_vfork_events is true, I'd think.

> +	  sprintf (buf, "T%02x%s:;", signal, event);
> +	}
> +      break;
>      default:
>        error ("unhandled waitkind");
>        break;
> diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
> index 696a24e..09a5624 100644
> --- a/gdb/gdbserver/server.h
> +++ b/gdb/gdbserver/server.h
> @@ -85,6 +85,7 @@ extern int disable_packet_qfThreadInfo;
>  extern int run_once;
>  extern int multi_process;
>  extern int report_fork_events;
> +extern int report_vfork_events;
>  extern int non_stop;
>  
>  /* True if the "swbreak+" feature is active.  In that case, GDB wants
> diff --git a/gdb/remote.c b/gdb/remote.c
> index 899c7a2..d1ba62d 100644
> --- a/gdb/remote.c
> +++ b/gdb/remote.c
> @@ -4526,15 +4526,20 @@ remote_follow_fork (struct target_ops *ops, int follow_child,
>  {
>    struct remote_state *rs = get_remote_state ();
>  
> +  /* If fork events aren't supported, then return.  We know that if
> +     fork events are supported, then so are vfork events, since they
> +     were both introduced in the same version of the linux kernel.  */

"Linux", caps.

But this really doesn't make sense -- if we define different
qSupported features for fork and vfork, then we should check
them separately.  We don't know (and shouldn't care) whether
we're debugging against a Linux target here.  The packets/features
should work with other OSs.

>    if (remote_fork_event_p (rs))
>      {
>        if (detach_fork && !follow_child)
>  	{
>  	  ptid_t parent_ptid;
>  	  ptid_t child_ptid;
> +	  enum target_waitkind kind;
>  

Thanks,
Pedro Alves
  

Patch

diff --git a/gdb/NEWS b/gdb/NEWS
index ce1bb03..38f5ed7 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -77,8 +77,11 @@  vFile:fstat:
   Return information about files on the remote system.
 
 T Stop Reply Packet's reason
-  The T stop reply packet supports a new stop reason 'xfork', which
-  signifies that the specified inferior has executed a fork.
+  The T stop reply packet supports new stop reasons 'xfork', 'vfork',
+  and 'vforkdone'.  The 'xfork' and 'vfork' reasons signify that the
+  specified inferior has executed a fork or vfork, respectively. The
+  'vforkdone' reason signifies that the a vforked child process has
+  executed either an exec or exit.
 
 * The info record command now shows the recording format and the
   branch tracing configuration for the current thread when using
@@ -102,8 +105,8 @@  Itanium running HP-UX         ia64-*-hpux*
   GDB's qSupported query.
 
   GDBserver extended-remote Linux targets now provides basic support
-  for fork events.  This enables follow-fork-mode and detach-on-fork
-  for those targets with Linux kernels 2.5.60 and later.
+  for fork and vfork events.  This enables follow-fork-mode and
+  detach-on-fork for those targets with Linux kernels 2.5.60 and later.
 
 *** Changes in GDB 7.9
 
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index b62b37f..11e9850 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -35206,6 +35206,20 @@  The packet indicates that @code{fork} was called, and @var{r}
 is the ptid of the new child process.  This packet is only
 applicable to targets that support fork events.
 
+@cindex vfork events, remote reply
+@item vfork
+The packet indicates that @code{vfork} was called, and @var{r}
+is the ptid of the new child process.  This packet is only
+applicable to targets that support vfork events.
+
+@cindex vforkdone events, remote reply
+@item vforkdone
+The packet indicates that a child process created by a vfork
+has either called @code{exec} or terminated, so that the
+address spaces of the parent and child process are no longer
+shared. The @var{r} part is ignored.  This packet is only
+applicable to targets that support vforkdone events.
+
 @cindex replay log events, remote reply
 @item replaylog
 The packet indicates that the target cannot continue replaying 
diff --git a/gdb/gdbserver/linux-low.c b/gdb/gdbserver/linux-low.c
index 07dc244..0ca15ec 100644
--- a/gdb/gdbserver/linux-low.c
+++ b/gdb/gdbserver/linux-low.c
@@ -382,7 +382,8 @@  handle_extended_wait (struct lwp_info *event_lwp, int wstat)
   struct thread_info *event_thr = get_lwp_thread (event_lwp);
   struct lwp_info *new_lwp;
 
-  if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_CLONE))
+  if ((event == PTRACE_EVENT_FORK) || (event == PTRACE_EVENT_VFORK)
+      || (event == PTRACE_EVENT_CLONE))
     {
       ptid_t ptid;
       unsigned long new_pid;
@@ -408,7 +409,7 @@  handle_extended_wait (struct lwp_info *event_lwp, int wstat)
 	    warning ("wait returned unexpected status 0x%x", status);
 	}
 
-      if (event == PTRACE_EVENT_FORK)
+      if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK)
 	{
 	  struct process_info *parent_proc;
 	  struct process_info *child_proc;
@@ -451,8 +452,13 @@  handle_extended_wait (struct lwp_info *event_lwp, int wstat)
 	    the_low_target.new_fork (parent_proc, child_proc);
 
 	  /* Save fork info in the parent thread.  */
-	  event_lwp->waitstatus.kind = TARGET_WAITKIND_FORKED;
+	  if (event == PTRACE_EVENT_FORK)
+	    event_lwp->waitstatus.kind = TARGET_WAITKIND_FORKED;
+	  else if (event == PTRACE_EVENT_VFORK)
+	    event_lwp->waitstatus.kind = TARGET_WAITKIND_VFORKED;
+
 	  event_lwp->waitstatus.value.related_pid = ptid;
+
 	  /* The status_pending field contains bits denoting the
 	     extended event, so when the pending event is handled,
 	     the handler will look at lwp->waitstatus.  */
@@ -495,6 +501,14 @@  handle_extended_wait (struct lwp_info *event_lwp, int wstat)
       /* Don't report the event.  */
       return 1;
     }
+  else if (event == PTRACE_EVENT_VFORK_DONE)
+    {
+      struct lwp_info *parent_lwp;
+
+      parent_lwp = get_thread_lwp (event_thr);
+      parent_lwp->waitstatus.kind = TARGET_WAITKIND_VFORK_DONE;
+      return 0;
+    }
 
   internal_error (__FILE__, __LINE__, _("unknown ptrace event %d"), event);
 }
@@ -1899,6 +1913,8 @@  linux_low_enable_events (pid_t pid, int attached)
 {
   if (!report_fork_events)
     linux_ptrace_clear_flags (PTRACE_O_TRACEFORK);
+  if (!report_vfork_events)
+    linux_ptrace_clear_flags (PTRACE_O_TRACEVFORK);
 
   linux_enable_event_reporting (pid, attached);
 }
@@ -2590,7 +2606,9 @@  extended_event_reported (const struct target_waitstatus *waitstatus)
   if (waitstatus == NULL)
     return 0;
 
-  return (waitstatus->kind == TARGET_WAITKIND_FORKED);
+  return (waitstatus->kind == TARGET_WAITKIND_FORKED
+	  || waitstatus->kind == TARGET_WAITKIND_VFORKED
+	  || waitstatus->kind == TARGET_WAITKIND_VFORK_DONE);
 }
 
 /* Wait for process, returns status.  */
@@ -6458,6 +6476,8 @@  initialize_low (void)
   initialize_low_arch ();
 
   /* Enable extended ptrace events.  */
-  linux_ptrace_set_additional_flags (PTRACE_O_TRACEFORK);
+  linux_ptrace_set_additional_flags (PTRACE_O_TRACEFORK
+					       | PTRACE_O_TRACEVFORK
+					       | PTRACE_O_TRACEVFORKDONE);
   linux_check_ptrace_features ();
 }
diff --git a/gdb/gdbserver/remote-utils.c b/gdb/gdbserver/remote-utils.c
index dc43e38..42c3ec5 100644
--- a/gdb/gdbserver/remote-utils.c
+++ b/gdb/gdbserver/remote-utils.c
@@ -1115,15 +1115,19 @@  prepare_resume_reply (char *buf, ptid_t ptid,
     {
     case TARGET_WAITKIND_STOPPED:
     case TARGET_WAITKIND_FORKED:
+    case TARGET_WAITKIND_VFORKED:
       {
 	struct thread_info *saved_thread;
 	const char **regp;
 	struct regcache *regcache;
 
-	if (status->kind == TARGET_WAITKIND_FORKED && report_fork_events)
+	if ((status->kind == TARGET_WAITKIND_FORKED && report_fork_events)
+	     || (status->kind == TARGET_WAITKIND_VFORKED
+		 && report_vfork_events))
 	  {
 	    enum gdb_signal signal = GDB_SIGNAL_TRAP;
-	    const char *event = "xfork";
+	    const char *event = (status->kind == TARGET_WAITKIND_FORKED
+				 ? "xfork" : "vfork");
 
 	    sprintf (buf, "T%02x%s:", signal, event);
 	    buf += strlen (buf);
@@ -1245,6 +1249,15 @@  prepare_resume_reply (char *buf, ptid_t ptid,
       else
 	sprintf (buf, "X%02x", status->value.sig);
       break;
+    case TARGET_WAITKIND_VFORK_DONE:
+      if (multi_process)
+	{
+	  enum gdb_signal signal = GDB_SIGNAL_TRAP;
+	  const char *event = "vforkdone";
+
+	  sprintf (buf, "T%02x%s:;", signal, event);
+	}
+      break;
     default:
       error ("unhandled waitkind");
       break;
diff --git a/gdb/gdbserver/server.h b/gdb/gdbserver/server.h
index 696a24e..09a5624 100644
--- a/gdb/gdbserver/server.h
+++ b/gdb/gdbserver/server.h
@@ -85,6 +85,7 @@  extern int disable_packet_qfThreadInfo;
 extern int run_once;
 extern int multi_process;
 extern int report_fork_events;
+extern int report_vfork_events;
 extern int non_stop;
 
 /* True if the "swbreak+" feature is active.  In that case, GDB wants
diff --git a/gdb/remote.c b/gdb/remote.c
index 899c7a2..d1ba62d 100644
--- a/gdb/remote.c
+++ b/gdb/remote.c
@@ -4526,15 +4526,20 @@  remote_follow_fork (struct target_ops *ops, int follow_child,
 {
   struct remote_state *rs = get_remote_state ();
 
+  /* If fork events aren't supported, then return.  We know that if
+     fork events are supported, then so are vfork events, since they
+     were both introduced in the same version of the linux kernel.  */
   if (remote_fork_event_p (rs))
     {
       if (detach_fork && !follow_child)
 	{
 	  ptid_t parent_ptid;
 	  ptid_t child_ptid;
+	  enum target_waitkind kind;
 
-	  gdb_assert (inferior_thread ()->pending_follow.kind
-		      == TARGET_WAITKIND_FORKED);
+	  kind = inferior_thread ()->pending_follow.kind;
+	  gdb_assert (kind == TARGET_WAITKIND_FORKED
+		      || kind == TARGET_WAITKIND_VFORKED);
 
 	  /* remote_detach_1 detaches inferior_ptid, which is currently
 	     the ptid of the parent.  Switch inferior_ptid to the ptid
@@ -5724,6 +5729,23 @@  Packet: '%s'\n"),
 	      event->ws.value.related_pid = read_ptid (++p1, &p);
 	      event->ws.kind = TARGET_WAITKIND_FORKED;
 	    }
+	  else if (strncmp (p, "vfork", p1 - p) == 0)
+	    {
+	      event->ws.value.related_pid = read_ptid (++p1, &p);
+	      event->ws.kind = TARGET_WAITKIND_VFORKED;
+	    }
+	  else if (strncmp (p, "vforkdone", p1 - p) == 0)
+	    {
+	      char *p_temp;
+
+	      p1++;
+	      p_temp = p1;
+	      while (*p_temp && *p_temp != ';')
+		p_temp++;
+
+	      event->ws.kind = TARGET_WAITKIND_VFORK_DONE;
+	      p = p_temp;
+	    }
 	  else
 	    {
 	      ULONGEST pnum;