#include "gdbthread.h"
#include <sys/types.h>
#include <sys/procfs.h>
+#include <sys/ptrace.h>
#include <sys/sysctl.h>
+#include <sys/wait.h>
#ifdef HAVE_KINFO_GETVMMAP
#include <sys/user.h>
#include <libutil.h>
}
#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
+ }
+ 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
+#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
+#endif
add_target (t);
}