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