[PATCHv2,5/5] gdb/freebsd: port core file context parsing to FreeBSD

Message ID 3de7e75b9b8773f354c1e8e1a89305c6353770c1.1730141493.git.aburgess@redhat.com
State New
Headers
Series Better executable auto-loading when opening a core file |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_gdb_check--master-arm fail Test failed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 success Test passed

Commit Message

Andrew Burgess Oct. 28, 2024, 6:53 p.m. UTC
  This commit implements the gdbarch_core_parse_exec_context method for
FreeBSD.

This is much simpler than for Linux.  On FreeBSD, at least the
version (13.x) that I have installer, there are additional entries in
the auxv vector that point directly to the argument and environment
vectors, this makes it trivial to find this information.

If these extra auxv entries are not available on earlier FreeBSD, then
that's fine.  The fallback behaviour will be for GDB to act as it
always has up to this point, you'll just not get the extra
functionality.

Other differences compared to Linux are that FreeBSD has
AT_FREEBSD_EXECPATH instead of AT_EXECFN, the AT_FREEBSD_EXECPATH is
the full path to the executable.  On Linux AT_EXECFN is the command
the user typed, so this can be a relative path.

This difference is handy as on FreeBSD we don't parse the mapped files
from the core file (are they even available?).  So having the EXECPATH
means we can use that as the absolute path to the executable.

However, if the user ran a symlink then AT_FREEBSD_EXECPATH will be
the absolute path to the symlink, not to the underlying file.  This is
probably a good thing, but it does mean there is one case we test on
Linux that fails on FreeBSD.

On Linux if we create a symlink to an executable, then run the symlink
and generate a corefile.  Now delete the symlink and load the core
file.  On Linux GDB will still find (and open) the original
executable.  This is because we use the mapped file information to
find the absolute path to the executable, and the mapped file
information only stores the real file names, not symlink names.

This is a total edge case, I only added the deleted symlink test
originally because I could see that this would work on Linux.  Though
it is neat that Linux finds this, I don't feel too bad that this fails
on FreeBSD.

Other than this, everything seems to work on x86-64 FreeBSD (13.4)
which is all I have setup right now.  I don't see why other
architectures wouldn't work too, but I haven't tested them.
---
 gdb/fbsd-tdep.c                               | 134 ++++++++++++++++++
 .../gdb.base/corefile-exec-context.exp        |   2 +-
 gdb/testsuite/gdb.base/corefile-find-exec.exp |  12 +-
 3 files changed, 146 insertions(+), 2 deletions(-)
  

Patch

diff --git a/gdb/fbsd-tdep.c b/gdb/fbsd-tdep.c
index e97ff52d5bf..804a72c4205 100644
--- a/gdb/fbsd-tdep.c
+++ b/gdb/fbsd-tdep.c
@@ -33,6 +33,7 @@ 
 #include "elf-bfd.h"
 #include "fbsd-tdep.h"
 #include "gcore-elf.h"
+#include "arch-utils.h"
 
 /* This enum is derived from FreeBSD's <sys/signal.h>.  */
 
@@ -2361,6 +2362,137 @@  fbsd_vdso_range (struct gdbarch *gdbarch, struct mem_range *range)
   return range->length != 0;
 }
 
+/* Try to extract the inferior arguments, environment, and executable name
+   from CBFD.  */
+
+static core_file_exec_context
+fbsd_corefile_parse_exec_context_1 (struct gdbarch *gdbarch, bfd *cbfd)
+{
+  gdb_assert (gdbarch != nullptr);
+
+  /* If there's no core file loaded then we're done.  */
+  if (cbfd == nullptr)
+    return {};
+
+  int ptr_bytes = gdbarch_ptr_bit (gdbarch) / TARGET_CHAR_BIT;
+
+  /* Find the .auxv section in the core file. The BFD library creates this
+     for us from the AUXV note when the BFD is opened.  If the section
+     can't be found then there's nothing more we can do.  */
+  struct bfd_section * section = bfd_get_section_by_name (cbfd, ".auxv");
+  if (section == nullptr)
+    return {};
+
+  /* Grab the contents of the .auxv section.  If we can't get the contents
+     then there's nothing more we can do.  */
+  bfd_size_type size = bfd_section_size (section);
+  if (bfd_section_size_insane (cbfd, section))
+    return {};
+  gdb::byte_vector contents (size);
+  if (!bfd_get_section_contents (cbfd, section, contents.data (), 0, size))
+    return {};
+
+  /* Read AT_FREEBSD_ARGV, the address of the argument string vector.  */
+  CORE_ADDR argv_addr;
+  if (target_auxv_search (contents, current_inferior ()->top_target (),
+			  gdbarch, AT_FREEBSD_ARGV, &argv_addr) != 1)
+    return {};
+
+  /* Read AT_FREEBSD_ARGV, the address of the environment string vector.  */
+  CORE_ADDR envv_addr;
+  if (target_auxv_search (contents, current_inferior ()->top_target (),
+			  gdbarch, AT_FREEBSD_ENVV, &envv_addr) != 1)
+    return {};
+
+  /* Read the AT_EXECPATH string.  It's OK if we can't get this
+     information.  */
+  gdb::unique_xmalloc_ptr<char> execpath;
+  CORE_ADDR execpath_string_addr;
+  if (target_auxv_search (contents, current_inferior ()->top_target (),
+			  gdbarch, AT_FREEBSD_EXECPATH,
+			  &execpath_string_addr) == 1)
+    execpath = target_read_string (execpath_string_addr, INT_MAX);
+
+  /* The byte order.  */
+  enum bfd_endian byte_order = gdbarch_byte_order (gdbarch);
+
+  /* On FreeBSD the command the user ran is found in argv[0].  When we
+     read the first argument we place it into EXECFN.  */
+  gdb::unique_xmalloc_ptr<char> execfn;
+
+  /* Read strings from AT_FREEBSD_ARGV until we find a NULL marker.  The
+     first argument is placed into EXECFN as the command name.  */
+  std::vector<gdb::unique_xmalloc_ptr<char>> arguments;
+  CORE_ADDR str_addr;
+  while ((str_addr
+	  = (CORE_ADDR) read_memory_unsigned_integer (argv_addr, ptr_bytes,
+						      byte_order)) != 0)
+    {
+      gdb::unique_xmalloc_ptr<char> str
+	= target_read_string (str_addr, INT_MAX);
+      if (str == nullptr)
+	return {};
+
+      if (execfn == nullptr)
+	execfn = std::move (str);
+      else
+	arguments.emplace_back (std::move (str));
+
+      argv_addr += ptr_bytes;
+    }
+
+  /* Read strings from AT_FREEBSD_ENVV until we find a NULL marker.  */
+  std::vector<gdb::unique_xmalloc_ptr<char>> environment;
+  while ((str_addr
+	  = (uint64_t) read_memory_unsigned_integer (envv_addr, ptr_bytes,
+						     byte_order)) != 0)
+    {
+      gdb::unique_xmalloc_ptr<char> str
+	= target_read_string (str_addr, INT_MAX);
+      if (str == nullptr)
+	return {};
+
+      environment.emplace_back (std::move (str));
+      envv_addr += ptr_bytes;
+    }
+
+  return core_file_exec_context (std::move (execfn),
+				 std::move (execpath),
+				 std::move (arguments),
+				 std::move (environment));
+}
+
+/* See elf-corelow.h.  */
+
+static core_file_exec_context
+fbsd_corefile_parse_exec_context (struct gdbarch *gdbarch, bfd *cbfd)
+{
+  /* Catch and discard memory errors.
+
+     If the core file format is not as we expect then we can easily trigger
+     a memory error while parsing the core file.  We don't want this to
+     prevent the user from opening the core file; the information provided
+     by this function is helpful, but not critical, debugging can continue
+     without it.  Instead just give a warning and return an empty context
+     object.  */
+  try
+    {
+      return fbsd_corefile_parse_exec_context_1 (gdbarch, cbfd);
+    }
+  catch (const gdb_exception_error &ex)
+    {
+      if (ex.error == MEMORY_ERROR)
+	{
+	  warning
+	    (_("failed to parse execution context from corefile: %s"),
+	     ex.message->c_str ());
+	  return {};
+	}
+      else
+	throw;
+    }
+}
+
 /* Return the address range of the vDSO for the current inferior.  */
 
 static int
@@ -2404,4 +2536,6 @@  fbsd_init_abi (struct gdbarch_info info, struct gdbarch *gdbarch)
   /* `catch syscall' */
   set_xml_syscall_file_name (gdbarch, "syscalls/freebsd.xml");
   set_gdbarch_get_syscall_number (gdbarch, fbsd_get_syscall_number);
+  set_gdbarch_core_parse_exec_context (gdbarch,
+				       fbsd_corefile_parse_exec_context);
 }
diff --git a/gdb/testsuite/gdb.base/corefile-exec-context.exp b/gdb/testsuite/gdb.base/corefile-exec-context.exp
index ac97754fe71..73e13e60d75 100644
--- a/gdb/testsuite/gdb.base/corefile-exec-context.exp
+++ b/gdb/testsuite/gdb.base/corefile-exec-context.exp
@@ -18,7 +18,7 @@ 
 #
 # Currently, only Linux supports reading full executable and arguments
 # from a core file.
-require {istarget *-linux*}
+require {is_any_target "*-*-linux*" "*-*-freebsd*"}
 
 standard_testfile
 
diff --git a/gdb/testsuite/gdb.base/corefile-find-exec.exp b/gdb/testsuite/gdb.base/corefile-find-exec.exp
index 40324c1f01c..07e660d85e8 100644
--- a/gdb/testsuite/gdb.base/corefile-find-exec.exp
+++ b/gdb/testsuite/gdb.base/corefile-find-exec.exp
@@ -18,7 +18,7 @@ 
 #
 # Currently, only Linux supports reading full executable and arguments
 # from a core file.
-require {istarget *-linux*}
+require {is_any_target "*-*-linux*" "*-*-freebsd*"}
 
 standard_testfile
 
@@ -115,6 +115,16 @@  with_test_prefix "absolute path" {
 
 	remote_exec build "rm -f $symlink"
 
+	# FreeBSD is unable to figure out the actual underlying mapped
+	# file, so when the symlink is deleted, FeeeBSD is stuck.
+	#
+	# There is some argument that this shouldn't even be a
+	# failure, the user ran the symlink, and if the symlink is
+	# gone, should we really expect GDB to find the underlying
+	# file?  That we can on Linux is really just a quirk of how
+	# the mapped file list works.
+	setup_xfail "*-*-freebsd*"
+
 	test_load $corefile_3 $binfile $symlink
     }