public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 0/5] New Python hook for missing debug information
@ 2023-10-18 10:53 Andrew Burgess
  2023-10-18 10:53 ` [PATCH 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c Andrew Burgess
                   ` (5 more replies)
  0 siblings, 6 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-10-18 10:53 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

This series adds a new Python hook to GDB, this hook allows a user to
register a handler object which will be called whenever GDB fails to
locate any debug information for an objfile it is loading.  These
handler objects can (potentially) perform host or project specific
actions to help GDB locate the missing debug information.

Commits #1 and #2 are refactoring.  These merge the separate debug
information lookup code from coffread.c and elfread.c.

Commit #3 is more refactoring, this simplifies the now merged code.

Commit #4 implements the framework for the new hook within GDB, but
doesn't implement the Python side of things yet.

Commit #5 is where I add the Python side of things, it's only at this
point that a user can actually hook into GDB.

---

Andrew Burgess (5):
  gdb/coffread: bring separate debug file logic into line with elfread.c
  gdb: merge debug symbol file lookup code from coffread & elfread paths
  gdb: refactor objfile::find_and_add_separate_symbol_file
  gdb: add an extension language hook for missing debug info
  gdb: implement missing debug handler hook for Python

 gdb/NEWS                                      |  26 +
 gdb/coffread.c                                |  28 +-
 gdb/data-directory/Makefile.in                |   2 +
 gdb/doc/python.texi                           | 136 +++++
 gdb/elfread.c                                 |  57 +--
 gdb/extension-priv.h                          |   7 +
 gdb/extension.c                               |  19 +
 gdb/extension.h                               |  62 +++
 gdb/objfiles.h                                |  10 +
 gdb/python/lib/gdb/__init__.py                |  41 ++
 gdb/python/lib/gdb/command/missing_debug.py   | 226 +++++++++
 gdb/python/lib/gdb/missing_debug.py           | 169 +++++++
 gdb/python/py-progspace.c                     |  51 ++
 gdb/python/python.c                           |  83 ++-
 gdb/symfile-debug.c                           | 154 ++++++
 gdb/testsuite/gdb.python/py-missing-debug.c   |  22 +
 gdb/testsuite/gdb.python/py-missing-debug.exp | 473 ++++++++++++++++++
 gdb/testsuite/gdb.python/py-missing-debug.py  | 120 +++++
 18 files changed, 1608 insertions(+), 78 deletions(-)
 create mode 100644 gdb/python/lib/gdb/command/missing_debug.py
 create mode 100644 gdb/python/lib/gdb/missing_debug.py
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.c
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.exp
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.py


base-commit: 5d4a870e05ac45e3f5a301c672a4079995b5db7a
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCH 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c
  2023-10-18 10:53 [PATCH 0/5] New Python hook for missing debug information Andrew Burgess
@ 2023-10-18 10:53 ` Andrew Burgess
  2023-10-20 17:35   ` Tom Tromey
  2023-10-18 10:53 ` [PATCH 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths Andrew Burgess
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 30+ messages in thread
From: Andrew Burgess @ 2023-10-18 10:53 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

In this commit:

  commit 8a92335bfca80cc9b4cd217505ea0dcbfdefbf07
  Date:   Fri Feb 1 19:39:04 2013 +0000

the logic for when we try to load a separate debug file in elfread.c
was extended.  The new code checks that the objfile doesn't already
have a separate debug objfile linked to it, and that the objfile isn't
itself a separate debug objfile for some other objfile.

The coffread code wasn't extended at the same time.

I don't know if it's possible for the coffread code to get into the
same state where these checks are needed, but I don't see why having
these checks would be a problem.  In a later commit I plan to merge
this part of the elfread and coffread code, so bringing these two
pieces of code into line first makes that job easier.

I haven't tested this, I don't have a coff target to hand.
---
 gdb/coffread.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/gdb/coffread.c b/gdb/coffread.c
index 4a6a83b15ad..e1415d6b258 100644
--- a/gdb/coffread.c
+++ b/gdb/coffread.c
@@ -725,7 +725,9 @@ coff_symfile_read (struct objfile *objfile, symfile_add_flags symfile_flags)
     }
 
   /* Try to add separate debug file if no symbols table found.   */
-  if (!objfile->has_partial_symbols ())
+  else if (!objfile->has_partial_symbols ()
+	   && objfile->separate_debug_objfile == NULL
+	   && objfile->separate_debug_objfile_backlink == NULL)
     {
       deferred_warnings warnings;
       std::string debugfile
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCH 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths
  2023-10-18 10:53 [PATCH 0/5] New Python hook for missing debug information Andrew Burgess
  2023-10-18 10:53 ` [PATCH 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c Andrew Burgess
@ 2023-10-18 10:53 ` Andrew Burgess
  2023-10-18 13:18   ` Jon Turney
  2023-10-18 10:53 ` [PATCH 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file Andrew Burgess
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 30+ messages in thread
From: Andrew Burgess @ 2023-10-18 10:53 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

This commit merges the code that looks for and loads the separate
debug symbol files from coffread.c and elfread.c.  The factored out
code is moved into a new objfile::find_and_add_separate_symbol_file()
method.

For the elfread.c path there should be no user visible changes after
this commit.

For the coffread.c path GDB will now attempt to perform a debuginfod
lookup for the missing debug information, assuming that GDB can find a
build-id in the COFF file.

I don't know if COFF files can include a build-id, but I the existing
coffread.c code already includes a call to
find_separate_debug_file_by_build-id, so I know that it is at least OK
for GDB to ask a COFF file for a build-id.  If the COFF file doesn't
include a build-id then the debuginfod lookup code will not trigger
and the new code is harmless.

If the COFF file does include a build-id, then we're going to end up
asking debuginfod for the debug file.  As build-ids should be unique,
this should be harmless, even if debuginfod doesn't contain any
suitable debug data, it just costs us one debuginfod lookup, so I'm
not too worried about this for now.

I don't have access to a COFF target right now, so beyond compiling
it, the coffread.c changes are completely untested.
---
 gdb/coffread.c      | 24 ++---------------
 gdb/elfread.c       | 57 +++------------------------------------
 gdb/objfiles.h      | 10 +++++++
 gdb/symfile-debug.c | 66 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 81 insertions(+), 76 deletions(-)

diff --git a/gdb/coffread.c b/gdb/coffread.c
index e1415d6b258..5898b3a8e08 100644
--- a/gdb/coffread.c
+++ b/gdb/coffread.c
@@ -40,8 +40,6 @@
 
 #include "coff-pe-read.h"
 
-#include "build-id.h"
-
 /* The objfile we are currently reading.  */
 
 static struct objfile *coffread_objfile;
@@ -729,26 +727,8 @@ coff_symfile_read (struct objfile *objfile, symfile_add_flags symfile_flags)
 	   && objfile->separate_debug_objfile == NULL
 	   && objfile->separate_debug_objfile_backlink == NULL)
     {
-      deferred_warnings warnings;
-      std::string debugfile
-	= find_separate_debug_file_by_buildid (objfile, &warnings);
-
-      if (debugfile.empty ())
-	debugfile
-	  = find_separate_debug_file_by_debuglink (objfile, &warnings);
-
-      if (!debugfile.empty ())
-	{
-	  gdb_bfd_ref_ptr debug_bfd (symfile_bfd_open (debugfile.c_str ()));
-
-	  symbol_file_add_separate (debug_bfd, debugfile.c_str (),
-				    symfile_flags, objfile);
-	}
-      /* If all the methods to collect the debuginfo failed, print any
-	 warnings that were collected, this is a no-op if there are no
-	 warnings.  */
-      if (debugfile.empty ())
-	warnings.emit ();
+      if (objfile->find_and_add_separate_symbol_file (symfile_flags))
+	gdb_assert (objfile->separate_debug_objfile != nullptr);
     }
 }
 
diff --git a/gdb/elfread.c b/gdb/elfread.c
index 7900dfbc388..86e7f61586e 100644
--- a/gdb/elfread.c
+++ b/gdb/elfread.c
@@ -41,14 +41,12 @@
 #include "regcache.h"
 #include "bcache.h"
 #include "gdb_bfd.h"
-#include "build-id.h"
 #include "location.h"
 #include "auxv.h"
 #include "mdebugread.h"
 #include "ctfread.h"
 #include "gdbsupport/gdb_string_view.h"
 #include "gdbsupport/scoped_fd.h"
-#include "debuginfod-support.h"
 #include "dwarf2/public.h"
 #include "cli/cli-cmds.h"
 
@@ -1218,59 +1216,10 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
 	   && objfile->separate_debug_objfile == NULL
 	   && objfile->separate_debug_objfile_backlink == NULL)
     {
-      deferred_warnings warnings;
-
-      std::string debugfile
-	= find_separate_debug_file_by_buildid (objfile, &warnings);
-
-      if (debugfile.empty ())
-	debugfile = find_separate_debug_file_by_debuglink (objfile, &warnings);
-
-      if (!debugfile.empty ())
-	{
-	  gdb_bfd_ref_ptr debug_bfd
-	    (symfile_bfd_open_no_error (debugfile.c_str ()));
-
-	  if (debug_bfd != nullptr)
-	    symbol_file_add_separate (debug_bfd, debugfile.c_str (),
-				      symfile_flags, objfile);
-	}
+      if (objfile->find_and_add_separate_symbol_file (symfile_flags))
+	gdb_assert (objfile->separate_debug_objfile != nullptr);
       else
-	{
-	  has_dwarf2 = false;
-	  const struct bfd_build_id *build_id
-	    = build_id_bfd_get (objfile->obfd.get ());
-	  const char *filename = bfd_get_filename (objfile->obfd.get ());
-
-	  if (build_id != nullptr)
-	    {
-	      gdb::unique_xmalloc_ptr<char> symfile_path;
-	      scoped_fd fd (debuginfod_debuginfo_query (build_id->data,
-							build_id->size,
-							filename,
-							&symfile_path));
-
-	      if (fd.get () >= 0)
-		{
-		  /* File successfully retrieved from server.  */
-		  gdb_bfd_ref_ptr debug_bfd
-		    (symfile_bfd_open_no_error (symfile_path.get ()));
-
-		  if (debug_bfd != nullptr
-		      && build_id_verify (debug_bfd.get (), build_id->size,
-					  build_id->data))
-		    {
-		      symbol_file_add_separate (debug_bfd, symfile_path.get (),
-						symfile_flags, objfile);
-		      has_dwarf2 = true;
-		    }
-		}
-	    }
-	}
-      /* If all the methods to collect the debuginfo failed, print the
-	 warnings, this is a no-op if there are no warnings.  */
-      if (debugfile.empty () && !has_dwarf2)
-	warnings.emit ();
+	has_dwarf2 = false;
     }
 
   return has_dwarf2;
diff --git a/gdb/objfiles.h b/gdb/objfiles.h
index 4b8aa9bfcec..ec9d354e4a7 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -513,6 +513,16 @@ struct objfile
 
   bool has_partial_symbols ();
 
+  /* Look for a separate debug symbol file for this objfile, make use of
+     build-id, debug-link, and debuginfod as necessary.  If a suitable
+     separate debug symbol file is found then it is loaded using a call to
+     symbol_file_add_separate (SYMFILE_FLAGS is passed through unmodified
+     to this call) and this function returns true.  If no suitable separate
+     debug symbol file is found and loaded then this function returns
+     false.  */
+
+  bool find_and_add_separate_symbol_file (symfile_add_flags symfile_flags);
+
   /* Return true if this objfile has any unexpanded symbols.  A return
      value of false indicates either, that this objfile has all its
      symbols fully expanded (i.e. fully read in), or that this objfile has
diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index 850da4147a3..961ae2327f7 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -35,6 +35,8 @@
 #include "block.h"
 #include "filenames.h"
 #include "cli/cli-style.h"
+#include "build-id.h"
+#include "debuginfod-support.h"
 
 /* We need to save a pointer to the real symbol functions.
    Plus, the debug versions are malloc'd because we have to NULL out the
@@ -558,6 +560,70 @@ objfile::require_partial_symbols (bool verbose)
     }
 }
 
+/* See objfiles.h.  */
+
+bool
+objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
+{
+  bool has_dwarf2 = true;
+
+  deferred_warnings warnings;
+
+  std::string debugfile
+    = find_separate_debug_file_by_buildid (this, &warnings);
+
+  if (debugfile.empty ())
+    debugfile = find_separate_debug_file_by_debuglink (this, &warnings);
+
+  if (!debugfile.empty ())
+    {
+      gdb_bfd_ref_ptr debug_bfd
+	(symfile_bfd_open_no_error (debugfile.c_str ()));
+
+      if (debug_bfd != nullptr)
+	symbol_file_add_separate (debug_bfd, debugfile.c_str (),
+				  symfile_flags, this);
+    }
+  else
+    {
+      has_dwarf2 = false;
+      const struct bfd_build_id *build_id
+	= build_id_bfd_get (this->obfd.get ());
+      const char *filename = bfd_get_filename (this->obfd.get ());
+
+      if (build_id != nullptr)
+	{
+	  gdb::unique_xmalloc_ptr<char> symfile_path;
+	  scoped_fd fd (debuginfod_debuginfo_query (build_id->data,
+						    build_id->size,
+						    filename,
+						    &symfile_path));
+
+	  if (fd.get () >= 0)
+	    {
+	      /* File successfully retrieved from server.  */
+	      gdb_bfd_ref_ptr debug_bfd
+		(symfile_bfd_open_no_error (symfile_path.get ()));
+
+	      if (debug_bfd != nullptr
+		  && build_id_verify (debug_bfd.get (), build_id->size,
+				      build_id->data))
+		{
+		  symbol_file_add_separate (debug_bfd, symfile_path.get (),
+					    symfile_flags, this);
+		  has_dwarf2 = true;
+		}
+	    }
+	}
+    }
+  /* If all the methods to collect the debuginfo failed, print the
+     warnings, this is a no-op if there are no warnings.  */
+  if (debugfile.empty () && !has_dwarf2)
+    warnings.emit ();
+
+  return has_dwarf2;
+}
+
 \f
 /* Debugging version of struct sym_probe_fns.  */
 
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCH 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file
  2023-10-18 10:53 [PATCH 0/5] New Python hook for missing debug information Andrew Burgess
  2023-10-18 10:53 ` [PATCH 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c Andrew Burgess
  2023-10-18 10:53 ` [PATCH 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths Andrew Burgess
@ 2023-10-18 10:53 ` Andrew Burgess
  2023-10-20 17:50   ` Tom Tromey
  2023-10-18 10:53 ` [PATCH 4/5] gdb: add an extension language hook for missing debug info Andrew Burgess
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 30+ messages in thread
From: Andrew Burgess @ 2023-10-18 10:53 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

This is purely a refactoring commit.

This commit splits objfile::find_and_add_separate_symbol_file into
some separate helper functions.  My hope is that the steps for looking
up separate debug information are now clearer.

In a later commit I'm going to extend
objfile::find_and_add_separate_symbol_file, with some additional
logic, so starting with a simpler function will make the following
changes easier.

There should be no user visible changes after this commit.
---
 gdb/symfile-debug.c | 127 ++++++++++++++++++++++++++++----------------
 1 file changed, 82 insertions(+), 45 deletions(-)

diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index 961ae2327f7..0a4499320c7 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -560,65 +560,102 @@ objfile::require_partial_symbols (bool verbose)
     }
 }
 
+/* Call LOOKUP_FUNC to find the filename of a file containing the separate
+   debug information matching OBJFILE.  If LOOKUP_FUNC does return a
+   filename then open this file and return a std::pair containing the
+   gdb_bfd_ref_ptr of the open file and the filename returned by
+   LOOKUP_FUNC, otherwise this function returns an empty pair; the first
+   item will be nullptr, and the second will be an empty string.
+
+   Any warnings generated by this function, or by calling LOOKUP_FUNC are
+   placed into WARNINGS, these warnings are only displayed to the user if
+   GDB is unable to find the separate debug information via any route.  */
+static std::pair<gdb_bfd_ref_ptr, std::string>
+simple_find_and_open_separate_symbol_file
+  (struct objfile *objfile,
+   std::string (*lookup_func) (struct objfile *, deferred_warnings *),
+   deferred_warnings *warnings)
+{
+  std::string filename = lookup_func (objfile, warnings);
+
+  if (!filename.empty ())
+    {
+      gdb_bfd_ref_ptr symfile_bfd
+	= symfile_bfd_open_no_error (filename.c_str ());
+      if (symfile_bfd != nullptr)
+	return { symfile_bfd, filename };
+    }
+
+  return {};
+}
+
+/* Lookup separate debug information for OBJFILE via debuginfod.  If
+   successful the debug information will be have been downloaded into the
+   debuginfod cache and this function will return a std::pair containing a
+   gdb_bfd_ref_ptr of the open debug information file and the filename for
+   the file within the debuginfod cache.  If no debug information could be
+   found then this function returns an empty pair; the first item will be
+   nullptr, and the second will be an empty string.  */
+
+static std::pair<gdb_bfd_ref_ptr, std::string>
+debuginfod_find_and_open_separate_symbol_file (struct objfile * objfile)
+{
+  const struct bfd_build_id *build_id
+    = build_id_bfd_get (objfile->obfd.get ());
+  const char *filename = bfd_get_filename (objfile->obfd.get ());
+
+  if (build_id != nullptr)
+    {
+      gdb::unique_xmalloc_ptr<char> symfile_path;
+      scoped_fd fd (debuginfod_debuginfo_query (build_id->data, build_id->size,
+						filename, &symfile_path));
+
+      if (fd.get () >= 0)
+	{
+	  /* File successfully retrieved from server.  */
+	  gdb_bfd_ref_ptr debug_bfd
+	    (symfile_bfd_open_no_error (symfile_path.get ()));
+
+	  if (debug_bfd != nullptr
+	      && build_id_verify (debug_bfd.get (),
+				  build_id->size, build_id->data))
+	    return { debug_bfd, std::string (symfile_path.get ()) };
+	}
+    }
+
+  return {};
+}
+
 /* See objfiles.h.  */
 
 bool
 objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
 {
-  bool has_dwarf2 = true;
-
+  bool has_dwarf2 = false;
   deferred_warnings warnings;
 
-  std::string debugfile
-    = find_separate_debug_file_by_buildid (this, &warnings);
+  std::pair<gdb_bfd_ref_ptr, std::string> result;
 
-  if (debugfile.empty ())
-    debugfile = find_separate_debug_file_by_debuglink (this, &warnings);
-
-  if (!debugfile.empty ())
-    {
-      gdb_bfd_ref_ptr debug_bfd
-	(symfile_bfd_open_no_error (debugfile.c_str ()));
+  result = simple_find_and_open_separate_symbol_file
+    (this, find_separate_debug_file_by_buildid, &warnings);
 
-      if (debug_bfd != nullptr)
-	symbol_file_add_separate (debug_bfd, debugfile.c_str (),
-				  symfile_flags, this);
-    }
-  else
-    {
-      has_dwarf2 = false;
-      const struct bfd_build_id *build_id
-	= build_id_bfd_get (this->obfd.get ());
-      const char *filename = bfd_get_filename (this->obfd.get ());
+  if (result.first == nullptr)
+    result = simple_find_and_open_separate_symbol_file
+      (this, find_separate_debug_file_by_debuglink, &warnings);
 
-      if (build_id != nullptr)
-	{
-	  gdb::unique_xmalloc_ptr<char> symfile_path;
-	  scoped_fd fd (debuginfod_debuginfo_query (build_id->data,
-						    build_id->size,
-						    filename,
-						    &symfile_path));
+  if (result.first == nullptr)
+    result = debuginfod_find_and_open_separate_symbol_file (this);
 
-	  if (fd.get () >= 0)
-	    {
-	      /* File successfully retrieved from server.  */
-	      gdb_bfd_ref_ptr debug_bfd
-		(symfile_bfd_open_no_error (symfile_path.get ()));
-
-	      if (debug_bfd != nullptr
-		  && build_id_verify (debug_bfd.get (), build_id->size,
-				      build_id->data))
-		{
-		  symbol_file_add_separate (debug_bfd, symfile_path.get (),
-					    symfile_flags, this);
-		  has_dwarf2 = true;
-		}
-	    }
-	}
+  if (result.first != nullptr)
+    {
+      symbol_file_add_separate (result.first, result.second.c_str (),
+				symfile_flags, this);
+      has_dwarf2 = true;
     }
+
   /* If all the methods to collect the debuginfo failed, print the
      warnings, this is a no-op if there are no warnings.  */
-  if (debugfile.empty () && !has_dwarf2)
+  if (!has_dwarf2)
     warnings.emit ();
 
   return has_dwarf2;
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCH 4/5] gdb: add an extension language hook for missing debug info
  2023-10-18 10:53 [PATCH 0/5] New Python hook for missing debug information Andrew Burgess
                   ` (2 preceding siblings ...)
  2023-10-18 10:53 ` [PATCH 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file Andrew Burgess
@ 2023-10-18 10:53 ` Andrew Burgess
  2023-10-18 10:53 ` [PATCH 5/5] gdb: implement missing debug handler hook for Python Andrew Burgess
  2023-11-08 15:48 ` [PATCHv2 0/5] New Python hook for missing debug information Andrew Burgess
  5 siblings, 0 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-10-18 10:53 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

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(-)

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;
 }
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCH 5/5] gdb: implement missing debug handler hook for Python
  2023-10-18 10:53 [PATCH 0/5] New Python hook for missing debug information Andrew Burgess
                   ` (3 preceding siblings ...)
  2023-10-18 10:53 ` [PATCH 4/5] gdb: add an extension language hook for missing debug info Andrew Burgess
@ 2023-10-18 10:53 ` Andrew Burgess
  2023-10-18 12:08   ` Eli Zaretskii
  2023-11-08 15:48 ` [PATCHv2 0/5] New Python hook for missing debug information Andrew Burgess
  5 siblings, 1 reply; 30+ messages in thread
From: Andrew Burgess @ 2023-10-18 10:53 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

This commit builds on the previous commit, and implements the
extension_language_ops::handle_missing_debuginfo function for Python.
This hook will give user supplied Python code a chance to help find
missing debug information.

The implementation of the new hook is pretty minimal within GDB's C++
code; most of the work is out-sourced to a Python implementation which
is modelled heavily on how GDB's Python frame unwinders are
implemented.

The following new commands are added as commands implemented in
Python, this is similar to how the Python unwinder commands are
implemented:

  info missing-debug-handlers
  enable missing-debug-handler LOCUS HANDLER
  disable missing-debug-handler LOCUS HANDLER

To make use of this extension hook a user will create missing debug
information handler objects, and registers these handlers with GDB.
When GDB encounters an objfile that is missing debug information, each
handler is called in turn until one is able to help.  Here is a
minimal handler that does nothing useful:

  import gdb
  import gdb.missing_debug

  class MyFirstHandler(gdb.missing_debug.MissingDebugHandler):
      def __init__(self):
          super().__init__("my_first_handler")

      def __call__(self, objfile):
          # This handler does nothing useful.
          return None

  gdb.missing_debug.register_handler(None, MyFirstHandler())

Returning None from the __call__ method tells GDB that this handler
was unable to find the missing debug information, and GDB should ask
any other registered handlers.

By extending the __call__ method it is possible for the Python
extension to locate the debug information for objfile and return a
value that tells GDB how to use the information that has been located.

Possible return values from a handler:

  - None: This means the handler couldn't help.  GDB will call other
          registered handlers to see if they can help instead.

  - False: The handler has done all it can, but the debug information
           for the objfile still couldn't be found.  GDB will not call
	   any other handlers, and will continue without the debug
	   information for objfile.

  - True: The handler has installed the debug information into a
          location where GDB would normally expect to find it.  GDB
	  should look again for the debug information.

  - A string: The handler can return a filename, which is the file
              containing the missing debug information.  GDB will load
	      this file.

When a handler returns True, GDB will look again for the debug
information, but only using the standard built-in build-id and
.gnu_debuglink based lookup strategies.  It is not possible for an
extension to trigger another debuginfod lookup; the assumption is that
the debuginfod server is remote, and out of the control of extensions
running within GDB.

Handlers can be registered globally, or per program space.  GDB checks
the handlers for the current program space first, and then all of the
global handles.  The first handler that returns a value that is not
None, has "handled" the objfile, at which point GDB continues.
---
 gdb/NEWS                                      |  26 +
 gdb/data-directory/Makefile.in                |   2 +
 gdb/doc/python.texi                           | 136 +++++
 gdb/python/lib/gdb/__init__.py                |  41 ++
 gdb/python/lib/gdb/command/missing_debug.py   | 226 +++++++++
 gdb/python/lib/gdb/missing_debug.py           | 169 +++++++
 gdb/python/py-progspace.c                     |  51 ++
 gdb/python/python.c                           |  83 ++-
 gdb/testsuite/gdb.python/py-missing-debug.c   |  22 +
 gdb/testsuite/gdb.python/py-missing-debug.exp | 473 ++++++++++++++++++
 gdb/testsuite/gdb.python/py-missing-debug.py  | 120 +++++
 11 files changed, 1348 insertions(+), 1 deletion(-)
 create mode 100644 gdb/python/lib/gdb/command/missing_debug.py
 create mode 100644 gdb/python/lib/gdb/missing_debug.py
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.c
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.exp
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.py

diff --git a/gdb/NEWS b/gdb/NEWS
index 81264c0cfb3..509586b9589 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -6,11 +6,37 @@
 * GDB index now contains information about the main function. This speeds up
   startup when it is being used for some large binaries.
 
+* New Commands
+
+info missing-debug-handler
+  List all the registered missing debug handlers.
+
+enable missing-debug-handler LOCUS HANDLER
+disable missing-debug-handler LOCUS HANDLER
+  Enable or disable a missing debug handler with a name matching the
+  regular expression HANDLER, in LOCUS.
+
+  LOCUS can be 'global' to operate on global missing debug handler,
+  'progspace' to operate on handlers within the current program space,
+  or can be a regular expression which is matched against the filename
+  of the primary executable in each program space.
+
 * Python API
 
   ** New function gdb.notify_mi(NAME, DATA), that emits custom
      GDB/MI async notification.
 
+  ** New module gdb.missing_debug that facilitates dealing with
+     objfiles that are missing any debug information.
+
+  ** New function gdb.missing_debug.register_handler that can register
+     an instance of a sub-class of gdb.missing_debug.MissingDebugInfo
+     as a handler for objfiles that are missing debug information.
+
+  ** New class gdb.missing_debug.MissingDebugInfo which can be
+     sub-classed to create handlers for objfiles with missing debug
+     information.
+
 *** Changes in GDB 14
 
 * GDB now supports the AArch64 Scalable Matrix Extension 2 (SME2), which
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 04a8c8eca69..7af6bafb0c9 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -73,6 +73,7 @@ PYTHON_FILE_LIST = \
 	gdb/FrameDecorator.py \
 	gdb/FrameIterator.py \
 	gdb/frames.py \
+	gdb/missing_debug.py \
 	gdb/printing.py \
 	gdb/prompt.py \
 	gdb/styling.py \
@@ -82,6 +83,7 @@ PYTHON_FILE_LIST = \
 	gdb/command/__init__.py \
 	gdb/command/explore.py \
 	gdb/command/frame_filters.py \
+	gdb/command/missing_debug.py \
 	gdb/command/pretty_printers.py \
 	gdb/command/prompt.py \
 	gdb/command/type_printers.py \
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 546b4d4b962..5aa1062699a 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -230,6 +230,7 @@
 * Connections In Python::       Python representation of connections.
 * TUI Windows In Python::       Implementing new TUI windows.
 * Disassembly In Python::       Instruction Disassembly In Python
+* Missing Debug Info In Python:: Handle missing debug info from Python.
 @end menu
 
 @node Basic Python
@@ -5236,6 +5237,12 @@
 objects.  @xref{Frame Filter API}, for more information.
 @end defvar
 
+@defvar Progspace.missing_debug_handlers
+The @code{missing_debug_handlers} attribute is a list of the missing
+debug handler objects for this program space.  @xref{Missing Debug
+Info In Python}, for more information.
+@end defvar
+
 A program space has the following methods:
 
 @defun Progspace.block_for_pc (pc)
@@ -7803,6 +7810,135 @@
 gdb.disassembler.register_disassembler(NibbleSwapDisassembler())
 @end smallexample
 
+@node Missing Debug Info In Python
+@subsubsection Missing Debug Info In Python
+@cindex python, handle missing debug information
+
+When @value{GDBN} encounters a new objfile (@pxref{Objfiles In
+Python}), e.g.@: the primary executable, or any shared libraries used
+by the inferior, @value{GDBN} will attempt to load the corresponding
+debug information for that objfile.  The debug information might be
+found within the objfile itself, or within a separate objfile which
+@value{GDBN} will automatically locate and load.
+
+Sometimes though, @value{GDBN} might not find any debug information
+for an objfile, in this case the debugging experience will be
+restricted.
+
+If @value{GDBN} fails to locate any debug information for a particular
+objfile, there is an opportunity for a Python extension to step in.  A
+Python extension can potentially locate the missing debug information
+using some platform, or project, specific steps, and inform
+@value{GDBN} of its location.  Or a Python extension might provide
+some platform, or project specific advice to the user about how to
+obtain the missing debug information.
+
+A missing debug information Python extension consists of a handler
+object which has the @code{name} and @code{enabled} attributes, and
+implements the @code{__call__} method.  When @value{GDBN} encounters
+an objfile for which it is unable to find any debug information the
+@code{__call__} method is invoked.  Full details of how handlers are
+written can be found below.
+
+@subheading The @code{gdb.missing_debug} Module
+
+@value{GDBN} comes with a @code{gdb.missing_debug} module which
+contains the following class and global function:
+
+@deftp{class} gdb.missing_debug.MissingDebugHandler
+
+The @code{MissingDebugHandler} is a base class from which user created
+handlers can derive, though it is not required that handlers derive
+from this class, so long as any user created handler has the
+@code{name} and @code{enabled} attributes, and implements the
+@code{__call__} method.
+
+@defun MissingDebugHandler.__init__ (name)
+The @var{name} is a string used to reference this unwinder within some
+@value{GDBN} commands.  Valid names consist of the characters
+@code{[-_a-zA-Z0-9]}, creating a handler with an invalid name raises a
+@code{ValueError} exception.
+@end defun
+
+@defun MissingDebugHandler.__call__ (objfile)
+Sub-classes must override the @code{__call__} method.  The
+@var{objfile} argument will be a @code{gdb.Objfile}, this is the
+objfile for which @value{GDBN} was unable to find any debug
+information.
+
+The return value from the @code{__call__} method indicates what
+@value{GDBN} should do next.  The possible return values are:
+
+@itemize @bullet
+@item @code{None}
+
+This indicates that this handler could not help with @var{objfile},
+@value{GDBN} should call any other registered handlers.
+
+@item @code{True}
+
+This indicates that this handler has installed the debug information
+into a location where @value{GDBN} would normally expect to find it
+when looking for separate debug information files (@pxref{Separate
+Debug Files}).  @value{GDBN} will repeat the normal lookup process,
+which should now find the separate debug file.
+
+If @value{GDBN} still doesn't find the separate debug information file
+after this second attempt, then the Python missing debug information
+handlers are not invoked a second time, this prevents a badly behaved
+handler causing @value{GDBN} to get stuck in a loop.  @value{GDBN}
+will continue without any debug information for @var{objfile}.
+
+@item @code{False}
+
+This indicates that this handler has done everything that it intends
+to do with @var{objfile}, but no separate debug information can be
+found.  @value{GDBN} will not call any other registered handlers for
+@var{objfile}.  @value{GDBN} will continue without debugging
+information for @var{objfile}.
+
+@item A string
+
+The returned string should contain a filename.  @value{GDBN} will not
+call any further registered handlers, and will instead load the debug
+information from the file identified by the returned filename.
+@end itemize
+@end defun
+
+@defvar MissingDebugHandler.name
+A read-only attribute which is a string, the name of this handler.
+@end defvar
+
+@defvar MissingDebugHandler.enabled
+A modifiable attribute containing a boolean; when @code{True}, the
+handler is enabled, and will be used by @value{GDBN}.  When
+@code{False}, the handler has been disabled, and will not be used.
+@end defvar
+@end deftp
+
+@defun gdb.missing_debug.register_handler (locus, handler, replace=@code{False})
+Register a new missing debug handler with @value{GDBN}.
+
+@var{handler} is an instance of a sub-class of
+@code{MissingDebugHandler}, or at least an instance of an object that
+has the same attributes and methods as @code{MissingDebugHandler}.
+
+@var{locus} specifies to which handler list to prepend @var{handler}.
+It can be either a @code{gdb.Progspace} (@pxref{Progspaces In Python})
+or @code{None}, in which case the handler is registered globally.  The
+newly registered @var{handler} will be called before any other handler
+from the same locus.  Two handlers in the same locus cannot have the
+same name, an attempt to add a handler with an already existing name
+raises an exception unless @var{replace} is @code{True}, in which case
+the old handler is deleted and the new handler is prepended to the
+selected handler list.
+
+@value{GDBN} first calls the handlers for the current program space,
+and then the globally registered handlers.  As soon as a handler
+returns a value other than @code{None}, no further handlers are called
+for this objfile.
+@end defun
+
 @node Python Auto-loading
 @subsection Python Auto-loading
 @cindex Python auto-loading
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index b3124369fe8..93ed50effcb 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -84,6 +84,8 @@ xmethods = []
 frame_filters = {}
 # Initial frame unwinders.
 frame_unwinders = []
+# Initial missing debug handlers.
+missing_debug_handlers = []
 
 
 def _execute_unwinders(pending_frame):
@@ -291,3 +293,42 @@ class Thread(threading.Thread):
         # threads.
         with blocked_signals():
             super().start()
+
+
+def _handle_missing_debuginfo(objfile):
+    """Internal function called from GDB to execute missing debug
+    handlers.
+
+    Run each of the currently registered, and enabled missing debug
+    handler objects for the current program space and then from the
+    global list.  Stop after the first handler that returns a result
+    other than None.
+
+    Arguments:
+        objfile: A gdb.Objfile for which GDB could not find any debug
+                 information.
+
+    Returns:
+        None: No debug information could be found for objfile.
+        False: A handler has done all it can with objfile, but no
+               debug information could be found.
+        True: Debug information might have been installed by a
+              handler, GDB should check again.
+        A string: This is the filename of a file containing the
+                  required debug information.
+    """
+    pspace = objfile.progspace
+
+    for handler in pspace.missing_debug_handlers:
+        if handler.enabled:
+            result = handler(objfile)
+            if result is not None:
+                return result
+
+    for handler in missing_debug_handlers:
+        if handler.enabled:
+            result = handler(objfile)
+            if result is not None:
+                return result
+
+    return None
diff --git a/gdb/python/lib/gdb/command/missing_debug.py b/gdb/python/lib/gdb/command/missing_debug.py
new file mode 100644
index 00000000000..4ffd1d59800
--- /dev/null
+++ b/gdb/python/lib/gdb/command/missing_debug.py
@@ -0,0 +1,226 @@
+# Missing debug related commands.
+#
+# Copyright 2023 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+import re
+
+
+def validate_regexp(exp, idstring):
+    """Compile exp into a compiler regular expression object.
+
+    Arguments:
+        exp: The string to compile into a re.Pattern object.
+        idstring: A string, what exp is a regexp for.
+
+    Returns:
+        A re.Pattern object representing exp.
+
+    Raises:
+        SyntaxError: If exp is an invalid regexp.
+    """
+    try:
+        return re.compile(exp)
+    except SyntaxError:
+        raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp))
+
+
+def parse_missing_debug_command_args(arg):
+    """Internal utility to parse missing debug handler command argv.
+
+    Arguments:
+        arg: The arguments to the command. The format is:
+             [locus-regexp [name-regexp]]
+
+    Returns:
+        A 2-tuple of compiled regular expressions.
+
+    Raises:
+        SyntaxError: an error processing ARG
+    """
+    argv = gdb.string_to_argv(arg)
+    argc = len(argv)
+    if argc > 2:
+        raise SyntaxError("Too many arguments.")
+    locus_regexp = ""
+    name_regexp = ""
+    if argc >= 1:
+        locus_regexp = argv[0]
+        if argc >= 2:
+            name_regexp = argv[1]
+    return (
+        validate_regexp(locus_regexp, "locus"),
+        validate_regexp(name_regexp, "handler"),
+    )
+
+
+class InfoMissingDebugHanders(gdb.Command):
+    """GDB command to list missing debug handlers.
+
+    Usage: info missing-debug-handlers [LOCUS-REGEXP [NAME-REGEXP]]
+
+    LOCUS-REGEXP is a regular expression matching the location of the
+    handler.  If it is omitted, all registered handlers from all
+    loci are listed.  A locus can be 'global', 'progspace' to list
+    the handlers from the current progspace, or a regular expression
+    matching filenames of progspaces.
+
+    NAME-REGEXP is a regular expression to filter missing debug
+    handler names.  If this omitted for a specified locus, then all
+    registered handlers in the locus are listed.
+    """
+    
+    def __init__(self):
+        super().__init__("info missing-debug-handlers", gdb.COMMAND_FILES)
+
+    def list_handlers(self, title, handlers, name_re):
+        """Lists the missing debug handlers whose name matches regexp.
+
+        Arguments:
+            title: The line to print before the list.
+            handlers: The list of the missing debug handlers.
+            name_re: handler name filter.
+        """
+        if not handlers:
+            return
+        print(title)
+        for handler in handlers:
+            if name_re.match(handler.name):
+                print(
+                    "  %s%s" % (handler.name, "" if handler.enabled else " [disabled]")
+                )
+
+    def invoke(self, arg, from_tty):
+        locus_re, name_re = parse_missing_debug_command_args(arg)
+
+        if locus_re.match("progspace") and locus_re.pattern != "":
+            cp = gdb.current_progspace()
+            self.list_handlers(
+                "Progspace %s:" % cp.filename, cp.missing_debug_handlers, name_re
+            )
+
+        for progspace in gdb.progspaces():
+            filename = progspace.filename or ""
+            if locus_re.match(filename):
+                if filename == "":
+                    if progspace == gdb.current_progspace():
+                        msg = "Current Progspace:"
+                    else:
+                        msg = "Progspace <no-file>:"
+                else:
+                    msg = "Progspace %s:" % filename
+                self.list_handlers(
+                    msg,
+                    progspace.missing_debug_handlers,
+                    name_re,
+                )
+
+        # Print global handlers last, as these are invoked last.
+        if locus_re.match("global"):
+            self.list_handlers("Global:", gdb.missing_debug_handlers, name_re)
+
+
+def do_enable_handler1(handlers, name_re, flag):
+    """Enable/disable missing debug handlers whose names match given regex.
+
+    Arguments:
+        handlers: The list of missing debug handlers.
+        name_re: Handler name filter.
+        flag: A boolean indicating if we should enable or disable.
+
+    Returns:
+        The number of handlers affected.
+    """
+    total = 0
+    for handler in handlers:
+        if name_re.match(handler.name) and handler.enabled != flag:
+            handler.enabled = flag
+            total += 1
+    return total
+
+
+def do_enable_handler(arg, flag):
+    """Enable or disable missing debug handlers."""
+    (locus_re, name_re) = parse_missing_debug_command_args(arg)
+    total = 0
+    if locus_re.match("global"):
+        total += do_enable_handler1(gdb.missing_debug_handlers, name_re, flag)
+    if locus_re.match("progspace") and locus_re.pattern != "":
+        total += do_enable_handler1(
+            gdb.current_progspace().missing_debug_handlers, name_re, flag
+        )
+    for progspace in gdb.progspaces():
+        filename = progspace.filename or ""
+        if locus_re.match(filename):
+            total += do_enable_handler1(progspace.missing_debug_handlers, name_re, flag)
+    print(
+        "%d missing debug handler%s %s"
+        % (total, "" if total == 1 else "s", "enabled" if flag else "disabled")
+    )
+
+
+class EnableMissingDebugHandler(gdb.Command):
+    """GDB command to enable missing debug handlers.
+
+    Usage: enable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]]
+
+    LOCUS-REGEXP is a regular expression specifying the handlers to
+    enable.  It can be 'global', 'progspace' for the current
+    progspace, or the filename for a file associated with a progspace.
+
+    NAME_REGEXP is a regular expression to filter handler names.  If
+    this omitted for a specified locus, then all registered handlers
+    in the locus are affected.
+    """
+
+    def __init__(self):
+        super().__init__("enable missing-debug-handler", gdb.COMMAND_FILES)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_handler(arg, True)
+
+
+class DisableMissingDebugHandler(gdb.Command):
+    """GDB command to disable missing debug handlers.
+
+    Usage: disable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]]
+
+    LOCUS-REGEXP is a regular expression specifying the handlers to
+    enable.  It can be 'global', 'progspace' for the current
+    progspace, or the filename for a file associated with a progspace.
+
+    NAME_REGEXP is a regular expression to filter handler names.  If
+    this omitted for a specified locus, then all registered handlers
+    in the locus are affected.
+    """
+
+    def __init__(self):
+        super().__init__("disable missing-debug-handler", gdb.COMMAND_FILES)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_handler(arg, False)
+
+
+def register_missing_debug_handler_commands():
+    """Installs the missing debug handler commands."""
+    InfoMissingDebugHanders()
+    EnableMissingDebugHandler()
+    DisableMissingDebugHandler()
+
+
+register_missing_debug_handler_commands()
diff --git a/gdb/python/lib/gdb/missing_debug.py b/gdb/python/lib/gdb/missing_debug.py
new file mode 100644
index 00000000000..42d69858f96
--- /dev/null
+++ b/gdb/python/lib/gdb/missing_debug.py
@@ -0,0 +1,169 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+MissingDebugHandler base class, and register_handler function.
+"""
+
+import gdb
+
+
+def _validate_name(name):
+    """Validate a missing debug handler name string.
+
+    If name is valid as a missing debug handler name, then this
+    function does nothing.  If name is not valid then an exception is
+    raised.
+
+    Arguments:
+        name: A string, the name of a missing debug handler.
+
+    Returns:
+        Nothing.
+
+    Raises:
+        ValueError: If name is invalid as a missing debug handler
+                    name.
+    """
+    for ch in name:
+        if not ch.isascii() or not (ch.isalnum() or ch in "_-"):
+            raise ValueError("invalid character '%s' in handler name: %s" % (ch, name))
+
+
+class MissingDebugHandler(object):
+    """Base class for missing debug handlers written in Python.
+
+    A missing debug handler has a single method __call__ along with
+    the read/write attribute enabled, and a read-only attribute name.
+
+    Attributes:
+        name: Read-only attribute, the name of this handler.
+        enabled: When true this handler is enabled.
+    """
+
+    def __init__(self, name):
+        """Constructor.
+
+        Args:
+            name: An identifying name for this handler.
+
+        Raises:
+            TypeError: name is not a string.
+            ValueError: name contains invalid characters.
+        """
+
+        if not isinstance(name, str):
+            raise TypeError("incorrect type for name: %s" % type(name))
+
+        _validate_name(name)
+
+        self._name = name
+        self._enabled = True
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, value):
+        if not isinstance(value, bool):
+            raise TypeError("incorrect type for enabled attribute: %s" % type(value))
+        self._enabled = value
+
+    def __call__(self, objfile):
+        """GDB handle missing debug information for an objfile.
+
+        Arguments:
+            objfile: A gdb.Objfile for which GDB could not find any
+                debug information.
+
+        Returns:
+            True: GDB should try again to locate the debug information
+                for objfile, the handler may have installed the
+                missing information.
+            False: GDB should move on without the debug information
+                for objfile.
+            A string: GDB should load the file at the given path; it
+                contains the debug information for objfile.
+            None: This handler can't help with objfile.  GDB should
+                try any other registered handlers.
+        """
+        raise NotImplementedError("MissingDebugHandler.__call__()")
+
+
+def register_handler(locus, handler, replace=False):
+    """Register handler in given locus.
+
+    The handler is prepended to the locus's missing debug handlers
+    list. The name of handler should be unique (or replace must be
+    True).
+
+    Arguments:
+        locus: Either a progspace, or None (in which case the unwinder
+               is registered globally).
+        handler: An object of a gdb.MissingDebugHandler subclass.
+
+        replace: If True, replaces existing handler with the same name
+                 within locus.  Otherwise, raises RuntimeException if
+                 unwinder with the same name already exists.
+
+    Returns:
+        Nothing.
+
+    Raises:
+        RuntimeError: The name of handler is not unique.
+        TypeError: Bad locus type.
+        AttributeError: Required attributes of handler are missing.
+    """
+
+    if locus is None:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering global %s handler ...\n" % handler.name)
+        locus = gdb
+    elif isinstance(locus, gdb.Progspace):
+        if gdb.parameter("verbose"):
+            gdb.write(
+                "Registering %s handler for %s ...\n" % (handler.name, locus.filename)
+            )
+    else:
+        raise TypeError("locus should be gdb.Progspace or None")
+
+    # Some sanity checks on HANDLER.  Calling getattr will raise an
+    # exception if the attribute doesn't exist, which is what we want.
+    # These checks are not exhaustive; we don't check the attributes
+    # have the correct types, or the method has the correct signature,
+    # but this should catch some basic mistakes.
+    getattr(handler, "name")
+    getattr(handler, "enabled")
+    call_method = getattr(handler, "__call__")
+    if not callable(call_method):
+        raise AttributeError(
+            "'%s' object's '__call__' attribute is not callable"
+            % type(handler).__name__
+        )
+
+    i = 0
+    for needle in locus.missing_debug_handlers:
+        if needle.name == handler.name:
+            if replace:
+                del locus.missing_debug_handlers[i]
+            else:
+                raise RuntimeError("Handler %s already exists." % handler.name)
+        i += 1
+    locus.missing_debug_handlers.insert(0, handler)
diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
index f636ffd0460..0797ef1fa6b 100644
--- a/gdb/python/py-progspace.c
+++ b/gdb/python/py-progspace.c
@@ -55,6 +55,9 @@ struct pspace_object
 
   /* The debug method list.  */
   PyObject *xmethods;
+
+  /* The missing debug handler list.  */
+  PyObject *missing_debug_handlers;
 };
 
 extern PyTypeObject pspace_object_type
@@ -164,6 +167,7 @@ pspy_dealloc (PyObject *self)
   Py_XDECREF (ps_self->frame_unwinders);
   Py_XDECREF (ps_self->type_printers);
   Py_XDECREF (ps_self->xmethods);
+  Py_XDECREF (ps_self->missing_debug_handlers);
   Py_TYPE (self)->tp_free (self);
 }
 
@@ -199,6 +203,10 @@ pspy_initialize (pspace_object *self)
   if (self->xmethods == NULL)
     return 0;
 
+  self->missing_debug_handlers = PyList_New (0);
+  if (self->missing_debug_handlers == nullptr)
+    return 0;
+
   return 1;
 }
 
@@ -353,6 +361,47 @@ pspy_get_xmethods (PyObject *o, void *ignore)
   return self->xmethods;
 }
 
+/* Return the list of missing debug handlers for this program space.  */
+
+static PyObject *
+pspy_get_missing_debug_handlers (PyObject *o, void *ignore)
+{
+  pspace_object *self = (pspace_object *) o;
+
+  Py_INCREF (self->missing_debug_handlers);
+  return self->missing_debug_handlers;
+}
+
+/* Set this program space's list of missing debug handlers to HANDLERS.  */
+
+static int
+pspy_set_missing_debug_handlers (PyObject *o, PyObject *handlers,
+				 void *ignore)
+{
+  pspace_object *self = (pspace_object *) o;
+
+  if (handlers == nullptr)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "cannot delete the missing debug handlers list");
+      return -1;
+    }
+
+  if (!PyList_Check (handlers))
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "the missing debug handlers attribute must be a list");
+      return -1;
+    }
+
+  /* Take care in case the LHS and RHS are related somehow.  */
+  gdbpy_ref<> tmp (self->missing_debug_handlers);
+  Py_INCREF (handlers);
+  self->missing_debug_handlers = handlers;
+
+  return 0;
+}
+
 /* Set the 'type_printers' attribute.  */
 
 static int
@@ -745,6 +794,8 @@ static gdb_PyGetSetDef pspace_getset[] =
     "Type printers.", NULL },
   { "xmethods", pspy_get_xmethods, NULL,
     "Debug methods.", NULL },
+  { "missing_debug_handlers", pspy_get_missing_debug_handlers,
+    pspy_set_missing_debug_handlers, "Missing debug handlers.", NULL },
   { NULL }
 };
 
diff --git a/gdb/python/python.c b/gdb/python/python.c
index d3dea088c3b..5caad5b227a 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -124,7 +124,9 @@ static enum ext_lang_rc gdbpy_before_prompt_hook
 static gdb::optional<std::string> gdbpy_colorize
   (const std::string &filename, const std::string &contents);
 static gdb::optional<std::string> gdbpy_colorize_disasm
-  (const std::string &content, gdbarch *gdbarch);
+(const std::string &content, gdbarch *gdbarch);
+static ext_lang_missing_debuginfo_result gdbpy_handle_missing_debuginfo
+  (const struct extension_language_defn *extlang, struct objfile *objfile);
 
 /* The interface between gdb proper and loading of python scripts.  */
 
@@ -170,6 +172,8 @@ static const struct extension_language_ops python_extension_ops =
   gdbpy_colorize_disasm,
 
   gdbpy_print_insn,
+
+  gdbpy_handle_missing_debuginfo
 };
 
 #endif /* HAVE_PYTHON */
@@ -1685,6 +1689,83 @@ gdbpy_get_current_objfile (PyObject *unused1, PyObject *unused2)
   return objfile_to_objfile_object (gdbpy_current_objfile).release ();
 }
 
+/* Implement the 'handle_missing_debuginfo' hook for Python.  GDB has
+   failed to find any debug information for OBJFILE.  The extension has a
+   chance to record this, or even install the required debug information.
+   See the description of ext_lang_missing_debuginfo_result in
+   extension-priv.h for details of the return value.  */
+
+static ext_lang_missing_debuginfo_result
+gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang,
+				struct objfile *objfile)
+{
+  /* Early exit if Python is not initialised.  */
+  if (!gdb_python_initialized)
+    return {};
+
+  struct gdbarch *gdbarch = objfile->arch ();
+
+  gdbpy_enter enter_py (gdbarch);
+
+  /* Convert OBJFILE into the corresponding Python object.  */
+  gdbpy_ref<> pyo_objfile = objfile_to_objfile_object (objfile);
+  if (pyo_objfile == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  /* Lookup the helper function within the GDB module.  */
+  gdbpy_ref<> pyo_handler
+    (PyObject_GetAttrString (gdb_python_module, "_handle_missing_debuginfo"));
+  if (pyo_handler == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  /* Call the function, passing in the Python objfile object.  */
+  gdbpy_ref<> pyo_execute_ret
+    (PyObject_CallFunctionObjArgs (pyo_handler.get (), pyo_objfile.get (),
+				   nullptr));
+  if (pyo_execute_ret == nullptr)
+    {
+      /* If the handler is cancelled due to a Ctrl-C, then propagate
+	 the Ctrl-C as a GDB exception instead of swallowing it.  */
+      gdbpy_print_stack_or_quit ();
+      return {};
+    }
+
+  /* Parse the result, and convert it back to the C++ object.  */
+  if (pyo_execute_ret == Py_None)
+    return {};
+
+  if (PyBool_Check (pyo_execute_ret.get ()))
+    {
+      bool try_again = PyObject_IsTrue (pyo_execute_ret.get ());
+      return ext_lang_missing_debuginfo_result (try_again);
+    }
+
+  if (!gdbpy_is_string (pyo_execute_ret.get ()))
+    {
+      PyErr_SetString (PyExc_ValueError,
+		       "return value from _handle_missing_debuginfo should "
+		       "be None, a Bool, or a String");
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  gdb::unique_xmalloc_ptr<char> filename
+    = python_string_to_host_string (pyo_execute_ret.get ());
+  if (filename == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  return ext_lang_missing_debuginfo_result (std::string (filename.get ()));
+}
+
 /* Compute the list of active python type printers and store them in
    EXT_PRINTERS->py_type_printers.  The product of this function is used by
    gdbpy_apply_type_printers, and freed by gdbpy_free_type_printers.
diff --git a/gdb/testsuite/gdb.python/py-missing-debug.c b/gdb/testsuite/gdb.python/py-missing-debug.c
new file mode 100644
index 00000000000..8cbea3b6892
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-missing-debug.c
@@ -0,0 +1,22 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2023 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+int
+main ()
+{
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.python/py-missing-debug.exp b/gdb/testsuite/gdb.python/py-missing-debug.exp
new file mode 100644
index 00000000000..3e0d70b9d22
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-missing-debug.exp
@@ -0,0 +1,473 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+standard_testfile
+
+if {[build_executable "failed to prepare" ${testfile} ${srcfile}]} {
+    return -1
+}
+
+# Remove debug information from BINFILE and place it into
+# BINFILE.debug.
+if {[gdb_gnu_strip_debug $binfile]} {
+    unsupported "cannot produce separate debug info files"
+    return -1
+}
+
+set remote_python_file \
+    [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+set debug_filename ${binfile}.debug
+set hidden_filename ${binfile}.hidden
+
+# Start GDB.
+clean_restart
+
+# Some initial sanity checks; initially, we can find the debug information
+# (this will use the .gnu_debuglink), then after we move the debug
+# information, reload the executable, now the debug can't be found.
+with_test_prefix "initial checks" {
+    # Load BINFILE, we should find the separate debug information.
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "debug"} \
+	"debug info is found"
+
+    # Rename the debug information file, re-load BINFILE, GDB should fail
+    # to find the debug information
+    remote_exec build "mv $debug_filename $hidden_filename"
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
+	"debug info no longer found"
+}
+
+# Load the Python script into GDB.
+gdb_test "source $remote_python_file" "^Success" \
+    "source python script"
+
+# Setup the separate debug info directory.  This isn't actually needed until
+# some of the later tests, but might as well get this done now.
+set debug_directory [standard_output_file "debug-dir"]
+remote_exec build "mkdir -p $debug_directory"
+gdb_test_no_output "set debug-file-directory $debug_directory" \
+    "set debug-file-directory"
+
+# Initially the missing debug handler we install is in a mode where it
+# returns None, indicating that it can't help locate the debug information.
+# Check this works as expected.
+with_test_prefix "handler returning None" {
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(None, handler_obj)" \
+	"register the initial handler"
+
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
+	"debug info not found"
+
+    # Check the handler was only called once.
+    gdb_test "python print(handler_obj.call_count)" "^1" \
+	"check handler was only called once"
+}
+
+# Now configure the handler to move the debug file back to the
+# .gnu_debuglink location and then return True, this will cause GDB to
+# recheck, at which point it should find the debug info.
+with_test_prefix "handler in gnu_debuglink mode" {
+    gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \
+						    \"$hidden_filename\", \
+						    \"$debug_filename\")" \
+	"confirgure handler"
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
+
+    # Check the handler was only called once.
+    gdb_test "python print(handler_obj.call_count)" "^1" \
+	"check handler was only called once"
+}
+
+# Setup a directory structure based on the build-id of BINFILE, but don't
+# move the debug information into place just yet.
+#
+# Instead, configure the handler to move the debug info into the build-id
+# directory.
+#
+# Reload BINFILE, at which point the handler will move the debug info into
+# the build-id directory and return True, GDB will then recheck for the
+# debug information, and should find it.
+with_test_prefix "handler in build-id mode" {
+    # Move the debug file out of the way once more.
+    remote_exec build "mv $debug_filename $hidden_filename"
+
+    # Create the build-id based directory in which the debug information
+    # will be placed.
+    set build_id_filename \
+	$debug_directory/[build_id_debug_filename_get $binfile]
+    remote_exec build "mkdir -p [file dirname $build_id_filename]"
+
+    # Configure the handler to move the debug info into the build-id dir.
+    gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \
+						    \"$hidden_filename\", \
+						    \"$build_id_filename\")" \
+	"confirgure handler"
+
+    # Reload the binary and check the debug information is found.
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
+
+    # Check the handler was only called once.
+    gdb_test "python print(handler_obj.call_count)" "^1" \
+	"check handler was only called once"
+}
+
+# Move the debug information back to a hidden location and configure the
+# handler to return the filename of the hidden debug info location.  GDB
+# should immediately use this file as the debug information.
+with_test_prefix "handler returning a string" {
+    remote_exec build "mv $build_id_filename $hidden_filename"
+
+    # Configure the handler return a filename string.
+    gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_STRING, \
+						    \"$hidden_filename\")" \
+	"confirgure handler"
+
+    # Reload the binary and check the debug information is found.
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
+
+    # Check the handler was only called once.
+    gdb_test "python print(handler_obj.call_count)" "^1" \
+	"check handler was only called once"
+}
+
+# Register another global handler, this one raises an exception.  Reload the
+# debug information, the bad handler should be invoked first, which raises
+# an excetption, at which point GDB should skip further Python handlers.
+with_test_prefix "handler raises an exception" {
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(None, rhandler)"
+
+    foreach_with_prefix exception_type {gdb.GdbError TypeError} {
+	gdb_test_no_output \
+	    "python rhandler.exception_type = $exception_type"
+
+	gdb_file_cmd $binfile
+	gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
+	    "debug info not found"
+
+	set re [string_to_regexp \
+		    "Python Exception <class '$exception_type'>: message"]
+	gdb_assert {[regexp $re $gdb_file_cmd_msg]} \
+	    "check for exception in file command output"
+
+	# Our original handler is still registered, but should not have been
+	# called again (as the exception occurs first).
+	gdb_test "python print(handler_obj.call_count)" "^1" \
+	    "check good handler hasn't been called again"
+    }
+}
+
+gdb_test "info missing-debug-handlers" \
+    [multi_line \
+	 "Global:" \
+	 "  exception_handler" \
+	 "  handler"] \
+    "check both handlers are visible"
+
+# Re-start GDB.
+clean_restart
+
+# Load the Python script into GDB.
+gdb_test "source $remote_python_file" "^Success" \
+    "source python script for bad handler name checks"
+
+# Attempt to register a missing-debug-handler with NAME.  The expectation is
+# that this should fail as NAME contains some invalid characters.
+proc check_bad_name {name} {
+    set name_re [string_to_regexp $name]
+    set re \
+	[multi_line \
+	     "ValueError: invalid character '.' in handler name: $name_re" \
+	     "Error while executing Python code\\."]
+
+    gdb_test "python register(\"$name\")" $re \
+	"check that '$name' is not accepted"
+}
+
+# We don't attempt to be exhaustive here, just check a few random examples
+# of invalid names.
+check_bad_name "!! Bad Name"
+check_bad_name "Bad Name"
+check_bad_name "(Bad Name)"
+check_bad_name "Bad \[Name\]"
+check_bad_name "Bad,Name"
+check_bad_name "Bad;Name"
+
+# Check that there are no handlers registered.
+gdb_test_no_output "info missing-debug-handlers" \
+    "check no handlers are registered"
+
+# Check we can use the enable/disable commands where there are no handlers
+# registered.
+gdb_test "enable missing-debug-handler foo" \
+    "^0 missing debug handlers enabled"
+gdb_test "disable missing-debug-handler foo" \
+    "^0 missing debug handlers disabled"
+
+# Grab the current program space object, used for registering handler later.
+gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
+
+# Now register some handlers.
+foreach hspec {{\"Foo\" None}
+    {\"-bar\" None}
+    {\"baz-\" pspace}
+    {\"abc-def\" pspace}} {
+    lassign $hspec name locus
+    gdb_test "python register($name, $locus)"
+}
+
+with_test_prefix "all handlers enabled" {
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Current Progspace:" \
+	     "  abc-def" \
+	     "  baz-" \
+	     "Global:" \
+	     "  -bar" \
+	     "  Foo"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def', 'baz-', '-bar', 'Foo']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "disable 'baz-'" {
+    gdb_test "disable missing-debug-handler progspace baz-" \
+	"^1 missing debug handler disabled"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def" \
+	     "  baz- \\\[disabled\\\]" \
+	     "Global:" \
+	     "  -bar" \
+	     "  Foo"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def', '-bar', 'Foo']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "disable 'Foo'" {
+    gdb_test "disable missing-debug-handler .* Foo" \
+	"^1 missing debug handler disabled"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def" \
+	     "  baz- \\\[disabled\\\]" \
+	     "Global:" \
+	     "  -bar" \
+	     "  Foo \\\[disabled\\\]"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def', '-bar']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "disable everything" {
+    gdb_test "disable missing-debug-handler .* .*" \
+	"^2 missing debug handlers disabled"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def \\\[disabled\\\]" \
+	     "  baz- \\\[disabled\\\]" \
+	     "Global:" \
+	     "  -bar \\\[disabled\\\]" \
+	     "  Foo \\\[disabled\\\]"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {[]}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "enable 'abc-def'" {
+    set re [string_to_regexp $binfile]
+
+    gdb_test "enable missing-debug-handler \"$re\" abc-def" \
+	"^1 missing debug handler enabled"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def" \
+	     "  baz- \\\[disabled\\\]" \
+	     "Global:" \
+	     "  -bar \\\[disabled\\\]" \
+	     "  Foo \\\[disabled\\\]"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "enable global handlers" {
+    set re [string_to_regexp $binfile]
+
+    gdb_test "enable missing-debug-handler global" \
+	"^2 missing debug handlers enabled"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def" \
+	     "  baz- \\\[disabled\\\]" \
+	     "Global:" \
+	     "  -bar" \
+	     "  Foo"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def', '-bar', 'Foo']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+# Add handler_obj to the global handler list, and configure it to
+# return False.  We should call all of the program space specific
+# handlers (which return None), and then call handler_obj from the
+# global list, which returns False, at which point we shouldn't call
+# anyone else.
+with_test_prefix "return False handler in progspace list" {
+    gdb_test "enable missing-debug-handler progspace" \
+	"^1 missing debug handler enabled"
+
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(None, handler_obj)" \
+	"register the initial handler"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def" \
+	     "  baz-" \
+	     "Global:" \
+	     "  handler" \
+	     "  -bar" \
+	     "  Foo"]
+
+    gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_FALSE)" \
+	"confirgure handler"
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def', 'baz-', 'handler']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+# Now add handler_obj to the current program space's handler list.  We
+# use the same handler object here, that's fine.  We should only see a
+# call to the first handler object in the call log.
+with_test_prefix "return False handler in global list" {
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(pspace, handler_obj)" \
+	"register the initial handler"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  handler" \
+	     "  abc-def" \
+	     "  baz-" \
+	     "Global:" \
+	     "  handler" \
+	     "  -bar" \
+	     "  Foo"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['handler']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "check handler replacement" {
+    # First, check we can have the same name appear in both program
+    # space and global lists without giving an error.
+    gdb_test_no_output "python register(\"Foo\", pspace)"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  Foo" \
+	     "  handler" \
+	     "  abc-def" \
+	     "  baz-" \
+	     "Global:" \
+	     "  handler" \
+	     "  -bar" \
+	     "  Foo"]
+
+    # Now check that we get an error if we try to add a handler with
+    # the same name.
+    gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
+	[multi_line \
+	     "RuntimeError: Handler Foo already exists\\." \
+	     "Error while executing Python code\\."]
+
+    gdb_test "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=pspace)" \
+	[multi_line \
+	     "RuntimeError: Handler Foo already exists\\." \
+	     "Error while executing Python code\\."]
+
+    # And now try again, but this time with 'replace=True', we
+    # shouldn't get an error in this case.
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)"
+
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=None, replace=True)"
+
+    # Now disable a handler and check we still need to use 'replace=True'.
+    gdb_test "disable missing-debug-handler progspace Foo" \
+	"^1 missing debug handler disabled"
+
+    gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
+	[multi_line \
+	     "RuntimeError: Handler Foo already exists\\." \
+	     "Error while executing Python code\\."] \
+	"still get an error when handler is disabled"
+
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)" \
+	"can replace a disabled handler"
+}
diff --git a/gdb/testsuite/gdb.python/py-missing-debug.py b/gdb/testsuite/gdb.python/py-missing-debug.py
new file mode 100644
index 00000000000..720648e18f0
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-missing-debug.py
@@ -0,0 +1,120 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+from gdb.missing_debug import MissingDebugHandler
+from enum import Enum
+import os
+
+# A global log that is filled in by instances of the LOG_HANDLER class
+# when they are called.
+handler_call_log = []
+
+
+class Mode(Enum):
+    RETURN_NONE = 0
+    RETURN_TRUE = 1
+    RETURN_FALSE = 2
+    RETURN_STRING = 3
+
+
+class handler(MissingDebugHandler):
+    def __init__(self):
+        super().__init__("handler")
+        self._call_count = 0
+        self._mode = Mode.RETURN_NONE
+
+    def __call__(self, objfile):
+        global handler_call_log
+        handler_call_log.append(self.name)
+        self._call_count += 1
+        if self._mode == Mode.RETURN_NONE:
+            return None
+
+        if self._mode == Mode.RETURN_TRUE:
+            os.rename(self._src, self._dest)
+            return True
+
+        if self._mode == Mode.RETURN_FALSE:
+            return False
+
+        if self._mode == Mode.RETURN_STRING:
+            return self._dest
+
+        assert False
+
+    @property
+    def call_count(self):
+        """Return a count, the number of calls to __call__ since the last
+        call to set_mode.
+        """
+        return self._call_count
+
+    def set_mode(self, mode, *args):
+        self._call_count = 0
+        self._mode = mode
+
+        if mode == Mode.RETURN_NONE:
+            assert len(args) == 0
+            return
+
+        if mode == Mode.RETURN_TRUE:
+            assert len(args) == 2
+            self._src = args[0]
+            self._dest = args[1]
+            return
+
+        if mode == Mode.RETURN_FALSE:
+            assert len(args) == 0
+            return
+
+        if mode == Mode.RETURN_STRING:
+            assert len(args) == 1
+            self._dest = args[0]
+            return
+
+        assert False
+
+
+class exception_handler(MissingDebugHandler):
+    def __init__(self):
+        super().__init__("exception_handler")
+        self.exception_type = None
+
+    def __call__(self, objfile):
+        global handler_call_log
+        handler_call_log.append(self.name)
+        assert self.exception_type is not None
+        raise self.exception_type("message")
+
+
+class log_handler(MissingDebugHandler):
+    def __call__(self, objfile):
+        global handler_call_log
+        handler_call_log.append(self.name)
+        return None
+
+
+# A basic helper function, this keeps lines shorter in the TCL script.
+def register(name, locus=None):
+    gdb.missing_debug.register_handler(locus, log_handler(name))
+
+
+# Create instances of the handlers, but don't install any.  We install
+# these as needed from the TCL script.
+rhandler = exception_handler()
+handler_obj = handler()
+
+print("Success")
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH 5/5] gdb: implement missing debug handler hook for Python
  2023-10-18 10:53 ` [PATCH 5/5] gdb: implement missing debug handler hook for Python Andrew Burgess
@ 2023-10-18 12:08   ` Eli Zaretskii
  0 siblings, 0 replies; 30+ messages in thread
From: Eli Zaretskii @ 2023-10-18 12:08 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

> From: Andrew Burgess <aburgess@redhat.com>
> Cc: Andrew Burgess <aburgess@redhat.com>
> Date: Wed, 18 Oct 2023 11:53:23 +0100
> 
>  gdb/NEWS                                      |  26 +
>  gdb/data-directory/Makefile.in                |   2 +
>  gdb/doc/python.texi                           | 136 +++++
>  gdb/python/lib/gdb/__init__.py                |  41 ++
>  gdb/python/lib/gdb/command/missing_debug.py   | 226 +++++++++
>  gdb/python/lib/gdb/missing_debug.py           | 169 +++++++
>  gdb/python/py-progspace.c                     |  51 ++
>  gdb/python/python.c                           |  83 ++-
>  gdb/testsuite/gdb.python/py-missing-debug.c   |  22 +
>  gdb/testsuite/gdb.python/py-missing-debug.exp | 473 ++++++++++++++++++
>  gdb/testsuite/gdb.python/py-missing-debug.py  | 120 +++++
>  11 files changed, 1348 insertions(+), 1 deletion(-)
>  create mode 100644 gdb/python/lib/gdb/command/missing_debug.py
>  create mode 100644 gdb/python/lib/gdb/missing_debug.py
>  create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.c
>  create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.exp
>  create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.py

Thanks.

> diff --git a/gdb/NEWS b/gdb/NEWS
> index 81264c0cfb3..509586b9589 100644
> --- a/gdb/NEWS
> +++ b/gdb/NEWS
> @@ -6,11 +6,37 @@
>  * GDB index now contains information about the main function. This speeds up
>    startup when it is being used for some large binaries.
>  
> +* New Commands
> +
> +info missing-debug-handler
> +  List all the registered missing debug handlers.
> +
> +enable missing-debug-handler LOCUS HANDLER
> +disable missing-debug-handler LOCUS HANDLER
> +  Enable or disable a missing debug handler with a name matching the
> +  regular expression HANDLER, in LOCUS.
> +
> +  LOCUS can be 'global' to operate on global missing debug handler,
> +  'progspace' to operate on handlers within the current program space,
> +  or can be a regular expression which is matched against the filename
> +  of the primary executable in each program space.
> +
>  * Python API
>  
>    ** New function gdb.notify_mi(NAME, DATA), that emits custom
>       GDB/MI async notification.
>  
> +  ** New module gdb.missing_debug that facilitates dealing with
> +     objfiles that are missing any debug information.
> +
> +  ** New function gdb.missing_debug.register_handler that can register
> +     an instance of a sub-class of gdb.missing_debug.MissingDebugInfo
> +     as a handler for objfiles that are missing debug information.
> +
> +  ** New class gdb.missing_debug.MissingDebugInfo which can be
> +     sub-classed to create handlers for objfiles with missing debug
> +     information.
> +
>  *** Changes in GDB 14
>  
>  * GDB now supports the AArch64 Scalable Matrix Extension 2 (SME2), which

This part is OK.

> +If @value{GDBN} fails to locate any debug information for a particular
> +objfile, there is an opportunity for a Python extension to step in.  A
> +Python extension can potentially locate the missing debug information
> +using some platform, or project, specific steps, and inform
> +@value{GDBN} of its location.  Or a Python extension might provide
> +some platform, or project specific advice to the user about how to
> +obtain the missing debug information.

I suggest to use "platform- or project-specific steps" here.  That is,
use a dash instead of a comma. 

> +A missing debug information Python extension consists of a handler
> +object which has the @code{name} and @code{enabled} attributes, and
> +implements the @code{__call__} method.  When @value{GDBN} encounters
> +an objfile for which it is unable to find any debug information the
> +@code{__call__} method is invoked.

The last sentence uses passive tense unnecessarily.  Suggest to
reword:

  When @value{GDBN} encounters an objfile for which it is unable to
  find any debug information, it invokes the @code{__call__} method.

> +The @code{MissingDebugHandler} is a base class from which user created

Suggest to use "user-created", with a dash (here and elsewhere).

> +@defun MissingDebugHandler.__init__ (name)
> +The @var{name} is a string used to reference this unwinder within some
                                                     ^^^^^^^^
"unwinder"?

Reviewed-By: Eli Zaretskii <eliz@gnu.org>

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths
  2023-10-18 10:53 ` [PATCH 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths Andrew Burgess
@ 2023-10-18 13:18   ` Jon Turney
  2023-10-18 17:25     ` Andrew Burgess
  0 siblings, 1 reply; 30+ messages in thread
From: Jon Turney @ 2023-10-18 13:18 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches

On 18/10/2023 11:53, Andrew Burgess wrote:
> This commit merges the code that looks for and loads the separate
> debug symbol files from coffread.c and elfread.c.  The factored out
> code is moved into a new objfile::find_and_add_separate_symbol_file()
> method.
> 
> For the elfread.c path there should be no user visible changes after
> this commit.
> 
> For the coffread.c path GDB will now attempt to perform a debuginfod
> lookup for the missing debug information, assuming that GDB can find a
> build-id in the COFF file.
> 
> I don't know if COFF files can include a build-id, but I the existing

This is at least possible for PE/COFF.

(I wrote the code to add support for this back in 2015, see commit 
c74f7d1c6c5a968330208757f476c67a4bb66643)

> coffread.c code already includes a call to
> find_separate_debug_file_by_build-id, so I know that it is at least OK
> for GDB to ask a COFF file for a build-id.  If the COFF file doesn't
> include a build-id then the debuginfod lookup code will not trigger
> and the new code is harmless.
> 
> If the COFF file does include a build-id, then we're going to end up
> asking debuginfod for the debug file.  As build-ids should be unique,
> this should be harmless, even if debuginfod doesn't contain any
> suitable debug data, it just costs us one debuginfod lookup, so I'm
> not too worried about this for now.

But yes, as you say, should be harmless.

> I don't have access to a COFF target right now, so beyond compiling
> it, the coffread.c changes are completely untested.


^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths
  2023-10-18 13:18   ` Jon Turney
@ 2023-10-18 17:25     ` Andrew Burgess
  0 siblings, 0 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-10-18 17:25 UTC (permalink / raw)
  To: Jon Turney, gdb-patches

Jon Turney <jon.turney@dronecode.org.uk> writes:

> On 18/10/2023 11:53, Andrew Burgess wrote:
>> This commit merges the code that looks for and loads the separate
>> debug symbol files from coffread.c and elfread.c.  The factored out
>> code is moved into a new objfile::find_and_add_separate_symbol_file()
>> method.
>> 
>> For the elfread.c path there should be no user visible changes after
>> this commit.
>> 
>> For the coffread.c path GDB will now attempt to perform a debuginfod
>> lookup for the missing debug information, assuming that GDB can find a
>> build-id in the COFF file.
>> 
>> I don't know if COFF files can include a build-id, but I the existing
>
> This is at least possible for PE/COFF.
>
> (I wrote the code to add support for this back in 2015, see commit 
> c74f7d1c6c5a968330208757f476c67a4bb66643)

Thanks for the link.  I figured they probably could based on the code,
but I didn't dig too hard as I didn't think it really mattered for this
change.

Thanks,
Andrew

>
>> coffread.c code already includes a call to
>> find_separate_debug_file_by_build-id, so I know that it is at least OK
>> for GDB to ask a COFF file for a build-id.  If the COFF file doesn't
>> include a build-id then the debuginfod lookup code will not trigger
>> and the new code is harmless.
>> 
>> If the COFF file does include a build-id, then we're going to end up
>> asking debuginfod for the debug file.  As build-ids should be unique,
>> this should be harmless, even if debuginfod doesn't contain any
>> suitable debug data, it just costs us one debuginfod lookup, so I'm
>> not too worried about this for now.
>
> But yes, as you say, should be harmless.
>
>> I don't have access to a COFF target right now, so beyond compiling
>> it, the coffread.c changes are completely untested.


^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c
  2023-10-18 10:53 ` [PATCH 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c Andrew Burgess
@ 2023-10-20 17:35   ` Tom Tromey
  2023-10-24 11:59     ` Andrew Burgess
  0 siblings, 1 reply; 30+ messages in thread
From: Tom Tromey @ 2023-10-20 17:35 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:

Andrew> I haven't tested this, I don't have a coff target to hand.

I suspect you can do a smoke test by building a "hello world" with the
the mingw toolchain shipped in Fedora.  IIRC this goes through
coff_symfile_read.

The suspected-to-be-dead part of coffread is coffstab_build_psymtabs.

Tom

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file
  2023-10-18 10:53 ` [PATCH 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file Andrew Burgess
@ 2023-10-20 17:50   ` Tom Tromey
  2023-10-24 12:03     ` Andrew Burgess
  2023-11-08 15:22     ` Andrew Burgess
  0 siblings, 2 replies; 30+ messages in thread
From: Tom Tromey @ 2023-10-20 17:50 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches

>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:

Andrew> This is purely a refactoring commit.
Andrew> This commit splits objfile::find_and_add_separate_symbol_file into
Andrew> some separate helper functions.  My hope is that the steps for looking
Andrew> up separate debug information are now clearer.

Andrew> In a later commit I'm going to extend
Andrew> objfile::find_and_add_separate_symbol_file, with some additional
Andrew> logic, so starting with a simpler function will make the following
Andrew> changes easier.

Andrew> There should be no user visible changes after this commit.

Andrew> +static std::pair<gdb_bfd_ref_ptr, std::string>
Andrew> +simple_find_and_open_separate_symbol_file
...
Andrew> +  result = simple_find_and_open_separate_symbol_file
Andrew> +    (this, find_separate_debug_file_by_buildid, &warnings);
... 
Andrew> +  if (result.first == nullptr)

If this happens to go in after the C++17 series, this code could be
cleaned up a little using structured bindings.

Tom

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c
  2023-10-20 17:35   ` Tom Tromey
@ 2023-10-24 11:59     ` Andrew Burgess
  0 siblings, 0 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-10-24 11:59 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

Tom Tromey <tom@tromey.com> writes:

>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>
> Andrew> I haven't tested this, I don't have a coff target to hand.
>
> I suspect you can do a smoke test by building a "hello world" with the
> the mingw toolchain shipped in Fedora.  IIRC this goes through
> coff_symfile_read.

Yes it does.  I've given this a try and I still see the debug
information being loaded so I've updated the commit message to indicate
that minimal testing has been done.

> The suspected-to-be-dead part of coffread is coffstab_build_psymtabs.

I didn't understand this part.

Thanks,
Andrew

---

commit 3ea19ccbd59c20d5534c9331c10766eb707ce81d
Author: Andrew Burgess <aburgess@redhat.com>
Date:   Thu Oct 12 19:42:19 2023 +0100

    gdb/coffread: bring separate debug file logic into line with elfread.c
    
    In this commit:
    
      commit 8a92335bfca80cc9b4cd217505ea0dcbfdefbf07
      Date:   Fri Feb 1 19:39:04 2013 +0000
    
    the logic for when we try to load a separate debug file in elfread.c
    was extended.  The new code checks that the objfile doesn't already
    have a separate debug objfile linked to it, and that the objfile isn't
    itself a separate debug objfile for some other objfile.
    
    The coffread code wasn't extended at the same time.
    
    I don't know if it's possible for the coffread code to get into the
    same state where these checks are needed, but I don't see why having
    these checks would be a problem.  In a later commit I plan to merge
    this part of the elfread and coffread code, so bringing these two
    pieces of code into line first makes that job easier.
    
    I've tested this with a simple test binary compiled with the mingw
    toolchain on a Linux host.  After compiling the binary and splitting
    out the debug info GDB still finds and loads the separate debug info.

diff --git a/gdb/coffread.c b/gdb/coffread.c
index 4a6a83b15ad..e1415d6b258 100644
--- a/gdb/coffread.c
+++ b/gdb/coffread.c
@@ -725,7 +725,9 @@ coff_symfile_read (struct objfile *objfile, symfile_add_flags symfile_flags)
     }
 
   /* Try to add separate debug file if no symbols table found.   */
-  if (!objfile->has_partial_symbols ())
+  else if (!objfile->has_partial_symbols ()
+	   && objfile->separate_debug_objfile == NULL
+	   && objfile->separate_debug_objfile_backlink == NULL)
     {
       deferred_warnings warnings;
       std::string debugfile


^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file
  2023-10-20 17:50   ` Tom Tromey
@ 2023-10-24 12:03     ` Andrew Burgess
  2023-11-08 15:22     ` Andrew Burgess
  1 sibling, 0 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-10-24 12:03 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

Tom Tromey <tom@tromey.com> writes:

>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>
> Andrew> This is purely a refactoring commit.
> Andrew> This commit splits objfile::find_and_add_separate_symbol_file into
> Andrew> some separate helper functions.  My hope is that the steps for looking
> Andrew> up separate debug information are now clearer.
>
> Andrew> In a later commit I'm going to extend
> Andrew> objfile::find_and_add_separate_symbol_file, with some additional
> Andrew> logic, so starting with a simpler function will make the following
> Andrew> changes easier.
>
> Andrew> There should be no user visible changes after this commit.
>
> Andrew> +static std::pair<gdb_bfd_ref_ptr, std::string>
> Andrew> +simple_find_and_open_separate_symbol_file
> ...
> Andrew> +  result = simple_find_and_open_separate_symbol_file
> Andrew> +    (this, find_separate_debug_file_by_buildid, &warnings);
> ... 
> Andrew> +  if (result.first == nullptr)
>
> If this happens to go in after the C++17 series, this code could be
> cleaned up a little using structured bindings.

I'll keep that in mind.

Thanks,
Andrew


^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCH 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file
  2023-10-20 17:50   ` Tom Tromey
  2023-10-24 12:03     ` Andrew Burgess
@ 2023-11-08 15:22     ` Andrew Burgess
  1 sibling, 0 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-11-08 15:22 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches

Tom Tromey <tom@tromey.com> writes:

>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>
> Andrew> This is purely a refactoring commit.
> Andrew> This commit splits objfile::find_and_add_separate_symbol_file into
> Andrew> some separate helper functions.  My hope is that the steps for looking
> Andrew> up separate debug information are now clearer.
>
> Andrew> In a later commit I'm going to extend
> Andrew> objfile::find_and_add_separate_symbol_file, with some additional
> Andrew> logic, so starting with a simpler function will make the following
> Andrew> changes easier.
>
> Andrew> There should be no user visible changes after this commit.
>
> Andrew> +static std::pair<gdb_bfd_ref_ptr, std::string>
> Andrew> +simple_find_and_open_separate_symbol_file
> ...
> Andrew> +  result = simple_find_and_open_separate_symbol_file
> Andrew> +    (this, find_separate_debug_file_by_buildid, &warnings);
> ... 
> Andrew> +  if (result.first == nullptr)
>
> If this happens to go in after the C++17 series, this code could be
> cleaned up a little using structured bindings.

I took a look at making use of structured bindings here, and I don't
think it offers any improvements.

I can write something like this:

  auto [debug_bfd, filename] = some_function ();

But, I can't write this:

  [debug_bfd, filename] = some_function ();

Which means if, what I really want to write is:

  [debug_bfd, filename] = some_function ();
  if (debug_bfd == nullptr)
    [debug_bfd, filename] = some_other_function ();
  if (debug_bfd == nullptr)
    [debug_bfd, filename] = yet_other_function ();

then every structured binding needs to write into a new set of local
variable, which makes the logic much more complex.

However, while researching this, I did discover std::tie, which seems to
do a similar thing as structured bindings, but without the variable
reuse limitation, and removing the .first, .second does make the code
more readable.  I'll post a V2 shortly, which will include this change.

Thanks,
Andrew


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCHv2 0/5] New Python hook for missing debug information
  2023-10-18 10:53 [PATCH 0/5] New Python hook for missing debug information Andrew Burgess
                   ` (4 preceding siblings ...)
  2023-10-18 10:53 ` [PATCH 5/5] gdb: implement missing debug handler hook for Python Andrew Burgess
@ 2023-11-08 15:48 ` Andrew Burgess
  2023-11-08 15:48   ` [PATCHv2 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c Andrew Burgess
                     ` (6 more replies)
  5 siblings, 7 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-11-08 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess, Tom Tromey

This series adds a new Python hook to GDB, this hook allows a user to
register a handler object which will be called whenever GDB fails to
locate any debug information for an objfile it is loading.  These
handler objects can (potentially) perform host or project specific
actions to help GDB locate the missing debug information.

Commits #1 and #2 are refactoring.  These merge the separate debug
information lookup code from coffread.c and elfread.c.

Commit #3 is more refactoring, this simplifies the now merged code.

Commit #4 implements the framework for the new hook within GDB, but
doesn't implement the Python side of things yet.

Commit #5 is where I add the Python side of things, it's only at this
point that a user can actually hook into GDB.

---

Changes in v2:

  - Rebased onto current HEAD,

  - Make use of std::tie in patch #3,

  - Addressed Eli's doc comments in patch #5.

---

Andrew Burgess (5):
  gdb/coffread: bring separate debug file logic into line with elfread.c
  gdb: merge debug symbol file lookup code from coffread & elfread paths
  gdb: refactor objfile::find_and_add_separate_symbol_file
  gdb: add an extension language hook for missing debug info
  gdb: implement missing debug handler hook for Python

 gdb/NEWS                                      |  26 +
 gdb/coffread.c                                |  28 +-
 gdb/data-directory/Makefile.in                |   2 +
 gdb/doc/python.texi                           | 140 ++++++
 gdb/elfread.c                                 |  57 +--
 gdb/extension-priv.h                          |   7 +
 gdb/extension.c                               |  19 +
 gdb/extension.h                               |  62 +++
 gdb/objfiles.h                                |  10 +
 gdb/python/lib/gdb/__init__.py                |  41 ++
 gdb/python/lib/gdb/command/missing_debug.py   | 226 +++++++++
 gdb/python/lib/gdb/missing_debug.py           | 169 +++++++
 gdb/python/py-progspace.c                     |  51 ++
 gdb/python/python.c                           |  83 ++-
 gdb/symfile-debug.c                           | 157 ++++++
 gdb/testsuite/gdb.python/py-missing-debug.c   |  22 +
 gdb/testsuite/gdb.python/py-missing-debug.exp | 473 ++++++++++++++++++
 gdb/testsuite/gdb.python/py-missing-debug.py  | 120 +++++
 18 files changed, 1615 insertions(+), 78 deletions(-)
 create mode 100644 gdb/python/lib/gdb/command/missing_debug.py
 create mode 100644 gdb/python/lib/gdb/missing_debug.py
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.c
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.exp
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.py


base-commit: 3c09fd57e6f825672964581e152bd50b507e5630
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCHv2 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c
  2023-11-08 15:48 ` [PATCHv2 0/5] New Python hook for missing debug information Andrew Burgess
@ 2023-11-08 15:48   ` Andrew Burgess
  2023-11-08 15:48   ` [PATCHv2 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths Andrew Burgess
                     ` (5 subsequent siblings)
  6 siblings, 0 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-11-08 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess, Tom Tromey

In this commit:

  commit 8a92335bfca80cc9b4cd217505ea0dcbfdefbf07
  Date:   Fri Feb 1 19:39:04 2013 +0000

the logic for when we try to load a separate debug file in elfread.c
was extended.  The new code checks that the objfile doesn't already
have a separate debug objfile linked to it, and that the objfile isn't
itself a separate debug objfile for some other objfile.

The coffread code wasn't extended at the same time.

I don't know if it's possible for the coffread code to get into the
same state where these checks are needed, but I don't see why having
these checks would be a problem.  In a later commit I plan to merge
this part of the elfread and coffread code, so bringing these two
pieces of code into line first makes that job easier.

I've tested this with a simple test binary compiled with the mingw
toolchain on a Linux host.  After compiling the binary and splitting
out the debug info GDB still finds and loads the separate debug info.
---
 gdb/coffread.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/gdb/coffread.c b/gdb/coffread.c
index 4a6a83b15ad..e1415d6b258 100644
--- a/gdb/coffread.c
+++ b/gdb/coffread.c
@@ -725,7 +725,9 @@ coff_symfile_read (struct objfile *objfile, symfile_add_flags symfile_flags)
     }
 
   /* Try to add separate debug file if no symbols table found.   */
-  if (!objfile->has_partial_symbols ())
+  else if (!objfile->has_partial_symbols ()
+	   && objfile->separate_debug_objfile == NULL
+	   && objfile->separate_debug_objfile_backlink == NULL)
     {
       deferred_warnings warnings;
       std::string debugfile
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCHv2 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths
  2023-11-08 15:48 ` [PATCHv2 0/5] New Python hook for missing debug information Andrew Burgess
  2023-11-08 15:48   ` [PATCHv2 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c Andrew Burgess
@ 2023-11-08 15:48   ` Andrew Burgess
  2023-11-08 15:48   ` [PATCHv2 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file Andrew Burgess
                     ` (4 subsequent siblings)
  6 siblings, 0 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-11-08 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess, Tom Tromey

This commit merges the code that looks for and loads the separate
debug symbol files from coffread.c and elfread.c.  The factored out
code is moved into a new objfile::find_and_add_separate_symbol_file()
method.

For the elfread.c path there should be no user visible changes after
this commit.

For the coffread.c path GDB will now attempt to perform a debuginfod
lookup for the missing debug information, assuming that GDB can find a
build-id in the COFF file.

I don't know if COFF files can include a build-id, but I the existing
coffread.c code already includes a call to
find_separate_debug_file_by_build-id, so I know that it is at least OK
for GDB to ask a COFF file for a build-id.  If the COFF file doesn't
include a build-id then the debuginfod lookup code will not trigger
and the new code is harmless.

If the COFF file does include a build-id, then we're going to end up
asking debuginfod for the debug file.  As build-ids should be unique,
this should be harmless, even if debuginfod doesn't contain any
suitable debug data, it just costs us one debuginfod lookup, so I'm
not too worried about this for now.

As with the previous commit, I've done some minimal testing using the
mingw toolchain on a Linux machine, GDB seems to still access the
split debug information just fine.
---
 gdb/coffread.c      | 24 ++---------------
 gdb/elfread.c       | 57 +++------------------------------------
 gdb/objfiles.h      | 10 +++++++
 gdb/symfile-debug.c | 66 +++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 81 insertions(+), 76 deletions(-)

diff --git a/gdb/coffread.c b/gdb/coffread.c
index e1415d6b258..5898b3a8e08 100644
--- a/gdb/coffread.c
+++ b/gdb/coffread.c
@@ -40,8 +40,6 @@
 
 #include "coff-pe-read.h"
 
-#include "build-id.h"
-
 /* The objfile we are currently reading.  */
 
 static struct objfile *coffread_objfile;
@@ -729,26 +727,8 @@ coff_symfile_read (struct objfile *objfile, symfile_add_flags symfile_flags)
 	   && objfile->separate_debug_objfile == NULL
 	   && objfile->separate_debug_objfile_backlink == NULL)
     {
-      deferred_warnings warnings;
-      std::string debugfile
-	= find_separate_debug_file_by_buildid (objfile, &warnings);
-
-      if (debugfile.empty ())
-	debugfile
-	  = find_separate_debug_file_by_debuglink (objfile, &warnings);
-
-      if (!debugfile.empty ())
-	{
-	  gdb_bfd_ref_ptr debug_bfd (symfile_bfd_open (debugfile.c_str ()));
-
-	  symbol_file_add_separate (debug_bfd, debugfile.c_str (),
-				    symfile_flags, objfile);
-	}
-      /* If all the methods to collect the debuginfo failed, print any
-	 warnings that were collected, this is a no-op if there are no
-	 warnings.  */
-      if (debugfile.empty ())
-	warnings.emit ();
+      if (objfile->find_and_add_separate_symbol_file (symfile_flags))
+	gdb_assert (objfile->separate_debug_objfile != nullptr);
     }
 }
 
diff --git a/gdb/elfread.c b/gdb/elfread.c
index 7900dfbc388..86e7f61586e 100644
--- a/gdb/elfread.c
+++ b/gdb/elfread.c
@@ -41,14 +41,12 @@
 #include "regcache.h"
 #include "bcache.h"
 #include "gdb_bfd.h"
-#include "build-id.h"
 #include "location.h"
 #include "auxv.h"
 #include "mdebugread.h"
 #include "ctfread.h"
 #include "gdbsupport/gdb_string_view.h"
 #include "gdbsupport/scoped_fd.h"
-#include "debuginfod-support.h"
 #include "dwarf2/public.h"
 #include "cli/cli-cmds.h"
 
@@ -1218,59 +1216,10 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
 	   && objfile->separate_debug_objfile == NULL
 	   && objfile->separate_debug_objfile_backlink == NULL)
     {
-      deferred_warnings warnings;
-
-      std::string debugfile
-	= find_separate_debug_file_by_buildid (objfile, &warnings);
-
-      if (debugfile.empty ())
-	debugfile = find_separate_debug_file_by_debuglink (objfile, &warnings);
-
-      if (!debugfile.empty ())
-	{
-	  gdb_bfd_ref_ptr debug_bfd
-	    (symfile_bfd_open_no_error (debugfile.c_str ()));
-
-	  if (debug_bfd != nullptr)
-	    symbol_file_add_separate (debug_bfd, debugfile.c_str (),
-				      symfile_flags, objfile);
-	}
+      if (objfile->find_and_add_separate_symbol_file (symfile_flags))
+	gdb_assert (objfile->separate_debug_objfile != nullptr);
       else
-	{
-	  has_dwarf2 = false;
-	  const struct bfd_build_id *build_id
-	    = build_id_bfd_get (objfile->obfd.get ());
-	  const char *filename = bfd_get_filename (objfile->obfd.get ());
-
-	  if (build_id != nullptr)
-	    {
-	      gdb::unique_xmalloc_ptr<char> symfile_path;
-	      scoped_fd fd (debuginfod_debuginfo_query (build_id->data,
-							build_id->size,
-							filename,
-							&symfile_path));
-
-	      if (fd.get () >= 0)
-		{
-		  /* File successfully retrieved from server.  */
-		  gdb_bfd_ref_ptr debug_bfd
-		    (symfile_bfd_open_no_error (symfile_path.get ()));
-
-		  if (debug_bfd != nullptr
-		      && build_id_verify (debug_bfd.get (), build_id->size,
-					  build_id->data))
-		    {
-		      symbol_file_add_separate (debug_bfd, symfile_path.get (),
-						symfile_flags, objfile);
-		      has_dwarf2 = true;
-		    }
-		}
-	    }
-	}
-      /* If all the methods to collect the debuginfo failed, print the
-	 warnings, this is a no-op if there are no warnings.  */
-      if (debugfile.empty () && !has_dwarf2)
-	warnings.emit ();
+	has_dwarf2 = false;
     }
 
   return has_dwarf2;
diff --git a/gdb/objfiles.h b/gdb/objfiles.h
index 4b8aa9bfcec..ec9d354e4a7 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -513,6 +513,16 @@ struct objfile
 
   bool has_partial_symbols ();
 
+  /* Look for a separate debug symbol file for this objfile, make use of
+     build-id, debug-link, and debuginfod as necessary.  If a suitable
+     separate debug symbol file is found then it is loaded using a call to
+     symbol_file_add_separate (SYMFILE_FLAGS is passed through unmodified
+     to this call) and this function returns true.  If no suitable separate
+     debug symbol file is found and loaded then this function returns
+     false.  */
+
+  bool find_and_add_separate_symbol_file (symfile_add_flags symfile_flags);
+
   /* Return true if this objfile has any unexpanded symbols.  A return
      value of false indicates either, that this objfile has all its
      symbols fully expanded (i.e. fully read in), or that this objfile has
diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index 850da4147a3..961ae2327f7 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -35,6 +35,8 @@
 #include "block.h"
 #include "filenames.h"
 #include "cli/cli-style.h"
+#include "build-id.h"
+#include "debuginfod-support.h"
 
 /* We need to save a pointer to the real symbol functions.
    Plus, the debug versions are malloc'd because we have to NULL out the
@@ -558,6 +560,70 @@ objfile::require_partial_symbols (bool verbose)
     }
 }
 
+/* See objfiles.h.  */
+
+bool
+objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
+{
+  bool has_dwarf2 = true;
+
+  deferred_warnings warnings;
+
+  std::string debugfile
+    = find_separate_debug_file_by_buildid (this, &warnings);
+
+  if (debugfile.empty ())
+    debugfile = find_separate_debug_file_by_debuglink (this, &warnings);
+
+  if (!debugfile.empty ())
+    {
+      gdb_bfd_ref_ptr debug_bfd
+	(symfile_bfd_open_no_error (debugfile.c_str ()));
+
+      if (debug_bfd != nullptr)
+	symbol_file_add_separate (debug_bfd, debugfile.c_str (),
+				  symfile_flags, this);
+    }
+  else
+    {
+      has_dwarf2 = false;
+      const struct bfd_build_id *build_id
+	= build_id_bfd_get (this->obfd.get ());
+      const char *filename = bfd_get_filename (this->obfd.get ());
+
+      if (build_id != nullptr)
+	{
+	  gdb::unique_xmalloc_ptr<char> symfile_path;
+	  scoped_fd fd (debuginfod_debuginfo_query (build_id->data,
+						    build_id->size,
+						    filename,
+						    &symfile_path));
+
+	  if (fd.get () >= 0)
+	    {
+	      /* File successfully retrieved from server.  */
+	      gdb_bfd_ref_ptr debug_bfd
+		(symfile_bfd_open_no_error (symfile_path.get ()));
+
+	      if (debug_bfd != nullptr
+		  && build_id_verify (debug_bfd.get (), build_id->size,
+				      build_id->data))
+		{
+		  symbol_file_add_separate (debug_bfd, symfile_path.get (),
+					    symfile_flags, this);
+		  has_dwarf2 = true;
+		}
+	    }
+	}
+    }
+  /* If all the methods to collect the debuginfo failed, print the
+     warnings, this is a no-op if there are no warnings.  */
+  if (debugfile.empty () && !has_dwarf2)
+    warnings.emit ();
+
+  return has_dwarf2;
+}
+
 \f
 /* Debugging version of struct sym_probe_fns.  */
 
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCHv2 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file
  2023-11-08 15:48 ` [PATCHv2 0/5] New Python hook for missing debug information Andrew Burgess
  2023-11-08 15:48   ` [PATCHv2 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c Andrew Burgess
  2023-11-08 15:48   ` [PATCHv2 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths Andrew Burgess
@ 2023-11-08 15:48   ` Andrew Burgess
  2023-11-08 15:48   ` [PATCHv2 4/5] gdb: add an extension language hook for missing debug info Andrew Burgess
                     ` (3 subsequent siblings)
  6 siblings, 0 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-11-08 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess, Tom Tromey

This is purely a refactoring commit.

This commit splits objfile::find_and_add_separate_symbol_file into
some separate helper functions.  My hope is that the steps for looking
up separate debug information are now clearer.

In a later commit I'm going to extend
objfile::find_and_add_separate_symbol_file, with some additional
logic, so starting with a simpler function will make the following
changes easier.

When reading objfile::find_and_add_separate_symbol_file after this
commit, you might be tempted to think that removing the `has_dwarf`
local variable would be a good additional cleanup.  After the next
commit though it makes more sense to retain this local, so I've left
this in place for now.

There should be no user visible changes after this commit.
---
 gdb/symfile-debug.c | 135 +++++++++++++++++++++++++++++---------------
 1 file changed, 88 insertions(+), 47 deletions(-)

diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index 961ae2327f7..0b6dc4752df 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -560,68 +560,109 @@ objfile::require_partial_symbols (bool verbose)
     }
 }
 
+/* Call LOOKUP_FUNC to find the filename of a file containing the separate
+   debug information matching OBJFILE.  If LOOKUP_FUNC does return a
+   filename then open this file and return a std::pair containing the
+   gdb_bfd_ref_ptr of the open file and the filename returned by
+   LOOKUP_FUNC, otherwise this function returns an empty pair; the first
+   item will be nullptr, and the second will be an empty string.
+
+   Any warnings generated by this function, or by calling LOOKUP_FUNC are
+   placed into WARNINGS, these warnings are only displayed to the user if
+   GDB is unable to find the separate debug information via any route.  */
+static std::pair<gdb_bfd_ref_ptr, std::string>
+simple_find_and_open_separate_symbol_file
+  (struct objfile *objfile,
+   std::string (*lookup_func) (struct objfile *, deferred_warnings *),
+   deferred_warnings *warnings)
+{
+  std::string filename = lookup_func (objfile, warnings);
+
+  if (!filename.empty ())
+    {
+      gdb_bfd_ref_ptr symfile_bfd
+	= symfile_bfd_open_no_error (filename.c_str ());
+      if (symfile_bfd != nullptr)
+	return { symfile_bfd, filename };
+    }
+
+  return {};
+}
+
+/* Lookup separate debug information for OBJFILE via debuginfod.  If
+   successful the debug information will be have been downloaded into the
+   debuginfod cache and this function will return a std::pair containing a
+   gdb_bfd_ref_ptr of the open debug information file and the filename for
+   the file within the debuginfod cache.  If no debug information could be
+   found then this function returns an empty pair; the first item will be
+   nullptr, and the second will be an empty string.  */
+
+static std::pair<gdb_bfd_ref_ptr, std::string>
+debuginfod_find_and_open_separate_symbol_file (struct objfile * objfile)
+{
+  const struct bfd_build_id *build_id
+    = build_id_bfd_get (objfile->obfd.get ());
+  const char *filename = bfd_get_filename (objfile->obfd.get ());
+
+  if (build_id != nullptr)
+    {
+      gdb::unique_xmalloc_ptr<char> symfile_path;
+      scoped_fd fd (debuginfod_debuginfo_query (build_id->data, build_id->size,
+						filename, &symfile_path));
+
+      if (fd.get () >= 0)
+	{
+	  /* File successfully retrieved from server.  */
+	  gdb_bfd_ref_ptr debug_bfd
+	    (symfile_bfd_open_no_error (symfile_path.get ()));
+
+	  if (debug_bfd != nullptr
+	      && build_id_verify (debug_bfd.get (),
+				  build_id->size, build_id->data))
+	    return { debug_bfd, std::string (symfile_path.get ()) };
+	}
+    }
+
+  return {};
+}
+
 /* See objfiles.h.  */
 
 bool
 objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
 {
-  bool has_dwarf2 = true;
+  bool has_dwarf = false;
 
   deferred_warnings warnings;
 
-  std::string debugfile
-    = find_separate_debug_file_by_buildid (this, &warnings);
-
-  if (debugfile.empty ())
-    debugfile = find_separate_debug_file_by_debuglink (this, &warnings);
+  gdb_bfd_ref_ptr debug_bfd;
+  std::string filename;
 
-  if (!debugfile.empty ())
-    {
-      gdb_bfd_ref_ptr debug_bfd
-	(symfile_bfd_open_no_error (debugfile.c_str ()));
+  std::tie (debug_bfd, filename) = simple_find_and_open_separate_symbol_file
+    (this, find_separate_debug_file_by_buildid, &warnings);
 
-      if (debug_bfd != nullptr)
-	symbol_file_add_separate (debug_bfd, debugfile.c_str (),
-				  symfile_flags, this);
-    }
-  else
-    {
-      has_dwarf2 = false;
-      const struct bfd_build_id *build_id
-	= build_id_bfd_get (this->obfd.get ());
-      const char *filename = bfd_get_filename (this->obfd.get ());
-
-      if (build_id != nullptr)
-	{
-	  gdb::unique_xmalloc_ptr<char> symfile_path;
-	  scoped_fd fd (debuginfod_debuginfo_query (build_id->data,
-						    build_id->size,
-						    filename,
-						    &symfile_path));
+  if (debug_bfd == nullptr)
+    std::tie (debug_bfd, filename)
+      = simple_find_and_open_separate_symbol_file
+	  (this, find_separate_debug_file_by_debuglink, &warnings);
 
-	  if (fd.get () >= 0)
-	    {
-	      /* File successfully retrieved from server.  */
-	      gdb_bfd_ref_ptr debug_bfd
-		(symfile_bfd_open_no_error (symfile_path.get ()));
+  if (debug_bfd == nullptr)
+    std::tie (debug_bfd, filename)
+      = debuginfod_find_and_open_separate_symbol_file (this);
 
-	      if (debug_bfd != nullptr
-		  && build_id_verify (debug_bfd.get (), build_id->size,
-				      build_id->data))
-		{
-		  symbol_file_add_separate (debug_bfd, symfile_path.get (),
-					    symfile_flags, this);
-		  has_dwarf2 = true;
-		}
-	    }
-	}
+  if (debug_bfd != nullptr)
+    {
+      symbol_file_add_separate (debug_bfd, filename.c_str (), symfile_flags,
+				this);
+      has_dwarf = true;
     }
-  /* If all the methods to collect the debuginfo failed, print the
-     warnings, this is a no-op if there are no warnings.  */
-  if (debugfile.empty () && !has_dwarf2)
+
+  /* If we still have not got a separate debug symbol file, then
+     emit any warnings we've collected so far.  */
+  if (!has_dwarf)
     warnings.emit ();
 
-  return has_dwarf2;
+  return has_dwarf;
 }
 
 \f
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCHv2 4/5] gdb: add an extension language hook for missing debug info
  2023-11-08 15:48 ` [PATCHv2 0/5] New Python hook for missing debug information Andrew Burgess
                     ` (2 preceding siblings ...)
  2023-11-08 15:48   ` [PATCHv2 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file Andrew Burgess
@ 2023-11-08 15:48   ` Andrew Burgess
  2023-11-08 15:48   ` [PATCHv2 5/5] gdb: implement missing debug handler hook for Python Andrew Burgess
                     ` (2 subsequent siblings)
  6 siblings, 0 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-11-08 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess, Tom Tromey

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  | 106 +++++++++++++++++++++++++++++++------------
 4 files changed, 166 insertions(+), 28 deletions(-)

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 0b6dc4752df..85c43719dee 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -631,38 +631,88 @@ debuginfod_find_and_open_separate_symbol_file (struct objfile * objfile)
 bool
 objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
 {
-  bool has_dwarf = false;
-
-  deferred_warnings warnings;
-
-  gdb_bfd_ref_ptr debug_bfd;
-  std::string filename;
-
-  std::tie (debug_bfd, filename) = simple_find_and_open_separate_symbol_file
-    (this, find_separate_debug_file_by_buildid, &warnings);
-
-  if (debug_bfd == nullptr)
-    std::tie (debug_bfd, filename)
-      = simple_find_and_open_separate_symbol_file
-	  (this, find_separate_debug_file_by_debuglink, &warnings);
+  bool has_dwarf2 = false;
+
+  /* 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);
+
+      deferred_warnings warnings;
+      gdb_bfd_ref_ptr debug_bfd;
+      std::string filename;
+
+      std::tie (debug_bfd, filename)
+	= simple_find_and_open_separate_symbol_file
+	    (this, find_separate_debug_file_by_buildid, &warnings);
+
+      if (debug_bfd == nullptr)
+	std::tie (debug_bfd, filename)
+	  = simple_find_and_open_separate_symbol_file
+	      (this, find_separate_debug_file_by_debuglink, &warnings);
+
+      /* 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 (debug_bfd == nullptr && attempt == 0)
+	std::tie (debug_bfd, filename)
+	  = debuginfod_find_and_open_separate_symbol_file (this);
+
+      if (debug_bfd != 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 (debug_bfd, filename.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.  */
+	      debug_bfd
+		= symfile_bfd_open_no_error (ext_result.filename ().c_str ());
 
-  if (debug_bfd == nullptr)
-    std::tie (debug_bfd, filename)
-      = debuginfod_find_and_open_separate_symbol_file (this);
+	      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 (debug_bfd != nullptr)
-    {
-      symbol_file_add_separate (debug_bfd, filename.c_str (), symfile_flags,
-				this);
-      has_dwarf = true;
+      /* 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 ();
     }
 
-  /* If we still have not got a separate debug symbol file, then
-     emit any warnings we've collected so far.  */
-  if (!has_dwarf)
-    warnings.emit ();
-
-  return has_dwarf;
+  return has_dwarf2;
 }
 
 \f
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* [PATCHv2 5/5] gdb: implement missing debug handler hook for Python
  2023-11-08 15:48 ` [PATCHv2 0/5] New Python hook for missing debug information Andrew Burgess
                     ` (3 preceding siblings ...)
  2023-11-08 15:48   ` [PATCHv2 4/5] gdb: add an extension language hook for missing debug info Andrew Burgess
@ 2023-11-08 15:48   ` Andrew Burgess
  2023-11-12 22:38     ` Tom Tromey
  2023-11-15 12:36     ` Tom de Vries
  2023-11-12 22:39   ` [PATCHv2 0/5] New Python hook for missing debug information Tom Tromey
  2023-11-12 22:40   ` Tom Tromey
  6 siblings, 2 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-11-08 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess, Tom Tromey, Eli Zaretskii

Eli,

You've already reviewed the docs in this patch.  I fixed your
comments, and made a couple of very minor tweaks in v2.

Thanks,
Andrew


---

This commit builds on the previous commit, and implements the
extension_language_ops::handle_missing_debuginfo function for Python.
This hook will give user supplied Python code a chance to help find
missing debug information.

The implementation of the new hook is pretty minimal within GDB's C++
code; most of the work is out-sourced to a Python implementation which
is modelled heavily on how GDB's Python frame unwinders are
implemented.

The following new commands are added as commands implemented in
Python, this is similar to how the Python unwinder commands are
implemented:

  info missing-debug-handlers
  enable missing-debug-handler LOCUS HANDLER
  disable missing-debug-handler LOCUS HANDLER

To make use of this extension hook a user will create missing debug
information handler objects, and registers these handlers with GDB.
When GDB encounters an objfile that is missing debug information, each
handler is called in turn until one is able to help.  Here is a
minimal handler that does nothing useful:

  import gdb
  import gdb.missing_debug

  class MyFirstHandler(gdb.missing_debug.MissingDebugHandler):
      def __init__(self):
          super().__init__("my_first_handler")

      def __call__(self, objfile):
          # This handler does nothing useful.
          return None

  gdb.missing_debug.register_handler(None, MyFirstHandler())

Returning None from the __call__ method tells GDB that this handler
was unable to find the missing debug information, and GDB should ask
any other registered handlers.

By extending the __call__ method it is possible for the Python
extension to locate the debug information for objfile and return a
value that tells GDB how to use the information that has been located.

Possible return values from a handler:

  - None: This means the handler couldn't help.  GDB will call other
          registered handlers to see if they can help instead.

  - False: The handler has done all it can, but the debug information
           for the objfile still couldn't be found.  GDB will not call
	   any other handlers, and will continue without the debug
	   information for objfile.

  - True: The handler has installed the debug information into a
          location where GDB would normally expect to find it.  GDB
	  should look again for the debug information.

  - A string: The handler can return a filename, which is the file
              containing the missing debug information.  GDB will load
	      this file.

When a handler returns True, GDB will look again for the debug
information, but only using the standard built-in build-id and
.gnu_debuglink based lookup strategies.  It is not possible for an
extension to trigger another debuginfod lookup; the assumption is that
the debuginfod server is remote, and out of the control of extensions
running within GDB.

Handlers can be registered globally, or per program space.  GDB checks
the handlers for the current program space first, and then all of the
global handles.  The first handler that returns a value that is not
None, has "handled" the objfile, at which point GDB continues.

Reviewed-By: Eli Zaretskii <eliz@gnu.org>
---
 gdb/NEWS                                      |  26 +
 gdb/data-directory/Makefile.in                |   2 +
 gdb/doc/python.texi                           | 140 ++++++
 gdb/python/lib/gdb/__init__.py                |  41 ++
 gdb/python/lib/gdb/command/missing_debug.py   | 226 +++++++++
 gdb/python/lib/gdb/missing_debug.py           | 169 +++++++
 gdb/python/py-progspace.c                     |  51 ++
 gdb/python/python.c                           |  83 ++-
 gdb/testsuite/gdb.python/py-missing-debug.c   |  22 +
 gdb/testsuite/gdb.python/py-missing-debug.exp | 473 ++++++++++++++++++
 gdb/testsuite/gdb.python/py-missing-debug.py  | 120 +++++
 11 files changed, 1352 insertions(+), 1 deletion(-)
 create mode 100644 gdb/python/lib/gdb/command/missing_debug.py
 create mode 100644 gdb/python/lib/gdb/missing_debug.py
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.c
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.exp
 create mode 100644 gdb/testsuite/gdb.python/py-missing-debug.py

diff --git a/gdb/NEWS b/gdb/NEWS
index 3851114a9f7..fe8420fb978 100644
--- a/gdb/NEWS
+++ b/gdb/NEWS
@@ -16,6 +16,21 @@ disassemble
   command will now give an error.  Previously the 'b' flag would
   always override the 'r' flag.
 
+* New Commands
+
+info missing-debug-handler
+  List all the registered missing debug handlers.
+
+enable missing-debug-handler LOCUS HANDLER
+disable missing-debug-handler LOCUS HANDLER
+  Enable or disable a missing debug handler with a name matching the
+  regular expression HANDLER, in LOCUS.
+
+  LOCUS can be 'global' to operate on global missing debug handler,
+  'progspace' to operate on handlers within the current program space,
+  or can be a regular expression which is matched against the filename
+  of the primary executable in each program space.
+
 * Python API
 
   ** New function gdb.notify_mi(NAME, DATA), that emits custom
@@ -24,6 +39,17 @@ disassemble
   ** New read/write attribute gdb.Value.bytes that contains a bytes
      object holding the contents of this value.
 
+  ** New module gdb.missing_debug that facilitates dealing with
+     objfiles that are missing any debug information.
+
+  ** New function gdb.missing_debug.register_handler that can register
+     an instance of a sub-class of gdb.missing_debug.MissingDebugInfo
+     as a handler for objfiles that are missing debug information.
+
+  ** New class gdb.missing_debug.MissingDebugInfo which can be
+     sub-classed to create handlers for objfiles with missing debug
+     information.
+
 *** Changes in GDB 14
 
 * GDB now supports the AArch64 Scalable Matrix Extension 2 (SME2), which
diff --git a/gdb/data-directory/Makefile.in b/gdb/data-directory/Makefile.in
index 04a8c8eca69..7af6bafb0c9 100644
--- a/gdb/data-directory/Makefile.in
+++ b/gdb/data-directory/Makefile.in
@@ -73,6 +73,7 @@ PYTHON_FILE_LIST = \
 	gdb/FrameDecorator.py \
 	gdb/FrameIterator.py \
 	gdb/frames.py \
+	gdb/missing_debug.py \
 	gdb/printing.py \
 	gdb/prompt.py \
 	gdb/styling.py \
@@ -82,6 +83,7 @@ PYTHON_FILE_LIST = \
 	gdb/command/__init__.py \
 	gdb/command/explore.py \
 	gdb/command/frame_filters.py \
+	gdb/command/missing_debug.py \
 	gdb/command/pretty_printers.py \
 	gdb/command/prompt.py \
 	gdb/command/type_printers.py \
diff --git a/gdb/doc/python.texi b/gdb/doc/python.texi
index 8cc3f92cbfe..c26949e0510 100644
--- a/gdb/doc/python.texi
+++ b/gdb/doc/python.texi
@@ -230,6 +230,7 @@
 * Connections In Python::       Python representation of connections.
 * TUI Windows In Python::       Implementing new TUI windows.
 * Disassembly In Python::       Instruction Disassembly In Python
+* Missing Debug Info In Python:: Handle missing debug info from Python.
 @end menu
 
 @node Basic Python
@@ -5252,6 +5253,12 @@
 objects.  @xref{Frame Filter API}, for more information.
 @end defvar
 
+@defvar Progspace.missing_debug_handlers
+The @code{missing_debug_handlers} attribute is a list of the missing
+debug handler objects for this program space.  @xref{Missing Debug
+Info In Python}, for more information.
+@end defvar
+
 A program space has the following methods:
 
 @defun Progspace.block_for_pc (pc)
@@ -7819,6 +7826,139 @@
 gdb.disassembler.register_disassembler(NibbleSwapDisassembler())
 @end smallexample
 
+@node Missing Debug Info In Python
+@subsubsection Missing Debug Info In Python
+@cindex python, handle missing debug information
+
+When @value{GDBN} encounters a new objfile (@pxref{Objfiles In
+Python}), e.g.@: the primary executable, or any shared libraries used
+by the inferior, @value{GDBN} will attempt to load the corresponding
+debug information for that objfile.  The debug information might be
+found within the objfile itself, or within a separate objfile which
+@value{GDBN} will automatically locate and load.
+
+Sometimes though, @value{GDBN} might not find any debug information
+for an objfile, in this case the debugging experience will be
+restricted.
+
+If @value{GDBN} fails to locate any debug information for a particular
+objfile, there is an opportunity for a Python extension to step in.  A
+Python extension can potentially locate the missing debug information
+using some platform- or project-specific steps, and inform
+@value{GDBN} of its location.  Or a Python extension might provide
+some platform- or project-specific advice to the user about how to
+obtain the missing debug information.
+
+A missing debug information Python extension consists of a handler
+object which has the @code{name} and @code{enabled} attributes, and
+implements the @code{__call__} method.  When @value{GDBN} encounters
+an objfile for which it is unable to find any debug information, it
+invokes the @code{__call__} method.  Full details of how handlers are
+written can be found below.
+
+@subheading The @code{gdb.missing_debug} Module
+
+@value{GDBN} comes with a @code{gdb.missing_debug} module which
+contains the following class and global function:
+
+@deftp{class} gdb.missing_debug.MissingDebugHandler
+
+@code{MissingDebugHandler} is a base class from which user-created
+handlers can derive, though it is not required that handlers derive
+from this class, so long as any user created handler has the
+@code{name} and @code{enabled} attributes, and implements the
+@code{__call__} method.
+
+@defun MissingDebugHandler.__init__ (name)
+The @var{name} is a string used to reference this missing debug
+handler within some @value{GDBN} commands.  Valid names consist of the
+characters @code{[-_a-zA-Z0-9]}, creating a handler with an invalid
+name raises a @code{ValueError} exception.
+@end defun
+
+@defun MissingDebugHandler.__call__ (objfile)
+Sub-classes must override the @code{__call__} method.  The
+@var{objfile} argument will be a @code{gdb.Objfile}, this is the
+objfile for which @value{GDBN} was unable to find any debug
+information.
+
+The return value from the @code{__call__} method indicates what
+@value{GDBN} should do next.  The possible return values are:
+
+@itemize @bullet
+@item @code{None}
+
+This indicates that this handler could not help with @var{objfile},
+@value{GDBN} should call any other registered handlers.
+
+@item @code{True}
+
+This indicates that this handler has installed the debug information
+into a location where @value{GDBN} would normally expect to find it
+when looking for separate debug information files (@pxref{Separate
+Debug Files}).  @value{GDBN} will repeat the normal lookup process,
+which should now find the separate debug file.
+
+If @value{GDBN} still doesn't find the separate debug information file
+after this second attempt, then the Python missing debug information
+handlers are not invoked a second time, this prevents a badly behaved
+handler causing @value{GDBN} to get stuck in a loop.  @value{GDBN}
+will continue without any debug information for @var{objfile}.
+
+@item @code{False}
+
+This indicates that this handler has done everything that it intends
+to do with @var{objfile}, but no separate debug information can be
+found.  @value{GDBN} will not call any other registered handlers for
+@var{objfile}.  @value{GDBN} will continue without debugging
+information for @var{objfile}.
+
+@item A string
+
+The returned string should contain a filename.  @value{GDBN} will not
+call any further registered handlers, and will instead load the debug
+information from the file identified by the returned filename.
+@end itemize
+
+Invoking the @code{__call__} method from this base class will raise a
+@code{NotImplementedError} exception.
+@end defun
+
+@defvar MissingDebugHandler.name
+A read-only attribute which is a string, the name of this handler
+passed to the @code{__init__} method.
+@end defvar
+
+@defvar MissingDebugHandler.enabled
+A modifiable attribute containing a boolean; when @code{True}, the
+handler is enabled, and will be used by @value{GDBN}.  When
+@code{False}, the handler has been disabled, and will not be used.
+@end defvar
+@end deftp
+
+@defun gdb.missing_debug.register_handler (locus, handler, replace=@code{False})
+Register a new missing debug handler with @value{GDBN}.
+
+@var{handler} is an instance of a sub-class of
+@code{MissingDebugHandler}, or at least an instance of an object that
+has the same attributes and methods as @code{MissingDebugHandler}.
+
+@var{locus} specifies to which handler list to prepend @var{handler}.
+It can be either a @code{gdb.Progspace} (@pxref{Progspaces In Python})
+or @code{None}, in which case the handler is registered globally.  The
+newly registered @var{handler} will be called before any other handler
+from the same locus.  Two handlers in the same locus cannot have the
+same name, an attempt to add a handler with an already existing name
+raises an exception unless @var{replace} is @code{True}, in which case
+the old handler is deleted and the new handler is prepended to the
+selected handler list.
+
+@value{GDBN} first calls the handlers for the current program space,
+and then the globally registered handlers.  As soon as a handler
+returns a value other than @code{None}, no further handlers are called
+for this objfile.
+@end defun
+
 @node Python Auto-loading
 @subsection Python Auto-loading
 @cindex Python auto-loading
diff --git a/gdb/python/lib/gdb/__init__.py b/gdb/python/lib/gdb/__init__.py
index b3124369fe8..93ed50effcb 100644
--- a/gdb/python/lib/gdb/__init__.py
+++ b/gdb/python/lib/gdb/__init__.py
@@ -84,6 +84,8 @@ xmethods = []
 frame_filters = {}
 # Initial frame unwinders.
 frame_unwinders = []
+# Initial missing debug handlers.
+missing_debug_handlers = []
 
 
 def _execute_unwinders(pending_frame):
@@ -291,3 +293,42 @@ class Thread(threading.Thread):
         # threads.
         with blocked_signals():
             super().start()
+
+
+def _handle_missing_debuginfo(objfile):
+    """Internal function called from GDB to execute missing debug
+    handlers.
+
+    Run each of the currently registered, and enabled missing debug
+    handler objects for the current program space and then from the
+    global list.  Stop after the first handler that returns a result
+    other than None.
+
+    Arguments:
+        objfile: A gdb.Objfile for which GDB could not find any debug
+                 information.
+
+    Returns:
+        None: No debug information could be found for objfile.
+        False: A handler has done all it can with objfile, but no
+               debug information could be found.
+        True: Debug information might have been installed by a
+              handler, GDB should check again.
+        A string: This is the filename of a file containing the
+                  required debug information.
+    """
+    pspace = objfile.progspace
+
+    for handler in pspace.missing_debug_handlers:
+        if handler.enabled:
+            result = handler(objfile)
+            if result is not None:
+                return result
+
+    for handler in missing_debug_handlers:
+        if handler.enabled:
+            result = handler(objfile)
+            if result is not None:
+                return result
+
+    return None
diff --git a/gdb/python/lib/gdb/command/missing_debug.py b/gdb/python/lib/gdb/command/missing_debug.py
new file mode 100644
index 00000000000..4ffd1d59800
--- /dev/null
+++ b/gdb/python/lib/gdb/command/missing_debug.py
@@ -0,0 +1,226 @@
+# Missing debug related commands.
+#
+# Copyright 2023 Free Software Foundation, Inc.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+import re
+
+
+def validate_regexp(exp, idstring):
+    """Compile exp into a compiler regular expression object.
+
+    Arguments:
+        exp: The string to compile into a re.Pattern object.
+        idstring: A string, what exp is a regexp for.
+
+    Returns:
+        A re.Pattern object representing exp.
+
+    Raises:
+        SyntaxError: If exp is an invalid regexp.
+    """
+    try:
+        return re.compile(exp)
+    except SyntaxError:
+        raise SyntaxError("Invalid %s regexp: %s." % (idstring, exp))
+
+
+def parse_missing_debug_command_args(arg):
+    """Internal utility to parse missing debug handler command argv.
+
+    Arguments:
+        arg: The arguments to the command. The format is:
+             [locus-regexp [name-regexp]]
+
+    Returns:
+        A 2-tuple of compiled regular expressions.
+
+    Raises:
+        SyntaxError: an error processing ARG
+    """
+    argv = gdb.string_to_argv(arg)
+    argc = len(argv)
+    if argc > 2:
+        raise SyntaxError("Too many arguments.")
+    locus_regexp = ""
+    name_regexp = ""
+    if argc >= 1:
+        locus_regexp = argv[0]
+        if argc >= 2:
+            name_regexp = argv[1]
+    return (
+        validate_regexp(locus_regexp, "locus"),
+        validate_regexp(name_regexp, "handler"),
+    )
+
+
+class InfoMissingDebugHanders(gdb.Command):
+    """GDB command to list missing debug handlers.
+
+    Usage: info missing-debug-handlers [LOCUS-REGEXP [NAME-REGEXP]]
+
+    LOCUS-REGEXP is a regular expression matching the location of the
+    handler.  If it is omitted, all registered handlers from all
+    loci are listed.  A locus can be 'global', 'progspace' to list
+    the handlers from the current progspace, or a regular expression
+    matching filenames of progspaces.
+
+    NAME-REGEXP is a regular expression to filter missing debug
+    handler names.  If this omitted for a specified locus, then all
+    registered handlers in the locus are listed.
+    """
+    
+    def __init__(self):
+        super().__init__("info missing-debug-handlers", gdb.COMMAND_FILES)
+
+    def list_handlers(self, title, handlers, name_re):
+        """Lists the missing debug handlers whose name matches regexp.
+
+        Arguments:
+            title: The line to print before the list.
+            handlers: The list of the missing debug handlers.
+            name_re: handler name filter.
+        """
+        if not handlers:
+            return
+        print(title)
+        for handler in handlers:
+            if name_re.match(handler.name):
+                print(
+                    "  %s%s" % (handler.name, "" if handler.enabled else " [disabled]")
+                )
+
+    def invoke(self, arg, from_tty):
+        locus_re, name_re = parse_missing_debug_command_args(arg)
+
+        if locus_re.match("progspace") and locus_re.pattern != "":
+            cp = gdb.current_progspace()
+            self.list_handlers(
+                "Progspace %s:" % cp.filename, cp.missing_debug_handlers, name_re
+            )
+
+        for progspace in gdb.progspaces():
+            filename = progspace.filename or ""
+            if locus_re.match(filename):
+                if filename == "":
+                    if progspace == gdb.current_progspace():
+                        msg = "Current Progspace:"
+                    else:
+                        msg = "Progspace <no-file>:"
+                else:
+                    msg = "Progspace %s:" % filename
+                self.list_handlers(
+                    msg,
+                    progspace.missing_debug_handlers,
+                    name_re,
+                )
+
+        # Print global handlers last, as these are invoked last.
+        if locus_re.match("global"):
+            self.list_handlers("Global:", gdb.missing_debug_handlers, name_re)
+
+
+def do_enable_handler1(handlers, name_re, flag):
+    """Enable/disable missing debug handlers whose names match given regex.
+
+    Arguments:
+        handlers: The list of missing debug handlers.
+        name_re: Handler name filter.
+        flag: A boolean indicating if we should enable or disable.
+
+    Returns:
+        The number of handlers affected.
+    """
+    total = 0
+    for handler in handlers:
+        if name_re.match(handler.name) and handler.enabled != flag:
+            handler.enabled = flag
+            total += 1
+    return total
+
+
+def do_enable_handler(arg, flag):
+    """Enable or disable missing debug handlers."""
+    (locus_re, name_re) = parse_missing_debug_command_args(arg)
+    total = 0
+    if locus_re.match("global"):
+        total += do_enable_handler1(gdb.missing_debug_handlers, name_re, flag)
+    if locus_re.match("progspace") and locus_re.pattern != "":
+        total += do_enable_handler1(
+            gdb.current_progspace().missing_debug_handlers, name_re, flag
+        )
+    for progspace in gdb.progspaces():
+        filename = progspace.filename or ""
+        if locus_re.match(filename):
+            total += do_enable_handler1(progspace.missing_debug_handlers, name_re, flag)
+    print(
+        "%d missing debug handler%s %s"
+        % (total, "" if total == 1 else "s", "enabled" if flag else "disabled")
+    )
+
+
+class EnableMissingDebugHandler(gdb.Command):
+    """GDB command to enable missing debug handlers.
+
+    Usage: enable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]]
+
+    LOCUS-REGEXP is a regular expression specifying the handlers to
+    enable.  It can be 'global', 'progspace' for the current
+    progspace, or the filename for a file associated with a progspace.
+
+    NAME_REGEXP is a regular expression to filter handler names.  If
+    this omitted for a specified locus, then all registered handlers
+    in the locus are affected.
+    """
+
+    def __init__(self):
+        super().__init__("enable missing-debug-handler", gdb.COMMAND_FILES)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_handler(arg, True)
+
+
+class DisableMissingDebugHandler(gdb.Command):
+    """GDB command to disable missing debug handlers.
+
+    Usage: disable missing-debug-handler [LOCUS-REGEXP [NAME-REGEXP]]
+
+    LOCUS-REGEXP is a regular expression specifying the handlers to
+    enable.  It can be 'global', 'progspace' for the current
+    progspace, or the filename for a file associated with a progspace.
+
+    NAME_REGEXP is a regular expression to filter handler names.  If
+    this omitted for a specified locus, then all registered handlers
+    in the locus are affected.
+    """
+
+    def __init__(self):
+        super().__init__("disable missing-debug-handler", gdb.COMMAND_FILES)
+
+    def invoke(self, arg, from_tty):
+        """GDB calls this to perform the command."""
+        do_enable_handler(arg, False)
+
+
+def register_missing_debug_handler_commands():
+    """Installs the missing debug handler commands."""
+    InfoMissingDebugHanders()
+    EnableMissingDebugHandler()
+    DisableMissingDebugHandler()
+
+
+register_missing_debug_handler_commands()
diff --git a/gdb/python/lib/gdb/missing_debug.py b/gdb/python/lib/gdb/missing_debug.py
new file mode 100644
index 00000000000..42d69858f96
--- /dev/null
+++ b/gdb/python/lib/gdb/missing_debug.py
@@ -0,0 +1,169 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+MissingDebugHandler base class, and register_handler function.
+"""
+
+import gdb
+
+
+def _validate_name(name):
+    """Validate a missing debug handler name string.
+
+    If name is valid as a missing debug handler name, then this
+    function does nothing.  If name is not valid then an exception is
+    raised.
+
+    Arguments:
+        name: A string, the name of a missing debug handler.
+
+    Returns:
+        Nothing.
+
+    Raises:
+        ValueError: If name is invalid as a missing debug handler
+                    name.
+    """
+    for ch in name:
+        if not ch.isascii() or not (ch.isalnum() or ch in "_-"):
+            raise ValueError("invalid character '%s' in handler name: %s" % (ch, name))
+
+
+class MissingDebugHandler(object):
+    """Base class for missing debug handlers written in Python.
+
+    A missing debug handler has a single method __call__ along with
+    the read/write attribute enabled, and a read-only attribute name.
+
+    Attributes:
+        name: Read-only attribute, the name of this handler.
+        enabled: When true this handler is enabled.
+    """
+
+    def __init__(self, name):
+        """Constructor.
+
+        Args:
+            name: An identifying name for this handler.
+
+        Raises:
+            TypeError: name is not a string.
+            ValueError: name contains invalid characters.
+        """
+
+        if not isinstance(name, str):
+            raise TypeError("incorrect type for name: %s" % type(name))
+
+        _validate_name(name)
+
+        self._name = name
+        self._enabled = True
+
+    @property
+    def name(self):
+        return self._name
+
+    @property
+    def enabled(self):
+        return self._enabled
+
+    @enabled.setter
+    def enabled(self, value):
+        if not isinstance(value, bool):
+            raise TypeError("incorrect type for enabled attribute: %s" % type(value))
+        self._enabled = value
+
+    def __call__(self, objfile):
+        """GDB handle missing debug information for an objfile.
+
+        Arguments:
+            objfile: A gdb.Objfile for which GDB could not find any
+                debug information.
+
+        Returns:
+            True: GDB should try again to locate the debug information
+                for objfile, the handler may have installed the
+                missing information.
+            False: GDB should move on without the debug information
+                for objfile.
+            A string: GDB should load the file at the given path; it
+                contains the debug information for objfile.
+            None: This handler can't help with objfile.  GDB should
+                try any other registered handlers.
+        """
+        raise NotImplementedError("MissingDebugHandler.__call__()")
+
+
+def register_handler(locus, handler, replace=False):
+    """Register handler in given locus.
+
+    The handler is prepended to the locus's missing debug handlers
+    list. The name of handler should be unique (or replace must be
+    True).
+
+    Arguments:
+        locus: Either a progspace, or None (in which case the unwinder
+               is registered globally).
+        handler: An object of a gdb.MissingDebugHandler subclass.
+
+        replace: If True, replaces existing handler with the same name
+                 within locus.  Otherwise, raises RuntimeException if
+                 unwinder with the same name already exists.
+
+    Returns:
+        Nothing.
+
+    Raises:
+        RuntimeError: The name of handler is not unique.
+        TypeError: Bad locus type.
+        AttributeError: Required attributes of handler are missing.
+    """
+
+    if locus is None:
+        if gdb.parameter("verbose"):
+            gdb.write("Registering global %s handler ...\n" % handler.name)
+        locus = gdb
+    elif isinstance(locus, gdb.Progspace):
+        if gdb.parameter("verbose"):
+            gdb.write(
+                "Registering %s handler for %s ...\n" % (handler.name, locus.filename)
+            )
+    else:
+        raise TypeError("locus should be gdb.Progspace or None")
+
+    # Some sanity checks on HANDLER.  Calling getattr will raise an
+    # exception if the attribute doesn't exist, which is what we want.
+    # These checks are not exhaustive; we don't check the attributes
+    # have the correct types, or the method has the correct signature,
+    # but this should catch some basic mistakes.
+    getattr(handler, "name")
+    getattr(handler, "enabled")
+    call_method = getattr(handler, "__call__")
+    if not callable(call_method):
+        raise AttributeError(
+            "'%s' object's '__call__' attribute is not callable"
+            % type(handler).__name__
+        )
+
+    i = 0
+    for needle in locus.missing_debug_handlers:
+        if needle.name == handler.name:
+            if replace:
+                del locus.missing_debug_handlers[i]
+            else:
+                raise RuntimeError("Handler %s already exists." % handler.name)
+        i += 1
+    locus.missing_debug_handlers.insert(0, handler)
diff --git a/gdb/python/py-progspace.c b/gdb/python/py-progspace.c
index f636ffd0460..0797ef1fa6b 100644
--- a/gdb/python/py-progspace.c
+++ b/gdb/python/py-progspace.c
@@ -55,6 +55,9 @@ struct pspace_object
 
   /* The debug method list.  */
   PyObject *xmethods;
+
+  /* The missing debug handler list.  */
+  PyObject *missing_debug_handlers;
 };
 
 extern PyTypeObject pspace_object_type
@@ -164,6 +167,7 @@ pspy_dealloc (PyObject *self)
   Py_XDECREF (ps_self->frame_unwinders);
   Py_XDECREF (ps_self->type_printers);
   Py_XDECREF (ps_self->xmethods);
+  Py_XDECREF (ps_self->missing_debug_handlers);
   Py_TYPE (self)->tp_free (self);
 }
 
@@ -199,6 +203,10 @@ pspy_initialize (pspace_object *self)
   if (self->xmethods == NULL)
     return 0;
 
+  self->missing_debug_handlers = PyList_New (0);
+  if (self->missing_debug_handlers == nullptr)
+    return 0;
+
   return 1;
 }
 
@@ -353,6 +361,47 @@ pspy_get_xmethods (PyObject *o, void *ignore)
   return self->xmethods;
 }
 
+/* Return the list of missing debug handlers for this program space.  */
+
+static PyObject *
+pspy_get_missing_debug_handlers (PyObject *o, void *ignore)
+{
+  pspace_object *self = (pspace_object *) o;
+
+  Py_INCREF (self->missing_debug_handlers);
+  return self->missing_debug_handlers;
+}
+
+/* Set this program space's list of missing debug handlers to HANDLERS.  */
+
+static int
+pspy_set_missing_debug_handlers (PyObject *o, PyObject *handlers,
+				 void *ignore)
+{
+  pspace_object *self = (pspace_object *) o;
+
+  if (handlers == nullptr)
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "cannot delete the missing debug handlers list");
+      return -1;
+    }
+
+  if (!PyList_Check (handlers))
+    {
+      PyErr_SetString (PyExc_TypeError,
+		       "the missing debug handlers attribute must be a list");
+      return -1;
+    }
+
+  /* Take care in case the LHS and RHS are related somehow.  */
+  gdbpy_ref<> tmp (self->missing_debug_handlers);
+  Py_INCREF (handlers);
+  self->missing_debug_handlers = handlers;
+
+  return 0;
+}
+
 /* Set the 'type_printers' attribute.  */
 
 static int
@@ -745,6 +794,8 @@ static gdb_PyGetSetDef pspace_getset[] =
     "Type printers.", NULL },
   { "xmethods", pspy_get_xmethods, NULL,
     "Debug methods.", NULL },
+  { "missing_debug_handlers", pspy_get_missing_debug_handlers,
+    pspy_set_missing_debug_handlers, "Missing debug handlers.", NULL },
   { NULL }
 };
 
diff --git a/gdb/python/python.c b/gdb/python/python.c
index d569fb5a3e4..4523e30ed24 100644
--- a/gdb/python/python.c
+++ b/gdb/python/python.c
@@ -127,7 +127,9 @@ static enum ext_lang_rc gdbpy_before_prompt_hook
 static gdb::optional<std::string> gdbpy_colorize
   (const std::string &filename, const std::string &contents);
 static gdb::optional<std::string> gdbpy_colorize_disasm
-  (const std::string &content, gdbarch *gdbarch);
+(const std::string &content, gdbarch *gdbarch);
+static ext_lang_missing_debuginfo_result gdbpy_handle_missing_debuginfo
+  (const struct extension_language_defn *extlang, struct objfile *objfile);
 
 /* The interface between gdb proper and loading of python scripts.  */
 
@@ -173,6 +175,8 @@ static const struct extension_language_ops python_extension_ops =
   gdbpy_colorize_disasm,
 
   gdbpy_print_insn,
+
+  gdbpy_handle_missing_debuginfo
 };
 
 #endif /* HAVE_PYTHON */
@@ -1688,6 +1692,83 @@ gdbpy_get_current_objfile (PyObject *unused1, PyObject *unused2)
   return objfile_to_objfile_object (gdbpy_current_objfile).release ();
 }
 
+/* Implement the 'handle_missing_debuginfo' hook for Python.  GDB has
+   failed to find any debug information for OBJFILE.  The extension has a
+   chance to record this, or even install the required debug information.
+   See the description of ext_lang_missing_debuginfo_result in
+   extension-priv.h for details of the return value.  */
+
+static ext_lang_missing_debuginfo_result
+gdbpy_handle_missing_debuginfo (const struct extension_language_defn *extlang,
+				struct objfile *objfile)
+{
+  /* Early exit if Python is not initialised.  */
+  if (!gdb_python_initialized)
+    return {};
+
+  struct gdbarch *gdbarch = objfile->arch ();
+
+  gdbpy_enter enter_py (gdbarch);
+
+  /* Convert OBJFILE into the corresponding Python object.  */
+  gdbpy_ref<> pyo_objfile = objfile_to_objfile_object (objfile);
+  if (pyo_objfile == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  /* Lookup the helper function within the GDB module.  */
+  gdbpy_ref<> pyo_handler
+    (PyObject_GetAttrString (gdb_python_module, "_handle_missing_debuginfo"));
+  if (pyo_handler == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  /* Call the function, passing in the Python objfile object.  */
+  gdbpy_ref<> pyo_execute_ret
+    (PyObject_CallFunctionObjArgs (pyo_handler.get (), pyo_objfile.get (),
+				   nullptr));
+  if (pyo_execute_ret == nullptr)
+    {
+      /* If the handler is cancelled due to a Ctrl-C, then propagate
+	 the Ctrl-C as a GDB exception instead of swallowing it.  */
+      gdbpy_print_stack_or_quit ();
+      return {};
+    }
+
+  /* Parse the result, and convert it back to the C++ object.  */
+  if (pyo_execute_ret == Py_None)
+    return {};
+
+  if (PyBool_Check (pyo_execute_ret.get ()))
+    {
+      bool try_again = PyObject_IsTrue (pyo_execute_ret.get ());
+      return ext_lang_missing_debuginfo_result (try_again);
+    }
+
+  if (!gdbpy_is_string (pyo_execute_ret.get ()))
+    {
+      PyErr_SetString (PyExc_ValueError,
+		       "return value from _handle_missing_debuginfo should "
+		       "be None, a Bool, or a String");
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  gdb::unique_xmalloc_ptr<char> filename
+    = python_string_to_host_string (pyo_execute_ret.get ());
+  if (filename == nullptr)
+    {
+      gdbpy_print_stack ();
+      return {};
+    }
+
+  return ext_lang_missing_debuginfo_result (std::string (filename.get ()));
+}
+
 /* Compute the list of active python type printers and store them in
    EXT_PRINTERS->py_type_printers.  The product of this function is used by
    gdbpy_apply_type_printers, and freed by gdbpy_free_type_printers.
diff --git a/gdb/testsuite/gdb.python/py-missing-debug.c b/gdb/testsuite/gdb.python/py-missing-debug.c
new file mode 100644
index 00000000000..8cbea3b6892
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-missing-debug.c
@@ -0,0 +1,22 @@
+/* This test program is part of GDB, the GNU debugger.
+
+   Copyright 2023 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+int
+main ()
+{
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.python/py-missing-debug.exp b/gdb/testsuite/gdb.python/py-missing-debug.exp
new file mode 100644
index 00000000000..3e0d70b9d22
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-missing-debug.exp
@@ -0,0 +1,473 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+load_lib gdb-python.exp
+
+require allow_python_tests
+
+standard_testfile
+
+if {[build_executable "failed to prepare" ${testfile} ${srcfile}]} {
+    return -1
+}
+
+# Remove debug information from BINFILE and place it into
+# BINFILE.debug.
+if {[gdb_gnu_strip_debug $binfile]} {
+    unsupported "cannot produce separate debug info files"
+    return -1
+}
+
+set remote_python_file \
+    [gdb_remote_download host ${srcdir}/${subdir}/${testfile}.py]
+
+set debug_filename ${binfile}.debug
+set hidden_filename ${binfile}.hidden
+
+# Start GDB.
+clean_restart
+
+# Some initial sanity checks; initially, we can find the debug information
+# (this will use the .gnu_debuglink), then after we move the debug
+# information, reload the executable, now the debug can't be found.
+with_test_prefix "initial checks" {
+    # Load BINFILE, we should find the separate debug information.
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "debug"} \
+	"debug info is found"
+
+    # Rename the debug information file, re-load BINFILE, GDB should fail
+    # to find the debug information
+    remote_exec build "mv $debug_filename $hidden_filename"
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
+	"debug info no longer found"
+}
+
+# Load the Python script into GDB.
+gdb_test "source $remote_python_file" "^Success" \
+    "source python script"
+
+# Setup the separate debug info directory.  This isn't actually needed until
+# some of the later tests, but might as well get this done now.
+set debug_directory [standard_output_file "debug-dir"]
+remote_exec build "mkdir -p $debug_directory"
+gdb_test_no_output "set debug-file-directory $debug_directory" \
+    "set debug-file-directory"
+
+# Initially the missing debug handler we install is in a mode where it
+# returns None, indicating that it can't help locate the debug information.
+# Check this works as expected.
+with_test_prefix "handler returning None" {
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(None, handler_obj)" \
+	"register the initial handler"
+
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
+	"debug info not found"
+
+    # Check the handler was only called once.
+    gdb_test "python print(handler_obj.call_count)" "^1" \
+	"check handler was only called once"
+}
+
+# Now configure the handler to move the debug file back to the
+# .gnu_debuglink location and then return True, this will cause GDB to
+# recheck, at which point it should find the debug info.
+with_test_prefix "handler in gnu_debuglink mode" {
+    gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \
+						    \"$hidden_filename\", \
+						    \"$debug_filename\")" \
+	"confirgure handler"
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
+
+    # Check the handler was only called once.
+    gdb_test "python print(handler_obj.call_count)" "^1" \
+	"check handler was only called once"
+}
+
+# Setup a directory structure based on the build-id of BINFILE, but don't
+# move the debug information into place just yet.
+#
+# Instead, configure the handler to move the debug info into the build-id
+# directory.
+#
+# Reload BINFILE, at which point the handler will move the debug info into
+# the build-id directory and return True, GDB will then recheck for the
+# debug information, and should find it.
+with_test_prefix "handler in build-id mode" {
+    # Move the debug file out of the way once more.
+    remote_exec build "mv $debug_filename $hidden_filename"
+
+    # Create the build-id based directory in which the debug information
+    # will be placed.
+    set build_id_filename \
+	$debug_directory/[build_id_debug_filename_get $binfile]
+    remote_exec build "mkdir -p [file dirname $build_id_filename]"
+
+    # Configure the handler to move the debug info into the build-id dir.
+    gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_TRUE, \
+						    \"$hidden_filename\", \
+						    \"$build_id_filename\")" \
+	"confirgure handler"
+
+    # Reload the binary and check the debug information is found.
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
+
+    # Check the handler was only called once.
+    gdb_test "python print(handler_obj.call_count)" "^1" \
+	"check handler was only called once"
+}
+
+# Move the debug information back to a hidden location and configure the
+# handler to return the filename of the hidden debug info location.  GDB
+# should immediately use this file as the debug information.
+with_test_prefix "handler returning a string" {
+    remote_exec build "mv $build_id_filename $hidden_filename"
+
+    # Configure the handler return a filename string.
+    gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_STRING, \
+						    \"$hidden_filename\")" \
+	"confirgure handler"
+
+    # Reload the binary and check the debug information is found.
+    gdb_file_cmd $binfile
+    gdb_assert {$gdb_file_cmd_debug_info == "debug"} "debug info found"
+
+    # Check the handler was only called once.
+    gdb_test "python print(handler_obj.call_count)" "^1" \
+	"check handler was only called once"
+}
+
+# Register another global handler, this one raises an exception.  Reload the
+# debug information, the bad handler should be invoked first, which raises
+# an excetption, at which point GDB should skip further Python handlers.
+with_test_prefix "handler raises an exception" {
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(None, rhandler)"
+
+    foreach_with_prefix exception_type {gdb.GdbError TypeError} {
+	gdb_test_no_output \
+	    "python rhandler.exception_type = $exception_type"
+
+	gdb_file_cmd $binfile
+	gdb_assert {$gdb_file_cmd_debug_info == "nodebug"} \
+	    "debug info not found"
+
+	set re [string_to_regexp \
+		    "Python Exception <class '$exception_type'>: message"]
+	gdb_assert {[regexp $re $gdb_file_cmd_msg]} \
+	    "check for exception in file command output"
+
+	# Our original handler is still registered, but should not have been
+	# called again (as the exception occurs first).
+	gdb_test "python print(handler_obj.call_count)" "^1" \
+	    "check good handler hasn't been called again"
+    }
+}
+
+gdb_test "info missing-debug-handlers" \
+    [multi_line \
+	 "Global:" \
+	 "  exception_handler" \
+	 "  handler"] \
+    "check both handlers are visible"
+
+# Re-start GDB.
+clean_restart
+
+# Load the Python script into GDB.
+gdb_test "source $remote_python_file" "^Success" \
+    "source python script for bad handler name checks"
+
+# Attempt to register a missing-debug-handler with NAME.  The expectation is
+# that this should fail as NAME contains some invalid characters.
+proc check_bad_name {name} {
+    set name_re [string_to_regexp $name]
+    set re \
+	[multi_line \
+	     "ValueError: invalid character '.' in handler name: $name_re" \
+	     "Error while executing Python code\\."]
+
+    gdb_test "python register(\"$name\")" $re \
+	"check that '$name' is not accepted"
+}
+
+# We don't attempt to be exhaustive here, just check a few random examples
+# of invalid names.
+check_bad_name "!! Bad Name"
+check_bad_name "Bad Name"
+check_bad_name "(Bad Name)"
+check_bad_name "Bad \[Name\]"
+check_bad_name "Bad,Name"
+check_bad_name "Bad;Name"
+
+# Check that there are no handlers registered.
+gdb_test_no_output "info missing-debug-handlers" \
+    "check no handlers are registered"
+
+# Check we can use the enable/disable commands where there are no handlers
+# registered.
+gdb_test "enable missing-debug-handler foo" \
+    "^0 missing debug handlers enabled"
+gdb_test "disable missing-debug-handler foo" \
+    "^0 missing debug handlers disabled"
+
+# Grab the current program space object, used for registering handler later.
+gdb_test_no_output "python pspace = gdb.selected_inferior().progspace"
+
+# Now register some handlers.
+foreach hspec {{\"Foo\" None}
+    {\"-bar\" None}
+    {\"baz-\" pspace}
+    {\"abc-def\" pspace}} {
+    lassign $hspec name locus
+    gdb_test "python register($name, $locus)"
+}
+
+with_test_prefix "all handlers enabled" {
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Current Progspace:" \
+	     "  abc-def" \
+	     "  baz-" \
+	     "Global:" \
+	     "  -bar" \
+	     "  Foo"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def', 'baz-', '-bar', 'Foo']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "disable 'baz-'" {
+    gdb_test "disable missing-debug-handler progspace baz-" \
+	"^1 missing debug handler disabled"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def" \
+	     "  baz- \\\[disabled\\\]" \
+	     "Global:" \
+	     "  -bar" \
+	     "  Foo"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def', '-bar', 'Foo']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "disable 'Foo'" {
+    gdb_test "disable missing-debug-handler .* Foo" \
+	"^1 missing debug handler disabled"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def" \
+	     "  baz- \\\[disabled\\\]" \
+	     "Global:" \
+	     "  -bar" \
+	     "  Foo \\\[disabled\\\]"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def', '-bar']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "disable everything" {
+    gdb_test "disable missing-debug-handler .* .*" \
+	"^2 missing debug handlers disabled"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def \\\[disabled\\\]" \
+	     "  baz- \\\[disabled\\\]" \
+	     "Global:" \
+	     "  -bar \\\[disabled\\\]" \
+	     "  Foo \\\[disabled\\\]"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {[]}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "enable 'abc-def'" {
+    set re [string_to_regexp $binfile]
+
+    gdb_test "enable missing-debug-handler \"$re\" abc-def" \
+	"^1 missing debug handler enabled"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def" \
+	     "  baz- \\\[disabled\\\]" \
+	     "Global:" \
+	     "  -bar \\\[disabled\\\]" \
+	     "  Foo \\\[disabled\\\]"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "enable global handlers" {
+    set re [string_to_regexp $binfile]
+
+    gdb_test "enable missing-debug-handler global" \
+	"^2 missing debug handlers enabled"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def" \
+	     "  baz- \\\[disabled\\\]" \
+	     "Global:" \
+	     "  -bar" \
+	     "  Foo"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def', '-bar', 'Foo']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+# Add handler_obj to the global handler list, and configure it to
+# return False.  We should call all of the program space specific
+# handlers (which return None), and then call handler_obj from the
+# global list, which returns False, at which point we shouldn't call
+# anyone else.
+with_test_prefix "return False handler in progspace list" {
+    gdb_test "enable missing-debug-handler progspace" \
+	"^1 missing debug handler enabled"
+
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(None, handler_obj)" \
+	"register the initial handler"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  abc-def" \
+	     "  baz-" \
+	     "Global:" \
+	     "  handler" \
+	     "  -bar" \
+	     "  Foo"]
+
+    gdb_test_no_output "python handler_obj.set_mode(Mode.RETURN_FALSE)" \
+	"confirgure handler"
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['abc-def', 'baz-', 'handler']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+# Now add handler_obj to the current program space's handler list.  We
+# use the same handler object here, that's fine.  We should only see a
+# call to the first handler object in the call log.
+with_test_prefix "return False handler in global list" {
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(pspace, handler_obj)" \
+	"register the initial handler"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  handler" \
+	     "  abc-def" \
+	     "  baz-" \
+	     "Global:" \
+	     "  handler" \
+	     "  -bar" \
+	     "  Foo"]
+
+    gdb_file_cmd $binfile
+    gdb_test "python print(handler_call_log)" \
+	[string_to_regexp {['handler']}]
+    gdb_test_no_output "python handler_call_log = \[\]" \
+	"reset call log"
+}
+
+with_test_prefix "check handler replacement" {
+    # First, check we can have the same name appear in both program
+    # space and global lists without giving an error.
+    gdb_test_no_output "python register(\"Foo\", pspace)"
+
+    gdb_test "info missing-debug-handlers" \
+	[multi_line \
+	     "Progspace \[^\r\n\]+:" \
+	     "  Foo" \
+	     "  handler" \
+	     "  abc-def" \
+	     "  baz-" \
+	     "Global:" \
+	     "  handler" \
+	     "  -bar" \
+	     "  Foo"]
+
+    # Now check that we get an error if we try to add a handler with
+    # the same name.
+    gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
+	[multi_line \
+	     "RuntimeError: Handler Foo already exists\\." \
+	     "Error while executing Python code\\."]
+
+    gdb_test "python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=pspace)" \
+	[multi_line \
+	     "RuntimeError: Handler Foo already exists\\." \
+	     "Error while executing Python code\\."]
+
+    # And now try again, but this time with 'replace=True', we
+    # shouldn't get an error in this case.
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)"
+
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(handler=log_handler(\"Foo\"), locus=None, replace=True)"
+
+    # Now disable a handler and check we still need to use 'replace=True'.
+    gdb_test "disable missing-debug-handler progspace Foo" \
+	"^1 missing debug handler disabled"
+
+    gdb_test "python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"))" \
+	[multi_line \
+	     "RuntimeError: Handler Foo already exists\\." \
+	     "Error while executing Python code\\."] \
+	"still get an error when handler is disabled"
+
+    gdb_test_no_output \
+	"python gdb.missing_debug.register_handler(pspace, log_handler(\"Foo\"), replace=True)" \
+	"can replace a disabled handler"
+}
diff --git a/gdb/testsuite/gdb.python/py-missing-debug.py b/gdb/testsuite/gdb.python/py-missing-debug.py
new file mode 100644
index 00000000000..720648e18f0
--- /dev/null
+++ b/gdb/testsuite/gdb.python/py-missing-debug.py
@@ -0,0 +1,120 @@
+# Copyright (C) 2023 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import gdb
+from gdb.missing_debug import MissingDebugHandler
+from enum import Enum
+import os
+
+# A global log that is filled in by instances of the LOG_HANDLER class
+# when they are called.
+handler_call_log = []
+
+
+class Mode(Enum):
+    RETURN_NONE = 0
+    RETURN_TRUE = 1
+    RETURN_FALSE = 2
+    RETURN_STRING = 3
+
+
+class handler(MissingDebugHandler):
+    def __init__(self):
+        super().__init__("handler")
+        self._call_count = 0
+        self._mode = Mode.RETURN_NONE
+
+    def __call__(self, objfile):
+        global handler_call_log
+        handler_call_log.append(self.name)
+        self._call_count += 1
+        if self._mode == Mode.RETURN_NONE:
+            return None
+
+        if self._mode == Mode.RETURN_TRUE:
+            os.rename(self._src, self._dest)
+            return True
+
+        if self._mode == Mode.RETURN_FALSE:
+            return False
+
+        if self._mode == Mode.RETURN_STRING:
+            return self._dest
+
+        assert False
+
+    @property
+    def call_count(self):
+        """Return a count, the number of calls to __call__ since the last
+        call to set_mode.
+        """
+        return self._call_count
+
+    def set_mode(self, mode, *args):
+        self._call_count = 0
+        self._mode = mode
+
+        if mode == Mode.RETURN_NONE:
+            assert len(args) == 0
+            return
+
+        if mode == Mode.RETURN_TRUE:
+            assert len(args) == 2
+            self._src = args[0]
+            self._dest = args[1]
+            return
+
+        if mode == Mode.RETURN_FALSE:
+            assert len(args) == 0
+            return
+
+        if mode == Mode.RETURN_STRING:
+            assert len(args) == 1
+            self._dest = args[0]
+            return
+
+        assert False
+
+
+class exception_handler(MissingDebugHandler):
+    def __init__(self):
+        super().__init__("exception_handler")
+        self.exception_type = None
+
+    def __call__(self, objfile):
+        global handler_call_log
+        handler_call_log.append(self.name)
+        assert self.exception_type is not None
+        raise self.exception_type("message")
+
+
+class log_handler(MissingDebugHandler):
+    def __call__(self, objfile):
+        global handler_call_log
+        handler_call_log.append(self.name)
+        return None
+
+
+# A basic helper function, this keeps lines shorter in the TCL script.
+def register(name, locus=None):
+    gdb.missing_debug.register_handler(locus, log_handler(name))
+
+
+# Create instances of the handlers, but don't install any.  We install
+# these as needed from the TCL script.
+rhandler = exception_handler()
+handler_obj = handler()
+
+print("Success")
-- 
2.25.4


^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCHv2 5/5] gdb: implement missing debug handler hook for Python
  2023-11-08 15:48   ` [PATCHv2 5/5] gdb: implement missing debug handler hook for Python Andrew Burgess
@ 2023-11-12 22:38     ` Tom Tromey
  2023-11-15 12:36     ` Tom de Vries
  1 sibling, 0 replies; 30+ messages in thread
From: Tom Tromey @ 2023-11-12 22:38 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Tom Tromey, Eli Zaretskii

>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:

Andrew> +enable missing-debug-handler LOCUS HANDLER
Andrew> +disable missing-debug-handler LOCUS HANDLER
Andrew> +  Enable or disable a missing debug handler with a name matching the
Andrew> +  regular expression HANDLER, in LOCUS.

I've long thought that the use of regexps here was a mistake.
Completion is nicer than being able to use a regexp here, IMO.  However,
I guess all these other commands do it, so I suppose we should continue.

Tom

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCHv2 0/5] New Python hook for missing debug information
  2023-11-08 15:48 ` [PATCHv2 0/5] New Python hook for missing debug information Andrew Burgess
                     ` (4 preceding siblings ...)
  2023-11-08 15:48   ` [PATCHv2 5/5] gdb: implement missing debug handler hook for Python Andrew Burgess
@ 2023-11-12 22:39   ` Tom Tromey
  2023-11-13 16:04     ` Andrew Burgess
  2023-11-12 22:40   ` Tom Tromey
  6 siblings, 1 reply; 30+ messages in thread
From: Tom Tromey @ 2023-11-12 22:39 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Tom Tromey

>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:

Andrew> This series adds a new Python hook to GDB, this hook allows a user to
Andrew> register a handler object which will be called whenever GDB fails to
Andrew> locate any debug information for an objfile it is loading.  These
Andrew> handler objects can (potentially) perform host or project specific
Andrew> actions to help GDB locate the missing debug information.

Do you have some specific scenario in mind for this?  I'm wondering when
this would be useful, and when Objfile.add_separate_debug_file wouldn't
be sufficient.

Anyway the patches all seem fine to me.
Approved-By: Tom Tromey <tom@tromey.com>

Tom

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCHv2 0/5] New Python hook for missing debug information
  2023-11-08 15:48 ` [PATCHv2 0/5] New Python hook for missing debug information Andrew Burgess
                     ` (5 preceding siblings ...)
  2023-11-12 22:39   ` [PATCHv2 0/5] New Python hook for missing debug information Tom Tromey
@ 2023-11-12 22:40   ` Tom Tromey
  6 siblings, 0 replies; 30+ messages in thread
From: Tom Tromey @ 2023-11-12 22:40 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Tom Tromey

>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:

Forgot to mention...

Andrew>   - Make use of std::tie in patch #3,

I thought I'd be pretty meh on std::tie but I was surprised to find it
reads pretty nicely.

Tom

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCHv2 0/5] New Python hook for missing debug information
  2023-11-12 22:39   ` [PATCHv2 0/5] New Python hook for missing debug information Tom Tromey
@ 2023-11-13 16:04     ` Andrew Burgess
  2023-11-13 17:18       ` Tom Tromey
  0 siblings, 1 reply; 30+ messages in thread
From: Andrew Burgess @ 2023-11-13 16:04 UTC (permalink / raw)
  To: Tom Tromey; +Cc: gdb-patches, Tom Tromey

Tom Tromey <tom@tromey.com> writes:

>>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:
>
> Andrew> This series adds a new Python hook to GDB, this hook allows a user to
> Andrew> register a handler object which will be called whenever GDB fails to
> Andrew> locate any debug information for an objfile it is loading.  These
> Andrew> handler objects can (potentially) perform host or project specific
> Andrew> actions to help GDB locate the missing debug information.
>
> Do you have some specific scenario in mind for this?  I'm wondering when
> this would be useful, and when Objfile.add_separate_debug_file wouldn't
> be sufficient.

Fedora and RHEL GDB has, for many years now, carried a local patch that
will suggest to the user a suitable RPM to install should GDB encounter
some missing debug information.

To some degree, maybe even mostly, this functionality has been replaced
by debuginfod, but there are still some situations where we're finding
this useful.

So, I'm trying to replace the RH local (C++ patch) with a Python
extension.

For RPM suggestion, Python only really need to be notified when GDB
encounters some missing debug info, and initially I did implement this
as a new event.

But when I looked at it, I didn't think this added much complexity to
core GDB, and given we don't really like to change the Python API, I
figure I might as well go with the fuller featured choice.

>
> Anyway the patches all seem fine to me.
> Approved-By: Tom Tromey <tom@tromey.com>

Thanks,
Andrew


^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCHv2 0/5] New Python hook for missing debug information
  2023-11-13 16:04     ` Andrew Burgess
@ 2023-11-13 17:18       ` Tom Tromey
  0 siblings, 0 replies; 30+ messages in thread
From: Tom Tromey @ 2023-11-13 17:18 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: Tom Tromey, gdb-patches

>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:

Andrew> Fedora and RHEL GDB has, for many years now, carried a local patch that
Andrew> will suggest to the user a suitable RPM to install should GDB encounter
Andrew> some missing debug information.

Oh yeah, I forgot about that.

Andrew> For RPM suggestion, Python only really need to be notified when GDB
Andrew> encounters some missing debug info, and initially I did implement this
Andrew> as a new event.

Andrew> But when I looked at it, I didn't think this added much complexity to
Andrew> core GDB, and given we don't really like to change the Python API, I
Andrew> figure I might as well go with the fuller featured choice.

Thanks for the explanation.

Tom

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCHv2 5/5] gdb: implement missing debug handler hook for Python
  2023-11-08 15:48   ` [PATCHv2 5/5] gdb: implement missing debug handler hook for Python Andrew Burgess
  2023-11-12 22:38     ` Tom Tromey
@ 2023-11-15 12:36     ` Tom de Vries
  2023-11-16 10:59       ` Andrew Burgess
  1 sibling, 1 reply; 30+ messages in thread
From: Tom de Vries @ 2023-11-15 12:36 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches; +Cc: Tom Tromey, Eli Zaretskii

On 11/8/23 16:48, Andrew Burgess wrote:
> + for ch in name: + if not ch.isascii() or not (ch.isalnum() or ch in 
> "_-"): + raise ValueError("invalid character '%s' in handler name: %s" % 
> (ch, name)) + +

on openSUSE leap 15.4 with python 3.6 I run into:
...
(gdb) PASS: gdb.python/py-missing-debug.exp: initial checks: debug info 
no longer found
source 
/data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py^M
Traceback (most recent call last):^M
   File 
"/data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py", 
line 117, in <module>^M
     rhandler = exception_handler()^M
   File 
"/data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py", 
line 93, in __init__^M
     super().__init__("exception_handler")^M
   File 
"/data/vries/gdb/leap-15-4/build/gdb/data-directory/python/gdb/missing_debug.py", 
line 70, in __init__^M
     _validate_name(name)^M
   File 
"/data/vries/gdb/leap-15-4/build/gdb/data-directory/python/gdb/missing_debug.py", 
line 41, in _validate_name^M
     if not ch.isascii() or not (ch.isalnum() or ch in "_-"):^M
AttributeError: 'str' object has no attribute 'isascii'^M
(gdb) FAIL: gdb.python/py-missing-debug.exp: source python script
...

Thanks,
- Tom

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCHv2 5/5] gdb: implement missing debug handler hook for Python
  2023-11-15 12:36     ` Tom de Vries
@ 2023-11-16 10:59       ` Andrew Burgess
  2023-11-16 11:16         ` Tom de Vries
  2023-11-16 15:26         ` Tom Tromey
  0 siblings, 2 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-11-16 10:59 UTC (permalink / raw)
  To: Tom de Vries, gdb-patches; +Cc: Tom Tromey, Eli Zaretskii

Tom de Vries <tdevries@suse.de> writes:

> On 11/8/23 16:48, Andrew Burgess wrote:
>> + for ch in name: + if not ch.isascii() or not (ch.isalnum() or ch in 
>> "_-"): + raise ValueError("invalid character '%s' in handler name: %s" % 
>> (ch, name)) + +
>
> on openSUSE leap 15.4 with python 3.6 I run into:
> ...
> (gdb) PASS: gdb.python/py-missing-debug.exp: initial checks: debug info 
> no longer found
> source 
> /data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py^M
> Traceback (most recent call last):^M
>    File 
> "/data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py", 
> line 117, in <module>^M
>      rhandler = exception_handler()^M
>    File 
> "/data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py", 
> line 93, in __init__^M
>      super().__init__("exception_handler")^M
>    File 
> "/data/vries/gdb/leap-15-4/build/gdb/data-directory/python/gdb/missing_debug.py", 
> line 70, in __init__^M
>      _validate_name(name)^M
>    File 
> "/data/vries/gdb/leap-15-4/build/gdb/data-directory/python/gdb/missing_debug.py", 
> line 41, in _validate_name^M
>      if not ch.isascii() or not (ch.isalnum() or ch in "_-"):^M
> AttributeError: 'str' object has no attribute 'isascii'^M
> (gdb) FAIL: gdb.python/py-missing-debug.exp: source python script

Sorry about this Tom.  Turns out str.isascii() (and friends) were only
added in 3.7.

I believe the patch below should fix things.  Local testing here with
3.6 suggests it is fine.  I'll push this tomorrow if I don't get any
feedback before then.

Thanks,
Andrew

---

commit 32ce7ac847349d2aa15e51878119a4e9df4c331f
Author: Andrew Burgess <aburgess@redhat.com>
Date:   Thu Nov 16 10:53:34 2023 +0000

    gdb/python: remove use of str.isascii()
    
    This commit:
    
      commit 8f6c452b5a4e50fbb55ff1d13328b392ad1fd416
      Date:   Sun Oct 15 22:48:42 2023 +0100
    
          gdb: implement missing debug handler hook for Python
    
    introduced a use of str.isascii(), which was only added in Python 3.7.
    
    This commit switches to use curses.ascii.isascii(), as this was
    available in 3.6.
    
    The same is true for str.isalnum(), which is replaced with
    curses.ascii.isalnum().
    
    There should be no user visible changes after this commit.

diff --git a/gdb/python/lib/gdb/missing_debug.py b/gdb/python/lib/gdb/missing_debug.py
index 42d69858f96..bb233a66c73 100644
--- a/gdb/python/lib/gdb/missing_debug.py
+++ b/gdb/python/lib/gdb/missing_debug.py
@@ -18,6 +18,7 @@ MissingDebugHandler base class, and register_handler function.
 """
 
 import gdb
+from curses.ascii import isascii, isalnum
 
 
 def _validate_name(name):
@@ -38,7 +39,7 @@ def _validate_name(name):
                     name.
     """
     for ch in name:
-        if not ch.isascii() or not (ch.isalnum() or ch in "_-"):
+        if not isascii(ch) or not (isalnum(ch) or ch in "_-"):
             raise ValueError("invalid character '%s' in handler name: %s" % (ch, name))
 
 


^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCHv2 5/5] gdb: implement missing debug handler hook for Python
  2023-11-16 10:59       ` Andrew Burgess
@ 2023-11-16 11:16         ` Tom de Vries
  2023-11-16 17:21           ` Andrew Burgess
  2023-11-16 15:26         ` Tom Tromey
  1 sibling, 1 reply; 30+ messages in thread
From: Tom de Vries @ 2023-11-16 11:16 UTC (permalink / raw)
  To: Andrew Burgess, gdb-patches; +Cc: Tom Tromey, Eli Zaretskii

On 11/16/23 11:59, Andrew Burgess wrote:
> Tom de Vries <tdevries@suse.de> writes:
> 
>> On 11/8/23 16:48, Andrew Burgess wrote:
>>> + for ch in name: + if not ch.isascii() or not (ch.isalnum() or ch in
>>> "_-"): + raise ValueError("invalid character '%s' in handler name: %s" %
>>> (ch, name)) + +
>>
>> on openSUSE leap 15.4 with python 3.6 I run into:
>> ...
>> (gdb) PASS: gdb.python/py-missing-debug.exp: initial checks: debug info
>> no longer found
>> source
>> /data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py^M
>> Traceback (most recent call last):^M
>>     File
>> "/data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py",
>> line 117, in <module>^M
>>       rhandler = exception_handler()^M
>>     File
>> "/data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py",
>> line 93, in __init__^M
>>       super().__init__("exception_handler")^M
>>     File
>> "/data/vries/gdb/leap-15-4/build/gdb/data-directory/python/gdb/missing_debug.py",
>> line 70, in __init__^M
>>       _validate_name(name)^M
>>     File
>> "/data/vries/gdb/leap-15-4/build/gdb/data-directory/python/gdb/missing_debug.py",
>> line 41, in _validate_name^M
>>       if not ch.isascii() or not (ch.isalnum() or ch in "_-"):^M
>> AttributeError: 'str' object has no attribute 'isascii'^M
>> (gdb) FAIL: gdb.python/py-missing-debug.exp: source python script
> 
> Sorry about this Tom.  Turns out str.isascii() (and friends) were only
> added in 3.7.
> 

Np, thanks for fixing this.

> I believe the patch below should fix things.  Local testing here with
> 3.6 suggests it is fine.  I'll push this tomorrow if I don't get any
> feedback before then.

With this patch the test-case passes cleanly for me:
...
Running 
/data/vries/gdb/src/gdb/testsuite/gdb.python/py-missing-debug.exp ...

		=== gdb Summary ===

# of expected passes		83
...
so, LGTM.

Thanks,
- Tom

> 
> Thanks,
> Andrew
> 
> ---
> 
> commit 32ce7ac847349d2aa15e51878119a4e9df4c331f
> Author: Andrew Burgess <aburgess@redhat.com>
> Date:   Thu Nov 16 10:53:34 2023 +0000
> 
>      gdb/python: remove use of str.isascii()
>      
>      This commit:
>      
>        commit 8f6c452b5a4e50fbb55ff1d13328b392ad1fd416
>        Date:   Sun Oct 15 22:48:42 2023 +0100
>      
>            gdb: implement missing debug handler hook for Python
>      
>      introduced a use of str.isascii(), which was only added in Python 3.7.
>      
>      This commit switches to use curses.ascii.isascii(), as this was
>      available in 3.6.
>      
>      The same is true for str.isalnum(), which is replaced with
>      curses.ascii.isalnum().
>      
>      There should be no user visible changes after this commit.
> 
> diff --git a/gdb/python/lib/gdb/missing_debug.py b/gdb/python/lib/gdb/missing_debug.py
> index 42d69858f96..bb233a66c73 100644
> --- a/gdb/python/lib/gdb/missing_debug.py
> +++ b/gdb/python/lib/gdb/missing_debug.py
> @@ -18,6 +18,7 @@ MissingDebugHandler base class, and register_handler function.
>   """
>   
>   import gdb
> +from curses.ascii import isascii, isalnum
>   
>   
>   def _validate_name(name):
> @@ -38,7 +39,7 @@ def _validate_name(name):
>                       name.
>       """
>       for ch in name:
> -        if not ch.isascii() or not (ch.isalnum() or ch in "_-"):
> +        if not isascii(ch) or not (isalnum(ch) or ch in "_-"):
>               raise ValueError("invalid character '%s' in handler name: %s" % (ch, name))
>   
>   
> 


^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCHv2 5/5] gdb: implement missing debug handler hook for Python
  2023-11-16 10:59       ` Andrew Burgess
  2023-11-16 11:16         ` Tom de Vries
@ 2023-11-16 15:26         ` Tom Tromey
  1 sibling, 0 replies; 30+ messages in thread
From: Tom Tromey @ 2023-11-16 15:26 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: Tom de Vries, gdb-patches, Tom Tromey, Eli Zaretskii

>>>>> "Andrew" == Andrew Burgess <aburgess@redhat.com> writes:

Andrew>  import gdb
Andrew> +from curses.ascii import isascii, isalnum
 
Maybe a comment mentioning Python 3.6 would be good here, so we remove
it if we ever stop supporting that version.

Tom

^ permalink raw reply	[flat|nested] 30+ messages in thread

* Re: [PATCHv2 5/5] gdb: implement missing debug handler hook for Python
  2023-11-16 11:16         ` Tom de Vries
@ 2023-11-16 17:21           ` Andrew Burgess
  0 siblings, 0 replies; 30+ messages in thread
From: Andrew Burgess @ 2023-11-16 17:21 UTC (permalink / raw)
  To: Tom de Vries, gdb-patches; +Cc: Tom Tromey, Eli Zaretskii

Tom de Vries <tdevries@suse.de> writes:

> On 11/16/23 11:59, Andrew Burgess wrote:
>> Tom de Vries <tdevries@suse.de> writes:
>> 
>>> On 11/8/23 16:48, Andrew Burgess wrote:
>>>> + for ch in name: + if not ch.isascii() or not (ch.isalnum() or ch in
>>>> "_-"): + raise ValueError("invalid character '%s' in handler name: %s" %
>>>> (ch, name)) + +
>>>
>>> on openSUSE leap 15.4 with python 3.6 I run into:
>>> ...
>>> (gdb) PASS: gdb.python/py-missing-debug.exp: initial checks: debug info
>>> no longer found
>>> source
>>> /data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py^M
>>> Traceback (most recent call last):^M
>>>     File
>>> "/data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py",
>>> line 117, in <module>^M
>>>       rhandler = exception_handler()^M
>>>     File
>>> "/data/vries/gdb/leap-15-4/build/gdb/testsuite/outputs/gdb.python/py-missing-debug/py-missing-debug.py",
>>> line 93, in __init__^M
>>>       super().__init__("exception_handler")^M
>>>     File
>>> "/data/vries/gdb/leap-15-4/build/gdb/data-directory/python/gdb/missing_debug.py",
>>> line 70, in __init__^M
>>>       _validate_name(name)^M
>>>     File
>>> "/data/vries/gdb/leap-15-4/build/gdb/data-directory/python/gdb/missing_debug.py",
>>> line 41, in _validate_name^M
>>>       if not ch.isascii() or not (ch.isalnum() or ch in "_-"):^M
>>> AttributeError: 'str' object has no attribute 'isascii'^M
>>> (gdb) FAIL: gdb.python/py-missing-debug.exp: source python script
>> 
>> Sorry about this Tom.  Turns out str.isascii() (and friends) were only
>> added in 3.7.
>> 
>
> Np, thanks for fixing this.
>
>> I believe the patch below should fix things.  Local testing here with
>> 3.6 suggests it is fine.  I'll push this tomorrow if I don't get any
>> feedback before then.
>
> With this patch the test-case passes cleanly for me:
> ...
> Running 
> /data/vries/gdb/src/gdb/testsuite/gdb.python/py-missing-debug.exp ...
>
> 		=== gdb Summary ===
>
> # of expected passes		83
> ...
> so, LGTM.

I've gone ahead and pushed this.

Thanks,
Andrew


^ permalink raw reply	[flat|nested] 30+ messages in thread

end of thread, other threads:[~2023-11-16 17:21 UTC | newest]

Thread overview: 30+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-10-18 10:53 [PATCH 0/5] New Python hook for missing debug information Andrew Burgess
2023-10-18 10:53 ` [PATCH 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c Andrew Burgess
2023-10-20 17:35   ` Tom Tromey
2023-10-24 11:59     ` Andrew Burgess
2023-10-18 10:53 ` [PATCH 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths Andrew Burgess
2023-10-18 13:18   ` Jon Turney
2023-10-18 17:25     ` Andrew Burgess
2023-10-18 10:53 ` [PATCH 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file Andrew Burgess
2023-10-20 17:50   ` Tom Tromey
2023-10-24 12:03     ` Andrew Burgess
2023-11-08 15:22     ` Andrew Burgess
2023-10-18 10:53 ` [PATCH 4/5] gdb: add an extension language hook for missing debug info Andrew Burgess
2023-10-18 10:53 ` [PATCH 5/5] gdb: implement missing debug handler hook for Python Andrew Burgess
2023-10-18 12:08   ` Eli Zaretskii
2023-11-08 15:48 ` [PATCHv2 0/5] New Python hook for missing debug information Andrew Burgess
2023-11-08 15:48   ` [PATCHv2 1/5] gdb/coffread: bring separate debug file logic into line with elfread.c Andrew Burgess
2023-11-08 15:48   ` [PATCHv2 2/5] gdb: merge debug symbol file lookup code from coffread & elfread paths Andrew Burgess
2023-11-08 15:48   ` [PATCHv2 3/5] gdb: refactor objfile::find_and_add_separate_symbol_file Andrew Burgess
2023-11-08 15:48   ` [PATCHv2 4/5] gdb: add an extension language hook for missing debug info Andrew Burgess
2023-11-08 15:48   ` [PATCHv2 5/5] gdb: implement missing debug handler hook for Python Andrew Burgess
2023-11-12 22:38     ` Tom Tromey
2023-11-15 12:36     ` Tom de Vries
2023-11-16 10:59       ` Andrew Burgess
2023-11-16 11:16         ` Tom de Vries
2023-11-16 17:21           ` Andrew Burgess
2023-11-16 15:26         ` Tom Tromey
2023-11-12 22:39   ` [PATCHv2 0/5] New Python hook for missing debug information Tom Tromey
2023-11-13 16:04     ` Andrew Burgess
2023-11-13 17:18       ` Tom Tromey
2023-11-12 22:40   ` Tom Tromey

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).