Patchwork [v2,1/5] Support 'info proc' for FreeBSD process core dumps.

login
register
mail settings
Submitter John Baldwin
Date Jan. 5, 2018, 7:21 p.m.
Message ID <1752022.FNtOcQG6Rg@ralph.baldwin.cx>
Download mbox | patch
Permalink /patch/25238/
State New
Headers show

Comments

John Baldwin - Jan. 5, 2018, 7:21 p.m.
On Thursday, January 04, 2018 09:53:47 PM Simon Marchi wrote:
> Hi John,
> 
> I have two little comments, but otherwise it LGTM.
> 
> On 2018-01-03 08:49 PM, John Baldwin wrote:
> > +/* Implement "info proc status" for a corefile.  */
> > +
> > +static void
> > +fbsd_core_info_proc_status (struct gdbarch *gdbarch)
> > +{
> > +  const struct kinfo_proc_layout *kp;
> > +  asection *section;
> > +  const char *state;
> > +  unsigned char *descdata, *descend;
> 
> I get this:
> 
> /home/simark/src/binutils-gdb/gdb/fbsd-tdep.c: In function ‘void fbsd_core_info_proc_status(gdbarch*)’:
> /home/simark/src/binutils-gdb/gdb/fbsd-tdep.c:791:29: error: variable ‘descend’ set but not used [-Werror=unused-but-set-variable]
>    unsigned char *descdata, *descend;
>                              ^~~~~~~

Oops, too much copy and paste (and I've only been building with clang so far).

> > +  int addr_bit, long_bit;
> > +  size_t note_size;
> > +  ULONGEST value;
> > +  LONGEST sec;
> > +
> > +  section = bfd_get_section_by_name (core_bfd, ".note.freebsdcore.proc");
> > +  if (section == NULL)
> > +    {
> > +      warning (_("unable to find process info in core file"));
> > +      return;
> > +    }
> > +
> > +  addr_bit = gdbarch_addr_bit (gdbarch);
> > +  if (addr_bit == 64)
> > +    kp = &kinfo_proc_layout_64;
> > +  else if (bfd_get_arch (core_bfd) == bfd_arch_i386)
> > +    kp = &kinfo_proc_layout_i386;
> > +  else
> > +    kp = &kinfo_proc_layout_32;
> > +
> > +  note_size = bfd_get_section_size (section);
> > +  if (note_size < 4 + kp->ki_rusage_ch + kp->ru_majflt + addr_bit)
> 
> What's the rationale behind that computation?  The field ru_majflt in kp->ki_rusage_ch
> is the furthest field we'll actually need in this function?  And addr_bit is the size of
> that field?  And 4 is because there's a field at the beginning containing the size of
> the structure that comes after?

Correct.  This is the bounds check to avoid overflowing the note buffer,
but it does warrany a comment to explain what it is doing.  And actually,
writing that comment made me realize a bug.  It should be 'addr_bit /
TARGET_CHAR_BIT', not addr_bit directly.  Also, I noticed that the
'long_bit' variable at the start of the quoted section above also isn't
used, but it's probably cleaner to be explicit and assign it to
gdbarch_long_bit() and use it in place of addr_bit for structure members
that are of type long rather than assuming addr_bit == long_bit (even
though that is true on all of the architectures FreeBSD supports or is
likely to support).

Ends up like this:

commit b64825d47b446c6a160a2bf28b62641492d07d82
Author: John Baldwin <jhb@FreeBSD.org>
Date:   Fri Jan 5 11:19:02 2018 -0800

    Support 'info proc' for FreeBSD process core dumps.
    
    - Command line arguments are obtained from the pr_psargs[] array
      saved in the NT_PRPSINFO note.
    - The 'cwd' and 'exe' values are obtained from the per-process file
      descriptor table stored in the NT_PROCSTAT_FILES core note.
    - 'mappings' is implemented by walking the array of VM map entries
      stored in the NT_PROCSTAT_VMMAP core note.
    - 'status' output is generated by outputting fields from
      the first structure stored in the NT_PROCSTAT_PROC core note.
    - 'stat' is aliased to 'status'.
    
    gdb/ChangeLog:
    
            * fbsd-tdep.c (KVE_STRUCTSIZE, KVE_START, KVE_END, KVE_OFFSET)
            (KVE_FLAGS, KVE_PROTECTION, KVE_PATH, KINFO_VME_PROT_READ)
            (KINFO_VME_PROT_WRITE, KINFO_VME_PROT_EXEC, KINFO_VME_FLAG_COW)
            (KINFO_VME_FLAG_NEEDS_COPY, KINFO_VME_FLAG_NOCOREDUMP)
            (KINFO_VME_FLAG_SUPER, KINFO_VME_FLAG_GROWS_UP)
            (KINFO_VME_FLAG_GROWS_DOWN, KF_STRUCTSIZE, KF_TYPE, KF_FD)
            (KF_PATH, KINFO_FILE_TYPE_VNODE, KINFO_FILE_FD_TYPE_CWD)
            (KINFO_FILE_FD_TYPE_TEXT, SIG_WORDS, struct kinfo_proc_layout)
            (kinfo_proc_layout_32, kinfo_proc_layout_i386)
            (kinfo_proc_layout_64, fbsd_vm_map_entry_flags)
            (fbsd_core_info_proc_mappings, fbsd_core_vnode_path)
            (fbsd_core_fetch_timeval, fbsd_print_sigset)
            (fbsd_core_info_proc_status, fbsd_core_info_proc): New.
            (fbsd_init_abi):  Install gdbarch "core_info_proc" method.
            * fbsd-tdep.h (fbsd_vm_map_entry_flags): New.
Simon Marchi - Jan. 9, 2018, 7:29 p.m.
On 2018-01-05 02:21 PM, John Baldwin wrote:
> On Thursday, January 04, 2018 09:53:47 PM Simon Marchi wrote:
>> Hi John,
>>
>> I have two little comments, but otherwise it LGTM.
>>
>> On 2018-01-03 08:49 PM, John Baldwin wrote:
>>> +/* Implement "info proc status" for a corefile.  */
>>> +
>>> +static void
>>> +fbsd_core_info_proc_status (struct gdbarch *gdbarch)
>>> +{
>>> +  const struct kinfo_proc_layout *kp;
>>> +  asection *section;
>>> +  const char *state;
>>> +  unsigned char *descdata, *descend;
>>
>> I get this:
>>
>> /home/simark/src/binutils-gdb/gdb/fbsd-tdep.c: In function ‘void fbsd_core_info_proc_status(gdbarch*)’:
>> /home/simark/src/binutils-gdb/gdb/fbsd-tdep.c:791:29: error: variable ‘descend’ set but not used [-Werror=unused-but-set-variable]
>>    unsigned char *descdata, *descend;
>>                              ^~~~~~~
> 
> Oops, too much copy and paste (and I've only been building with clang so far).
> 
>>> +  int addr_bit, long_bit;
>>> +  size_t note_size;
>>> +  ULONGEST value;
>>> +  LONGEST sec;
>>> +
>>> +  section = bfd_get_section_by_name (core_bfd, ".note.freebsdcore.proc");
>>> +  if (section == NULL)
>>> +    {
>>> +      warning (_("unable to find process info in core file"));
>>> +      return;
>>> +    }
>>> +
>>> +  addr_bit = gdbarch_addr_bit (gdbarch);
>>> +  if (addr_bit == 64)
>>> +    kp = &kinfo_proc_layout_64;
>>> +  else if (bfd_get_arch (core_bfd) == bfd_arch_i386)
>>> +    kp = &kinfo_proc_layout_i386;
>>> +  else
>>> +    kp = &kinfo_proc_layout_32;
>>> +
>>> +  note_size = bfd_get_section_size (section);
>>> +  if (note_size < 4 + kp->ki_rusage_ch + kp->ru_majflt + addr_bit)
>>
>> What's the rationale behind that computation?  The field ru_majflt in kp->ki_rusage_ch
>> is the furthest field we'll actually need in this function?  And addr_bit is the size of
>> that field?  And 4 is because there's a field at the beginning containing the size of
>> the structure that comes after?
> 
> Correct.  This is the bounds check to avoid overflowing the note buffer,
> but it does warrany a comment to explain what it is doing.  And actually,
> writing that comment made me realize a bug.  It should be 'addr_bit /
> TARGET_CHAR_BIT', not addr_bit directly.  Also, I noticed that the
> 'long_bit' variable at the start of the quoted section above also isn't
> used, but it's probably cleaner to be explicit and assign it to
> gdbarch_long_bit() and use it in place of addr_bit for structure members
> that are of type long rather than assuming addr_bit == long_bit (even
> though that is true on all of the architectures FreeBSD supports or is
> likely to support).
> 
> Ends up like this:

That LGTM, the comment you added is clear.

Simon

Patch

diff --git a/gdb/ChangeLog b/gdb/ChangeLog
index 24ccfe6882..dfdf4336ff 100644
--- a/gdb/ChangeLog
+++ b/gdb/ChangeLog
@@ -1,3 +1,21 @@ 
+2018-01-05  John Baldwin  <jhb@FreeBSD.org>
+
+	* fbsd-tdep.c (KVE_STRUCTSIZE, KVE_START, KVE_END, KVE_OFFSET)
+	(KVE_FLAGS, KVE_PROTECTION, KVE_PATH, KINFO_VME_PROT_READ)
+	(KINFO_VME_PROT_WRITE, KINFO_VME_PROT_EXEC, KINFO_VME_FLAG_COW)
+	(KINFO_VME_FLAG_NEEDS_COPY, KINFO_VME_FLAG_NOCOREDUMP)
+	(KINFO_VME_FLAG_SUPER, KINFO_VME_FLAG_GROWS_UP)
+	(KINFO_VME_FLAG_GROWS_DOWN, KF_STRUCTSIZE, KF_TYPE, KF_FD)
+	(KF_PATH, KINFO_FILE_TYPE_VNODE, KINFO_FILE_FD_TYPE_CWD)
+	(KINFO_FILE_FD_TYPE_TEXT, SIG_WORDS, struct kinfo_proc_layout)
+	(kinfo_proc_layout_32, kinfo_proc_layout_i386)
+	(kinfo_proc_layout_64, fbsd_vm_map_entry_flags)
+	(fbsd_core_info_proc_mappings, fbsd_core_vnode_path)
+	(fbsd_core_fetch_timeval, fbsd_print_sigset)
+	(fbsd_core_info_proc_status, fbsd_core_info_proc): New.
+	(fbsd_init_abi):  Install gdbarch "core_info_proc" method.
+	* fbsd-tdep.h (fbsd_vm_map_entry_flags): New.
+
 2018-01-05  Pedro Alves  <palves@redhat.com>
 
 	PR gdb/18653
diff --git a/gdb/fbsd-tdep.c b/gdb/fbsd-tdep.c
index ce17c672d9..8aa0243d54 100644
--- a/gdb/fbsd-tdep.c
+++ b/gdb/fbsd-tdep.c
@@ -52,6 +52,228 @@ 
 #define	SIZE64_SIGINFO_T	80
 #define	SIZE32_SIGINFO_T	64
 
+/* Offsets in data structure used in NT_FREEBSD_PROCSTAT_VMMAP core
+   dump notes.  See <sys/user.h> for the definition of struct
+   kinfo_vmentry.  This data structure should have the same layout on
+   all architectures.  */
+
+#define	KVE_STRUCTSIZE		0x0
+#define	KVE_START		0x8
+#define	KVE_END			0x10
+#define	KVE_OFFSET		0x18
+#define	KVE_FLAGS		0x2c
+#define	KVE_PROTECTION		0x56
+#define	KVE_PATH		0x88
+
+/* Flags in the 'kve_protection' field in struct kinfo_vmentry.  These
+   match the KVME_PROT_* constants in <sys/user.h>.  */
+
+#define	KINFO_VME_PROT_READ	0x00000001
+#define	KINFO_VME_PROT_WRITE	0x00000002
+#define	KINFO_VME_PROT_EXEC	0x00000004
+
+/* Flags in the 'kve_flags' field in struct kinfo_vmentry.  These
+   match the KVME_FLAG_* constants in <sys/user.h>.  */
+
+#define	KINFO_VME_FLAG_COW		0x00000001
+#define	KINFO_VME_FLAG_NEEDS_COPY	0x00000002
+#define	KINFO_VME_FLAG_NOCOREDUMP	0x00000004
+#define	KINFO_VME_FLAG_SUPER		0x00000008
+#define	KINFO_VME_FLAG_GROWS_UP		0x00000010
+#define	KINFO_VME_FLAG_GROWS_DOWN	0x00000020
+
+/* Offsets in data structure used in NT_FREEBSD_PROCSTAT_FILES core
+   dump notes.  See <sys/user.h> for the definition of struct
+   kinfo_file.  This data structure should have the same layout on all
+   architectures.  */
+
+#define	KF_STRUCTSIZE		0x0
+#define	KF_TYPE			0x4
+#define	KF_FD			0x8
+#define	KF_PATH			0x170
+
+/* Constants for the 'kf_type' field in struct kinfo_file.  These
+   match the KF_TYPE_* constants in <sys/user.h>.  */
+
+#define	KINFO_FILE_TYPE_VNODE	1
+
+/* Special values for the 'kf_fd' field in struct kinfo_file.  These
+   match the KF_FD_TYPE_* constants in <sys/user.h>.  */
+
+#define	KINFO_FILE_FD_TYPE_CWD	-1
+#define	KINFO_FILE_FD_TYPE_TEXT	-5
+
+/* Number of 32-bit words in a signal set.  This matches _SIG_WORDS in
+   <sys/_sigset.h> and is the same value on all architectures.  */
+
+#define	SIG_WORDS		4
+
+/* Offsets in data structure used in NT_FREEBSD_PROCSTAT_PROC core
+   dump notes.  See <sys/user.h> for the definition of struct
+   kinfo_proc.  This data structure has different layouts on different
+   architectures mostly due to ILP32 vs LP64.  However, FreeBSD/i386
+   uses a 32-bit time_t while all other architectures use a 64-bit
+   time_t.
+
+   The core dump note actually contains one kinfo_proc structure for
+   each thread, but all of the process-wide data can be obtained from
+   the first structure.  One result of this note's format is that some
+   of the process-wide status available in the native target method
+   from the kern.proc.pid.<pid> sysctl such as ki_stat and ki_siglist
+   is not available from a core dump.  Instead, the per-thread data
+   structures contain the value of these fields for individual
+   threads.  */
+
+struct kinfo_proc_layout
+{
+  /* Offsets of struct kinfo_proc members.  */
+  int ki_layout;
+  int ki_pid;
+  int ki_ppid;
+  int ki_pgid;
+  int ki_tpgid;
+  int ki_sid;
+  int ki_tdev_freebsd11;
+  int ki_sigignore;
+  int ki_sigcatch;
+  int ki_uid;
+  int ki_ruid;
+  int ki_svuid;
+  int ki_rgid;
+  int ki_svgid;
+  int ki_ngroups;
+  int ki_groups;
+  int ki_size;
+  int ki_rssize;
+  int ki_tsize;
+  int ki_dsize;
+  int ki_ssize;
+  int ki_start;
+  int ki_nice;
+  int ki_comm;
+  int ki_tdev;
+  int ki_rusage;
+  int ki_rusage_ch;
+
+  /* Offsets of struct rusage members.  */
+  int ru_utime;
+  int ru_stime;
+  int ru_maxrss;
+  int ru_minflt;
+  int ru_majflt;
+};
+
+const struct kinfo_proc_layout kinfo_proc_layout_32 =
+  {
+    .ki_layout = 0x4,
+    .ki_pid = 0x28,
+    .ki_ppid = 0x2c,
+    .ki_pgid = 0x30,
+    .ki_tpgid = 0x34,
+    .ki_sid = 0x38,
+    .ki_tdev_freebsd11 = 0x44,
+    .ki_sigignore = 0x68,
+    .ki_sigcatch = 0x78,
+    .ki_uid = 0x88,
+    .ki_ruid = 0x8c,
+    .ki_svuid = 0x90,
+    .ki_rgid = 0x94,
+    .ki_svgid = 0x98,
+    .ki_ngroups = 0x9c,
+    .ki_groups = 0xa0,
+    .ki_size = 0xe0,
+    .ki_rssize = 0xe4,
+    .ki_tsize = 0xec,
+    .ki_dsize = 0xf0,
+    .ki_ssize = 0xf4,
+    .ki_start = 0x118,
+    .ki_nice = 0x145,
+    .ki_comm = 0x17f,
+    .ki_tdev = 0x1f0,
+    .ki_rusage = 0x220,
+    .ki_rusage_ch = 0x278,
+
+    .ru_utime = 0x0,
+    .ru_stime = 0x10,
+    .ru_maxrss = 0x20,
+    .ru_minflt = 0x30,
+    .ru_majflt = 0x34,
+  };
+
+const struct kinfo_proc_layout kinfo_proc_layout_i386 =
+  {
+    .ki_layout = 0x4,
+    .ki_pid = 0x28,
+    .ki_ppid = 0x2c,
+    .ki_pgid = 0x30,
+    .ki_tpgid = 0x34,
+    .ki_sid = 0x38,
+    .ki_tdev_freebsd11 = 0x44,
+    .ki_sigignore = 0x68,
+    .ki_sigcatch = 0x78,
+    .ki_uid = 0x88,
+    .ki_ruid = 0x8c,
+    .ki_svuid = 0x90,
+    .ki_rgid = 0x94,
+    .ki_svgid = 0x98,
+    .ki_ngroups = 0x9c,
+    .ki_groups = 0xa0,
+    .ki_size = 0xe0,
+    .ki_rssize = 0xe4,
+    .ki_tsize = 0xec,
+    .ki_dsize = 0xf0,
+    .ki_ssize = 0xf4,
+    .ki_start = 0x118,
+    .ki_nice = 0x135,
+    .ki_comm = 0x16f,
+    .ki_tdev = 0x1e0,
+    .ki_rusage = 0x210,
+    .ki_rusage_ch = 0x258,
+
+    .ru_utime = 0x0,
+    .ru_stime = 0x8,
+    .ru_maxrss = 0x10,
+    .ru_minflt = 0x20,
+    .ru_majflt = 0x24,
+  };
+
+const struct kinfo_proc_layout kinfo_proc_layout_64 =
+  {
+    .ki_layout = 0x4,
+    .ki_pid = 0x48,
+    .ki_ppid = 0x4c,
+    .ki_pgid = 0x50,
+    .ki_tpgid = 0x54,
+    .ki_sid = 0x58,
+    .ki_tdev_freebsd11 = 0x64,
+    .ki_sigignore = 0x88,
+    .ki_sigcatch = 0x98,
+    .ki_uid = 0xa8,
+    .ki_ruid = 0xac,
+    .ki_svuid = 0xb0,
+    .ki_rgid = 0xb4,
+    .ki_svgid = 0xb8,
+    .ki_ngroups = 0xbc,
+    .ki_groups = 0xc0,
+    .ki_size = 0x100,
+    .ki_rssize = 0x108,
+    .ki_tsize = 0x118,
+    .ki_dsize = 0x120,
+    .ki_ssize = 0x128,
+    .ki_start = 0x150,
+    .ki_nice = 0x185,
+    .ki_comm = 0x1bf,
+    .ki_tdev = 0x230,
+    .ki_rusage = 0x260,
+    .ki_rusage_ch = 0x2f0,
+
+    .ru_utime = 0x0,
+    .ru_stime = 0x10,
+    .ru_maxrss = 0x20,
+    .ru_minflt = 0x40,
+    .ru_majflt = 0x48,
+  };
+
 static struct gdbarch_data *fbsd_gdbarch_data_handle;
 
 struct fbsd_gdbarch_data
@@ -367,6 +589,433 @@  fbsd_make_corefile_notes (struct gdbarch *gdbarch, bfd *obfd, int *note_size)
   return note_data;
 }
 
+/* Helper function to generate mappings flags for a single VM map
+   entry in 'info proc mappings'.  */
+
+const char *
+fbsd_vm_map_entry_flags (int kve_flags, int kve_protection)
+{
+  static char vm_flags[9];
+
+  vm_flags[0] = (kve_protection & KINFO_VME_PROT_READ) ? 'r' : '-';
+  vm_flags[1] = (kve_protection & KINFO_VME_PROT_WRITE) ? 'w' : '-';
+  vm_flags[2] = (kve_protection & KINFO_VME_PROT_EXEC) ? 'x' : '-';
+  vm_flags[3] = ' ';
+  vm_flags[4] = (kve_flags & KINFO_VME_FLAG_COW) ? 'C' : '-';
+  vm_flags[5] = (kve_flags & KINFO_VME_FLAG_NEEDS_COPY) ? 'N' : '-';
+  vm_flags[6] = (kve_flags & KINFO_VME_FLAG_SUPER) ? 'S' : '-';
+  vm_flags[7] = (kve_flags & KINFO_VME_FLAG_GROWS_UP) ? 'U'
+    : (kve_flags & KINFO_VME_FLAG_GROWS_DOWN) ? 'D' : '-';
+  vm_flags[8] = '\0';
+
+  return vm_flags;
+}
+
+/* Implement "info proc mappings" for a corefile.  */
+
+static void
+fbsd_core_info_proc_mappings (struct gdbarch *gdbarch)
+{
+  asection *section;
+  unsigned char *descdata, *descend;
+  size_t note_size;
+
+  section = bfd_get_section_by_name (core_bfd, ".note.freebsdcore.vmmap");
+  if (section == NULL)
+    {
+      warning (_("unable to find mappings in core file"));
+      return;
+    }
+
+  note_size = bfd_get_section_size (section);
+  if (note_size < 4)
+    error (_("malformed core note - too short for header"));
+
+  gdb::def_vector<unsigned char> contents (note_size);
+  if (!bfd_get_section_contents (core_bfd, section, contents.data (),
+				 0, note_size))
+    error (_("could not get core note contents"));
+
+  descdata = contents.data ();
+  descend = descdata + note_size;
+
+  /* Skip over the structure size.  */
+  descdata += 4;
+
+  printf_filtered (_("Mapped address spaces:\n\n"));
+  if (gdbarch_addr_bit (gdbarch) == 64)
+    {
+      printf_filtered ("  %18s %18s %10s %10s %9s %s\n",
+		       "Start Addr",
+		       "  End Addr",
+		       "      Size", "    Offset", "Flags  ", "File");
+    }
+  else
+    {
+      printf_filtered ("\t%10s %10s %10s %10s %9s %s\n",
+		       "Start Addr",
+		       "  End Addr",
+		       "      Size", "    Offset", "Flags  ", "File");
+    }
+
+  while (descdata + KVE_PATH < descend)
+    {
+      ULONGEST start, end, offset, flags, prot, structsize;
+
+      structsize = bfd_get_32 (core_bfd, descdata + KVE_STRUCTSIZE);
+      if (structsize < KVE_PATH)
+	error (_("malformed core note - vmmap entry too small"));
+
+      start = bfd_get_64 (core_bfd, descdata + KVE_START);
+      end = bfd_get_64 (core_bfd, descdata + KVE_END);
+      offset = bfd_get_64 (core_bfd, descdata + KVE_OFFSET);
+      flags = bfd_get_32 (core_bfd, descdata + KVE_FLAGS);
+      prot = bfd_get_32 (core_bfd, descdata + KVE_PROTECTION);
+      if (gdbarch_addr_bit (gdbarch) == 64)
+	{
+	  printf_filtered ("  %18s %18s %10s %10s %9s %s\n",
+			   paddress (gdbarch, start),
+			   paddress (gdbarch, end),
+			   hex_string (end - start),
+			   hex_string (offset),
+			   fbsd_vm_map_entry_flags (flags, prot),
+			   descdata + KVE_PATH);
+	}
+      else
+	{
+	  printf_filtered ("\t%10s %10s %10s %10s %9s %s\n",
+			   paddress (gdbarch, start),
+			   paddress (gdbarch, end),
+			   hex_string (end - start),
+			   hex_string (offset),
+			   fbsd_vm_map_entry_flags (flags, prot),
+			   descdata + KVE_PATH);
+	}
+
+      descdata += structsize;
+    }
+}
+
+/* Fetch the pathname of a vnode for a single file descriptor from the
+   file table core note.  */
+
+static gdb::unique_xmalloc_ptr<char>
+fbsd_core_vnode_path (struct gdbarch *gdbarch, int fd)
+{
+  asection *section;
+  unsigned char *descdata, *descend;
+  size_t note_size;
+
+  section = bfd_get_section_by_name (core_bfd, ".note.freebsdcore.files");
+  if (section == NULL)
+    return nullptr;
+
+  note_size = bfd_get_section_size (section);
+  if (note_size < 4)
+    error (_("malformed core note - too short for header"));
+
+  gdb::def_vector<unsigned char> contents (note_size);
+  if (!bfd_get_section_contents (core_bfd, section, contents.data (),
+				 0, note_size))
+    error (_("could not get core note contents"));
+
+  descdata = contents.data ();
+  descend = descdata + note_size;
+
+  /* Skip over the structure size.  */
+  descdata += 4;
+
+  while (descdata + KVE_PATH < descend)
+    {
+      ULONGEST structsize;
+
+      structsize = bfd_get_32 (core_bfd, descdata + KF_STRUCTSIZE);
+      if (structsize < KVE_PATH)
+	error (_("malformed core note - vmmap entry too small"));
+
+      if (bfd_get_32 (core_bfd, descdata + KF_TYPE) == KINFO_FILE_TYPE_VNODE
+	  && bfd_get_signed_32 (core_bfd, descdata + KF_FD) == fd)
+	{
+	  char *path = (char *) descdata + KF_PATH;
+	  return gdb::unique_xmalloc_ptr<char> (xstrdup (path));
+	}
+
+      descdata += structsize;
+    }
+  return nullptr;
+}
+
+/* Helper function to read a struct timeval.  */
+
+static void
+fbsd_core_fetch_timeval (struct gdbarch *gdbarch, unsigned char *data,
+			 LONGEST &sec, ULONGEST &usec)
+{
+  if (gdbarch_addr_bit (gdbarch) == 64)
+    {
+      sec = bfd_get_signed_64 (core_bfd, data);
+      usec = bfd_get_64 (core_bfd, data + 8);
+    }
+  else if (bfd_get_arch (core_bfd) == bfd_arch_i386)
+    {
+      sec = bfd_get_signed_32 (core_bfd, data);
+      usec = bfd_get_32 (core_bfd, data + 4);
+    }
+  else
+    {
+      sec = bfd_get_signed_64 (core_bfd, data);
+      usec = bfd_get_32 (core_bfd, data + 8);
+    }
+}
+
+/* Print out the contents of a signal set.  */
+
+static void
+fbsd_print_sigset (const char *descr, unsigned char *sigset)
+{
+  printf_filtered ("%s: ", descr);
+  for (int i = 0; i < SIG_WORDS; i++)
+    printf_filtered ("%08x ",
+		     (unsigned int) bfd_get_32 (core_bfd, sigset + i * 4));
+  printf_filtered ("\n");
+}
+
+/* Implement "info proc status" for a corefile.  */
+
+static void
+fbsd_core_info_proc_status (struct gdbarch *gdbarch)
+{
+  const struct kinfo_proc_layout *kp;
+  asection *section;
+  const char *state;
+  unsigned char *descdata;
+  int addr_bit, long_bit;
+  size_t note_size;
+  ULONGEST value;
+  LONGEST sec;
+
+  section = bfd_get_section_by_name (core_bfd, ".note.freebsdcore.proc");
+  if (section == NULL)
+    {
+      warning (_("unable to find process info in core file"));
+      return;
+    }
+
+  addr_bit = gdbarch_addr_bit (gdbarch);
+  if (addr_bit == 64)
+    kp = &kinfo_proc_layout_64;
+  else if (bfd_get_arch (core_bfd) == bfd_arch_i386)
+    kp = &kinfo_proc_layout_i386;
+  else
+    kp = &kinfo_proc_layout_32;
+  long_bit = gdbarch_long_bit (gdbarch);
+
+  /*
+   * Ensure that the note is large enough for all of the fields fetched
+   * by this function.  In particular, the note must contain the 32-bit
+   * structure size, then it must be long enough to access the last
+   * field used (ki_rusage_ch.ru_majflt) which is the size of a long.
+   */
+  note_size = bfd_get_section_size (section);
+  if (note_size < (4 + kp->ki_rusage_ch + kp->ru_majflt
+		   + long_bit / TARGET_CHAR_BIT))
+    error (_("malformed core note - too short"));
+
+  gdb::def_vector<unsigned char> contents (note_size);
+  if (!bfd_get_section_contents (core_bfd, section, contents.data (),
+				 0, note_size))
+    error (_("could not get core note contents"));
+
+  descdata = contents.data ();
+
+  /* Skip over the structure size.  */
+  descdata += 4;
+
+  /* Verify 'ki_layout' is 0.  */
+  if (bfd_get_32 (core_bfd, descdata + kp->ki_layout) != 0)
+    {
+      warning (_("unsupported process information in core file"));
+      return;
+    }
+
+  printf_filtered ("Name: %.19s\n", descdata + kp->ki_comm);
+  printf_filtered ("Process ID: %s\n",
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_pid)));
+  printf_filtered ("Parent process: %s\n",
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_ppid)));
+  printf_filtered ("Process group: %s\n",
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_pgid)));
+  printf_filtered ("Session id: %s\n",
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_sid)));
+
+  /* FreeBSD 12.0 and later store a 64-bit dev_t at 'ki_tdev'.  Older
+     kernels store a 32-bit dev_t at 'ki_tdev_freebsd11'.  In older
+     kernels the 64-bit 'ki_tdev' field is in a reserved section of
+     the structure that is cleared to zero.  Assume that a zero value
+     in ki_tdev indicates a core dump from an older kernel and use the
+     value in 'ki_tdev_freebsd11' instead.  */
+  value = bfd_get_64 (core_bfd, descdata + kp->ki_tdev);
+  if (value == 0)
+    value = bfd_get_32 (core_bfd, descdata + kp->ki_tdev_freebsd11);
+  printf_filtered ("TTY: %s\n", pulongest (value));
+  printf_filtered ("TTY owner process group: %s\n",
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_tpgid)));
+  printf_filtered ("User IDs (real, effective, saved): %s %s %s\n",
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_ruid)),
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_uid)),
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_svuid)));
+  printf_filtered ("Group IDs (real, effective, saved): %s %s %s\n",
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_rgid)),
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_groups)),
+		   pulongest (bfd_get_32 (core_bfd, descdata + kp->ki_svgid)));
+  printf_filtered ("Groups: ");
+  uint16_t ngroups = bfd_get_16 (core_bfd, descdata + kp->ki_ngroups);
+  for (int i = 0; i < ngroups; i++)
+    printf_filtered ("%s ",
+		     pulongest (bfd_get_32 (core_bfd,
+					    descdata + kp->ki_groups + i * 4)));
+  printf_filtered ("\n");
+  value = bfd_get (long_bit, core_bfd,
+		   descdata + kp->ki_rusage + kp->ru_minflt);
+  printf_filtered ("Minor faults (no memory page): %s\n", pulongest (value));
+  value = bfd_get (long_bit, core_bfd,
+		   descdata + kp->ki_rusage_ch + kp->ru_minflt);
+  printf_filtered ("Minor faults, children: %s\n", pulongest (value));
+  value = bfd_get (long_bit, core_bfd,
+		   descdata + kp->ki_rusage + kp->ru_majflt);
+  printf_filtered ("Major faults (memory page faults): %s\n",
+		   pulongest (value));
+  value = bfd_get (long_bit, core_bfd,
+		   descdata + kp->ki_rusage_ch + kp->ru_majflt);
+  printf_filtered ("Major faults, children: %s\n", pulongest (value));
+  fbsd_core_fetch_timeval (gdbarch,
+			   descdata + kp->ki_rusage + kp->ru_utime,
+			   sec, value);
+  printf_filtered ("utime: %s.%06d\n", plongest (sec), (int) value);
+  fbsd_core_fetch_timeval (gdbarch,
+			   descdata + kp->ki_rusage + kp->ru_stime,
+			   sec, value);
+  printf_filtered ("stime: %s.%06d\n", plongest (sec), (int) value);
+  fbsd_core_fetch_timeval (gdbarch,
+			   descdata + kp->ki_rusage_ch + kp->ru_utime,
+			   sec, value);
+  printf_filtered ("utime, children: %s.%06d\n", plongest (sec), (int) value);
+  fbsd_core_fetch_timeval (gdbarch,
+			   descdata + kp->ki_rusage_ch + kp->ru_stime,
+			   sec, value);
+  printf_filtered ("stime, children: %s.%06d\n", plongest (sec), (int) value);
+  printf_filtered ("'nice' value: %d\n",
+		   bfd_get_signed_8 (core_bfd, descdata + kp->ki_nice));
+  fbsd_core_fetch_timeval (gdbarch, descdata + kp->ki_start, sec, value);
+  printf_filtered ("Start time: %s.%06d\n", plongest (sec), (int) value);
+  printf_filtered ("Virtual memory size: %s kB\n",
+		   pulongest (bfd_get (addr_bit, core_bfd,
+				       descdata + kp->ki_size) / 1024));
+  printf_filtered ("Data size: %s pages\n",
+		   pulongest (bfd_get (addr_bit, core_bfd,
+				       descdata + kp->ki_dsize)));
+  printf_filtered ("Stack size: %s pages\n",
+		   pulongest (bfd_get (addr_bit, core_bfd,
+				       descdata + kp->ki_ssize)));
+  printf_filtered ("Text size: %s pages\n",
+		   pulongest (bfd_get (addr_bit, core_bfd,
+				       descdata + kp->ki_tsize)));
+  printf_filtered ("Resident set size: %s pages\n",
+		   pulongest (bfd_get (addr_bit, core_bfd,
+				       descdata + kp->ki_rssize)));
+  printf_filtered ("Maximum RSS: %s pages\n",
+		   pulongest (bfd_get (long_bit, core_bfd,
+				       descdata + kp->ki_rusage
+				       + kp->ru_maxrss)));
+  fbsd_print_sigset ("Ignored Signals", descdata + kp->ki_sigignore);
+  fbsd_print_sigset ("Caught Signals", descdata + kp->ki_sigcatch);
+}
+
+/* Implement the "core_info_proc" gdbarch method.  */
+
+static void
+fbsd_core_info_proc (struct gdbarch *gdbarch, const char *args,
+		     enum info_proc_what what)
+{
+  bool do_cmdline = false;
+  bool do_cwd = false;
+  bool do_exe = false;
+  bool do_mappings = false;
+  bool do_status = false;
+  int pid;
+
+  switch (what)
+    {
+    case IP_MINIMAL:
+      do_cmdline = true;
+      do_cwd = true;
+      do_exe = true;
+      break;
+    case IP_MAPPINGS:
+      do_mappings = true;
+      break;
+    case IP_STATUS:
+    case IP_STAT:
+      do_status = true;
+      break;
+    case IP_CMDLINE:
+      do_cmdline = true;
+      break;
+    case IP_EXE:
+      do_exe = true;
+      break;
+    case IP_CWD:
+      do_cwd = true;
+      break;
+    case IP_ALL:
+      do_cmdline = true;
+      do_cwd = true;
+      do_exe = true;
+      do_mappings = true;
+      do_status = true;
+      break;
+    default:
+      return;
+    }
+
+  pid = bfd_core_file_pid (core_bfd);
+  if (pid != 0)
+    printf_filtered (_("process %d\n"), pid);
+
+  if (do_cmdline)
+    {
+      const char *cmdline;
+
+      cmdline = bfd_core_file_failing_command (core_bfd);
+      if (cmdline)
+	printf_filtered ("cmdline = '%s'\n", cmdline);
+      else
+	warning (_("Command line unavailable"));
+    }
+  if (do_cwd)
+    {
+      gdb::unique_xmalloc_ptr<char> cwd =
+	fbsd_core_vnode_path (gdbarch, KINFO_FILE_FD_TYPE_CWD);
+      if (cwd)
+	printf_filtered ("cwd = '%s'\n", cwd.get ());
+      else
+	warning (_("unable to read current working directory"));
+    }
+  if (do_exe)
+    {
+      gdb::unique_xmalloc_ptr<char> exe =
+	fbsd_core_vnode_path (gdbarch, KINFO_FILE_FD_TYPE_TEXT);
+      if (exe)
+	printf_filtered ("exe = '%s'\n", exe.get ());
+      else
+	warning (_("unable to read executable path name"));
+    }
+  if (do_mappings)
+    fbsd_core_info_proc_mappings (gdbarch);
+  if (do_status)
+    fbsd_core_info_proc_status (gdbarch);
+}
+
 /* Print descriptions of FreeBSD-specific AUXV entries to FILE.  */
 
 static void
@@ -519,6 +1168,7 @@  fbsd_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   set_gdbarch_core_thread_name (gdbarch, fbsd_core_thread_name);
   set_gdbarch_core_xfer_siginfo (gdbarch, fbsd_core_xfer_siginfo);
   set_gdbarch_make_corefile_notes (gdbarch, fbsd_make_corefile_notes);
+  set_gdbarch_core_info_proc (gdbarch, fbsd_core_info_proc);
   set_gdbarch_print_auxv_entry (gdbarch, fbsd_print_auxv_entry);
   set_gdbarch_get_siginfo_type (gdbarch, fbsd_get_siginfo_type);
 
diff --git a/gdb/fbsd-tdep.h b/gdb/fbsd-tdep.h
index 9769903dec..0b293e5a25 100644
--- a/gdb/fbsd-tdep.h
+++ b/gdb/fbsd-tdep.h
@@ -22,4 +22,11 @@ 
 
 extern void fbsd_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch);
 
+/* Helper function to generate mappings flags for a single VM map
+   entry in 'info proc mappings'.  The KVE_FLAGS and KVE_PROTECTION
+   parameters should contain the values of the corresponding fields in
+   a 'struct kinfo_vmentry'.  */
+
+extern const char *fbsd_vm_map_entry_flags (int kve_flags, int kve_protection);
+
 #endif /* fbsd-tdep.h */