Explicit locations: introduce explicit locations
[deliverable/binutils-gdb.git] / gdb / fbsd-nat.c
index 68b8e65de1ae78a9877ad8e9a4595fab9eaa3acd..9705d45a161e0f3be43a112759d1ffc4a5bb627b 100644 (file)
 #include "regcache.h"
 #include "regset.h"
 #include "gdbthread.h"
+#include "gdb_wait.h"
 #include <sys/types.h>
 #include <sys/procfs.h>
+#include <sys/ptrace.h>
 #include <sys/sysctl.h>
 #ifdef HAVE_KINFO_GETVMMAP
 #include <sys/user.h>
@@ -201,10 +203,311 @@ fbsd_find_memory_regions (struct target_ops *self,
 }
 #endif
 
+#ifdef PT_LWPINFO
+static ptid_t (*super_wait) (struct target_ops *,
+                            ptid_t,
+                            struct target_waitstatus *,
+                            int);
+
+#ifdef TDP_RFPPWAIT
+/*
+  To catch fork events, PT_FOLLOW_FORK is set on every traced process
+  to enable stops on returns from fork or vfork.  Note that both the
+  parent and child will always stop, even if system call stops are not
+  enabled.
+
+  After a fork, both the child and parent process will stop and report
+  an event.  However, there is no guarantee of order.  If the parent
+  reports its stop first, then fbsd_wait explicitly waits for the new
+  child before returning.  If the child reports its stop first, then
+  the event is saved on a list and ignored until the parent's stop is
+  reported.  fbsd_wait could have been changed to fetch the parent PID
+  of the new child and used that to wait for the parent explicitly.
+  However, if two threads in the parent fork at the same time, then
+  the wait on the parent might return the "wrong" fork event.
+
+  The initial version of PT_FOLLOW_FORK did not set PL_FLAG_CHILD for
+  the new child process.  This flag could be inferred by treating any
+  events for an unknown pid as a new child.
+
+  In addition, the initial version of PT_FOLLOW_FORK did not report a
+  stop event for the parent process of a vfork until after the child
+  process executed a new program or exited.  The kernel was changed to
+  defer the wait for exit or exec of the child until after posting the
+  stop event shortly after the change to introduce PL_FLAG_CHILD.
+  This could be worked around by reporting a vfork event when the
+  child event posted and ignoring the subsequent event from the
+  parent.
+
+  This implementation requires both of these fixes for simplicity's
+  sake.  FreeBSD versions newer than 9.1 contain both fixes.
+*/
+
+struct fbsd_fork_child_info
+{
+  struct fbsd_fork_child_info *next;
+  pid_t child;                 /* Pid of new child.  */
+};
+
+static struct fbsd_fork_child_info *fbsd_pending_children;
+
+/* Record a new child process event that is reported before the
+   corresponding fork event in the parent.  */
+
+static void
+fbsd_remember_child (pid_t pid)
+{
+  struct fbsd_fork_child_info *info;
+
+  info = xcalloc (1, sizeof *info);
+
+  info->child = pid;
+  info->next = fbsd_pending_children;
+  fbsd_pending_children = info;
+}
+
+/* Check for a previously-recorded new child process event for PID.
+   If one is found, remove it from the list.  */
+
+static int
+fbsd_is_child_pending (pid_t pid)
+{
+  struct fbsd_fork_child_info *info, *prev;
+
+  prev = NULL;
+  for (info = fbsd_pending_children; info; prev = info, info = info->next)
+    {
+      if (info->child == pid)
+       {
+         if (prev == NULL)
+           fbsd_pending_children = info->next;
+         else
+           prev->next = info->next;
+         xfree (info);
+         return 1;
+       }
+    }
+  return 0;
+}
+
+/* Fetch the external variant of the kernel's internal process
+   structure for the process PID into KP.  */
+
+static void
+fbsd_fetch_kinfo_proc (pid_t pid, struct kinfo_proc *kp)
+{
+  size_t len;
+  int mib[4];
+
+  len = sizeof *kp;
+  mib[0] = CTL_KERN;
+  mib[1] = KERN_PROC;
+  mib[2] = KERN_PROC_PID;
+  mib[3] = pid;
+  if (sysctl (mib, 4, kp, &len, NULL, 0) == -1)
+    perror_with_name (("sysctl"));
+}
+#endif
+
+/* Wait for the child specified by PTID to do something.  Return the
+   process ID of the child, or MINUS_ONE_PTID in case of error; store
+   the status in *OURSTATUS.  */
+
+static ptid_t
+fbsd_wait (struct target_ops *ops,
+          ptid_t ptid, struct target_waitstatus *ourstatus,
+          int target_options)
+{
+  ptid_t wptid;
+
+  while (1)
+    {
+      wptid = super_wait (ops, ptid, ourstatus, target_options);
+      if (ourstatus->kind == TARGET_WAITKIND_STOPPED)
+       {
+         struct ptrace_lwpinfo pl;
+         pid_t pid;
+         int status;
+
+         pid = ptid_get_pid (wptid);
+         if (ptrace (PT_LWPINFO, pid, (caddr_t)&pl, sizeof pl) == -1)
+           perror_with_name (("ptrace"));
+
+#ifdef TDP_RFPPWAIT
+         if (pl.pl_flags & PL_FLAG_FORKED)
+           {
+             struct kinfo_proc kp;
+             pid_t child;
+
+             child = pl.pl_child_pid;
+             ourstatus->kind = TARGET_WAITKIND_FORKED;
+             ourstatus->value.related_pid = pid_to_ptid (child);
+
+             /* Make sure the other end of the fork is stopped too.  */
+             if (!fbsd_is_child_pending (child))
+               {
+                 pid = waitpid (child, &status, 0);
+                 if (pid == -1)
+                   perror_with_name (("waitpid"));
+
+                 gdb_assert (pid == child);
+
+                 if (ptrace (PT_LWPINFO, child, (caddr_t)&pl, sizeof pl) == -1)
+                   perror_with_name (("ptrace"));
+
+                 gdb_assert (pl.pl_flags & PL_FLAG_CHILD);
+               }
+
+             /* For vfork, the child process will have the P_PPWAIT
+                flag set.  */
+             fbsd_fetch_kinfo_proc (child, &kp);
+             if (kp.ki_flag & P_PPWAIT)
+               ourstatus->kind = TARGET_WAITKIND_VFORKED;
+
+             return wptid;
+           }
+
+         if (pl.pl_flags & PL_FLAG_CHILD)
+           {
+             /* Remember that this child forked, but do not report it
+                until the parent reports its corresponding fork
+                event.  */
+             fbsd_remember_child (ptid_get_pid (wptid));
+             continue;
+           }
+#endif
+
+#ifdef PL_FLAG_EXEC
+         if (pl.pl_flags & PL_FLAG_EXEC)
+           {
+             ourstatus->kind = TARGET_WAITKIND_EXECD;
+             ourstatus->value.execd_pathname
+               = xstrdup (fbsd_pid_to_exec_file (NULL, pid));
+             return wptid;
+           }
+#endif
+       }
+      return wptid;
+    }
+}
+
+#ifdef TDP_RFPPWAIT
+/* Target hook for follow_fork.  On entry and at return inferior_ptid is
+   the ptid of the followed inferior.  */
+
+static int
+fbsd_follow_fork (struct target_ops *ops, int follow_child,
+                       int detach_fork)
+{
+  if (!follow_child)
+    {
+      struct thread_info *tp = inferior_thread ();
+      pid_t child_pid = ptid_get_pid (tp->pending_follow.value.related_pid);
+
+      /* Breakpoints have already been detached from the child by
+        infrun.c.  */
+
+      if (ptrace (PT_DETACH, child_pid, (PTRACE_TYPE_ARG3)1, 0) == -1)
+       perror_with_name (("ptrace"));
+    }
+
+  return 0;
+}
+
+static int
+fbsd_insert_fork_catchpoint (struct target_ops *self, int pid)
+{
+  return 0;
+}
+
+static int
+fbsd_remove_fork_catchpoint (struct target_ops *self, int pid)
+{
+  return 0;
+}
+
+static int
+fbsd_insert_vfork_catchpoint (struct target_ops *self, int pid)
+{
+  return 0;
+}
+
+static int
+fbsd_remove_vfork_catchpoint (struct target_ops *self, int pid)
+{
+  return 0;
+}
+
+/* Enable fork tracing for a specific process.
+   
+   To catch fork events, PT_FOLLOW_FORK is set on every traced process
+   to enable stops on returns from fork or vfork.  Note that both the
+   parent and child will always stop, even if system call stops are
+   not enabled.  */
+
+static void
+fbsd_enable_follow_fork (pid_t pid)
+{
+  if (ptrace (PT_FOLLOW_FORK, pid, (PTRACE_TYPE_ARG3)0, 1) == -1)
+    perror_with_name (("ptrace"));
+}
+
+/* Implement the "to_post_startup_inferior" target_ops method.  */
+
+static void
+fbsd_post_startup_inferior (struct target_ops *self, ptid_t pid)
+{
+  fbsd_enable_follow_fork (ptid_get_pid (pid));
+}
+
+/* Implement the "to_post_attach" target_ops method.  */
+
+static void
+fbsd_post_attach (struct target_ops *self, int pid)
+{
+  fbsd_enable_follow_fork (pid);
+}
+#endif
+
+#ifdef PL_FLAG_EXEC
+/* If the FreeBSD kernel supports PL_FLAG_EXEC, then traced processes
+   will always stop after exec.  */
+
+static int
+fbsd_insert_exec_catchpoint (struct target_ops *self, int pid)
+{
+  return 0;
+}
+
+static int
+fbsd_remove_exec_catchpoint (struct target_ops *self, int pid)
+{
+  return 0;
+}
+#endif
+#endif
+
 void
 fbsd_nat_add_target (struct target_ops *t)
 {
   t->to_pid_to_exec_file = fbsd_pid_to_exec_file;
   t->to_find_memory_regions = fbsd_find_memory_regions;
+#ifdef PT_LWPINFO
+  super_wait = t->to_wait;
+  t->to_wait = fbsd_wait;
+#ifdef TDP_RFPPWAIT
+  t->to_follow_fork = fbsd_follow_fork;
+  t->to_insert_fork_catchpoint = fbsd_insert_fork_catchpoint;
+  t->to_remove_fork_catchpoint = fbsd_remove_fork_catchpoint;
+  t->to_insert_vfork_catchpoint = fbsd_insert_vfork_catchpoint;
+  t->to_remove_vfork_catchpoint = fbsd_remove_vfork_catchpoint;
+  t->to_post_startup_inferior = fbsd_post_startup_inferior;
+  t->to_post_attach = fbsd_post_attach;
+#endif
+#ifdef PL_FLAG_EXEC
+  t->to_insert_exec_catchpoint = fbsd_insert_exec_catchpoint;
+  t->to_remove_exec_catchpoint = fbsd_remove_exec_catchpoint;
+#endif
+#endif
   add_target (t);
 }
This page took 0.02593 seconds and 4 git commands to generate.