[4/5] gdb: add an extension language hook for missing debug info

Message ID 95e0ba3218667b9e65968d83e98281c0984a1009.1697626088.git.aburgess@redhat.com
State New
Headers
Series New Python hook for missing debug information |

Checks

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

Commit Message

Andrew Burgess Oct. 18, 2023, 10:53 a.m. UTC
  This commit adds a new extension_language_ops hook which allows an
extension to handle the case where GDB can't find a separate debug
information file for a particular objfile.

This commit doesn't actually implement the hook for any of GDB's
extension languages, the next commit will do that.  This commit just
adds support for the hook to extension-priv.h and extension.[ch], and
then reworks symfile-debug.c to call the hook.

Right now the hook will always return its default value, which means
GDB should do nothing different.  As such, there should be no user
visible changes after this commit.

I'll give a brief description of what the hook does here so that we
can understand the changes in symfile-debug.c.  The next commit adds a
Python implementation for this new hook, and gives a fuller
description of the new functionality.

Currently, when looking for separate debug information GDB tries three
things, in this order:

  1. Use the build-id to find the required debug information,

  2. Check for .gnu_debuglink section and use that to look up the
  required debug information,

  3. Check with debuginfod to see if it can supply the required
  information.

The new extension_language_ops::handle_missing_debuginfo hook is
called if all three steps fail to find any debug information.  The
hook has three possible return values:

  a. Nothing, no debug information is found, GDB continues without the
  debug information for this objfile.  This matches the current
  behaviour of GDB, and is the default if nothing is implementing this
  new hook,

  b. Install debug information into a location that step #1 or #2
  above would normally check, and then request that GDB repeats steps
  #1 and #2 in the hope that GDB will now find the debug information.
  If the debug information is still not found then GDB carries on
  without the debug information.  If the debug information is found
  the GDB loads it and carries on,

  c. Return a filename for a file containing the required debug
  information.  GDB loads the contents of this file and carries on.

The changes in this commit mostly involve placing the core of
objfile::find_and_add_separate_symbol_file into a loop which allows
for steps #1 and #2 to be repeated.

We take care to ensure that debuginfod is only queried once, the first
time through.  The assumption is that no extension is going to be able
to control the replies from debuginfod, so there's no point making a
second request -- and as these requests go over the network, they
could potentially be slow.

The warnings that find_and_add_separate_symbol_file collects are
displayed only once assuming that no debug information is found.  If
debug information is found, even after the extension has operated,
then the warnings are not shown; remember, these are warnings from GDB
about failure to find any suitable debug information, so it makes
sense to hide these if debug information is found.
---
 gdb/extension-priv.h |  7 ++++
 gdb/extension.c      | 19 ++++++++++
 gdb/extension.h      | 62 ++++++++++++++++++++++++++++++
 gdb/symfile-debug.c  | 89 ++++++++++++++++++++++++++++++++++----------
 4 files changed, 158 insertions(+), 19 deletions(-)
  

Patch

diff --git a/gdb/extension-priv.h b/gdb/extension-priv.h
index 3442302a0be..e71eac20d4e 100644
--- a/gdb/extension-priv.h
+++ b/gdb/extension-priv.h
@@ -279,6 +279,13 @@  struct extension_language_ops
   gdb::optional<int> (*print_insn) (struct gdbarch *gdbarch,
 				    CORE_ADDR address,
 				    struct disassemble_info *info);
+
+  /* Give extension languages a chance to deal with missing debug
+     information.  OBJFILE is the file for which GDB was unable to find
+     any debug information.  */
+  ext_lang_missing_debuginfo_result
+    (*handle_missing_debuginfo) (const struct extension_language_defn *,
+				 struct objfile *objfile);
 };
 
 /* State necessary to restore a signal handler to its previous value.  */
diff --git a/gdb/extension.c b/gdb/extension.c
index 65f3bab32a7..9cb393e1d50 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -997,6 +997,25 @@  ext_lang_print_insn (struct gdbarch *gdbarch, CORE_ADDR address,
   return {};
 }
 
+/* See extension.h.  */
+
+ext_lang_missing_debuginfo_result
+ext_lang_handle_missing_debuginfo (struct objfile *objfile)
+{
+  for (const struct extension_language_defn *extlang : extension_languages)
+    {
+      if (extlang->ops == nullptr
+	  || extlang->ops->handle_missing_debuginfo == nullptr)
+	continue;
+      ext_lang_missing_debuginfo_result result
+	= extlang->ops->handle_missing_debuginfo (extlang, objfile);
+      if (!result.filename ().empty () || result.try_again ())
+	return result;
+    }
+
+  return {};
+}
+
 /* Called via an observer before gdb prints its prompt.
    Iterate over the extension languages giving them a chance to
    change the prompt.  The first one to change the prompt wins,
diff --git a/gdb/extension.h b/gdb/extension.h
index 28f9e3bc028..282d591be43 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -337,6 +337,68 @@  extern gdb::optional<std::string> ext_lang_colorize_disasm
 extern gdb::optional<int> ext_lang_print_insn
   (struct gdbarch *gdbarch, CORE_ADDR address, struct disassemble_info *info);
 
+/* When GDB calls into an extension language because an objfile was
+   discovered for which GDB couldn't find any debug information, this
+   structure holds the result that the extension language returns.
+
+   There are three possible actions that might be returned by an extension;
+   first an extension can return a filename, this is the path to the file
+   containing the required debug  information.  The second possibility is
+   to return a flag indicating that GDB should check again for the missing
+   debug information, this would imply that the extension has installed
+   the debug information into a location where GDB can be expected to find
+   it.  And the third option is for the extension to just return a null
+   result, indication there is nothing the extension can do to provide the
+   missing debug information.  */
+struct ext_lang_missing_debuginfo_result
+{
+  /* Default result.  The extension was unable to provide the missing debug
+     info.  */
+  ext_lang_missing_debuginfo_result ()
+  { /* Nothing.  */ }
+
+  /* When TRY_AGAIN is true GDB should try searching again, the extension
+     may have installed the missing debug info into a suitable location.
+     When TRY_AGAIN is false this is equivalent to the default, no
+     argument, constructor.  */
+  ext_lang_missing_debuginfo_result (bool try_again)
+    : m_try_again (try_again)
+  { /* Nothing.  */ }
+
+  /* Look in FILENAME for the missing debug info.  */
+  ext_lang_missing_debuginfo_result (std::string &&filename)
+    : m_filename (std::move (filename))
+  { /* Nothing.  */ }
+
+  /* The filename where GDB can find the missing debuginfo.  This is empty
+     if the extension didn't suggest a file that can be used.  */
+  const std::string &
+  filename () const
+  {
+    return m_filename;
+  }
+
+  /* Returns true if GDB should look again for the debug information.  */
+  const bool
+  try_again () const
+  {
+    return m_try_again;
+  }
+
+private:
+  /* The filename where the missing debuginfo can now be found.  */
+  std::string m_filename;
+
+  /* When true GDB will search again for the debuginfo using its standard
+     techniques.  When false GDB will not search again.  */
+  bool m_try_again = false;
+};
+
+/* Called when GDB failed to find any debug information for OBJFILE.  */
+
+extern ext_lang_missing_debuginfo_result ext_lang_handle_missing_debuginfo
+  (struct objfile *objfile);
+
 #if GDB_SELF_TEST
 namespace selftests {
 extern void (*hook_set_active_ext_lang) ();
diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index 0a4499320c7..fd9e55dffad 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -632,31 +632,82 @@  bool
 objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
 {
   bool has_dwarf2 = false;
-  deferred_warnings warnings;
 
-  std::pair<gdb_bfd_ref_ptr, std::string> result;
+  /* Usually we only make a single pass when looking for separate debug
+     information.  However, it is possible for an extension language hook
+     to request that GDB make a second pass, in which case max_attempts
+     will be updated, and the loop restarted.  */
+  for (unsigned attempt = 0, max_attempts = 1;
+       attempt < max_attempts && !has_dwarf2;
+       ++attempt)
+    {
+      gdb_assert (max_attempts <= 2);
 
-  result = simple_find_and_open_separate_symbol_file
-    (this, find_separate_debug_file_by_buildid, &warnings);
+      deferred_warnings warnings;
+      std::pair<gdb_bfd_ref_ptr, std::string> result;
 
-  if (result.first == nullptr)
-    result = simple_find_and_open_separate_symbol_file
-      (this, find_separate_debug_file_by_debuglink, &warnings);
+      result = simple_find_and_open_separate_symbol_file
+	(this, find_separate_debug_file_by_buildid, &warnings);
 
-  if (result.first == nullptr)
-    result = debuginfod_find_and_open_separate_symbol_file (this);
+      if (result.first == nullptr)
+	result = simple_find_and_open_separate_symbol_file
+	  (this, find_separate_debug_file_by_debuglink, &warnings);
 
-  if (result.first != nullptr)
-    {
-      symbol_file_add_separate (result.first, result.second.c_str (),
-				symfile_flags, this);
-      has_dwarf2 = true;
-    }
+      /* Only try debuginfod on the first attempt.  Sure, we could imagine
+	 an extension that somehow adds the required debug info to the
+	 debuginfod server but, at least for now, we don't support this
+	 scenario.  Better for the extension to return new debug info
+	 directly to GDB.  Plus, going to the debuginfod server might be
+	 slow, so that's a good argument for only doing this once.  */
+      if (result.first == nullptr && attempt == 0)
+	result
+	  = debuginfod_find_and_open_separate_symbol_file (this);
+
+      if (result.first != nullptr)
+	{
+	  /* We found a separate debug info symbol file.  If this is our
+	     first attempt then setting HAS_DWARF2 will cause us to break
+	     from the attempt loop.  */
+	  symbol_file_add_separate (result.first, result.second.c_str (),
+				    symfile_flags, this);
+	  has_dwarf2 = true;
+	}
+      else if (attempt == 0)
+	{
+	  /* Failed to find a separate debug info symbol file.  Call out to
+	     the extension languages.  The user might have registered an
+	     extension that can find the debug info for us, or maybe give
+	     the user a system specific message that guides them to finding
+	     the missing debug info.  */
+
+	  ext_lang_missing_debuginfo_result ext_result
+	    = ext_lang_handle_missing_debuginfo (this);
+	  if (!ext_result.filename ().empty ())
+	    {
+	      /* Extension found a suitable debug file for us.  */
+	      gdb_bfd_ref_ptr debug_bfd
+		= symfile_bfd_open_no_error (ext_result.filename ().c_str ());
 
-  /* If all the methods to collect the debuginfo failed, print the
-     warnings, this is a no-op if there are no warnings.  */
-  if (!has_dwarf2)
-    warnings.emit ();
+	      if (debug_bfd != nullptr)
+		{
+		  symbol_file_add_separate (debug_bfd,
+					    ext_result.filename ().c_str (),
+					    symfile_flags, this);
+		  has_dwarf2 = true;
+		}
+	    }
+	  else if (ext_result.try_again ())
+	    {
+	      max_attempts = 2;
+	      continue;
+	    }
+	}
+
+      /* If we still have not got a separate debug symbol file, then
+	 emit any warnings we've collected so far.  */
+      if (!has_dwarf2)
+	warnings.emit ();
+    }
 
   return has_dwarf2;
 }