Fake VFORK_DONE events when following only the parent after a vfork.
authorJohn Baldwin <jhb@FreeBSD.org>
Sat, 25 Jun 2016 04:00:04 +0000 (21:00 -0700)
committerJohn Baldwin <jhb@FreeBSD.org>
Fri, 1 Jul 2016 15:35:33 +0000 (08:35 -0700)
FreeBSD does not currently report a ptrace event for a parent process
after it resumes due to the child exiting the shared memory region after
a vfork.  Take the same approach used in linux-nat.c in this case of
sleeping for a while and then reporting a fake VFORK_DONE event.

gdb/ChangeLog:

* fbsd-nat.c (struct fbsd_fork_child_info): Rename to ...
(struct fbsd_fork_info): ... this.
(struct fbsd_fork_info) <child>: Rename to ...
(struct fbsd_fork_info) <ptid>: ... this.
(fbsd_pending_children): Update type.
(fbsd_remember_child): Update type and field name.
(fbsd_is_child_pending): Likewise.
(fbsd_pending_vfork_done): New variable.
(fbsd_is_vfork_done_pending): New function.
(fbsd_next_vfork_done): New function.
(fbsd_resume): Don't resume processes with a pending vfork done
event.
(fbsd_wait): Report pending vfork done events.
(fbsd_follow_fork): Delay and record a pending vfork done event
for a vfork parent when detaching the child.

gdb/ChangeLog
gdb/fbsd-nat.c

index 1a51fb231c7adad35b312c26d9c1f484aef053b4..58c9c787aa011720032c3a8dd0b1be9fe1fa37b4 100644 (file)
@@ -1,3 +1,21 @@
+2016-07-01  John Baldwin  <jhb@FreeBSD.org>
+
+       * fbsd-nat.c (struct fbsd_fork_child_info): Rename to ...
+       (struct fbsd_fork_info): ... this.
+       (struct fbsd_fork_info) <child>: Rename to ...
+       (struct fbsd_fork_info) <ptid>: ... this.
+       (fbsd_pending_children): Update type.
+       (fbsd_remember_child): Update type and field name.
+       (fbsd_is_child_pending): Likewise.
+       (fbsd_pending_vfork_done): New variable.
+       (fbsd_is_vfork_done_pending): New function.
+       (fbsd_next_vfork_done): New function.
+       (fbsd_resume): Don't resume processes with a pending vfork done
+       event.
+       (fbsd_wait): Report pending vfork done events.
+       (fbsd_follow_fork): Delay and record a pending vfork done event
+       for a vfork parent when detaching the child.
+
 2016-07-01  John Baldwin  <jhb@FreeBSD.org>
 
        * fbsd-nat.c (super_resume): Move earlier next to "super_wait".
index daf4580a579a0762d6c4d0b0eb8dcab8e410d395..fcb7ff5e1794d113c8a0d989a487a9ca8fb73cd2 100644 (file)
@@ -530,13 +530,13 @@ fbsd_update_thread_list (struct target_ops *ops)
   sake.  FreeBSD versions newer than 9.1 contain both fixes.
 */
 
-struct fbsd_fork_child_info
+struct fbsd_fork_info
 {
-  struct fbsd_fork_child_info *next;
-  ptid_t child;                        /* Pid of new child.  */
+  struct fbsd_fork_info *next;
+  ptid_t ptid;
 };
 
-static struct fbsd_fork_child_info *fbsd_pending_children;
+static struct fbsd_fork_info *fbsd_pending_children;
 
 /* Record a new child process event that is reported before the
    corresponding fork event in the parent.  */
@@ -544,9 +544,9 @@ static struct fbsd_fork_child_info *fbsd_pending_children;
 static void
 fbsd_remember_child (ptid_t pid)
 {
-  struct fbsd_fork_child_info *info = XCNEW (struct fbsd_fork_child_info);
+  struct fbsd_fork_info *info = XCNEW (struct fbsd_fork_info);
 
-  info->child = pid;
+  info->ptid = pid;
   info->next = fbsd_pending_children;
   fbsd_pending_children = info;
 }
@@ -557,25 +557,74 @@ fbsd_remember_child (ptid_t pid)
 static ptid_t
 fbsd_is_child_pending (pid_t pid)
 {
-  struct fbsd_fork_child_info *info, *prev;
+  struct fbsd_fork_info *info, *prev;
   ptid_t ptid;
 
   prev = NULL;
   for (info = fbsd_pending_children; info; prev = info, info = info->next)
     {
-      if (ptid_get_pid (info->child) == pid)
+      if (ptid_get_pid (info->ptid) == pid)
        {
          if (prev == NULL)
            fbsd_pending_children = info->next;
          else
            prev->next = info->next;
-         ptid = info->child;
+         ptid = info->ptid;
          xfree (info);
          return ptid;
        }
     }
   return null_ptid;
 }
+
+static struct fbsd_fork_info *fbsd_pending_vfork_done;
+
+/* Record a pending vfork done event.  */
+
+static void
+fbsd_add_vfork_done (ptid_t pid)
+{
+  struct fbsd_fork_info *info = XCNEW (struct fbsd_fork_info);
+
+  info->ptid = pid;
+  info->next = fbsd_pending_vfork_done;
+  fbsd_pending_vfork_done = info;
+}
+
+/* Check for a pending vfork done event for a specific PID.  */
+
+static int
+fbsd_is_vfork_done_pending (pid_t pid)
+{
+  struct fbsd_fork_info *info;
+
+  for (info = fbsd_pending_vfork_done; info != NULL; info = info->next)
+    {
+      if (ptid_get_pid (info->ptid) == pid)
+       return 1;
+    }
+  return 0;
+}
+
+/* Check for a pending vfork done event.  If one is found, remove it
+   from the list and return the PTID.  */
+
+static ptid
+fbsd_next_vfork_done (void)
+{
+  struct fbsd_fork_info *info;
+  ptid_t ptid;
+
+  if (fbsd_pending_vfork_done != NULL)
+    {
+      info = fbsd_pending_vfork_done;
+      fbsd_pending_vfork_done = info->next;
+      ptid = info->ptid;
+      xfree (info);
+      return ptid;
+    }
+  return null_ptid;
+}
 #endif
 
 static int
@@ -616,6 +665,17 @@ static void
 fbsd_resume (struct target_ops *ops,
             ptid_t ptid, int step, enum gdb_signal signo)
 {
+#ifdef TDP_RFPPWAIT
+  pid_t pid;
+
+  /* Don't PT_CONTINUE a process which has a pending vfork done event.  */
+  if (ptid_equal (minus_one_ptid, ptid))
+    pid = ptid_get_pid (inferior_ptid);
+  else
+    pid = ptid_get_pid (ptid);
+  if (fbsd_is_vfork_done_pending (pid))
+    return;
+#endif
 
   if (debug_fbsd_lwp)
     fprintf_unfiltered (gdb_stdlog,
@@ -650,6 +710,12 @@ fbsd_wait (struct target_ops *ops,
 
   while (1)
     {
+      wptid = fbsd_next_vfork_done ();
+      if (!ptid_equal (wptid, null_ptid))
+       {
+         ourstatus->kind = TARGET_WAITKIND_VFORK_DONE;
+         return wptid;
+       }
       wptid = super_wait (ops, ptid, ourstatus, target_options);
       if (ourstatus->kind == TARGET_WAITKIND_STOPPED)
        {
@@ -828,6 +894,7 @@ fbsd_follow_fork (struct target_ops *ops, int follow_child,
   if (!follow_child && detach_fork)
     {
       struct thread_info *tp = inferior_thread ();
+      int has_vforked = tp->pending_follow.kind == TARGET_WAITKIND_VFORKED;
       pid_t child_pid = ptid_get_pid (tp->pending_follow.value.related_pid);
 
       /* Breakpoints have already been detached from the child by
@@ -835,6 +902,33 @@ fbsd_follow_fork (struct target_ops *ops, int follow_child,
 
       if (ptrace (PT_DETACH, child_pid, (PTRACE_TYPE_ARG3)1, 0) == -1)
        perror_with_name (("ptrace"));
+
+      if (has_vforked)
+       {
+         /* We can't insert breakpoints until the child process has
+            finished with the shared memory region.  The parent
+            process doesn't wait for the child process to exit or
+            exec until after it has been resumed from the ptrace stop
+            to report the fork.  Once it has been resumed it doesn't
+            stop again before returning to userland, so there is no
+            reliable way to wait on the parent.
+
+            We can't stay attached to the child to wait for an exec
+            or exit because it may invoke ptrace(PT_TRACE_ME)
+            (e.g. if the parent process is a debugger forking a new
+            child process).
+
+            In the end, the best we can do is to make sure it runs
+            for a little while.  Hopefully it will be out of range of
+            any breakpoints we reinsert.  Usually this is only the
+            single-step breakpoint at vfork's return point.  */
+
+         usleep (10000);
+
+         /* Schedule a fake VFORK_DONE event to report on the next
+            wait.  */
+         fbsd_add_vfork_done (inferior_ptid);
+       }
     }
 
   return 0;