/* Native-dependent code for FreeBSD.
- Copyright (C) 2002-2014 Free Software Foundation, Inc.
+ Copyright (C) 2002-2015 Free Software Foundation, Inc.
This file is part of GDB.
#include "regcache.h"
#include "regset.h"
#include "gdbthread.h"
-
-#include "gdb_assert.h"
-#include <string.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>
+#include <libutil.h>
+#endif
#include "elf-bfd.h"
#include "fbsd-nat.h"
/* Return the name of a file that can be opened to get the symbols for
the child process identified by PID. */
-char *
+static char *
fbsd_pid_to_exec_file (struct target_ops *self, int pid)
{
ssize_t len = PATH_MAX;
return NULL;
}
+#ifdef HAVE_KINFO_GETVMMAP
+/* Iterate over all the memory regions in the current inferior,
+ calling FUNC for each memory region. OBFD is passed as the last
+ argument to FUNC. */
+
+static int
+fbsd_find_memory_regions (struct target_ops *self,
+ find_memory_region_ftype func, void *obfd)
+{
+ pid_t pid = ptid_get_pid (inferior_ptid);
+ struct kinfo_vmentry *vmentl, *kve;
+ uint64_t size;
+ struct cleanup *cleanup;
+ int i, nitems;
+
+ vmentl = kinfo_getvmmap (pid, &nitems);
+ if (vmentl == NULL)
+ perror_with_name (_("Couldn't fetch VM map entries."));
+ cleanup = make_cleanup (free, vmentl);
+
+ for (i = 0; i < nitems; i++)
+ {
+ kve = &vmentl[i];
+
+ /* Skip unreadable segments and those where MAP_NOCORE has been set. */
+ if (!(kve->kve_protection & KVME_PROT_READ)
+ || kve->kve_flags & KVME_FLAG_NOCOREDUMP)
+ continue;
+
+ /* Skip segments with an invalid type. */
+ if (kve->kve_type != KVME_TYPE_DEFAULT
+ && kve->kve_type != KVME_TYPE_VNODE
+ && kve->kve_type != KVME_TYPE_SWAP
+ && kve->kve_type != KVME_TYPE_PHYS)
+ continue;
+
+ size = kve->kve_end - kve->kve_start;
+ if (info_verbose)
+ {
+ fprintf_filtered (gdb_stdout,
+ "Save segment, %ld bytes at %s (%c%c%c)\n",
+ (long) size,
+ paddress (target_gdbarch (), kve->kve_start),
+ kve->kve_protection & KVME_PROT_READ ? 'r' : '-',
+ kve->kve_protection & KVME_PROT_WRITE ? 'w' : '-',
+ kve->kve_protection & KVME_PROT_EXEC ? 'x' : '-');
+ }
+
+ /* Invoke the callback function to create the corefile segment.
+ Pass MODIFIED as true, we do not know the real modification state. */
+ func (kve->kve_start, size, kve->kve_protection & KVME_PROT_READ,
+ kve->kve_protection & KVME_PROT_WRITE,
+ kve->kve_protection & KVME_PROT_EXEC, 1, obfd);
+ }
+ do_cleanups (cleanup);
+ return 0;
+}
+#else
static int
fbsd_read_mapping (FILE *mapfile, unsigned long *start, unsigned long *end,
char *protection)
calling FUNC for each memory region. OBFD is passed as the last
argument to FUNC. */
-int
+static int
fbsd_find_memory_regions (struct target_ops *self,
find_memory_region_ftype func, void *obfd)
{
do_cleanups (cleanup);
return 0;
}
+#endif
-static int
-find_signalled_thread (struct thread_info *info, void *data)
+#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
{
- if (info->suspend.stop_signal != GDB_SIGNAL_0
- && ptid_get_pid (info->ptid) == ptid_get_pid (inferior_ptid))
- return 1;
+ struct fbsd_fork_child_info *next;
+ pid_t child; /* Pid of new child. */
+};
- return 0;
+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;
}
-static enum gdb_signal
-find_stop_signal (void)
+/* 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 thread_info *info =
- iterate_over_threads (find_signalled_thread, NULL);
+ struct fbsd_fork_child_info *info, *prev;
- if (info)
- return info->suspend.stop_signal;
- else
- return GDB_SIGNAL_0;
+ 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;
}
-/* Create appropriate note sections for a corefile, returning them in
- allocated memory. */
+/* Fetch the external variant of the kernel's internal process
+ structure for the process PID into KP. */
-char *
-fbsd_make_corefile_notes (struct target_ops *self, bfd *obfd, int *note_size)
+static void
+fbsd_fetch_kinfo_proc (pid_t pid, struct kinfo_proc *kp)
{
- const struct regcache *regcache = get_current_regcache ();
- struct gdbarch *gdbarch = get_regcache_arch (regcache);
- gregset_t gregs;
- fpregset_t fpregs;
- char *note_data = NULL;
- Elf_Internal_Ehdr *i_ehdrp;
- const struct regset *regset;
- size_t size;
+ size_t len;
+ int mib[4];
- /* Put a "FreeBSD" label in the ELF header. */
- i_ehdrp = elf_elfheader (obfd);
- i_ehdrp->e_ident[EI_OSABI] = ELFOSABI_FREEBSD;
+ 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
- gdb_assert (gdbarch_regset_from_core_section_p (gdbarch));
+/* 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. */
- size = sizeof gregs;
- regset = gdbarch_regset_from_core_section (gdbarch, ".reg", size);
- gdb_assert (regset && regset->collect_regset);
- regset->collect_regset (regset, regcache, -1, &gregs, size);
+static ptid_t
+fbsd_wait (struct target_ops *ops,
+ ptid_t ptid, struct target_waitstatus *ourstatus,
+ int target_options)
+{
+ ptid_t wptid;
- note_data = elfcore_write_prstatus (obfd, note_data, note_size,
- ptid_get_pid (inferior_ptid),
- find_stop_signal (), &gregs);
+ 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
- size = sizeof fpregs;
- regset = gdbarch_regset_from_core_section (gdbarch, ".reg2", size);
- gdb_assert (regset && regset->collect_regset);
- regset->collect_regset (regset, regcache, -1, &fpregs, size);
+#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;
+ }
+}
- note_data = elfcore_write_prfpreg (obfd, note_data, note_size,
- &fpregs, sizeof (fpregs));
+#ifdef TDP_RFPPWAIT
+/* Target hook for follow_fork. On entry and at return inferior_ptid is
+ the ptid of the followed inferior. */
- if (get_exec_file (0))
+static int
+fbsd_follow_fork (struct target_ops *ops, int follow_child,
+ int detach_fork)
+{
+ if (!follow_child)
{
- const char *fname = lbasename (get_exec_file (0));
- char *psargs = xstrdup (fname);
+ struct thread_info *tp = inferior_thread ();
+ pid_t child_pid = ptid_get_pid (tp->pending_follow.value.related_pid);
- if (get_inferior_args ())
- psargs = reconcat (psargs, psargs, " ", get_inferior_args (),
- (char *) NULL);
+ /* Breakpoints have already been detached from the child by
+ infrun.c. */
- note_data = elfcore_write_prpsinfo (obfd, note_data, note_size,
- fname, psargs);
+ if (ptrace (PT_DETACH, child_pid, (PTRACE_TYPE_ARG3)1, 0) == -1)
+ perror_with_name (("ptrace"));
}
- make_cleanup (xfree, note_data);
- return note_data;
+ 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);
}