[3/4,v5] gdb/debuginfod: Support on-demand debuginfo downloading

Message ID 20240117154855.2057850-4-amerey@redhat.com
State New
Headers
Series On-demand debuginfo downloading |

Checks

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

Commit Message

Aaron Merey Jan. 17, 2024, 3:48 p.m. UTC
  v5 changes: Some tweaks needed rebase onto
objfile::find_and_add_separate_symbol_file changes in commit 661d98a.
v5 also implements suggestions from Thiago's review:
https://sourceware.org/pipermail/gdb-patches/2023-December/205506.html

At the beginning of a session, gdb may attempt to download debuginfo
for all shared libraries associated with the process or core file
being debugged.  This can be a waste of time and storage space when much
of the debuginfo ends up not being used during the session.

To reduce the gdb's startup latency and to download only the debuginfo
that is really needed, this patch adds on-demand downloading of debuginfo.

'set debuginfo enabled on' now causes gdb to attempt to download a .gdb_index
for each shared library instead of its full debuginfo.  Each corresponding
separate debuginfo will be deferred until gdb needs to expand symtabs
associated with the debuginfo's index.

Because these indices are significantly smaller than their corresponding
debuginfo, this generally reduces the total amount of data gdb downloads.
Reductions of 80%-95% have been observed when debugging large GUI programs.

    (gdb) set debuginfod enabled on
    (gdb) start
    Downloading section .gdb_index for /lib64/libcurl.so.4
    [...]
    1826        client->server_mhandle = curl_multi_init ();
    (gdb) step
    Downloading separate debug info for /lib64/libcurl.so.4
    Downloading separate debug info for [libcurl dwz]
    Downloading source file /usr/src/debug/curl-7.85.0-6.fc37.x86_64/build-full/lib/../../lib/multi.c
    curl_multi_init () at ../../lib/multi.c:457
    457     {
    (gdb)

Some of the key functions below include dwarf2_has_separate_index which
downloads the separate .gdb_index.  If successful, the shared library
objfile owns the index until the separate debug objfile is downloaded
or confirmed to not be available.

read_full_dwarf_from_debuginfod downloads the full debuginfo and
initializes the separate debug objfile.  It is called by functions
such as dwarf2_gdb_index::expand_symtabs_matching and
dwarf2_base_index_functions::find_pc_sect_compunit_symtab when symtab
expansion is required.
---
 gdb/dwarf2/frame.c                         |  13 ++
 gdb/dwarf2/frame.h                         |   4 +
 gdb/dwarf2/index-cache.c                   |  33 ++++
 gdb/dwarf2/index-cache.h                   |  13 ++
 gdb/dwarf2/public.h                        |   7 +
 gdb/dwarf2/read-gdb-index.c                | 125 +++++++++++++--
 gdb/dwarf2/read.c                          | 147 +++++++++++++++++-
 gdb/dwarf2/read.h                          |  10 ++
 gdb/dwarf2/section.c                       |   3 +-
 gdb/elfread.c                              |   3 +-
 gdb/frame.c                                |   7 +
 gdb/objfile-flags.h                        |   4 +
 gdb/objfiles.h                             |  20 +++
 gdb/quick-symbol.h                         |   4 +
 gdb/symfile-debug.c                        |  11 ++
 gdb/symfile.c                              |  13 +-
 gdb/symtab.c                               |  18 ++-
 gdb/testsuite/gdb.debuginfod/libsection1.c |  43 ++++++
 gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
 gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
 gdb/testsuite/gdb.debuginfod/section.exp   | 172 +++++++++++++++++++++
 gdb/testsuite/lib/debuginfod-support.exp   |  27 +++-
 gdb/testsuite/lib/gdb.exp                  |   8 +-
 23 files changed, 725 insertions(+), 26 deletions(-)
 create mode 100644 gdb/testsuite/gdb.debuginfod/libsection1.c
 create mode 100644 gdb/testsuite/gdb.debuginfod/libsection2.c
 create mode 100644 gdb/testsuite/gdb.debuginfod/section.c
 create mode 100644 gdb/testsuite/gdb.debuginfod/section.exp
  

Comments

Andrew Burgess Jan. 18, 2024, 3:34 p.m. UTC | #1
Aaron Merey <amerey@redhat.com> writes:

> v5 changes: Some tweaks needed rebase onto
> objfile::find_and_add_separate_symbol_file changes in commit 661d98a.
> v5 also implements suggestions from Thiago's review:
> https://sourceware.org/pipermail/gdb-patches/2023-December/205506.html
>
> At the beginning of a session, gdb may attempt to download debuginfo
> for all shared libraries associated with the process or core file
> being debugged.  This can be a waste of time and storage space when much
> of the debuginfo ends up not being used during the session.
>
> To reduce the gdb's startup latency and to download only the debuginfo
> that is really needed, this patch adds on-demand downloading of debuginfo.
>
> 'set debuginfo enabled on' now causes gdb to attempt to download a .gdb_index
> for each shared library instead of its full debuginfo.  Each corresponding
> separate debuginfo will be deferred until gdb needs to expand symtabs
> associated with the debuginfo's index.
>
> Because these indices are significantly smaller than their corresponding
> debuginfo, this generally reduces the total amount of data gdb downloads.
> Reductions of 80%-95% have been observed when debugging large GUI programs.
>
>     (gdb) set debuginfod enabled on
>     (gdb) start
>     Downloading section .gdb_index for /lib64/libcurl.so.4
>     [...]
>     1826        client->server_mhandle = curl_multi_init ();
>     (gdb) step
>     Downloading separate debug info for /lib64/libcurl.so.4
>     Downloading separate debug info for [libcurl dwz]
>     Downloading source file /usr/src/debug/curl-7.85.0-6.fc37.x86_64/build-full/lib/../../lib/multi.c
>     curl_multi_init () at ../../lib/multi.c:457
>     457     {
>     (gdb)
>
> Some of the key functions below include dwarf2_has_separate_index which
> downloads the separate .gdb_index.  If successful, the shared library
> objfile owns the index until the separate debug objfile is downloaded
> or confirmed to not be available.
>
> read_full_dwarf_from_debuginfod downloads the full debuginfo and
> initializes the separate debug objfile.  It is called by functions
> such as dwarf2_gdb_index::expand_symtabs_matching and
> dwarf2_base_index_functions::find_pc_sect_compunit_symtab when symtab
> expansion is required.
> ---
>  gdb/dwarf2/frame.c                         |  13 ++
>  gdb/dwarf2/frame.h                         |   4 +
>  gdb/dwarf2/index-cache.c                   |  33 ++++
>  gdb/dwarf2/index-cache.h                   |  13 ++
>  gdb/dwarf2/public.h                        |   7 +
>  gdb/dwarf2/read-gdb-index.c                | 125 +++++++++++++--
>  gdb/dwarf2/read.c                          | 147 +++++++++++++++++-
>  gdb/dwarf2/read.h                          |  10 ++
>  gdb/dwarf2/section.c                       |   3 +-
>  gdb/elfread.c                              |   3 +-
>  gdb/frame.c                                |   7 +
>  gdb/objfile-flags.h                        |   4 +
>  gdb/objfiles.h                             |  20 +++
>  gdb/quick-symbol.h                         |   4 +
>  gdb/symfile-debug.c                        |  11 ++
>  gdb/symfile.c                              |  13 +-
>  gdb/symtab.c                               |  18 ++-
>  gdb/testsuite/gdb.debuginfod/libsection1.c |  43 ++++++
>  gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
>  gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
>  gdb/testsuite/gdb.debuginfod/section.exp   | 172 +++++++++++++++++++++
>  gdb/testsuite/lib/debuginfod-support.exp   |  27 +++-
>  gdb/testsuite/lib/gdb.exp                  |   8 +-
>  23 files changed, 725 insertions(+), 26 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.debuginfod/libsection1.c
>  create mode 100644 gdb/testsuite/gdb.debuginfod/libsection2.c
>  create mode 100644 gdb/testsuite/gdb.debuginfod/section.c
>  create mode 100644 gdb/testsuite/gdb.debuginfod/section.exp
>
> diff --git a/gdb/dwarf2/frame.c b/gdb/dwarf2/frame.c
> index d3d1ecdf1f5..9e290af79f7 100644
> --- a/gdb/dwarf2/frame.c
> +++ b/gdb/dwarf2/frame.c
> @@ -1620,6 +1620,19 @@ set_comp_unit (struct objfile *objfile, struct comp_unit *unit)
>    return dwarf2_frame_bfd_data.set (abfd, unit);
>  }
>  
> +/* See frame.h.  */
> +
> +void
> +dwarf2_clear_frame_data (struct objfile *objfile)
> +{
> +  bfd *abfd = objfile->obfd.get ();
> +
> +  if (gdb_bfd_requires_relocations (abfd))
> +    dwarf2_frame_objfile_data.clear (objfile);
> +  else
> +    dwarf2_frame_bfd_data.clear (abfd);
> +}
> +
>  /* Find the FDE for *PC.  Return a pointer to the FDE, and store the
>     initial location associated with it into *PC.  */
>  
> diff --git a/gdb/dwarf2/frame.h b/gdb/dwarf2/frame.h
> index 5643e557513..2391e313e7c 100644
> --- a/gdb/dwarf2/frame.h
> +++ b/gdb/dwarf2/frame.h
> @@ -238,6 +238,10 @@ void dwarf2_append_unwinders (struct gdbarch *gdbarch);
>  extern const struct frame_base *
>    dwarf2_frame_base_sniffer (frame_info_ptr this_frame);
>  
> +/* Delete OBJFILEs comp_unit.  */
> +
> +extern void dwarf2_clear_frame_data (struct objfile * objfile);
> +
>  /* Compute the DWARF CFA for a frame.  */
>  
>  CORE_ADDR dwarf2_frame_cfa (frame_info_ptr this_frame);
> diff --git a/gdb/dwarf2/index-cache.c b/gdb/dwarf2/index-cache.c
> index 69f70642dc6..8c969ecd590 100644
> --- a/gdb/dwarf2/index-cache.c
> +++ b/gdb/dwarf2/index-cache.c
> @@ -240,6 +240,33 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
>    return {};
>  }
>  
> +/* See index-cache.h.  */
> +
> +gdb::array_view<const gdb_byte>
> +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> +					  std::unique_ptr<index_cache_resource> *resource)
> +{
> +  try
> +    {
> +      /* Try to map that file.  */
> +      index_cache_resource_mmap *mmap_resource
> +	= new index_cache_resource_mmap (index_path);
> +
> +      /* Hand the resource to the caller.  */
> +      resource->reset (mmap_resource);
> +
> +      return gdb::array_view<const gdb_byte>
> +	  ((const gdb_byte *) mmap_resource->mapping.get (),
> +	   mmap_resource->mapping.size ());
> +    }
> +  catch (const gdb_exception_error &except)
> +    {
> +      warning (_("Unable to read %s: %s"), index_path, except.what ());
> +    }
> +
> +  return {};
> +}
> +
>  #else /* !HAVE_SYS_MMAN_H */
>  
>  /* See dwarf-index-cache.h.  This is a no-op on unsupported systems.  */
> @@ -251,6 +278,12 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
>    return {};
>  }
>  
> +gdb::array_view<const gdb_byte>
> +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> +					  std::unique_ptr<index_cache_resource> *resource)
> +{
> +  return {};
> +}
>  #endif
>  
>  /* See dwarf-index-cache.h.  */
> diff --git a/gdb/dwarf2/index-cache.h b/gdb/dwarf2/index-cache.h
> index 023fc86fc89..0d845ebcadd 100644
> --- a/gdb/dwarf2/index-cache.h
> +++ b/gdb/dwarf2/index-cache.h
> @@ -90,6 +90,19 @@ class index_cache
>    lookup_gdb_index (const bfd_build_id *build_id,
>  		    std::unique_ptr<index_cache_resource> *resource);
>  
> +  /* Look for an index file located at INDEX_PATH in the debuginfod cache.
> +     Unlike lookup_gdb_index, this function does not exit early if the
> +     index cache has not been enabled.
> +
> +     If found, return the contents as an array_view and store the underlying
> +     resources (allocated memory, mapped file, etc) in RESOURCE.  The returned
> +     array_view is valid as long as RESOURCE is not destroyed.
> +
> +     If no matching index file is found, return an empty array view.  */
> +  gdb::array_view<const gdb_byte>
> +  lookup_gdb_index_debuginfod (const char *index_path,
> +			       std::unique_ptr<index_cache_resource> *resource);
> +
>    /* Return the number of cache hits.  */
>    unsigned int n_hits () const
>    { return m_n_hits; }
> diff --git a/gdb/dwarf2/public.h b/gdb/dwarf2/public.h
> index efb754b5fe8..4d3776465c3 100644
> --- a/gdb/dwarf2/public.h
> +++ b/gdb/dwarf2/public.h
> @@ -44,4 +44,11 @@ extern bool dwarf2_initialize_objfile
>  
>  extern void dwarf2_build_frame_info (struct objfile *);
>  
> +/* Query debuginfod for the .gdb_index associated with OBJFILE.  If
> +   successful, create an objfile to hold the .gdb_index information
> +   and act as a placeholder until the full debuginfo needs to be
> +   downloaded.  */
> +
> +extern bool dwarf2_has_separate_index (struct objfile *);
> +
>  #endif /* DWARF2_PUBLIC_H */
> diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
> index 4ca44540296..55b04391eb5 100644
> --- a/gdb/dwarf2/read-gdb-index.c
> +++ b/gdb/dwarf2/read-gdb-index.c
> @@ -139,6 +139,8 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>       gdb.dwarf2/gdb-index.exp testcase.  */
>    void dump (struct objfile *objfile) override;
>  
> +  /* Calls do_expand_symtabs_matching and triggers debuginfo downloading
> +     if necessary.  */
>    bool expand_symtabs_matching
>      (struct objfile *objfile,
>       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> @@ -148,8 +150,60 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>       block_search_flags search_flags,
>       domain_enum domain,
>       enum search_domain kind) override;
> +
> +  /* Expand symtabs matching a given symbol or file.  */
> +  bool do_expand_symtabs_matching
> +    (struct objfile *objfile,
> +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> +     const lookup_name_info *lookup_name,
> +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> +     block_search_flags search_flags,
> +     domain_enum domain,
> +     enum search_domain kind);
> +
> +  /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
> +     debuginfo if necessary.  */
> +  void expand_all_symtabs (struct objfile *objfile) override;
> +
> +  /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
> +     debuginfo if necessary.  */
> +  struct symtab *find_last_source_symtab (struct objfile *objfile) override;
>  };
>  
> +void
> +dwarf2_gdb_index::expand_all_symtabs (struct objfile *objfile)
> +{
> +  try
> +    {
> +      dwarf2_base_index_functions::expand_all_symtabs (objfile);
> +    }
> +  catch (const gdb_exception &e)
> +    {
> +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +	exception_print (gdb_stderr, e);
> +      else
> +	read_full_dwarf_from_debuginfod (objfile, this);
> +    }
> +}
> +
> +struct symtab *
> +dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
> +{
> +  try
> +    {
> +      return dwarf2_base_index_functions::find_last_source_symtab (objfile);
> +    }
> +  catch (const gdb_exception &e)
> +    {
> +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +	exception_print (gdb_stderr, e);
> +      else
> +	read_full_dwarf_from_debuginfod (objfile, this);
> +      return nullptr;
> +    }
> +}
> +
>  /* This dumps minimal information about the index.
>     It is called via "mt print objfiles".
>     One use is to verify .gdb_index has been loaded by the
> @@ -268,7 +322,7 @@ dw2_expand_marked_cus
>  }
>  
>  bool
> -dwarf2_gdb_index::expand_symtabs_matching
> +dwarf2_gdb_index::do_expand_symtabs_matching
>      (struct objfile *objfile,
>       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
>       const lookup_name_info *lookup_name,
> @@ -317,6 +371,39 @@ dwarf2_gdb_index::expand_symtabs_matching
>    return result;
>  }
>  
> +bool
> +dwarf2_gdb_index::expand_symtabs_matching
> +    (struct objfile *objfile,
> +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> +     const lookup_name_info *lookup_name,
> +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> +     block_search_flags search_flags,
> +     domain_enum domain,
> +     enum search_domain kind)
> +{
> +  if (objfile->flags & OBJF_READNEVER)

Should be:

  if ((objfile->flags & OBJF_READNEVER) != 0)

> +    return false;
> +
> +  try
> +    {
> +      return do_expand_symtabs_matching (objfile, file_matcher, lookup_name,
> +					 symbol_matcher, expansion_notify,
> +					 search_flags, domain, kind);
> +    }
> +  catch (const gdb_exception &e)
> +    {
> +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +	{
> +	  exception_print (gdb_stderr, e);
> +	  return false;
> +	}
> +
> +      read_full_dwarf_from_debuginfod (objfile, this);
> +      return true;
> +    }
> +}
> +
>  quick_symbol_functions_up
>  mapped_gdb_index::make_quick_functions () const
>  {
> @@ -652,28 +739,32 @@ dwarf2_read_gdb_index
>  
>    /* If there is a .dwz file, read it so we can get its CU list as
>       well.  */
> -  dwz = dwarf2_get_dwz_file (per_bfd);
> -  if (dwz != NULL)
> +  if (get_gdb_index_contents_dwz != nullptr)
>      {
>        mapped_gdb_index dwz_map;
>        const gdb_byte *dwz_types_ignore;
>        offset_type dwz_types_elements_ignore;
> +      dwz = dwarf2_get_dwz_file (per_bfd);
>  
> -      gdb::array_view<const gdb_byte> dwz_index_content
> -	= get_gdb_index_contents_dwz (objfile, dwz);
> -
> -      if (dwz_index_content.empty ())
> -	return 0;
> -
> -      if (!read_gdb_index_from_buffer (bfd_get_filename (dwz->dwz_bfd.get ()),
> -				       1, dwz_index_content, &dwz_map,
> -				       &dwz_list, &dwz_list_elements,
> -				       &dwz_types_ignore,
> -				       &dwz_types_elements_ignore))
> +      if (dwz != nullptr)
>  	{
> -	  warning (_("could not read '.gdb_index' section from %s; skipping"),
> -		   bfd_get_filename (dwz->dwz_bfd.get ()));
> -	  return 0;
> +	  gdb::array_view<const gdb_byte> dwz_index_content
> +	    = get_gdb_index_contents_dwz (objfile, dwz);
> +
> +	  if (dwz_index_content.empty ())
> +	    return 0;
> +
> +	  if (!read_gdb_index_from_buffer (bfd_get_filename
> +					     (dwz->dwz_bfd.get ()),
> +					   1, dwz_index_content, &dwz_map,
> +					   &dwz_list, &dwz_list_elements,
> +					   &dwz_types_ignore,
> +					   &dwz_types_elements_ignore))
> +	    {
> +	      warning (_("could not read '.gdb_index' section from %s; skipping"),
> +		       bfd_get_filename (dwz->dwz_bfd.get ()));
> +		return 0;
> +	    }
>  	}
>      }
>  
> diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> index 76dbeb8d536..8725a2fbcdb 100644
> --- a/gdb/dwarf2/read.c
> +++ b/gdb/dwarf2/read.c
> @@ -35,6 +35,7 @@
>  #include "dwarf2/attribute.h"
>  #include "dwarf2/comp-unit-head.h"
>  #include "dwarf2/cu.h"
> +#include "dwarf2/frame.h"
>  #include "dwarf2/index-cache.h"
>  #include "dwarf2/index-common.h"
>  #include "dwarf2/leb.h"
> @@ -96,6 +97,8 @@
>  #include "split-name.h"
>  #include "gdbsupport/thread-pool.h"
>  #include "run-on-main-thread.h"
> +#include "inferior.h"
> +#include "debuginfod-support.h"
>  
>  /* When == 1, print basic high level tracing messages.
>     When > 1, be more verbose.
> @@ -3003,7 +3006,7 @@ dwarf2_base_index_functions::find_per_cu (dwarf2_per_bfd *per_bfd,
>  }
>  
>  struct compunit_symtab *
> -dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> +dwarf2_base_index_functions::do_find_pc_sect_compunit_symtab
>       (struct objfile *objfile,
>        struct bound_minimal_symbol msymbol,
>        CORE_ADDR pc,
> @@ -3034,6 +3037,32 @@ dwarf2_base_index_functions::find_pc_sect_compunit_symtab
>    return result;
>  }
>  
> +struct compunit_symtab *
> +dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> +     (struct objfile *objfile,
> +      struct bound_minimal_symbol msymbol,
> +      CORE_ADDR pc,
> +      struct obj_section *section,
> +      int warn_if_readin)
> +{
> +  if (objfile->flags & OBJF_READNEVER)

Should be:

  if ((objfile->flags & OBJF_READNEVER) != 0)

> +    return nullptr;
> +
> +  try
> +    {
> +      return do_find_pc_sect_compunit_symtab (objfile, msymbol, pc,
> +					      section, warn_if_readin);
> +    }
> +  catch (const gdb_exception &e)
> +    {
> +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +	exception_print (gdb_stderr, e);
> +      else
> +	read_full_dwarf_from_debuginfod (objfile, this);
> +      return nullptr;
> +    }
> +}
> +
>  void
>  dwarf2_base_index_functions::map_symbol_filenames
>       (struct objfile *objfile,
> @@ -3190,6 +3219,29 @@ get_gdb_index_contents_from_cache_dwz (objfile *obj, dwz_file *dwz)
>    return global_index_cache.lookup_gdb_index (build_id, &dwz->index_cache_res);
>  }
>  
> +/* Query debuginfod for the .gdb_index matching OBJFILE's build-id.  Return the
> +   contents if successful.  */
> +
> +static gdb::array_view<const gdb_byte>
> +get_gdb_index_contents_from_debuginfod (objfile *objfile, dwarf2_per_bfd *per_bfd)
> +{
> +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> +  if (build_id == nullptr)
> +    return {};
> +
> +  gdb::unique_xmalloc_ptr<char> index_path;
> +  scoped_fd fd = debuginfod_section_query (build_id->data, build_id->size,
> +					   bfd_get_filename
> +					     (objfile->obfd.get ()),
> +					   ".gdb_index",
> +					   &index_path);
> +  if (fd.get () < 0)
> +    return {};
> +
> +  return global_index_cache.lookup_gdb_index_debuginfod
> +    (index_path.get (), &per_bfd->index_cache_res);
> +}
> +
>  static quick_symbol_functions_up make_cooked_index_funcs
>       (dwarf2_per_objfile *);
>  
> @@ -3200,7 +3252,8 @@ dwarf2_initialize_objfile (struct objfile *objfile,
>  			   const struct dwarf2_debug_sections *names,
>  			   bool can_copy)
>  {
> -  if (!dwarf2_has_info (objfile, names, can_copy))
> +  if (!dwarf2_has_info (objfile, names, can_copy)
> +      && (objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
>      return false;
>  
>    dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile);
> @@ -3262,6 +3315,16 @@ dwarf2_initialize_objfile (struct objfile *objfile,
>        global_index_cache.hit ();
>        objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
>      }
> +  /* Try to read just a separately downloaded gdb index.  */
> +  else if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED)

Should be:

  (objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0

> +	   && dwarf2_read_gdb_index (per_objfile,
> +				     get_gdb_index_contents_from_debuginfod,
> +				     nullptr))
> +    {
> +      dwarf_read_debug_printf ("found .gdb_index from debuginfod");
> +      objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
> +      objfile->qf.begin ()->get ()->from_separate_index = true;
> +    }
>    else
>      {
>        global_index_cache.miss ();
> @@ -3270,6 +3333,86 @@ dwarf2_initialize_objfile (struct objfile *objfile,
>    return true;
>  }
>  
> +/* See read.h.  */
> +
> +void
> +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> +				 dwarf2_base_index_functions *fncs)
> +{
> +  gdb_assert (objfile->flags & OBJF_DOWNLOAD_DEFERRED);

Should be:

  gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);

OK, I'm going to stop commenting on these now, but there's a few more of
them in the rest of the patch.  GDB style is not to rely on implicit
int -> bool conversion.

> +
> +  SCOPE_EXIT { objfile->remove_deferred_status (); };
> +
> +  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> +  const char *filename;
> +  gdb_bfd_ref_ptr debug_bfd;
> +  gdb::unique_xmalloc_ptr<char> symfile_path;
> +  scoped_fd fd;

Of these, filename, debug_bfd, and fd can be declared where they are
first defined.  You can probably move symfile_path closer to where it's
used too.

> +
> +  if (build_id == nullptr)
> +    return;
> +
> +  filename = bfd_get_filename (objfile->obfd.get ());
> +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> +				   filename, &symfile_path);
> +  if (fd.get () < 0)
> +    return;
> +
> +  /* Separate debuginfo successfully retrieved from server.  */
> +  debug_bfd = symfile_bfd_open (symfile_path.get ());
> +  if (debug_bfd == nullptr
> +      || !build_id_verify (debug_bfd.get (), build_id->size, build_id->data))
> +    {
> +      warning (_("File \"%s\" from debuginfod cannot be opened as bfd"),
> +	       filename);
> +      return;
> +    }
> +
> +  /* Clear frame data so it can be recalculated using DWARF.  */
> +  dwarf2_clear_frame_data (objfile);
> +
> +  /* This may also trigger a dwz download.  */
> +  symbol_file_add_separate (debug_bfd, symfile_path.get (),
> +			    current_inferior ()->symfile_flags, objfile);
> +}
> +
> +/* See public.h.  */
> +
> +bool
> +dwarf2_has_separate_index (struct objfile *objfile)
> +{
> +  if (objfile->flags & OBJF_DOWNLOAD_DEFERRED)
> +    return true;
> +  if (objfile->flags & OBJF_MAINLINE)
> +    return false;

Flag checks missing '!= 0' again.


> +  if (!IS_DIR_SEPARATOR (*objfile_filename (objfile)))
> +    return false;
> +
> +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> +
> +  if (build_id == nullptr)
> +    return false;
> +
> +  gdb::unique_xmalloc_ptr<char> index_path;
> +  scoped_fd fd = debuginfod_section_query (build_id->data,
> +					   build_id->size,
> +					   bfd_get_filename
> +					     (objfile->obfd.get ()),
> +					   ".gdb_index",
> +					   &index_path);
> +
> +  if (fd.get () < 0)
> +    return false;
> +
> +  /* We found a separate .gdb_index file so a separate debuginfo file
> +     should exist, but we don't want to download it until necessary.
> +     Attach the index to this objfile and defer the debuginfo download
> +     until gdb needs to expand symtabs referenced by the index.  */
> +  objfile->flags |= OBJF_DOWNLOAD_DEFERRED;
> +  dwarf2_initialize_objfile (objfile);
> +  return true;
> +}
> +
>  
>  
>  /* Find the base address of the compilation unit for range lists and
> diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> index d6631d45a54..8b6f1864458 100644
> --- a/gdb/dwarf2/read.h
> +++ b/gdb/dwarf2/read.h
> @@ -900,6 +900,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
>       CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
>         override;
>  
> +  struct compunit_symtab *do_find_pc_sect_compunit_symtab
> +    (struct objfile *objfile, struct bound_minimal_symbol msymbol,
> +     CORE_ADDR pc, struct obj_section *section, int warn_if_readin);
> +
>    struct compunit_symtab *find_compunit_symtab_by_address
>      (struct objfile *objfile, CORE_ADDR address) override
>    {
> @@ -972,4 +976,10 @@ extern void create_all_units (dwarf2_per_objfile *per_objfile);
>  
>  extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
>  
> +/* If OBJFILE contains information from a separately downloaded .gdb_index,
> +   attempt to download the full debuginfo.  */
> +
> +extern void read_full_dwarf_from_debuginfod (struct objfile *,
> +					     dwarf2_base_index_functions *);
> +
>  #endif /* DWARF2READ_H */
> diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
> index 1235f293f45..b674103c72f 100644
> --- a/gdb/dwarf2/section.c
> +++ b/gdb/dwarf2/section.c
> @@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
>        section = get_containing_section ();
>        gdb_assert (!section->is_virtual);
>      }
> -  gdb_assert (section->s.section != nullptr);
> +  if (section->s.section == nullptr)
> +    error (_("Can't find owner of DWARF section."));

This seems like an interesting change.  There's no test that triggers
this case, but I guess you ran into this at some point.  Could you
explain more about what's going on here?

>    return section->s.section->owner;
>  }
>  
> diff --git a/gdb/elfread.c b/gdb/elfread.c
> index c5ba8837217..764e991dfc2 100644
> --- a/gdb/elfread.c
> +++ b/gdb/elfread.c
> @@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
>  	   && objfile->separate_debug_objfile_backlink == NULL)
>      {
>        if (objfile->find_and_add_separate_symbol_file (symfile_flags))
> -	gdb_assert (objfile->separate_debug_objfile != nullptr);
> +	gdb_assert (objfile->separate_debug_objfile != nullptr
> +		    || objfile->flags & OBJF_DOWNLOAD_DEFERRED);
>        else
>  	has_dwarf2 = false;
>      }
> diff --git a/gdb/frame.c b/gdb/frame.c
> index 41003cc7771..d3409c8fd97 100644
> --- a/gdb/frame.c
> +++ b/gdb/frame.c
> @@ -1889,6 +1889,13 @@ get_selected_frame (const char *message)
>  	error (("%s"), message);
>  
>        lookup_selected_frame (selected_frame_id, selected_frame_level);
> +
> +      /* It is possible for lookup_selected_frame to cause a new objfile
> +	 to be loaded.  Some objfile observers may choose to clear
> +	 selected_frame when an objfile is loaded.  Work around this by
> +	 calling lookup_selected_frame again if the first try failed.  */
> +      if (selected_frame == nullptr)
> +	lookup_selected_frame (selected_frame_id, selected_frame_level);

I notice this case wasn't tested -- I added an abort here and reran the
test, and didn't see it hit.

I ask because this seems both interesting, and this doesn't feel like
the right solution... As a minimum you'd need a comment on
lookup_selected_frame that says "hey, you will need to call this
function twice to guarantee that it did what it promised".  So then I'd
suggest we should just rename lookup_selected_frame to
lookup_selected_frame_1, and have lookup_selected_frame be a wrapper
which does the double lookup.

But I was kind of hoping that we'd have a test that hit this case so I
could look at the stack trace and better understand what exactly is
happening when the selected frame is cleared.

>      }
>    /* There is always a frame.  */
>    gdb_assert (selected_frame != NULL);
> diff --git a/gdb/objfile-flags.h b/gdb/objfile-flags.h
> index 74aea1a88d3..3f922d9e84f 100644
> --- a/gdb/objfile-flags.h
> +++ b/gdb/objfile-flags.h
> @@ -56,6 +56,10 @@ enum objfile_flag : unsigned
>      /* User requested that we do not read this objfile's symbolic
>         information.  */
>      OBJF_READNEVER = 1 << 6,
> +
> +    /* A separate .gdb_index has been downloaded for this objfile.
> +       Debuginfo for this objfile can be downloaded when required.  */
> +    OBJF_DOWNLOAD_DEFERRED = 1 << 7,
>    };
>  
>  DEF_ENUM_FLAGS_TYPE (enum objfile_flag, objfile_flags);
> diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> index 34b3dac0b26..69222092b61 100644
> --- a/gdb/objfiles.h
> +++ b/gdb/objfiles.h
> @@ -622,6 +622,26 @@ struct objfile
>  					       domain_enum domain,
>  					       bool *symbol_found_p);
>  
> +  /* Indicate that the acquisition of this objfile's separate debug objfile
> +     is no longer deferred.  Used when the debug objfile has been acquired
> +     or could not be found.  */
> +  void remove_deferred_status ()
> +  {
> +    flags &= ~OBJF_DOWNLOAD_DEFERRED;
> +
> +    /* Remove quick_symbol_functions derived from a separately downloaded
> +       index.  If available the separate debug objfile's index will be used
> +       instead, since that objfile actually contains the symbols and CUs
> +       referenced in the index.
> +
> +       No more than one element of qf should have from_separate_index set
> +       to true.  */
> +    qf.remove_if ([&] (const quick_symbol_functions_up &qf_up)
> +      {
> +	return qf_up->from_separate_index;
> +      });
> +  }
> +
>    /* Return the relocation offset applied to SECTION.  */
>    CORE_ADDR section_offset (bfd_section *section) const
>    {
> diff --git a/gdb/quick-symbol.h b/gdb/quick-symbol.h
> index 4cd234162ae..01a228b446c 100644
> --- a/gdb/quick-symbol.h
> +++ b/gdb/quick-symbol.h
> @@ -193,6 +193,10 @@ struct quick_symbol_functions
>    virtual void compute_main_name (struct objfile *objfile)
>    {
>    }
> +
> +  /* True if this quick_symbol_functions is derived from a separately
> +     downloaded index.  */
> +  bool from_separate_index = false;
>  };
>  
>  typedef std::unique_ptr<quick_symbol_functions> quick_symbol_functions_up;
> diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
> index c684f9cdd43..3ed31661300 100644
> --- a/gdb/symfile-debug.c
> +++ b/gdb/symfile-debug.c
> @@ -37,6 +37,7 @@
>  #include "cli/cli-style.h"
>  #include "build-id.h"
>  #include "debuginfod-support.h"
> +#include "dwarf2/public.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
> @@ -617,6 +618,16 @@ objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
>  	  = simple_find_and_open_separate_symbol_file
>  	      (this, find_separate_debug_file_by_debuglink, &warnings);
>  
> +      /* Attempt to download only an index from the separate debug info.
> +	 As with debuginfod_find_and_open_separate_symbol_file, only attempt
> +	 this once.  */
> +      if (debug_bfd == nullptr && attempt == 0
> +	  && dwarf2_has_separate_index (this))
> +	{
> +	  has_dwarf2 = true;
> +	  break;
> +	}
> +
>        /* 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
> diff --git a/gdb/symfile.c b/gdb/symfile.c
> index bdcc27e7ee5..ec4b4da12ed 100644
> --- a/gdb/symfile.c
> +++ b/gdb/symfile.c
> @@ -989,6 +989,10 @@ syms_from_objfile (struct objfile *objfile,
>  static void
>  finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
>  {
> +  struct objfile *parent = objfile->separate_debug_objfile_backlink;
> +  bool was_deferred
> +    = (parent != nullptr) && (parent->flags & OBJF_DOWNLOAD_DEFERRED);
> +
>    /* If this is the main symbol file we have to clean up all users of the
>       old main symbol file.  Otherwise it is sufficient to fixup all the
>       breakpoints that may have been redefined by this symbol file.  */
> @@ -999,7 +1003,8 @@ finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
>  
>        clear_symtab_users (add_flags);
>      }
> -  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0)
> +  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0
> +	   && !was_deferred)
>      {
>        breakpoint_re_set ();
>      }
> @@ -1120,6 +1125,12 @@ symbol_file_add_with_addrs (const gdb_bfd_ref_ptr &abfd, const char *name,
>    if (objfile->sf != nullptr)
>      finish_new_objfile (objfile, add_flags);
>  
> +  /* Remove deferred status now in case any observers trigger symtab
> +     expansion.  Otherwise gdb might try to read parent for psymbols
> +     when it should read the separate debug objfile instead.  */
> +  if (parent != nullptr && (parent->flags & OBJF_DOWNLOAD_DEFERRED))
> +    parent->remove_deferred_status ();
> +
>    gdb::observers::new_objfile.notify (objfile);
>  
>    return objfile;
> diff --git a/gdb/symtab.c b/gdb/symtab.c
> index cc0bdb80c7c..ac9513608d7 100644
> --- a/gdb/symtab.c
> +++ b/gdb/symtab.c
> @@ -2926,14 +2926,30 @@ find_pc_sect_compunit_symtab (CORE_ADDR pc, struct obj_section *section)
>    if (best_cust != NULL)
>      return best_cust;
>  
> +  int warn_if_readin = 1;
> +
>    /* Not found in symtabs, search the "quick" symtabs (e.g. psymtabs).  */
>  
>    for (objfile *objf : current_program_space->objfiles ())
>      {
> +      bool was_deferred = objf->flags & OBJF_DOWNLOAD_DEFERRED;
> +
>        struct compunit_symtab *result
> -	= objf->find_pc_sect_compunit_symtab (msymbol, pc, section, 1);
> +	= objf->find_pc_sect_compunit_symtab (msymbol, pc, section,
> +					      warn_if_readin);
> +
>        if (result != NULL)
>  	return result;
> +
> +      /* If OBJF's separate debug info was just acquired, disable
> +	 warn_if_readin for the next iteration of this loop.  This prevents
> +	 a spurious warning in case an observer already triggered expansion
> +	 of the separate debug objfile's symtabs.  */
> +      if (was_deferred && objf->separate_debug_objfile != nullptr
> +	  && (objf->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +	warn_if_readin = 0;
> +      else if (warn_if_readin == 0)
> +	warn_if_readin = 1;
>      }
>  
>    return NULL;
> diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
> new file mode 100644
> index 00000000000..7ad19c276c4
> --- /dev/null
> +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> @@ -0,0 +1,43 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2023 Free Software Foundation, Inc.

Remember to update your copyright dates to '2023-2024' throughout.

> +
> +   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/>.  */
> +
> +#include <stdio.h>
> +#include <pthread.h>
> +#include <unistd.h>
> +
> +extern void libsection2_test ();
> +extern void *libsection2_thread_test (void *);
> +
> +static volatile int flag = 0;
> +
> +void
> +libsection1_test ()
> +{
> +  pthread_t thr;
> +
> +  printf ("In libsection1\n");
> +  libsection2_test ();
> +
> +  pthread_create (&thr, NULL, libsection2_thread_test, (void *) &flag);
> +
> +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> +  while (!flag)
> +    ;
> +
> +  printf ("Cancelling thread\n");
> +  pthread_cancel (thr);
> +}
> diff --git a/gdb/testsuite/gdb.debuginfod/libsection2.c b/gdb/testsuite/gdb.debuginfod/libsection2.c
> new file mode 100644
> index 00000000000..20edd68166b
> --- /dev/null
> +++ b/gdb/testsuite/gdb.debuginfod/libsection2.c
> @@ -0,0 +1,37 @@
> +/* This testcase 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/>.  */
> +
> +#include <stdio.h>
> +#include <pthread.h>
> +
> +void
> +libsection2_test ()
> +{
> +  printf ("In libsection2\n");
> +}
> +
> +void *
> +libsection2_thread_test (void *arg)
> +{
> +  int *flag = (int *) arg;
> +  printf ("In thread test\n");
> +
> +  while (1)
> +    *flag = 1;
> +
> +  return NULL;
> +}
> diff --git a/gdb/testsuite/gdb.debuginfod/section.c b/gdb/testsuite/gdb.debuginfod/section.c
> new file mode 100644
> index 00000000000..33e4faf2cac
> --- /dev/null
> +++ b/gdb/testsuite/gdb.debuginfod/section.c
> @@ -0,0 +1,29 @@
> +/* This testcase 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/>.  */
> +
> +#include <stdio.h>
> +
> +extern void libsection1_test ();
> +
> +int
> +main ()
> +{
> +  libsection1_test ();
> +  printf ("in section exec\n");
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
> new file mode 100644
> index 00000000000..c3872ecdfc9
> --- /dev/null
> +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> @@ -0,0 +1,172 @@
> +# 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/>.
> +
> +# Test debuginfod functionality
> +
> +standard_testfile
> +
> +load_lib debuginfod-support.exp
> +
> +require allow_debuginfod_tests
> +
> +set sourcetmp [standard_output_file tmp-${srcfile}]
> +set outputdir [standard_output_file {}]

Not sure what these two are for.  Maybe a copy&paste issue?

> +
> +# SECTEXEC is an executable which calls a function from LIB_SL1.
> +set sectfile "section"
> +set sectsrc $srcdir/$subdir/section.c
> +set sectexec [standard_output_file $sectfile]

I'd have expected you to use ${srcfile} and ${binfile} here which are
setup by `standard_testfile` based on the name of the test script.  So
section.exp will setup `section.c` and `/path/to/.../section`
respectively.

The source file doesn't have the directory prefix, but the compile procs
know how to find the source file, so that's OK.

standard_testfile also gives you `testfile` which is $binfile without
the path prefix, if that's needed.

It's good practice to use $binfile throughout the test unless there's
some compelling reason not too everyone knows what $binfile is.

> +
> +# Solib LIB_SL1 calls functions from LIB_SL2.
> +set libfile1 "libsection1"
> +set libsrc1 $srcdir/$subdir/$libfile1.c
> +set lib_sl1 [standard_output_file $libfile1.sl]
> +
> +set libfile2 "libsection2"
> +set libsrc2 $srcdir/$subdir/$libfile2.c
> +set lib_sl2 [standard_output_file $libfile2.sl]
> +
> +set lib_opts1 [list debug build-id shlib=$lib_sl2]
> +set lib_opts2 [list debug build-id]
> +set exec_opts [list debug build-id shlib=$lib_sl1 shlib=$lib_sl2]
> +
> +clean_restart
> +
> +if {[enable_section_downloads] == 0} {
> +    untested "GDB does not support debuginfod section downloads"
> +    return -1
> +}
> +
> +# Compile SECTEXEC, LIB_SL1 and LIB_SL2.
> +if { [gdb_compile_shlib $libsrc2 $lib_sl2 $lib_opts2] != "" } {
> +    untested "failed to compile $libfile2"
> +    return -1
> +}
> +
> +if { [gdb_compile_shlib_pthreads $libsrc1 $lib_sl1 $lib_opts1] != "" } {
> +    untested "failed to compile $libfile1"
> +    return -1
> +}
> +
> +if { [gdb_compile $sectsrc $sectexec executable $exec_opts] != "" } {
> +    untested "failed to compile $sectfile"
> +    return -1
> +}

Instead of calling all these compile worker functions directly, the
better approach is to call build_executable and pass through the correct
flags.  This seems to work for me:

  if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
  	  {debug build-id shlib}] != 0 } {
      return -1
  }
  
  if { [build_executable "build $libfile1" $lib_sl1 $libsrc1 \
  	  [list debug build-id shlib_pthreads shlib=$lib_sl2]] != 0 } {
      return -1
  }
  
  if { [build_executable "build executable" $sectexec $sectsrc \
  	  [list debug build-id shlib=$lib_sl1 shlib=$lib_sl2]] != 0 } {
      return -1
  }

I've inlined the flag lists here.  If you'd rather define them
separately still, that's fine.  Notice also that I removed the calls to
'untested', these are not needed, the testsuite will call 'untested' for
you if the build fails.

> +
> +# Add .gdb_index to solibs.
> +if { [ensure_gdb_index $lib_sl1 "" "libsection1"] != 1 } {
> +    untested "failed to add .gdb_index to $libfile1"
> +    return -1
> +}
> +
> +if { [ensure_gdb_index $lib_sl2 "" "libsection2"] != 1 } {
> +    untested "failed to add .gdb_index to $libfile2"
> +    return -1
> +}
> +
> +# Strip solib debuginfo into separate files.
> +if { [gdb_gnu_strip_debug $lib_sl1 ""] != 0} {
> +   fail "strip $lib_sl1 debuginfo"
> +   return -1
> +}
> +
> +if { [gdb_gnu_strip_debug $lib_sl2 ""] != 0} {
> +   fail "strip $lib_sl2 debuginfo"
> +   return -1
> +}
> +
> +# Move debuginfo files into directory that debuginfod will serve from.
> +set debugdir [standard_output_file "debug"]
> +set debuginfo_sl1 [standard_output_file $libfile1.sl.debug]
> +set debuginfo_sl2 [standard_output_file $libfile2.sl.debug]
> +
> +file mkdir $debugdir
> +file rename -force $debuginfo_sl1 $debugdir
> +file rename -force $debuginfo_sl2 $debugdir
> +
> +# Restart GDB and clear the debuginfod client cache.  Then load BINFILE into
> +# GDB and start running it.  Match output with pattern RES and use TESTNAME
> +# as the test name.
> +proc_with_prefix clean_restart_with_prompt { binfile testname } {
> +    global cache
> +
> +    # Delete client cache so debuginfo downloads again.
> +    file delete -force $cache
> +    clean_restart
> +
> +    gdb_test "set debuginfod enabled on" "" "clean_restart enable $testname"

You can use gdb_test_no_output here rather than passing an empty pattern.

> +    gdb_load $binfile
> +
> +    runto_main
> +}
> +
> +# Tests with no debuginfod server running.
> +proc_with_prefix no_url { } {
> +    global sectexec libfile1 libfile2
> +
> +    gdb_load $sectexec
> +    if {![runto_main]} {
> +       return
> +    }
> +
> +    # Check that no section is downloaded and no debuginfo is found.
> +    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile1.*" \
> +	     "found no url lib1"
> +    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile2.*" \
> +	     "found no url lib2"
> +}
> +
> +# Tests with a debuginfod server running.
> +proc_with_prefix local_url { } {
> +    global sectexec
> +    global libsrc1 lib_sl1 libfile1
> +    global libsrc2 lib_sl2 libfile2
> +    global debugdir db
> +
> +    set url [start_debuginfod $db $debugdir]
> +    if { $url == "" } {
> +	unresolved "failed to start debuginfod server"
> +	return
> +    }
> +
> +    # Point GDB to the server.
> +    setenv DEBUGINFOD_URLS $url
> +
> +    clean_restart_with_prompt $sectexec "index"
> +
> +    # Download debuginfo when stepping into a function.
> +    set res ".*separate debug info for $lib_sl1.*\"In ${libfile1}\\\\n\".*"
> +    gdb_test "step" $res "step"
> +
> +    clean_restart_with_prompt $sectexec "break"
> +
> +    # Download debuginfo when setting a breakpoint.
> +    set res "Download.*separate debug info for $lib_sl2.*"
> +    gdb_test "br libsection2_test" $res "break set"
> +
> +    # Hit the breakpoint.
> +    set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
> +    gdb_test "c" $res "break continue"
> +}
> +
> +# Create CACHE and DB directories ready for debuginfod to use.
> +prepare_for_debuginfod cache db
> +
> +with_debuginfod_env $cache {
> +    no_url
> +    local_url
> +}
> +
> +stop_debuginfod
> diff --git a/gdb/testsuite/lib/debuginfod-support.exp b/gdb/testsuite/lib/debuginfod-support.exp
> index 50a8b512a4a..e0b3dc39f51 100644
> --- a/gdb/testsuite/lib/debuginfod-support.exp
> +++ b/gdb/testsuite/lib/debuginfod-support.exp
> @@ -113,6 +113,8 @@ proc with_debuginfod_env { cache body } {
>  proc start_debuginfod { db debugdir } {
>      global debuginfod_spawn_id spawn_id
>  
> +    set logfile [standard_output_file "server_log"]
> +
>      # Find an unused port.
>      set port 7999
>      set found false
> @@ -127,7 +129,8 @@ proc start_debuginfod { db debugdir } {
>  	    set old_spawn_id $spawn_id
>  	}
>  
> -	spawn debuginfod -vvvv -d $db -p $port -F $debugdir
> +	spawn sh -c "debuginfod -vvvv -d $db -p $port -F $debugdir 2>&1 \
> +		| tee $logfile"
>  	set debuginfod_spawn_id $spawn_id
>  
>  	if { [info exists old_spawn_id] } {
> @@ -194,3 +197,25 @@ proc stop_debuginfod { } {
>  	unset debuginfod_spawn_id
>      }
>  }
> +
> +# Return 1 if gdb is configured to download ELF/DWARF sections from
> +# debuginfod servers.  Otherwise return 0.

We can use 'true' and 'false' in TCL code, see allow_debuginfod_tests as
an example.

> +proc enable_section_downloads { } {

I think this should be renamed to something like allow_section_downloads
(though I about allow_debuginfod_section_downloads to make it clear it's
a debuginfod function).  Then, in the test script you would say:

  require allow_debuginfod_section_downloads

Rather than the `if` check that you have right now.


> +    global gdb_prompt

This line isn't needed, gdb_prompt isn't used.

Thanks,
Andrew

> +
> +    set cmd "maint set debuginfod download-sections on"
> +    set msg "enable section downloads"
> +
> +    gdb_test_multiple $cmd $msg {
> +	-re -wrap ".*not compiled into GDB.*" {
> +	    return 0
> +	}
> +	-re -wrap "^" {
> +	    return 1
> +	}
> +	-re -wrap "" {
> +	    fail "$gdb_test_name (unexpected output)"
> +	    return 0
> +	}
> +    }
> +}
> diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
> index 9ac707be696..9b3c7e6a02c 100644
> --- a/gdb/testsuite/lib/gdb.exp
> +++ b/gdb/testsuite/lib/gdb.exp
> @@ -9414,10 +9414,14 @@ proc get_index_type { objfile { testname "" } } {
>  # STYLE controls which style of index to add, if needed.  The empty
>  # string (the default) means .gdb_index; "-dwarf-5" means .debug_names.
>  
> -proc ensure_gdb_index { binfile {style ""} } {
> +proc ensure_gdb_index { binfile {style ""} {testname_prefix ""} } {
>      set testfile [file tail $binfile]
> -
>      set test "check if index present"
> +
> +    if { $testname_prefix != "" } {
> +      set test "$testname_prefix $test"
> +    }
> +
>      set index_type [get_index_type $testfile $test]
>  
>      if { $index_type eq "gdb" || $index_type eq "dwarf5" } {
> -- 
> 2.43.0
  
Aaron Merey Jan. 19, 2024, 5:12 a.m. UTC | #2
Hi Andrew,

Thanks for the review.

On Thu, Jan 18, 2024 at 10:34 AM Andrew Burgess <aburgess@redhat.com> wrote:
>
> Should be:
>
>   gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
>
> OK, I'm going to stop commenting on these now, but there's a few more of
> them in the rest of the patch.  GDB style is not to rely on implicit
> int -> bool conversion.

Fixed.

>
> > +
> > +  SCOPE_EXIT { objfile->remove_deferred_status (); };
> > +
> > +  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > +  const char *filename;
> > +  gdb_bfd_ref_ptr debug_bfd;
> > +  gdb::unique_xmalloc_ptr<char> symfile_path;
> > +  scoped_fd fd;
>
> Of these, filename, debug_bfd, and fd can be declared where they are
> first defined.  You can probably move symfile_path closer to where it's
> used too.
>

Fixed.

> > diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
> > index 1235f293f45..b674103c72f 100644
> > --- a/gdb/dwarf2/section.c
> > +++ b/gdb/dwarf2/section.c
> > @@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
> >        section = get_containing_section ();
> >        gdb_assert (!section->is_virtual);
> >      }
> > -  gdb_assert (section->s.section != nullptr);
> > +  if (section->s.section == nullptr)
> > +    error (_("Can't find owner of DWARF section."));
>
> This seems like an interesting change.  There's no test that triggers
> this case, but I guess you ran into this at some point.  Could you
> explain more about what's going on here?

This can happen if gdb mistakenly tries to read debug info that
has been deferred and not yet downloaded.  For example if gdb attempted
to read symbols from a debuginfo-stripped parent objfile without first
downloading the full separate debug info, then this error would trigger.

> > @@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> >          && objfile->separate_debug_objfile_backlink == NULL)
> >      {
> >        if (objfile->find_and_add_separate_symbol_file (symfile_flags))
> > -     gdb_assert (objfile->separate_debug_objfile != nullptr);
> > +     gdb_assert (objfile->separate_debug_objfile != nullptr
> > +                 || objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> >        else
> >       has_dwarf2 = false;
> >      }
> > diff --git a/gdb/frame.c b/gdb/frame.c
> > index 41003cc7771..d3409c8fd97 100644
> > --- a/gdb/frame.c
> > +++ b/gdb/frame.c
> > @@ -1889,6 +1889,13 @@ get_selected_frame (const char *message)
> >       error (("%s"), message);
> >
> >        lookup_selected_frame (selected_frame_id, selected_frame_level);
> > +
> > +      /* It is possible for lookup_selected_frame to cause a new objfile
> > +      to be loaded.  Some objfile observers may choose to clear
> > +      selected_frame when an objfile is loaded.  Work around this by
> > +      calling lookup_selected_frame again if the first try failed.  */
> > +      if (selected_frame == nullptr)
> > +     lookup_selected_frame (selected_frame_id, selected_frame_level);
>
> I notice this case wasn't tested -- I added an abort here and reran the
> test, and didn't see it hit.

With the 2nd call to lookup_selected_frame removed, the test
"gdb.debuginfod/section.exp: local_url: line thread" becomes unresolved
when testing with --target_board=native-gdbserver.

>
> I ask because this seems both interesting, and this doesn't feel like
> the right solution... As a minimum you'd need a comment on
> lookup_selected_frame that says "hey, you will need to call this
> function twice to guarantee that it did what it promised".  So then I'd
> suggest we should just rename lookup_selected_frame to
> lookup_selected_frame_1, and have lookup_selected_frame be a wrapper
> which does the double lookup.

This is a better solution. Fixed.

> But I was kind of hoping that we'd have a test that hit this case so I
> could look at the stack trace and better understand what exactly is
> happening when the selected frame is cleared.
>
> > diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > new file mode 100644
> > index 00000000000..7ad19c276c4
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > @@ -0,0 +1,43 @@
> > +/* This testcase is part of GDB, the GNU debugger.
> > +
> > +   Copyright 2023 Free Software Foundation, Inc.
>
> Remember to update your copyright dates to '2023-2024' throughout.

Fixed.

> > +load_lib debuginfod-support.exp
> > +
> > +require allow_debuginfod_tests
> > +
> > +set sourcetmp [standard_output_file tmp-${srcfile}]
> > +set outputdir [standard_output_file {}]
>
> Not sure what these two are for.  Maybe a copy&paste issue?

Fixed.

>
> > +
> > +# SECTEXEC is an executable which calls a function from LIB_SL1.
> > +set sectfile "section"
> > +set sectsrc $srcdir/$subdir/section.c
> > +set sectexec [standard_output_file $sectfile]
>
> I'd have expected you to use ${srcfile} and ${binfile} here which are
> setup by `standard_testfile` based on the name of the test script.  So
> section.exp will setup `section.c` and `/path/to/.../section`
> respectively.
>
> The source file doesn't have the directory prefix, but the compile procs
> know how to find the source file, so that's OK.
>
> standard_testfile also gives you `testfile` which is $binfile without
> the path prefix, if that's needed.
>
> It's good practice to use $binfile throughout the test unless there's
> some compelling reason not too everyone knows what $binfile is.

Fixed.

> > +if { [gdb_compile $sectsrc $sectexec executable $exec_opts] != "" } {
> > +    untested "failed to compile $sectfile"
> > +    return -1
> > +}
>
> Instead of calling all these compile worker functions directly, the
> better approach is to call build_executable and pass through the correct
> flags.  This seems to work for me:
>
>   if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
>           {debug build-id shlib}] != 0 } {
>       return -1
>   }

Fixed.

> > +    gdb_test "set debuginfod enabled on" "" "clean_restart enable $testname"
>
> You can use gdb_test_no_output here rather than passing an empty pattern.

Fixed.

> > +# Return 1 if gdb is configured to download ELF/DWARF sections from
> > +# debuginfod servers.  Otherwise return 0.
>
> We can use 'true' and 'false' in TCL code, see allow_debuginfod_tests as
> an example.
>
> > +proc enable_section_downloads { } {
>
> I think this should be renamed to something like allow_section_downloads
> (though I about allow_debuginfod_section_downloads to make it clear it's
> a debuginfod function).  Then, in the test script you would say:
>
>   require allow_debuginfod_section_downloads
>
> Rather than the `if` check that you have right now.
>
>
> > +    global gdb_prompt
>
> This line isn't needed, gdb_prompt isn't used.

Fixed.  I've attached the updated patch below.

Aaron

Commit message:

At the beginning of a session, gdb may attempt to download debuginfo
for all shared libraries associated with the process or core file
being debugged.  This can be a waste of time and storage space when much
of the debuginfo ends up not being used during the session.

To reduce the gdb's startup latency and to download only the debuginfo
that is really needed, this patch adds on-demand downloading of debuginfo.

'set debuginfo enabled on' now causes gdb to attempt to download a .gdb_index
for each shared library instead of its full debuginfo.  Each corresponding
separate debuginfo will be deferred until gdb needs to expand symtabs
associated with the debuginfo's index.

Because these indices are significantly smaller than their corresponding
debuginfo, this generally reduces the total amount of data gdb downloads.
Reductions of 80%-95% have been observed when debugging large GUI programs.

    (gdb) set debuginfod enabled on
    (gdb) start
    Downloading section .gdb_index for /lib64/libcurl.so.4
    [...]
    1826        client->server_mhandle = curl_multi_init ();
    (gdb) step
    Downloading separate debug info for /lib64/libcurl.so.4
    Downloading separate debug info for [libcurl dwz]
    Downloading source file /usr/src/debug/curl-7.85.0-6.fc37.x86_64/build-full/lib/../../lib/multi.c
    curl_multi_init () at ../../lib/multi.c:457
    457     {
    (gdb)

Some of the key functions below include dwarf2_has_separate_index which
downloads the separate .gdb_index.  If successful, the shared library
objfile owns the index until the separate debug objfile is downloaded
or confirmed to not be available.

read_full_dwarf_from_debuginfod downloads the full debuginfo and
initializes the separate debug objfile.  It is called by functions
such as dwarf2_gdb_index::expand_symtabs_matching and
dwarf2_base_index_functions::find_pc_sect_compunit_symtab when symtab
expansion is required.
---
 gdb/dwarf2/frame.c                         |  13 ++
 gdb/dwarf2/frame.h                         |   4 +
 gdb/dwarf2/index-cache.c                   |  33 +++++
 gdb/dwarf2/index-cache.h                   |  13 ++
 gdb/dwarf2/public.h                        |   7 +
 gdb/dwarf2/read-gdb-index.c                | 125 +++++++++++++---
 gdb/dwarf2/read.c                          | 145 ++++++++++++++++++-
 gdb/dwarf2/read.h                          |  10 ++
 gdb/dwarf2/section.c                       |   3 +-
 gdb/elfread.c                              |   3 +-
 gdb/frame.c                                |  27 +++-
 gdb/objfile-flags.h                        |   4 +
 gdb/objfiles.h                             |  20 +++
 gdb/quick-symbol.h                         |   4 +
 gdb/symfile-debug.c                        |  11 ++
 gdb/symfile.c                              |  12 +-
 gdb/symtab.c                               |  18 ++-
 gdb/testsuite/gdb.debuginfod/libsection1.c |  43 ++++++
 gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
 gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
 gdb/testsuite/gdb.debuginfod/section.exp   | 157 +++++++++++++++++++++
 gdb/testsuite/lib/debuginfod-support.exp   |  25 +++-
 gdb/testsuite/lib/gdb.exp                  |   8 +-
 23 files changed, 719 insertions(+), 32 deletions(-)
 create mode 100644 gdb/testsuite/gdb.debuginfod/libsection1.c
 create mode 100644 gdb/testsuite/gdb.debuginfod/libsection2.c
 create mode 100644 gdb/testsuite/gdb.debuginfod/section.c
 create mode 100644 gdb/testsuite/gdb.debuginfod/section.exp

diff --git a/gdb/dwarf2/frame.c b/gdb/dwarf2/frame.c
index d3d1ecdf1f5..9e290af79f7 100644
--- a/gdb/dwarf2/frame.c
+++ b/gdb/dwarf2/frame.c
@@ -1620,6 +1620,19 @@ set_comp_unit (struct objfile *objfile, struct comp_unit *unit)
   return dwarf2_frame_bfd_data.set (abfd, unit);
 }
 
+/* See frame.h.  */
+
+void
+dwarf2_clear_frame_data (struct objfile *objfile)
+{
+  bfd *abfd = objfile->obfd.get ();
+
+  if (gdb_bfd_requires_relocations (abfd))
+    dwarf2_frame_objfile_data.clear (objfile);
+  else
+    dwarf2_frame_bfd_data.clear (abfd);
+}
+
 /* Find the FDE for *PC.  Return a pointer to the FDE, and store the
    initial location associated with it into *PC.  */
 
diff --git a/gdb/dwarf2/frame.h b/gdb/dwarf2/frame.h
index 5643e557513..2391e313e7c 100644
--- a/gdb/dwarf2/frame.h
+++ b/gdb/dwarf2/frame.h
@@ -238,6 +238,10 @@ void dwarf2_append_unwinders (struct gdbarch *gdbarch);
 extern const struct frame_base *
   dwarf2_frame_base_sniffer (frame_info_ptr this_frame);
 
+/* Delete OBJFILEs comp_unit.  */
+
+extern void dwarf2_clear_frame_data (struct objfile * objfile);
+
 /* Compute the DWARF CFA for a frame.  */
 
 CORE_ADDR dwarf2_frame_cfa (frame_info_ptr this_frame);
diff --git a/gdb/dwarf2/index-cache.c b/gdb/dwarf2/index-cache.c
index 69f70642dc6..8c969ecd590 100644
--- a/gdb/dwarf2/index-cache.c
+++ b/gdb/dwarf2/index-cache.c
@@ -240,6 +240,33 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
   return {};
 }
 
+/* See index-cache.h.  */
+
+gdb::array_view<const gdb_byte>
+index_cache::lookup_gdb_index_debuginfod (const char *index_path,
+					  std::unique_ptr<index_cache_resource> *resource)
+{
+  try
+    {
+      /* Try to map that file.  */
+      index_cache_resource_mmap *mmap_resource
+	= new index_cache_resource_mmap (index_path);
+
+      /* Hand the resource to the caller.  */
+      resource->reset (mmap_resource);
+
+      return gdb::array_view<const gdb_byte>
+	  ((const gdb_byte *) mmap_resource->mapping.get (),
+	   mmap_resource->mapping.size ());
+    }
+  catch (const gdb_exception_error &except)
+    {
+      warning (_("Unable to read %s: %s"), index_path, except.what ());
+    }
+
+  return {};
+}
+
 #else /* !HAVE_SYS_MMAN_H */
 
 /* See dwarf-index-cache.h.  This is a no-op on unsupported systems.  */
@@ -251,6 +278,12 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
   return {};
 }
 
+gdb::array_view<const gdb_byte>
+index_cache::lookup_gdb_index_debuginfod (const char *index_path,
+					  std::unique_ptr<index_cache_resource> *resource)
+{
+  return {};
+}
 #endif
 
 /* See dwarf-index-cache.h.  */
diff --git a/gdb/dwarf2/index-cache.h b/gdb/dwarf2/index-cache.h
index 023fc86fc89..0d845ebcadd 100644
--- a/gdb/dwarf2/index-cache.h
+++ b/gdb/dwarf2/index-cache.h
@@ -90,6 +90,19 @@ class index_cache
   lookup_gdb_index (const bfd_build_id *build_id,
 		    std::unique_ptr<index_cache_resource> *resource);
 
+  /* Look for an index file located at INDEX_PATH in the debuginfod cache.
+     Unlike lookup_gdb_index, this function does not exit early if the
+     index cache has not been enabled.
+
+     If found, return the contents as an array_view and store the underlying
+     resources (allocated memory, mapped file, etc) in RESOURCE.  The returned
+     array_view is valid as long as RESOURCE is not destroyed.
+
+     If no matching index file is found, return an empty array view.  */
+  gdb::array_view<const gdb_byte>
+  lookup_gdb_index_debuginfod (const char *index_path,
+			       std::unique_ptr<index_cache_resource> *resource);
+
   /* Return the number of cache hits.  */
   unsigned int n_hits () const
   { return m_n_hits; }
diff --git a/gdb/dwarf2/public.h b/gdb/dwarf2/public.h
index efb754b5fe8..4d3776465c3 100644
--- a/gdb/dwarf2/public.h
+++ b/gdb/dwarf2/public.h
@@ -44,4 +44,11 @@ extern bool dwarf2_initialize_objfile
 
 extern void dwarf2_build_frame_info (struct objfile *);
 
+/* Query debuginfod for the .gdb_index associated with OBJFILE.  If
+   successful, create an objfile to hold the .gdb_index information
+   and act as a placeholder until the full debuginfo needs to be
+   downloaded.  */
+
+extern bool dwarf2_has_separate_index (struct objfile *);
+
 #endif /* DWARF2_PUBLIC_H */
diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
index 4ca44540296..f4d77ed2d04 100644
--- a/gdb/dwarf2/read-gdb-index.c
+++ b/gdb/dwarf2/read-gdb-index.c
@@ -139,6 +139,8 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
      gdb.dwarf2/gdb-index.exp testcase.  */
   void dump (struct objfile *objfile) override;
 
+  /* Calls do_expand_symtabs_matching and triggers debuginfo downloading
+     if necessary.  */
   bool expand_symtabs_matching
     (struct objfile *objfile,
      gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
@@ -148,8 +150,60 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
      block_search_flags search_flags,
      domain_enum domain,
      enum search_domain kind) override;
+
+  /* Expand symtabs matching a given symbol or file.  */
+  bool do_expand_symtabs_matching
+    (struct objfile *objfile,
+     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
+     const lookup_name_info *lookup_name,
+     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
+     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
+     block_search_flags search_flags,
+     domain_enum domain,
+     enum search_domain kind);
+
+  /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
+     debuginfo if necessary.  */
+  void expand_all_symtabs (struct objfile *objfile) override;
+
+  /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
+     debuginfo if necessary.  */
+  struct symtab *find_last_source_symtab (struct objfile *objfile) override;
 };
 
+void
+dwarf2_gdb_index::expand_all_symtabs (struct objfile *objfile)
+{
+  try
+    {
+      dwarf2_base_index_functions::expand_all_symtabs (objfile);
+    }
+  catch (const gdb_exception &e)
+    {
+      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	exception_print (gdb_stderr, e);
+      else
+	read_full_dwarf_from_debuginfod (objfile, this);
+    }
+}
+
+struct symtab *
+dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
+{
+  try
+    {
+      return dwarf2_base_index_functions::find_last_source_symtab (objfile);
+    }
+  catch (const gdb_exception &e)
+    {
+      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	exception_print (gdb_stderr, e);
+      else
+	read_full_dwarf_from_debuginfod (objfile, this);
+      return nullptr;
+    }
+}
+
 /* This dumps minimal information about the index.
    It is called via "mt print objfiles".
    One use is to verify .gdb_index has been loaded by the
@@ -268,7 +322,7 @@ dw2_expand_marked_cus
 }
 
 bool
-dwarf2_gdb_index::expand_symtabs_matching
+dwarf2_gdb_index::do_expand_symtabs_matching
     (struct objfile *objfile,
      gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
      const lookup_name_info *lookup_name,
@@ -317,6 +371,39 @@ dwarf2_gdb_index::expand_symtabs_matching
   return result;
 }
 
+bool
+dwarf2_gdb_index::expand_symtabs_matching
+    (struct objfile *objfile,
+     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
+     const lookup_name_info *lookup_name,
+     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
+     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
+     block_search_flags search_flags,
+     domain_enum domain,
+     enum search_domain kind)
+{
+  if ((objfile->flags & OBJF_READNEVER) != 0)
+    return false;
+
+  try
+    {
+      return do_expand_symtabs_matching (objfile, file_matcher, lookup_name,
+					 symbol_matcher, expansion_notify,
+					 search_flags, domain, kind);
+    }
+  catch (const gdb_exception &e)
+    {
+      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	{
+	  exception_print (gdb_stderr, e);
+	  return false;
+	}
+
+      read_full_dwarf_from_debuginfod (objfile, this);
+      return true;
+    }
+}
+
 quick_symbol_functions_up
 mapped_gdb_index::make_quick_functions () const
 {
@@ -652,28 +739,32 @@ dwarf2_read_gdb_index
 
   /* If there is a .dwz file, read it so we can get its CU list as
      well.  */
-  dwz = dwarf2_get_dwz_file (per_bfd);
-  if (dwz != NULL)
+  if (get_gdb_index_contents_dwz != nullptr)
     {
       mapped_gdb_index dwz_map;
       const gdb_byte *dwz_types_ignore;
       offset_type dwz_types_elements_ignore;
+      dwz = dwarf2_get_dwz_file (per_bfd);
 
-      gdb::array_view<const gdb_byte> dwz_index_content
-	= get_gdb_index_contents_dwz (objfile, dwz);
-
-      if (dwz_index_content.empty ())
-	return 0;
-
-      if (!read_gdb_index_from_buffer (bfd_get_filename (dwz->dwz_bfd.get ()),
-				       1, dwz_index_content, &dwz_map,
-				       &dwz_list, &dwz_list_elements,
-				       &dwz_types_ignore,
-				       &dwz_types_elements_ignore))
+      if (dwz != nullptr)
 	{
-	  warning (_("could not read '.gdb_index' section from %s; skipping"),
-		   bfd_get_filename (dwz->dwz_bfd.get ()));
-	  return 0;
+	  gdb::array_view<const gdb_byte> dwz_index_content
+	    = get_gdb_index_contents_dwz (objfile, dwz);
+
+	  if (dwz_index_content.empty ())
+	    return 0;
+
+	  if (!read_gdb_index_from_buffer (bfd_get_filename
+					     (dwz->dwz_bfd.get ()),
+					   1, dwz_index_content, &dwz_map,
+					   &dwz_list, &dwz_list_elements,
+					   &dwz_types_ignore,
+					   &dwz_types_elements_ignore))
+	    {
+	      warning (_("could not read '.gdb_index' section from %s; skipping"),
+		       bfd_get_filename (dwz->dwz_bfd.get ()));
+		return 0;
+	    }
 	}
     }
 
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 76dbeb8d536..062f0e34938 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -35,6 +35,7 @@
 #include "dwarf2/attribute.h"
 #include "dwarf2/comp-unit-head.h"
 #include "dwarf2/cu.h"
+#include "dwarf2/frame.h"
 #include "dwarf2/index-cache.h"
 #include "dwarf2/index-common.h"
 #include "dwarf2/leb.h"
@@ -96,6 +97,8 @@
 #include "split-name.h"
 #include "gdbsupport/thread-pool.h"
 #include "run-on-main-thread.h"
+#include "inferior.h"
+#include "debuginfod-support.h"
 
 /* When == 1, print basic high level tracing messages.
    When > 1, be more verbose.
@@ -3003,7 +3006,7 @@ dwarf2_base_index_functions::find_per_cu (dwarf2_per_bfd *per_bfd,
 }
 
 struct compunit_symtab *
-dwarf2_base_index_functions::find_pc_sect_compunit_symtab
+dwarf2_base_index_functions::do_find_pc_sect_compunit_symtab
      (struct objfile *objfile,
       struct bound_minimal_symbol msymbol,
       CORE_ADDR pc,
@@ -3034,6 +3037,32 @@ dwarf2_base_index_functions::find_pc_sect_compunit_symtab
   return result;
 }
 
+struct compunit_symtab *
+dwarf2_base_index_functions::find_pc_sect_compunit_symtab
+     (struct objfile *objfile,
+      struct bound_minimal_symbol msymbol,
+      CORE_ADDR pc,
+      struct obj_section *section,
+      int warn_if_readin)
+{
+  if ((objfile->flags & OBJF_READNEVER) != 0)
+    return nullptr;
+
+  try
+    {
+      return do_find_pc_sect_compunit_symtab (objfile, msymbol, pc,
+					      section, warn_if_readin);
+    }
+  catch (const gdb_exception &e)
+    {
+      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	exception_print (gdb_stderr, e);
+      else
+	read_full_dwarf_from_debuginfod (objfile, this);
+      return nullptr;
+    }
+}
+
 void
 dwarf2_base_index_functions::map_symbol_filenames
      (struct objfile *objfile,
@@ -3190,6 +3219,29 @@ get_gdb_index_contents_from_cache_dwz (objfile *obj, dwz_file *dwz)
   return global_index_cache.lookup_gdb_index (build_id, &dwz->index_cache_res);
 }
 
+/* Query debuginfod for the .gdb_index matching OBJFILE's build-id.  Return the
+   contents if successful.  */
+
+static gdb::array_view<const gdb_byte>
+get_gdb_index_contents_from_debuginfod (objfile *objfile, dwarf2_per_bfd *per_bfd)
+{
+  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
+  if (build_id == nullptr)
+    return {};
+
+  gdb::unique_xmalloc_ptr<char> index_path;
+  scoped_fd fd = debuginfod_section_query (build_id->data, build_id->size,
+					   bfd_get_filename
+					     (objfile->obfd.get ()),
+					   ".gdb_index",
+					   &index_path);
+  if (fd.get () < 0)
+    return {};
+
+  return global_index_cache.lookup_gdb_index_debuginfod
+    (index_path.get (), &per_bfd->index_cache_res);
+}
+
 static quick_symbol_functions_up make_cooked_index_funcs
      (dwarf2_per_objfile *);
 
@@ -3200,7 +3252,8 @@ dwarf2_initialize_objfile (struct objfile *objfile,
 			   const struct dwarf2_debug_sections *names,
 			   bool can_copy)
 {
-  if (!dwarf2_has_info (objfile, names, can_copy))
+  if (!dwarf2_has_info (objfile, names, can_copy)
+      && (objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
     return false;
 
   dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile);
@@ -3262,6 +3315,16 @@ dwarf2_initialize_objfile (struct objfile *objfile,
       global_index_cache.hit ();
       objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
     }
+  /* Try to read just a separately downloaded gdb index.  */
+  else if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0
+	   && dwarf2_read_gdb_index (per_objfile,
+				     get_gdb_index_contents_from_debuginfod,
+				     nullptr))
+    {
+      dwarf_read_debug_printf ("found .gdb_index from debuginfod");
+      objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
+      objfile->qf.begin ()->get ()->from_separate_index = true;
+    }
   else
     {
       global_index_cache.miss ();
@@ -3270,6 +3333,84 @@ dwarf2_initialize_objfile (struct objfile *objfile,
   return true;
 }
 
+/* See read.h.  */
+
+void
+read_full_dwarf_from_debuginfod (struct objfile *objfile,
+				 dwarf2_base_index_functions *fncs)
+{
+  gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
+
+  SCOPE_EXIT { objfile->remove_deferred_status (); };
+
+  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
+  if (build_id == nullptr)
+    return;
+
+  const char *filename = bfd_get_filename (objfile->obfd.get ());
+  gdb::unique_xmalloc_ptr<char> symfile_path;
+  scoped_fd fd;
+
+  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
+				   filename, &symfile_path);
+  if (fd.get () < 0)
+    return;
+
+  /* Separate debuginfo successfully retrieved from server.  */
+  gdb_bfd_ref_ptr debug_bfd = symfile_bfd_open (symfile_path.get ());
+  if (debug_bfd == nullptr
+      || !build_id_verify (debug_bfd.get (), build_id->size, build_id->data))
+    {
+      warning (_("File \"%s\" from debuginfod cannot be opened as bfd"),
+	       filename);
+      return;
+    }
+
+  /* Clear frame data so it can be recalculated using DWARF.  */
+  dwarf2_clear_frame_data (objfile);
+
+  /* This may also trigger a dwz download.  */
+  symbol_file_add_separate (debug_bfd, symfile_path.get (),
+			    current_inferior ()->symfile_flags, objfile);
+}
+
+/* See public.h.  */
+
+bool
+dwarf2_has_separate_index (struct objfile *objfile)
+{
+  if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0)
+    return true;
+  if ((objfile->flags & OBJF_MAINLINE) != 0)
+    return false;
+  if (!IS_DIR_SEPARATOR (*objfile_filename (objfile)))
+    return false;
+
+  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
+
+  if (build_id == nullptr)
+    return false;
+
+  gdb::unique_xmalloc_ptr<char> index_path;
+  scoped_fd fd = debuginfod_section_query (build_id->data,
+					   build_id->size,
+					   bfd_get_filename
+					     (objfile->obfd.get ()),
+					   ".gdb_index",
+					   &index_path);
+
+  if (fd.get () < 0)
+    return false;
+
+  /* We found a separate .gdb_index file so a separate debuginfo file
+     should exist, but we don't want to download it until necessary.
+     Attach the index to this objfile and defer the debuginfo download
+     until gdb needs to expand symtabs referenced by the index.  */
+  objfile->flags |= OBJF_DOWNLOAD_DEFERRED;
+  dwarf2_initialize_objfile (objfile);
+  return true;
+}
+
 
 
 /* Find the base address of the compilation unit for range lists and
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index d6631d45a54..8b6f1864458 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -900,6 +900,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
      CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
        override;
 
+  struct compunit_symtab *do_find_pc_sect_compunit_symtab
+    (struct objfile *objfile, struct bound_minimal_symbol msymbol,
+     CORE_ADDR pc, struct obj_section *section, int warn_if_readin);
+
   struct compunit_symtab *find_compunit_symtab_by_address
     (struct objfile *objfile, CORE_ADDR address) override
   {
@@ -972,4 +976,10 @@ extern void create_all_units (dwarf2_per_objfile *per_objfile);
 
 extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
 
+/* If OBJFILE contains information from a separately downloaded .gdb_index,
+   attempt to download the full debuginfo.  */
+
+extern void read_full_dwarf_from_debuginfod (struct objfile *,
+					     dwarf2_base_index_functions *);
+
 #endif /* DWARF2READ_H */
diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
index 1235f293f45..b674103c72f 100644
--- a/gdb/dwarf2/section.c
+++ b/gdb/dwarf2/section.c
@@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
       section = get_containing_section ();
       gdb_assert (!section->is_virtual);
     }
-  gdb_assert (section->s.section != nullptr);
+  if (section->s.section == nullptr)
+    error (_("Can't find owner of DWARF section."));
   return section->s.section->owner;
 }
 
diff --git a/gdb/elfread.c b/gdb/elfread.c
index c5ba8837217..f14dc1bf80b 100644
--- a/gdb/elfread.c
+++ b/gdb/elfread.c
@@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
 	   && objfile->separate_debug_objfile_backlink == NULL)
     {
       if (objfile->find_and_add_separate_symbol_file (symfile_flags))
-	gdb_assert (objfile->separate_debug_objfile != nullptr);
+	gdb_assert (objfile->separate_debug_objfile != nullptr
+		    || (objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
       else
 	has_dwarf2 = false;
     }
diff --git a/gdb/frame.c b/gdb/frame.c
index 41003cc7771..7f57cf80a1b 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1772,14 +1772,10 @@ restore_selected_frame (frame_id frame_id, int frame_level)
   selected_frame = nullptr;
 }
 
-/* Lookup the frame_info object for the selected frame FRAME_ID /
-   FRAME_LEVEL and cache the result.
-
-   If FRAME_LEVEL > 0 and the originally selected frame isn't found,
-   warn and select the innermost (current) frame.  */
+/* See lookup_selected_frame.  */
 
 static void
-lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
+lookup_selected_frame_1 (struct frame_id &a_frame_id, int frame_level)
 {
   frame_info_ptr frame = NULL;
   int count;
@@ -1851,6 +1847,25 @@ lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
     }
 }
 
+/* Lookup the frame_info object for the selected frame FRAME_ID /
+   FRAME_LEVEL and cache the result.
+
+   If FRAME_LEVEL > 0 and the originally selected frame isn't found,
+   warn and select the innermost (current) frame.  */
+
+static void
+lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
+{
+  lookup_selected_frame_1 (selected_frame_id, selected_frame_level);
+
+  /* It is possible for lookup_selected_frame_1 to cause a new objfile
+     to be loaded.  However some objfile observers may choose to clear
+     selected_frame when an objfile is loaded.  Work around this by
+     calling lookup_selected_frame_1 again if the first call failed.  */
+  if (selected_frame == nullptr)
+    lookup_selected_frame_1 (selected_frame_id, selected_frame_level);
+}
+
 bool
 has_stack_frames ()
 {
diff --git a/gdb/objfile-flags.h b/gdb/objfile-flags.h
index 74aea1a88d3..3f922d9e84f 100644
--- a/gdb/objfile-flags.h
+++ b/gdb/objfile-flags.h
@@ -56,6 +56,10 @@ enum objfile_flag : unsigned
     /* User requested that we do not read this objfile's symbolic
        information.  */
     OBJF_READNEVER = 1 << 6,
+
+    /* A separate .gdb_index has been downloaded for this objfile.
+       Debuginfo for this objfile can be downloaded when required.  */
+    OBJF_DOWNLOAD_DEFERRED = 1 << 7,
   };
 
 DEF_ENUM_FLAGS_TYPE (enum objfile_flag, objfile_flags);
diff --git a/gdb/objfiles.h b/gdb/objfiles.h
index b917886988d..4a4db1e7058 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -622,6 +622,26 @@ struct objfile
 					       domain_enum domain,
 					       bool *symbol_found_p);
 
+  /* Indicate that the acquisition of this objfile's separate debug objfile
+     is no longer deferred.  Used when the debug objfile has been acquired
+     or could not be found.  */
+  void remove_deferred_status ()
+  {
+    flags &= ~OBJF_DOWNLOAD_DEFERRED;
+
+    /* Remove quick_symbol_functions derived from a separately downloaded
+       index.  If available the separate debug objfile's index will be used
+       instead, since that objfile actually contains the symbols and CUs
+       referenced in the index.
+
+       No more than one element of qf should have from_separate_index set
+       to true.  */
+    qf.remove_if ([&] (const quick_symbol_functions_up &qf_up)
+      {
+	return qf_up->from_separate_index;
+      });
+  }
+
   /* Return the relocation offset applied to SECTION.  */
   CORE_ADDR section_offset (bfd_section *section) const
   {
diff --git a/gdb/quick-symbol.h b/gdb/quick-symbol.h
index 4cd234162ae..01a228b446c 100644
--- a/gdb/quick-symbol.h
+++ b/gdb/quick-symbol.h
@@ -193,6 +193,10 @@ struct quick_symbol_functions
   virtual void compute_main_name (struct objfile *objfile)
   {
   }
+
+  /* True if this quick_symbol_functions is derived from a separately
+     downloaded index.  */
+  bool from_separate_index = false;
 };
 
 typedef std::unique_ptr<quick_symbol_functions> quick_symbol_functions_up;
diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index c684f9cdd43..3ed31661300 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -37,6 +37,7 @@
 #include "cli/cli-style.h"
 #include "build-id.h"
 #include "debuginfod-support.h"
+#include "dwarf2/public.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
@@ -617,6 +618,16 @@ objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
 	  = simple_find_and_open_separate_symbol_file
 	      (this, find_separate_debug_file_by_debuglink, &warnings);
 
+      /* Attempt to download only an index from the separate debug info.
+	 As with debuginfod_find_and_open_separate_symbol_file, only attempt
+	 this once.  */
+      if (debug_bfd == nullptr && attempt == 0
+	  && dwarf2_has_separate_index (this))
+	{
+	  has_dwarf2 = true;
+	  break;
+	}
+
       /* 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
diff --git a/gdb/symfile.c b/gdb/symfile.c
index bdcc27e7ee5..820eb061aca 100644
--- a/gdb/symfile.c
+++ b/gdb/symfile.c
@@ -989,6 +989,10 @@ syms_from_objfile (struct objfile *objfile,
 static void
 finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
 {
+  struct objfile *parent = objfile->separate_debug_objfile_backlink;
+  bool was_deferred
+    = (parent != nullptr) && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
+
   /* If this is the main symbol file we have to clean up all users of the
      old main symbol file.  Otherwise it is sufficient to fixup all the
      breakpoints that may have been redefined by this symbol file.  */
@@ -999,7 +1003,7 @@ finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
 
       clear_symtab_users (add_flags);
     }
-  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0)
+  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0 && !was_deferred)
     {
       breakpoint_re_set ();
     }
@@ -1120,6 +1124,12 @@ symbol_file_add_with_addrs (const gdb_bfd_ref_ptr &abfd, const char *name,
   if (objfile->sf != nullptr)
     finish_new_objfile (objfile, add_flags);
 
+  /* Remove deferred status now in case any observers trigger symtab
+     expansion.  Otherwise gdb might try to read parent for psymbols
+     when it should read the separate debug objfile instead.  */
+  if (parent != nullptr && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0))
+    parent->remove_deferred_status ();
+
   gdb::observers::new_objfile.notify (objfile);
 
   return objfile;
diff --git a/gdb/symtab.c b/gdb/symtab.c
index cc0bdb80c7c..d0219da6317 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -2926,14 +2926,30 @@ find_pc_sect_compunit_symtab (CORE_ADDR pc, struct obj_section *section)
   if (best_cust != NULL)
     return best_cust;
 
+  int warn_if_readin = 1;
+
   /* Not found in symtabs, search the "quick" symtabs (e.g. psymtabs).  */
 
   for (objfile *objf : current_program_space->objfiles ())
     {
+      bool was_deferred = (objf->flags & OBJF_DOWNLOAD_DEFERRED) != 0;
+
       struct compunit_symtab *result
-	= objf->find_pc_sect_compunit_symtab (msymbol, pc, section, 1);
+	= objf->find_pc_sect_compunit_symtab (msymbol, pc, section,
+					      warn_if_readin);
+
       if (result != NULL)
 	return result;
+
+      /* If OBJF's separate debug info was just acquired, disable
+	 warn_if_readin for the next iteration of this loop.  This prevents
+	 a spurious warning in case an observer already triggered expansion
+	 of the separate debug objfile's symtabs.  */
+      if (was_deferred && objf->separate_debug_objfile != nullptr
+	  && (objf->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	warn_if_readin = 0;
+      else if (warn_if_readin == 0)
+	warn_if_readin = 1;
     }
 
   return NULL;
diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
new file mode 100644
index 00000000000..e4f05db0a79
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
@@ -0,0 +1,43 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023-2024 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/>.  */
+
+#include <stdio.h>
+#include <pthread.h>
+#include <unistd.h>
+
+extern void libsection2_test ();
+extern void *libsection2_thread_test (void *);
+
+static volatile int flag = 0;
+
+void
+libsection1_test ()
+{
+  pthread_t thr;
+
+  printf ("In libsection1\n");
+  libsection2_test ();
+
+  pthread_create (&thr, NULL, libsection2_thread_test, (void *) &flag);
+
+  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
+  while (!flag)
+    ;
+
+  printf ("Cancelling thread\n");
+  pthread_cancel (thr);
+}
diff --git a/gdb/testsuite/gdb.debuginfod/libsection2.c b/gdb/testsuite/gdb.debuginfod/libsection2.c
new file mode 100644
index 00000000000..3c82808e5fe
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/libsection2.c
@@ -0,0 +1,37 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023-2024 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/>.  */
+
+#include <stdio.h>
+#include <pthread.h>
+
+void
+libsection2_test ()
+{
+  printf ("In libsection2\n");
+}
+
+void *
+libsection2_thread_test (void *arg)
+{
+  int *flag = (int *) arg;
+  printf ("In thread test\n");
+
+  while (1)
+    *flag = 1;
+
+  return NULL;
+}
diff --git a/gdb/testsuite/gdb.debuginfod/section.c b/gdb/testsuite/gdb.debuginfod/section.c
new file mode 100644
index 00000000000..fa9ac536a6e
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/section.c
@@ -0,0 +1,29 @@
+/* This testcase is part of GDB, the GNU debugger.
+
+   Copyright 2023-2024 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/>.  */
+
+#include <stdio.h>
+
+extern void libsection1_test ();
+
+int
+main ()
+{
+  libsection1_test ();
+  printf ("in section exec\n");
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
new file mode 100644
index 00000000000..35086d15705
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/section.exp
@@ -0,0 +1,157 @@
+# Copyright 2023-2024 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/>.
+
+# Test debuginfod functionality
+
+standard_testfile
+
+load_lib debuginfod-support.exp
+
+clean_restart
+require allow_debuginfod_tests
+require allow_debuginfod_section_downloads
+
+# BINFILE calls a function from LIB_SL1.
+set libfile1 "libsection1"
+set libsrc1 $srcdir/$subdir/$libfile1.c
+set lib_sl1 [standard_output_file $libfile1.sl]
+
+# LIB_SL1 calls functions from LIB_SL2.
+set libfile2 "libsection2"
+set libsrc2 $srcdir/$subdir/$libfile2.c
+set lib_sl2 [standard_output_file $libfile2.sl]
+
+# Build LIB_SL2, LIB_SL1 and BINFILE.
+if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
+	{debug build-id shlib}] != 0 } {
+    return -1
+}
+
+if { [build_executable "build $libfile1" $lib_sl1 $libsrc1 \
+	[list debug build-id shlib_pthreads shlib=$lib_sl2]] != 0 } {
+    return -1
+}
+
+if { [build_executable "build executable" $binfile $srcfile \
+	[list debug build-id shlib=$lib_sl1 shlib=$lib_sl2]] != 0 } {
+    return -1
+}
+
+# Make sure libsection1 and libsection2 contain .gdb_index.
+if { [ensure_gdb_index $lib_sl1 "" "libsection1"] != 1 } {
+    untested "failed to add .gdb_index to $libfile1"
+    return -1
+}
+
+if { [ensure_gdb_index $lib_sl2 "" "libsection2"] != 1 } {
+    untested "failed to add .gdb_index to $libfile2"
+    return -1
+}
+
+# Strip solib debuginfo into separate files.
+if { [gdb_gnu_strip_debug $lib_sl1 ""] != 0} {
+   fail "strip $lib_sl1 debuginfo"
+   return -1
+}
+
+if { [gdb_gnu_strip_debug $lib_sl2 ""] != 0} {
+   fail "strip $lib_sl2 debuginfo"
+   return -1
+}
+
+# Move debuginfo files into directory that debuginfod will serve from.
+set debugdir [standard_output_file "debug"]
+set debuginfo_sl1 [standard_output_file $libfile1.sl.debug]
+set debuginfo_sl2 [standard_output_file $libfile2.sl.debug]
+
+file mkdir $debugdir
+file rename -force $debuginfo_sl1 $debugdir
+file rename -force $debuginfo_sl2 $debugdir
+
+# Restart GDB and clear the debuginfod client cache.  Then load BINFILE into
+# GDB and start running it.  Match output with pattern RES and use TESTNAME
+# as the test name.
+proc_with_prefix clean_restart_with_prompt { binfile testname } {
+    global cache
+
+    # Delete client cache so debuginfo downloads again.
+    file delete -force $cache
+    clean_restart
+
+    gdb_test_no_output "set debuginfod enabled on" \
+		       "clean_restart enable $testname"
+    gdb_load $binfile
+
+    runto_main
+}
+
+# Tests with no debuginfod server running.
+proc_with_prefix no_url { } {
+    global binfile libfile1 libfile2
+
+    gdb_load $binfile
+    if {![runto_main]} {
+       return
+    }
+
+    # Check that no section is downloaded and no debuginfo is found.
+    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile1.*" \
+	     "found no url lib1"
+    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile2.*" \
+	     "found no url lib2"
+}
+
+# Tests with a debuginfod server running.
+proc_with_prefix local_url { } {
+    global binfile
+    global libsrc1 lib_sl1 libfile1
+    global libsrc2 lib_sl2 libfile2
+    global debugdir db
+
+    set url [start_debuginfod $db $debugdir]
+    if { $url == "" } {
+	unresolved "failed to start debuginfod server"
+	return
+    }
+
+    # Point GDB to the server.
+    setenv DEBUGINFOD_URLS $url
+
+    clean_restart_with_prompt $binfile "index"
+
+    # Download debuginfo when stepping into a function.
+    set res ".*separate debug info for $lib_sl1.*\"In ${libfile1}\\\\n\".*"
+    gdb_test "step" $res "step"
+
+    clean_restart_with_prompt $binfile "break"
+
+    # Download debuginfo when setting a breakpoint.
+    set res "Download.*separate debug info for $lib_sl2.*"
+    gdb_test "br libsection2_test" $res "break set"
+
+    # Hit the breakpoint.
+    set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
+    gdb_test "c" $res "break continue"
+}
+
+# Create CACHE and DB directories ready for debuginfod to use.
+prepare_for_debuginfod cache db
+
+with_debuginfod_env $cache {
+    no_url
+    local_url
+}
+
+stop_debuginfod
diff --git a/gdb/testsuite/lib/debuginfod-support.exp b/gdb/testsuite/lib/debuginfod-support.exp
index 50a8b512a4a..71964a90564 100644
--- a/gdb/testsuite/lib/debuginfod-support.exp
+++ b/gdb/testsuite/lib/debuginfod-support.exp
@@ -113,6 +113,8 @@ proc with_debuginfod_env { cache body } {
 proc start_debuginfod { db debugdir } {
     global debuginfod_spawn_id spawn_id
 
+    set logfile [standard_output_file "server_log"]
+
     # Find an unused port.
     set port 7999
     set found false
@@ -127,7 +129,8 @@ proc start_debuginfod { db debugdir } {
 	    set old_spawn_id $spawn_id
 	}
 
-	spawn debuginfod -vvvv -d $db -p $port -F $debugdir
+	spawn sh -c "debuginfod -vvvv -d $db -p $port -F $debugdir 2>&1 \
+		| tee $logfile"
 	set debuginfod_spawn_id $spawn_id
 
 	if { [info exists old_spawn_id] } {
@@ -194,3 +197,23 @@ proc stop_debuginfod { } {
 	unset debuginfod_spawn_id
     }
 }
+
+# Return true if gdb is configured to download ELF/DWARF sections from
+# debuginfod servers.  Otherwise return false.
+proc allow_debuginfod_section_downloads { } {
+    set cmd "maint set debuginfod download-sections on"
+    set msg "enable section downloads"
+
+    gdb_test_multiple $cmd $msg {
+	-re -wrap ".*not compiled into GDB.*" {
+	    return false
+	}
+	-re -wrap "^" {
+	    return true
+	}
+	-re -wrap "" {
+	    fail "$gdb_test_name (unexpected output)"
+	    return false
+	}
+    }
+}
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 9ac707be696..9b3c7e6a02c 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -9414,10 +9414,14 @@ proc get_index_type { objfile { testname "" } } {
 # STYLE controls which style of index to add, if needed.  The empty
 # string (the default) means .gdb_index; "-dwarf-5" means .debug_names.
 
-proc ensure_gdb_index { binfile {style ""} } {
+proc ensure_gdb_index { binfile {style ""} {testname_prefix ""} } {
     set testfile [file tail $binfile]
-
     set test "check if index present"
+
+    if { $testname_prefix != "" } {
+      set test "$testname_prefix $test"
+    }
+
     set index_type [get_index_type $testfile $test]
 
     if { $index_type eq "gdb" || $index_type eq "dwarf5" } {
  
Aaron Merey Feb. 7, 2024, 9:25 p.m. UTC | #3
Ping

Thanks,
Aaron

On Fri, Jan 19, 2024 at 12:12 AM Aaron Merey <amerey@redhat.com> wrote:
>
> Hi Andrew,
>
> Thanks for the review.
>
> On Thu, Jan 18, 2024 at 10:34 AM Andrew Burgess <aburgess@redhat.com> wrote:
> >
> > Should be:
> >
> >   gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> >
> > OK, I'm going to stop commenting on these now, but there's a few more of
> > them in the rest of the patch.  GDB style is not to rely on implicit
> > int -> bool conversion.
>
> Fixed.
>
> >
> > > +
> > > +  SCOPE_EXIT { objfile->remove_deferred_status (); };
> > > +
> > > +  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > +  const char *filename;
> > > +  gdb_bfd_ref_ptr debug_bfd;
> > > +  gdb::unique_xmalloc_ptr<char> symfile_path;
> > > +  scoped_fd fd;
> >
> > Of these, filename, debug_bfd, and fd can be declared where they are
> > first defined.  You can probably move symfile_path closer to where it's
> > used too.
> >
>
> Fixed.
>
> > > diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
> > > index 1235f293f45..b674103c72f 100644
> > > --- a/gdb/dwarf2/section.c
> > > +++ b/gdb/dwarf2/section.c
> > > @@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
> > >        section = get_containing_section ();
> > >        gdb_assert (!section->is_virtual);
> > >      }
> > > -  gdb_assert (section->s.section != nullptr);
> > > +  if (section->s.section == nullptr)
> > > +    error (_("Can't find owner of DWARF section."));
> >
> > This seems like an interesting change.  There's no test that triggers
> > this case, but I guess you ran into this at some point.  Could you
> > explain more about what's going on here?
>
> This can happen if gdb mistakenly tries to read debug info that
> has been deferred and not yet downloaded.  For example if gdb attempted
> to read symbols from a debuginfo-stripped parent objfile without first
> downloading the full separate debug info, then this error would trigger.
>
> > > @@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> > >          && objfile->separate_debug_objfile_backlink == NULL)
> > >      {
> > >        if (objfile->find_and_add_separate_symbol_file (symfile_flags))
> > > -     gdb_assert (objfile->separate_debug_objfile != nullptr);
> > > +     gdb_assert (objfile->separate_debug_objfile != nullptr
> > > +                 || objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> > >        else
> > >       has_dwarf2 = false;
> > >      }
> > > diff --git a/gdb/frame.c b/gdb/frame.c
> > > index 41003cc7771..d3409c8fd97 100644
> > > --- a/gdb/frame.c
> > > +++ b/gdb/frame.c
> > > @@ -1889,6 +1889,13 @@ get_selected_frame (const char *message)
> > >       error (("%s"), message);
> > >
> > >        lookup_selected_frame (selected_frame_id, selected_frame_level);
> > > +
> > > +      /* It is possible for lookup_selected_frame to cause a new objfile
> > > +      to be loaded.  Some objfile observers may choose to clear
> > > +      selected_frame when an objfile is loaded.  Work around this by
> > > +      calling lookup_selected_frame again if the first try failed.  */
> > > +      if (selected_frame == nullptr)
> > > +     lookup_selected_frame (selected_frame_id, selected_frame_level);
> >
> > I notice this case wasn't tested -- I added an abort here and reran the
> > test, and didn't see it hit.
>
> With the 2nd call to lookup_selected_frame removed, the test
> "gdb.debuginfod/section.exp: local_url: line thread" becomes unresolved
> when testing with --target_board=native-gdbserver.
>
> >
> > I ask because this seems both interesting, and this doesn't feel like
> > the right solution... As a minimum you'd need a comment on
> > lookup_selected_frame that says "hey, you will need to call this
> > function twice to guarantee that it did what it promised".  So then I'd
> > suggest we should just rename lookup_selected_frame to
> > lookup_selected_frame_1, and have lookup_selected_frame be a wrapper
> > which does the double lookup.
>
> This is a better solution. Fixed.
>
> > But I was kind of hoping that we'd have a test that hit this case so I
> > could look at the stack trace and better understand what exactly is
> > happening when the selected frame is cleared.
> >
> > > diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > new file mode 100644
> > > index 00000000000..7ad19c276c4
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > @@ -0,0 +1,43 @@
> > > +/* This testcase is part of GDB, the GNU debugger.
> > > +
> > > +   Copyright 2023 Free Software Foundation, Inc.
> >
> > Remember to update your copyright dates to '2023-2024' throughout.
>
> Fixed.
>
> > > +load_lib debuginfod-support.exp
> > > +
> > > +require allow_debuginfod_tests
> > > +
> > > +set sourcetmp [standard_output_file tmp-${srcfile}]
> > > +set outputdir [standard_output_file {}]
> >
> > Not sure what these two are for.  Maybe a copy&paste issue?
>
> Fixed.
>
> >
> > > +
> > > +# SECTEXEC is an executable which calls a function from LIB_SL1.
> > > +set sectfile "section"
> > > +set sectsrc $srcdir/$subdir/section.c
> > > +set sectexec [standard_output_file $sectfile]
> >
> > I'd have expected you to use ${srcfile} and ${binfile} here which are
> > setup by `standard_testfile` based on the name of the test script.  So
> > section.exp will setup `section.c` and `/path/to/.../section`
> > respectively.
> >
> > The source file doesn't have the directory prefix, but the compile procs
> > know how to find the source file, so that's OK.
> >
> > standard_testfile also gives you `testfile` which is $binfile without
> > the path prefix, if that's needed.
> >
> > It's good practice to use $binfile throughout the test unless there's
> > some compelling reason not too everyone knows what $binfile is.
>
> Fixed.
>
> > > +if { [gdb_compile $sectsrc $sectexec executable $exec_opts] != "" } {
> > > +    untested "failed to compile $sectfile"
> > > +    return -1
> > > +}
> >
> > Instead of calling all these compile worker functions directly, the
> > better approach is to call build_executable and pass through the correct
> > flags.  This seems to work for me:
> >
> >   if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
> >           {debug build-id shlib}] != 0 } {
> >       return -1
> >   }
>
> Fixed.
>
> > > +    gdb_test "set debuginfod enabled on" "" "clean_restart enable $testname"
> >
> > You can use gdb_test_no_output here rather than passing an empty pattern.
>
> Fixed.
>
> > > +# Return 1 if gdb is configured to download ELF/DWARF sections from
> > > +# debuginfod servers.  Otherwise return 0.
> >
> > We can use 'true' and 'false' in TCL code, see allow_debuginfod_tests as
> > an example.
> >
> > > +proc enable_section_downloads { } {
> >
> > I think this should be renamed to something like allow_section_downloads
> > (though I about allow_debuginfod_section_downloads to make it clear it's
> > a debuginfod function).  Then, in the test script you would say:
> >
> >   require allow_debuginfod_section_downloads
> >
> > Rather than the `if` check that you have right now.
> >
> >
> > > +    global gdb_prompt
> >
> > This line isn't needed, gdb_prompt isn't used.
>
> Fixed.  I've attached the updated patch below.
>
> Aaron
>
> Commit message:
>
> At the beginning of a session, gdb may attempt to download debuginfo
> for all shared libraries associated with the process or core file
> being debugged.  This can be a waste of time and storage space when much
> of the debuginfo ends up not being used during the session.
>
> To reduce the gdb's startup latency and to download only the debuginfo
> that is really needed, this patch adds on-demand downloading of debuginfo.
>
> 'set debuginfo enabled on' now causes gdb to attempt to download a .gdb_index
> for each shared library instead of its full debuginfo.  Each corresponding
> separate debuginfo will be deferred until gdb needs to expand symtabs
> associated with the debuginfo's index.
>
> Because these indices are significantly smaller than their corresponding
> debuginfo, this generally reduces the total amount of data gdb downloads.
> Reductions of 80%-95% have been observed when debugging large GUI programs.
>
>     (gdb) set debuginfod enabled on
>     (gdb) start
>     Downloading section .gdb_index for /lib64/libcurl.so.4
>     [...]
>     1826        client->server_mhandle = curl_multi_init ();
>     (gdb) step
>     Downloading separate debug info for /lib64/libcurl.so.4
>     Downloading separate debug info for [libcurl dwz]
>     Downloading source file /usr/src/debug/curl-7.85.0-6.fc37.x86_64/build-full/lib/../../lib/multi.c
>     curl_multi_init () at ../../lib/multi.c:457
>     457     {
>     (gdb)
>
> Some of the key functions below include dwarf2_has_separate_index which
> downloads the separate .gdb_index.  If successful, the shared library
> objfile owns the index until the separate debug objfile is downloaded
> or confirmed to not be available.
>
> read_full_dwarf_from_debuginfod downloads the full debuginfo and
> initializes the separate debug objfile.  It is called by functions
> such as dwarf2_gdb_index::expand_symtabs_matching and
> dwarf2_base_index_functions::find_pc_sect_compunit_symtab when symtab
> expansion is required.
> ---
>  gdb/dwarf2/frame.c                         |  13 ++
>  gdb/dwarf2/frame.h                         |   4 +
>  gdb/dwarf2/index-cache.c                   |  33 +++++
>  gdb/dwarf2/index-cache.h                   |  13 ++
>  gdb/dwarf2/public.h                        |   7 +
>  gdb/dwarf2/read-gdb-index.c                | 125 +++++++++++++---
>  gdb/dwarf2/read.c                          | 145 ++++++++++++++++++-
>  gdb/dwarf2/read.h                          |  10 ++
>  gdb/dwarf2/section.c                       |   3 +-
>  gdb/elfread.c                              |   3 +-
>  gdb/frame.c                                |  27 +++-
>  gdb/objfile-flags.h                        |   4 +
>  gdb/objfiles.h                             |  20 +++
>  gdb/quick-symbol.h                         |   4 +
>  gdb/symfile-debug.c                        |  11 ++
>  gdb/symfile.c                              |  12 +-
>  gdb/symtab.c                               |  18 ++-
>  gdb/testsuite/gdb.debuginfod/libsection1.c |  43 ++++++
>  gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
>  gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
>  gdb/testsuite/gdb.debuginfod/section.exp   | 157 +++++++++++++++++++++
>  gdb/testsuite/lib/debuginfod-support.exp   |  25 +++-
>  gdb/testsuite/lib/gdb.exp                  |   8 +-
>  23 files changed, 719 insertions(+), 32 deletions(-)
>  create mode 100644 gdb/testsuite/gdb.debuginfod/libsection1.c
>  create mode 100644 gdb/testsuite/gdb.debuginfod/libsection2.c
>  create mode 100644 gdb/testsuite/gdb.debuginfod/section.c
>  create mode 100644 gdb/testsuite/gdb.debuginfod/section.exp
>
> diff --git a/gdb/dwarf2/frame.c b/gdb/dwarf2/frame.c
> index d3d1ecdf1f5..9e290af79f7 100644
> --- a/gdb/dwarf2/frame.c
> +++ b/gdb/dwarf2/frame.c
> @@ -1620,6 +1620,19 @@ set_comp_unit (struct objfile *objfile, struct comp_unit *unit)
>    return dwarf2_frame_bfd_data.set (abfd, unit);
>  }
>
> +/* See frame.h.  */
> +
> +void
> +dwarf2_clear_frame_data (struct objfile *objfile)
> +{
> +  bfd *abfd = objfile->obfd.get ();
> +
> +  if (gdb_bfd_requires_relocations (abfd))
> +    dwarf2_frame_objfile_data.clear (objfile);
> +  else
> +    dwarf2_frame_bfd_data.clear (abfd);
> +}
> +
>  /* Find the FDE for *PC.  Return a pointer to the FDE, and store the
>     initial location associated with it into *PC.  */
>
> diff --git a/gdb/dwarf2/frame.h b/gdb/dwarf2/frame.h
> index 5643e557513..2391e313e7c 100644
> --- a/gdb/dwarf2/frame.h
> +++ b/gdb/dwarf2/frame.h
> @@ -238,6 +238,10 @@ void dwarf2_append_unwinders (struct gdbarch *gdbarch);
>  extern const struct frame_base *
>    dwarf2_frame_base_sniffer (frame_info_ptr this_frame);
>
> +/* Delete OBJFILEs comp_unit.  */
> +
> +extern void dwarf2_clear_frame_data (struct objfile * objfile);
> +
>  /* Compute the DWARF CFA for a frame.  */
>
>  CORE_ADDR dwarf2_frame_cfa (frame_info_ptr this_frame);
> diff --git a/gdb/dwarf2/index-cache.c b/gdb/dwarf2/index-cache.c
> index 69f70642dc6..8c969ecd590 100644
> --- a/gdb/dwarf2/index-cache.c
> +++ b/gdb/dwarf2/index-cache.c
> @@ -240,6 +240,33 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
>    return {};
>  }
>
> +/* See index-cache.h.  */
> +
> +gdb::array_view<const gdb_byte>
> +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> +                                         std::unique_ptr<index_cache_resource> *resource)
> +{
> +  try
> +    {
> +      /* Try to map that file.  */
> +      index_cache_resource_mmap *mmap_resource
> +       = new index_cache_resource_mmap (index_path);
> +
> +      /* Hand the resource to the caller.  */
> +      resource->reset (mmap_resource);
> +
> +      return gdb::array_view<const gdb_byte>
> +         ((const gdb_byte *) mmap_resource->mapping.get (),
> +          mmap_resource->mapping.size ());
> +    }
> +  catch (const gdb_exception_error &except)
> +    {
> +      warning (_("Unable to read %s: %s"), index_path, except.what ());
> +    }
> +
> +  return {};
> +}
> +
>  #else /* !HAVE_SYS_MMAN_H */
>
>  /* See dwarf-index-cache.h.  This is a no-op on unsupported systems.  */
> @@ -251,6 +278,12 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
>    return {};
>  }
>
> +gdb::array_view<const gdb_byte>
> +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> +                                         std::unique_ptr<index_cache_resource> *resource)
> +{
> +  return {};
> +}
>  #endif
>
>  /* See dwarf-index-cache.h.  */
> diff --git a/gdb/dwarf2/index-cache.h b/gdb/dwarf2/index-cache.h
> index 023fc86fc89..0d845ebcadd 100644
> --- a/gdb/dwarf2/index-cache.h
> +++ b/gdb/dwarf2/index-cache.h
> @@ -90,6 +90,19 @@ class index_cache
>    lookup_gdb_index (const bfd_build_id *build_id,
>                     std::unique_ptr<index_cache_resource> *resource);
>
> +  /* Look for an index file located at INDEX_PATH in the debuginfod cache.
> +     Unlike lookup_gdb_index, this function does not exit early if the
> +     index cache has not been enabled.
> +
> +     If found, return the contents as an array_view and store the underlying
> +     resources (allocated memory, mapped file, etc) in RESOURCE.  The returned
> +     array_view is valid as long as RESOURCE is not destroyed.
> +
> +     If no matching index file is found, return an empty array view.  */
> +  gdb::array_view<const gdb_byte>
> +  lookup_gdb_index_debuginfod (const char *index_path,
> +                              std::unique_ptr<index_cache_resource> *resource);
> +
>    /* Return the number of cache hits.  */
>    unsigned int n_hits () const
>    { return m_n_hits; }
> diff --git a/gdb/dwarf2/public.h b/gdb/dwarf2/public.h
> index efb754b5fe8..4d3776465c3 100644
> --- a/gdb/dwarf2/public.h
> +++ b/gdb/dwarf2/public.h
> @@ -44,4 +44,11 @@ extern bool dwarf2_initialize_objfile
>
>  extern void dwarf2_build_frame_info (struct objfile *);
>
> +/* Query debuginfod for the .gdb_index associated with OBJFILE.  If
> +   successful, create an objfile to hold the .gdb_index information
> +   and act as a placeholder until the full debuginfo needs to be
> +   downloaded.  */
> +
> +extern bool dwarf2_has_separate_index (struct objfile *);
> +
>  #endif /* DWARF2_PUBLIC_H */
> diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
> index 4ca44540296..f4d77ed2d04 100644
> --- a/gdb/dwarf2/read-gdb-index.c
> +++ b/gdb/dwarf2/read-gdb-index.c
> @@ -139,6 +139,8 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>       gdb.dwarf2/gdb-index.exp testcase.  */
>    void dump (struct objfile *objfile) override;
>
> +  /* Calls do_expand_symtabs_matching and triggers debuginfo downloading
> +     if necessary.  */
>    bool expand_symtabs_matching
>      (struct objfile *objfile,
>       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> @@ -148,8 +150,60 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>       block_search_flags search_flags,
>       domain_enum domain,
>       enum search_domain kind) override;
> +
> +  /* Expand symtabs matching a given symbol or file.  */
> +  bool do_expand_symtabs_matching
> +    (struct objfile *objfile,
> +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> +     const lookup_name_info *lookup_name,
> +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> +     block_search_flags search_flags,
> +     domain_enum domain,
> +     enum search_domain kind);
> +
> +  /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
> +     debuginfo if necessary.  */
> +  void expand_all_symtabs (struct objfile *objfile) override;
> +
> +  /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
> +     debuginfo if necessary.  */
> +  struct symtab *find_last_source_symtab (struct objfile *objfile) override;
>  };
>
> +void
> +dwarf2_gdb_index::expand_all_symtabs (struct objfile *objfile)
> +{
> +  try
> +    {
> +      dwarf2_base_index_functions::expand_all_symtabs (objfile);
> +    }
> +  catch (const gdb_exception &e)
> +    {
> +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +       exception_print (gdb_stderr, e);
> +      else
> +       read_full_dwarf_from_debuginfod (objfile, this);
> +    }
> +}
> +
> +struct symtab *
> +dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
> +{
> +  try
> +    {
> +      return dwarf2_base_index_functions::find_last_source_symtab (objfile);
> +    }
> +  catch (const gdb_exception &e)
> +    {
> +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +       exception_print (gdb_stderr, e);
> +      else
> +       read_full_dwarf_from_debuginfod (objfile, this);
> +      return nullptr;
> +    }
> +}
> +
>  /* This dumps minimal information about the index.
>     It is called via "mt print objfiles".
>     One use is to verify .gdb_index has been loaded by the
> @@ -268,7 +322,7 @@ dw2_expand_marked_cus
>  }
>
>  bool
> -dwarf2_gdb_index::expand_symtabs_matching
> +dwarf2_gdb_index::do_expand_symtabs_matching
>      (struct objfile *objfile,
>       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
>       const lookup_name_info *lookup_name,
> @@ -317,6 +371,39 @@ dwarf2_gdb_index::expand_symtabs_matching
>    return result;
>  }
>
> +bool
> +dwarf2_gdb_index::expand_symtabs_matching
> +    (struct objfile *objfile,
> +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> +     const lookup_name_info *lookup_name,
> +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> +     block_search_flags search_flags,
> +     domain_enum domain,
> +     enum search_domain kind)
> +{
> +  if ((objfile->flags & OBJF_READNEVER) != 0)
> +    return false;
> +
> +  try
> +    {
> +      return do_expand_symtabs_matching (objfile, file_matcher, lookup_name,
> +                                        symbol_matcher, expansion_notify,
> +                                        search_flags, domain, kind);
> +    }
> +  catch (const gdb_exception &e)
> +    {
> +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +       {
> +         exception_print (gdb_stderr, e);
> +         return false;
> +       }
> +
> +      read_full_dwarf_from_debuginfod (objfile, this);
> +      return true;
> +    }
> +}
> +
>  quick_symbol_functions_up
>  mapped_gdb_index::make_quick_functions () const
>  {
> @@ -652,28 +739,32 @@ dwarf2_read_gdb_index
>
>    /* If there is a .dwz file, read it so we can get its CU list as
>       well.  */
> -  dwz = dwarf2_get_dwz_file (per_bfd);
> -  if (dwz != NULL)
> +  if (get_gdb_index_contents_dwz != nullptr)
>      {
>        mapped_gdb_index dwz_map;
>        const gdb_byte *dwz_types_ignore;
>        offset_type dwz_types_elements_ignore;
> +      dwz = dwarf2_get_dwz_file (per_bfd);
>
> -      gdb::array_view<const gdb_byte> dwz_index_content
> -       = get_gdb_index_contents_dwz (objfile, dwz);
> -
> -      if (dwz_index_content.empty ())
> -       return 0;
> -
> -      if (!read_gdb_index_from_buffer (bfd_get_filename (dwz->dwz_bfd.get ()),
> -                                      1, dwz_index_content, &dwz_map,
> -                                      &dwz_list, &dwz_list_elements,
> -                                      &dwz_types_ignore,
> -                                      &dwz_types_elements_ignore))
> +      if (dwz != nullptr)
>         {
> -         warning (_("could not read '.gdb_index' section from %s; skipping"),
> -                  bfd_get_filename (dwz->dwz_bfd.get ()));
> -         return 0;
> +         gdb::array_view<const gdb_byte> dwz_index_content
> +           = get_gdb_index_contents_dwz (objfile, dwz);
> +
> +         if (dwz_index_content.empty ())
> +           return 0;
> +
> +         if (!read_gdb_index_from_buffer (bfd_get_filename
> +                                            (dwz->dwz_bfd.get ()),
> +                                          1, dwz_index_content, &dwz_map,
> +                                          &dwz_list, &dwz_list_elements,
> +                                          &dwz_types_ignore,
> +                                          &dwz_types_elements_ignore))
> +           {
> +             warning (_("could not read '.gdb_index' section from %s; skipping"),
> +                      bfd_get_filename (dwz->dwz_bfd.get ()));
> +               return 0;
> +           }
>         }
>      }
>
> diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> index 76dbeb8d536..062f0e34938 100644
> --- a/gdb/dwarf2/read.c
> +++ b/gdb/dwarf2/read.c
> @@ -35,6 +35,7 @@
>  #include "dwarf2/attribute.h"
>  #include "dwarf2/comp-unit-head.h"
>  #include "dwarf2/cu.h"
> +#include "dwarf2/frame.h"
>  #include "dwarf2/index-cache.h"
>  #include "dwarf2/index-common.h"
>  #include "dwarf2/leb.h"
> @@ -96,6 +97,8 @@
>  #include "split-name.h"
>  #include "gdbsupport/thread-pool.h"
>  #include "run-on-main-thread.h"
> +#include "inferior.h"
> +#include "debuginfod-support.h"
>
>  /* When == 1, print basic high level tracing messages.
>     When > 1, be more verbose.
> @@ -3003,7 +3006,7 @@ dwarf2_base_index_functions::find_per_cu (dwarf2_per_bfd *per_bfd,
>  }
>
>  struct compunit_symtab *
> -dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> +dwarf2_base_index_functions::do_find_pc_sect_compunit_symtab
>       (struct objfile *objfile,
>        struct bound_minimal_symbol msymbol,
>        CORE_ADDR pc,
> @@ -3034,6 +3037,32 @@ dwarf2_base_index_functions::find_pc_sect_compunit_symtab
>    return result;
>  }
>
> +struct compunit_symtab *
> +dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> +     (struct objfile *objfile,
> +      struct bound_minimal_symbol msymbol,
> +      CORE_ADDR pc,
> +      struct obj_section *section,
> +      int warn_if_readin)
> +{
> +  if ((objfile->flags & OBJF_READNEVER) != 0)
> +    return nullptr;
> +
> +  try
> +    {
> +      return do_find_pc_sect_compunit_symtab (objfile, msymbol, pc,
> +                                             section, warn_if_readin);
> +    }
> +  catch (const gdb_exception &e)
> +    {
> +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +       exception_print (gdb_stderr, e);
> +      else
> +       read_full_dwarf_from_debuginfod (objfile, this);
> +      return nullptr;
> +    }
> +}
> +
>  void
>  dwarf2_base_index_functions::map_symbol_filenames
>       (struct objfile *objfile,
> @@ -3190,6 +3219,29 @@ get_gdb_index_contents_from_cache_dwz (objfile *obj, dwz_file *dwz)
>    return global_index_cache.lookup_gdb_index (build_id, &dwz->index_cache_res);
>  }
>
> +/* Query debuginfod for the .gdb_index matching OBJFILE's build-id.  Return the
> +   contents if successful.  */
> +
> +static gdb::array_view<const gdb_byte>
> +get_gdb_index_contents_from_debuginfod (objfile *objfile, dwarf2_per_bfd *per_bfd)
> +{
> +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> +  if (build_id == nullptr)
> +    return {};
> +
> +  gdb::unique_xmalloc_ptr<char> index_path;
> +  scoped_fd fd = debuginfod_section_query (build_id->data, build_id->size,
> +                                          bfd_get_filename
> +                                            (objfile->obfd.get ()),
> +                                          ".gdb_index",
> +                                          &index_path);
> +  if (fd.get () < 0)
> +    return {};
> +
> +  return global_index_cache.lookup_gdb_index_debuginfod
> +    (index_path.get (), &per_bfd->index_cache_res);
> +}
> +
>  static quick_symbol_functions_up make_cooked_index_funcs
>       (dwarf2_per_objfile *);
>
> @@ -3200,7 +3252,8 @@ dwarf2_initialize_objfile (struct objfile *objfile,
>                            const struct dwarf2_debug_sections *names,
>                            bool can_copy)
>  {
> -  if (!dwarf2_has_info (objfile, names, can_copy))
> +  if (!dwarf2_has_info (objfile, names, can_copy)
> +      && (objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
>      return false;
>
>    dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile);
> @@ -3262,6 +3315,16 @@ dwarf2_initialize_objfile (struct objfile *objfile,
>        global_index_cache.hit ();
>        objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
>      }
> +  /* Try to read just a separately downloaded gdb index.  */
> +  else if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0
> +          && dwarf2_read_gdb_index (per_objfile,
> +                                    get_gdb_index_contents_from_debuginfod,
> +                                    nullptr))
> +    {
> +      dwarf_read_debug_printf ("found .gdb_index from debuginfod");
> +      objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
> +      objfile->qf.begin ()->get ()->from_separate_index = true;
> +    }
>    else
>      {
>        global_index_cache.miss ();
> @@ -3270,6 +3333,84 @@ dwarf2_initialize_objfile (struct objfile *objfile,
>    return true;
>  }
>
> +/* See read.h.  */
> +
> +void
> +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> +                                dwarf2_base_index_functions *fncs)
> +{
> +  gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> +
> +  SCOPE_EXIT { objfile->remove_deferred_status (); };
> +
> +  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> +  if (build_id == nullptr)
> +    return;
> +
> +  const char *filename = bfd_get_filename (objfile->obfd.get ());
> +  gdb::unique_xmalloc_ptr<char> symfile_path;
> +  scoped_fd fd;
> +
> +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> +                                  filename, &symfile_path);
> +  if (fd.get () < 0)
> +    return;
> +
> +  /* Separate debuginfo successfully retrieved from server.  */
> +  gdb_bfd_ref_ptr debug_bfd = symfile_bfd_open (symfile_path.get ());
> +  if (debug_bfd == nullptr
> +      || !build_id_verify (debug_bfd.get (), build_id->size, build_id->data))
> +    {
> +      warning (_("File \"%s\" from debuginfod cannot be opened as bfd"),
> +              filename);
> +      return;
> +    }
> +
> +  /* Clear frame data so it can be recalculated using DWARF.  */
> +  dwarf2_clear_frame_data (objfile);
> +
> +  /* This may also trigger a dwz download.  */
> +  symbol_file_add_separate (debug_bfd, symfile_path.get (),
> +                           current_inferior ()->symfile_flags, objfile);
> +}
> +
> +/* See public.h.  */
> +
> +bool
> +dwarf2_has_separate_index (struct objfile *objfile)
> +{
> +  if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0)
> +    return true;
> +  if ((objfile->flags & OBJF_MAINLINE) != 0)
> +    return false;
> +  if (!IS_DIR_SEPARATOR (*objfile_filename (objfile)))
> +    return false;
> +
> +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> +
> +  if (build_id == nullptr)
> +    return false;
> +
> +  gdb::unique_xmalloc_ptr<char> index_path;
> +  scoped_fd fd = debuginfod_section_query (build_id->data,
> +                                          build_id->size,
> +                                          bfd_get_filename
> +                                            (objfile->obfd.get ()),
> +                                          ".gdb_index",
> +                                          &index_path);
> +
> +  if (fd.get () < 0)
> +    return false;
> +
> +  /* We found a separate .gdb_index file so a separate debuginfo file
> +     should exist, but we don't want to download it until necessary.
> +     Attach the index to this objfile and defer the debuginfo download
> +     until gdb needs to expand symtabs referenced by the index.  */
> +  objfile->flags |= OBJF_DOWNLOAD_DEFERRED;
> +  dwarf2_initialize_objfile (objfile);
> +  return true;
> +}
> +
>
>
>  /* Find the base address of the compilation unit for range lists and
> diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> index d6631d45a54..8b6f1864458 100644
> --- a/gdb/dwarf2/read.h
> +++ b/gdb/dwarf2/read.h
> @@ -900,6 +900,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
>       CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
>         override;
>
> +  struct compunit_symtab *do_find_pc_sect_compunit_symtab
> +    (struct objfile *objfile, struct bound_minimal_symbol msymbol,
> +     CORE_ADDR pc, struct obj_section *section, int warn_if_readin);
> +
>    struct compunit_symtab *find_compunit_symtab_by_address
>      (struct objfile *objfile, CORE_ADDR address) override
>    {
> @@ -972,4 +976,10 @@ extern void create_all_units (dwarf2_per_objfile *per_objfile);
>
>  extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
>
> +/* If OBJFILE contains information from a separately downloaded .gdb_index,
> +   attempt to download the full debuginfo.  */
> +
> +extern void read_full_dwarf_from_debuginfod (struct objfile *,
> +                                            dwarf2_base_index_functions *);
> +
>  #endif /* DWARF2READ_H */
> diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
> index 1235f293f45..b674103c72f 100644
> --- a/gdb/dwarf2/section.c
> +++ b/gdb/dwarf2/section.c
> @@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
>        section = get_containing_section ();
>        gdb_assert (!section->is_virtual);
>      }
> -  gdb_assert (section->s.section != nullptr);
> +  if (section->s.section == nullptr)
> +    error (_("Can't find owner of DWARF section."));
>    return section->s.section->owner;
>  }
>
> diff --git a/gdb/elfread.c b/gdb/elfread.c
> index c5ba8837217..f14dc1bf80b 100644
> --- a/gdb/elfread.c
> +++ b/gdb/elfread.c
> @@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
>            && objfile->separate_debug_objfile_backlink == NULL)
>      {
>        if (objfile->find_and_add_separate_symbol_file (symfile_flags))
> -       gdb_assert (objfile->separate_debug_objfile != nullptr);
> +       gdb_assert (objfile->separate_debug_objfile != nullptr
> +                   || (objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
>        else
>         has_dwarf2 = false;
>      }
> diff --git a/gdb/frame.c b/gdb/frame.c
> index 41003cc7771..7f57cf80a1b 100644
> --- a/gdb/frame.c
> +++ b/gdb/frame.c
> @@ -1772,14 +1772,10 @@ restore_selected_frame (frame_id frame_id, int frame_level)
>    selected_frame = nullptr;
>  }
>
> -/* Lookup the frame_info object for the selected frame FRAME_ID /
> -   FRAME_LEVEL and cache the result.
> -
> -   If FRAME_LEVEL > 0 and the originally selected frame isn't found,
> -   warn and select the innermost (current) frame.  */
> +/* See lookup_selected_frame.  */
>
>  static void
> -lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> +lookup_selected_frame_1 (struct frame_id &a_frame_id, int frame_level)
>  {
>    frame_info_ptr frame = NULL;
>    int count;
> @@ -1851,6 +1847,25 @@ lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
>      }
>  }
>
> +/* Lookup the frame_info object for the selected frame FRAME_ID /
> +   FRAME_LEVEL and cache the result.
> +
> +   If FRAME_LEVEL > 0 and the originally selected frame isn't found,
> +   warn and select the innermost (current) frame.  */
> +
> +static void
> +lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> +{
> +  lookup_selected_frame_1 (selected_frame_id, selected_frame_level);
> +
> +  /* It is possible for lookup_selected_frame_1 to cause a new objfile
> +     to be loaded.  However some objfile observers may choose to clear
> +     selected_frame when an objfile is loaded.  Work around this by
> +     calling lookup_selected_frame_1 again if the first call failed.  */
> +  if (selected_frame == nullptr)
> +    lookup_selected_frame_1 (selected_frame_id, selected_frame_level);
> +}
> +
>  bool
>  has_stack_frames ()
>  {
> diff --git a/gdb/objfile-flags.h b/gdb/objfile-flags.h
> index 74aea1a88d3..3f922d9e84f 100644
> --- a/gdb/objfile-flags.h
> +++ b/gdb/objfile-flags.h
> @@ -56,6 +56,10 @@ enum objfile_flag : unsigned
>      /* User requested that we do not read this objfile's symbolic
>         information.  */
>      OBJF_READNEVER = 1 << 6,
> +
> +    /* A separate .gdb_index has been downloaded for this objfile.
> +       Debuginfo for this objfile can be downloaded when required.  */
> +    OBJF_DOWNLOAD_DEFERRED = 1 << 7,
>    };
>
>  DEF_ENUM_FLAGS_TYPE (enum objfile_flag, objfile_flags);
> diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> index b917886988d..4a4db1e7058 100644
> --- a/gdb/objfiles.h
> +++ b/gdb/objfiles.h
> @@ -622,6 +622,26 @@ struct objfile
>                                                domain_enum domain,
>                                                bool *symbol_found_p);
>
> +  /* Indicate that the acquisition of this objfile's separate debug objfile
> +     is no longer deferred.  Used when the debug objfile has been acquired
> +     or could not be found.  */
> +  void remove_deferred_status ()
> +  {
> +    flags &= ~OBJF_DOWNLOAD_DEFERRED;
> +
> +    /* Remove quick_symbol_functions derived from a separately downloaded
> +       index.  If available the separate debug objfile's index will be used
> +       instead, since that objfile actually contains the symbols and CUs
> +       referenced in the index.
> +
> +       No more than one element of qf should have from_separate_index set
> +       to true.  */
> +    qf.remove_if ([&] (const quick_symbol_functions_up &qf_up)
> +      {
> +       return qf_up->from_separate_index;
> +      });
> +  }
> +
>    /* Return the relocation offset applied to SECTION.  */
>    CORE_ADDR section_offset (bfd_section *section) const
>    {
> diff --git a/gdb/quick-symbol.h b/gdb/quick-symbol.h
> index 4cd234162ae..01a228b446c 100644
> --- a/gdb/quick-symbol.h
> +++ b/gdb/quick-symbol.h
> @@ -193,6 +193,10 @@ struct quick_symbol_functions
>    virtual void compute_main_name (struct objfile *objfile)
>    {
>    }
> +
> +  /* True if this quick_symbol_functions is derived from a separately
> +     downloaded index.  */
> +  bool from_separate_index = false;
>  };
>
>  typedef std::unique_ptr<quick_symbol_functions> quick_symbol_functions_up;
> diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
> index c684f9cdd43..3ed31661300 100644
> --- a/gdb/symfile-debug.c
> +++ b/gdb/symfile-debug.c
> @@ -37,6 +37,7 @@
>  #include "cli/cli-style.h"
>  #include "build-id.h"
>  #include "debuginfod-support.h"
> +#include "dwarf2/public.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
> @@ -617,6 +618,16 @@ objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
>           = simple_find_and_open_separate_symbol_file
>               (this, find_separate_debug_file_by_debuglink, &warnings);
>
> +      /* Attempt to download only an index from the separate debug info.
> +        As with debuginfod_find_and_open_separate_symbol_file, only attempt
> +        this once.  */
> +      if (debug_bfd == nullptr && attempt == 0
> +         && dwarf2_has_separate_index (this))
> +       {
> +         has_dwarf2 = true;
> +         break;
> +       }
> +
>        /* 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
> diff --git a/gdb/symfile.c b/gdb/symfile.c
> index bdcc27e7ee5..820eb061aca 100644
> --- a/gdb/symfile.c
> +++ b/gdb/symfile.c
> @@ -989,6 +989,10 @@ syms_from_objfile (struct objfile *objfile,
>  static void
>  finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
>  {
> +  struct objfile *parent = objfile->separate_debug_objfile_backlink;
> +  bool was_deferred
> +    = (parent != nullptr) && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> +
>    /* If this is the main symbol file we have to clean up all users of the
>       old main symbol file.  Otherwise it is sufficient to fixup all the
>       breakpoints that may have been redefined by this symbol file.  */
> @@ -999,7 +1003,7 @@ finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
>
>        clear_symtab_users (add_flags);
>      }
> -  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0)
> +  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0 && !was_deferred)
>      {
>        breakpoint_re_set ();
>      }
> @@ -1120,6 +1124,12 @@ symbol_file_add_with_addrs (const gdb_bfd_ref_ptr &abfd, const char *name,
>    if (objfile->sf != nullptr)
>      finish_new_objfile (objfile, add_flags);
>
> +  /* Remove deferred status now in case any observers trigger symtab
> +     expansion.  Otherwise gdb might try to read parent for psymbols
> +     when it should read the separate debug objfile instead.  */
> +  if (parent != nullptr && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0))
> +    parent->remove_deferred_status ();
> +
>    gdb::observers::new_objfile.notify (objfile);
>
>    return objfile;
> diff --git a/gdb/symtab.c b/gdb/symtab.c
> index cc0bdb80c7c..d0219da6317 100644
> --- a/gdb/symtab.c
> +++ b/gdb/symtab.c
> @@ -2926,14 +2926,30 @@ find_pc_sect_compunit_symtab (CORE_ADDR pc, struct obj_section *section)
>    if (best_cust != NULL)
>      return best_cust;
>
> +  int warn_if_readin = 1;
> +
>    /* Not found in symtabs, search the "quick" symtabs (e.g. psymtabs).  */
>
>    for (objfile *objf : current_program_space->objfiles ())
>      {
> +      bool was_deferred = (objf->flags & OBJF_DOWNLOAD_DEFERRED) != 0;
> +
>        struct compunit_symtab *result
> -       = objf->find_pc_sect_compunit_symtab (msymbol, pc, section, 1);
> +       = objf->find_pc_sect_compunit_symtab (msymbol, pc, section,
> +                                             warn_if_readin);
> +
>        if (result != NULL)
>         return result;
> +
> +      /* If OBJF's separate debug info was just acquired, disable
> +        warn_if_readin for the next iteration of this loop.  This prevents
> +        a spurious warning in case an observer already triggered expansion
> +        of the separate debug objfile's symtabs.  */
> +      if (was_deferred && objf->separate_debug_objfile != nullptr
> +         && (objf->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +       warn_if_readin = 0;
> +      else if (warn_if_readin == 0)
> +       warn_if_readin = 1;
>      }
>
>    return NULL;
> diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
> new file mode 100644
> index 00000000000..e4f05db0a79
> --- /dev/null
> +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> @@ -0,0 +1,43 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2023-2024 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/>.  */
> +
> +#include <stdio.h>
> +#include <pthread.h>
> +#include <unistd.h>
> +
> +extern void libsection2_test ();
> +extern void *libsection2_thread_test (void *);
> +
> +static volatile int flag = 0;
> +
> +void
> +libsection1_test ()
> +{
> +  pthread_t thr;
> +
> +  printf ("In libsection1\n");
> +  libsection2_test ();
> +
> +  pthread_create (&thr, NULL, libsection2_thread_test, (void *) &flag);
> +
> +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> +  while (!flag)
> +    ;
> +
> +  printf ("Cancelling thread\n");
> +  pthread_cancel (thr);
> +}
> diff --git a/gdb/testsuite/gdb.debuginfod/libsection2.c b/gdb/testsuite/gdb.debuginfod/libsection2.c
> new file mode 100644
> index 00000000000..3c82808e5fe
> --- /dev/null
> +++ b/gdb/testsuite/gdb.debuginfod/libsection2.c
> @@ -0,0 +1,37 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2023-2024 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/>.  */
> +
> +#include <stdio.h>
> +#include <pthread.h>
> +
> +void
> +libsection2_test ()
> +{
> +  printf ("In libsection2\n");
> +}
> +
> +void *
> +libsection2_thread_test (void *arg)
> +{
> +  int *flag = (int *) arg;
> +  printf ("In thread test\n");
> +
> +  while (1)
> +    *flag = 1;
> +
> +  return NULL;
> +}
> diff --git a/gdb/testsuite/gdb.debuginfod/section.c b/gdb/testsuite/gdb.debuginfod/section.c
> new file mode 100644
> index 00000000000..fa9ac536a6e
> --- /dev/null
> +++ b/gdb/testsuite/gdb.debuginfod/section.c
> @@ -0,0 +1,29 @@
> +/* This testcase is part of GDB, the GNU debugger.
> +
> +   Copyright 2023-2024 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/>.  */
> +
> +#include <stdio.h>
> +
> +extern void libsection1_test ();
> +
> +int
> +main ()
> +{
> +  libsection1_test ();
> +  printf ("in section exec\n");
> +
> +  return 0;
> +}
> diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
> new file mode 100644
> index 00000000000..35086d15705
> --- /dev/null
> +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> @@ -0,0 +1,157 @@
> +# Copyright 2023-2024 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/>.
> +
> +# Test debuginfod functionality
> +
> +standard_testfile
> +
> +load_lib debuginfod-support.exp
> +
> +clean_restart
> +require allow_debuginfod_tests
> +require allow_debuginfod_section_downloads
> +
> +# BINFILE calls a function from LIB_SL1.
> +set libfile1 "libsection1"
> +set libsrc1 $srcdir/$subdir/$libfile1.c
> +set lib_sl1 [standard_output_file $libfile1.sl]
> +
> +# LIB_SL1 calls functions from LIB_SL2.
> +set libfile2 "libsection2"
> +set libsrc2 $srcdir/$subdir/$libfile2.c
> +set lib_sl2 [standard_output_file $libfile2.sl]
> +
> +# Build LIB_SL2, LIB_SL1 and BINFILE.
> +if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
> +       {debug build-id shlib}] != 0 } {
> +    return -1
> +}
> +
> +if { [build_executable "build $libfile1" $lib_sl1 $libsrc1 \
> +       [list debug build-id shlib_pthreads shlib=$lib_sl2]] != 0 } {
> +    return -1
> +}
> +
> +if { [build_executable "build executable" $binfile $srcfile \
> +       [list debug build-id shlib=$lib_sl1 shlib=$lib_sl2]] != 0 } {
> +    return -1
> +}
> +
> +# Make sure libsection1 and libsection2 contain .gdb_index.
> +if { [ensure_gdb_index $lib_sl1 "" "libsection1"] != 1 } {
> +    untested "failed to add .gdb_index to $libfile1"
> +    return -1
> +}
> +
> +if { [ensure_gdb_index $lib_sl2 "" "libsection2"] != 1 } {
> +    untested "failed to add .gdb_index to $libfile2"
> +    return -1
> +}
> +
> +# Strip solib debuginfo into separate files.
> +if { [gdb_gnu_strip_debug $lib_sl1 ""] != 0} {
> +   fail "strip $lib_sl1 debuginfo"
> +   return -1
> +}
> +
> +if { [gdb_gnu_strip_debug $lib_sl2 ""] != 0} {
> +   fail "strip $lib_sl2 debuginfo"
> +   return -1
> +}
> +
> +# Move debuginfo files into directory that debuginfod will serve from.
> +set debugdir [standard_output_file "debug"]
> +set debuginfo_sl1 [standard_output_file $libfile1.sl.debug]
> +set debuginfo_sl2 [standard_output_file $libfile2.sl.debug]
> +
> +file mkdir $debugdir
> +file rename -force $debuginfo_sl1 $debugdir
> +file rename -force $debuginfo_sl2 $debugdir
> +
> +# Restart GDB and clear the debuginfod client cache.  Then load BINFILE into
> +# GDB and start running it.  Match output with pattern RES and use TESTNAME
> +# as the test name.
> +proc_with_prefix clean_restart_with_prompt { binfile testname } {
> +    global cache
> +
> +    # Delete client cache so debuginfo downloads again.
> +    file delete -force $cache
> +    clean_restart
> +
> +    gdb_test_no_output "set debuginfod enabled on" \
> +                      "clean_restart enable $testname"
> +    gdb_load $binfile
> +
> +    runto_main
> +}
> +
> +# Tests with no debuginfod server running.
> +proc_with_prefix no_url { } {
> +    global binfile libfile1 libfile2
> +
> +    gdb_load $binfile
> +    if {![runto_main]} {
> +       return
> +    }
> +
> +    # Check that no section is downloaded and no debuginfo is found.
> +    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile1.*" \
> +            "found no url lib1"
> +    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile2.*" \
> +            "found no url lib2"
> +}
> +
> +# Tests with a debuginfod server running.
> +proc_with_prefix local_url { } {
> +    global binfile
> +    global libsrc1 lib_sl1 libfile1
> +    global libsrc2 lib_sl2 libfile2
> +    global debugdir db
> +
> +    set url [start_debuginfod $db $debugdir]
> +    if { $url == "" } {
> +       unresolved "failed to start debuginfod server"
> +       return
> +    }
> +
> +    # Point GDB to the server.
> +    setenv DEBUGINFOD_URLS $url
> +
> +    clean_restart_with_prompt $binfile "index"
> +
> +    # Download debuginfo when stepping into a function.
> +    set res ".*separate debug info for $lib_sl1.*\"In ${libfile1}\\\\n\".*"
> +    gdb_test "step" $res "step"
> +
> +    clean_restart_with_prompt $binfile "break"
> +
> +    # Download debuginfo when setting a breakpoint.
> +    set res "Download.*separate debug info for $lib_sl2.*"
> +    gdb_test "br libsection2_test" $res "break set"
> +
> +    # Hit the breakpoint.
> +    set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
> +    gdb_test "c" $res "break continue"
> +}
> +
> +# Create CACHE and DB directories ready for debuginfod to use.
> +prepare_for_debuginfod cache db
> +
> +with_debuginfod_env $cache {
> +    no_url
> +    local_url
> +}
> +
> +stop_debuginfod
> diff --git a/gdb/testsuite/lib/debuginfod-support.exp b/gdb/testsuite/lib/debuginfod-support.exp
> index 50a8b512a4a..71964a90564 100644
> --- a/gdb/testsuite/lib/debuginfod-support.exp
> +++ b/gdb/testsuite/lib/debuginfod-support.exp
> @@ -113,6 +113,8 @@ proc with_debuginfod_env { cache body } {
>  proc start_debuginfod { db debugdir } {
>      global debuginfod_spawn_id spawn_id
>
> +    set logfile [standard_output_file "server_log"]
> +
>      # Find an unused port.
>      set port 7999
>      set found false
> @@ -127,7 +129,8 @@ proc start_debuginfod { db debugdir } {
>             set old_spawn_id $spawn_id
>         }
>
> -       spawn debuginfod -vvvv -d $db -p $port -F $debugdir
> +       spawn sh -c "debuginfod -vvvv -d $db -p $port -F $debugdir 2>&1 \
> +               | tee $logfile"
>         set debuginfod_spawn_id $spawn_id
>
>         if { [info exists old_spawn_id] } {
> @@ -194,3 +197,23 @@ proc stop_debuginfod { } {
>         unset debuginfod_spawn_id
>      }
>  }
> +
> +# Return true if gdb is configured to download ELF/DWARF sections from
> +# debuginfod servers.  Otherwise return false.
> +proc allow_debuginfod_section_downloads { } {
> +    set cmd "maint set debuginfod download-sections on"
> +    set msg "enable section downloads"
> +
> +    gdb_test_multiple $cmd $msg {
> +       -re -wrap ".*not compiled into GDB.*" {
> +           return false
> +       }
> +       -re -wrap "^" {
> +           return true
> +       }
> +       -re -wrap "" {
> +           fail "$gdb_test_name (unexpected output)"
> +           return false
> +       }
> +    }
> +}
> diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
> index 9ac707be696..9b3c7e6a02c 100644
> --- a/gdb/testsuite/lib/gdb.exp
> +++ b/gdb/testsuite/lib/gdb.exp
> @@ -9414,10 +9414,14 @@ proc get_index_type { objfile { testname "" } } {
>  # STYLE controls which style of index to add, if needed.  The empty
>  # string (the default) means .gdb_index; "-dwarf-5" means .debug_names.
>
> -proc ensure_gdb_index { binfile {style ""} } {
> +proc ensure_gdb_index { binfile {style ""} {testname_prefix ""} } {
>      set testfile [file tail $binfile]
> -
>      set test "check if index present"
> +
> +    if { $testname_prefix != "" } {
> +      set test "$testname_prefix $test"
> +    }
> +
>      set index_type [get_index_type $testfile $test]
>
>      if { $index_type eq "gdb" || $index_type eq "dwarf5" } {
> --
> 2.43.0
>
  
Aaron Merey Feb. 22, 2024, 10:23 p.m. UTC | #4
Ping

Thanks,
Aaron

On Wed, Feb 7, 2024 at 4:25 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Fri, Jan 19, 2024 at 12:12 AM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Hi Andrew,
> >
> > Thanks for the review.
> >
> > On Thu, Jan 18, 2024 at 10:34 AM Andrew Burgess <aburgess@redhat.com> wrote:
> > >
> > > Should be:
> > >
> > >   gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > >
> > > OK, I'm going to stop commenting on these now, but there's a few more of
> > > them in the rest of the patch.  GDB style is not to rely on implicit
> > > int -> bool conversion.
> >
> > Fixed.
> >
> > >
> > > > +
> > > > +  SCOPE_EXIT { objfile->remove_deferred_status (); };
> > > > +
> > > > +  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > > +  const char *filename;
> > > > +  gdb_bfd_ref_ptr debug_bfd;
> > > > +  gdb::unique_xmalloc_ptr<char> symfile_path;
> > > > +  scoped_fd fd;
> > >
> > > Of these, filename, debug_bfd, and fd can be declared where they are
> > > first defined.  You can probably move symfile_path closer to where it's
> > > used too.
> > >
> >
> > Fixed.
> >
> > > > diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
> > > > index 1235f293f45..b674103c72f 100644
> > > > --- a/gdb/dwarf2/section.c
> > > > +++ b/gdb/dwarf2/section.c
> > > > @@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
> > > >        section = get_containing_section ();
> > > >        gdb_assert (!section->is_virtual);
> > > >      }
> > > > -  gdb_assert (section->s.section != nullptr);
> > > > +  if (section->s.section == nullptr)
> > > > +    error (_("Can't find owner of DWARF section."));
> > >
> > > This seems like an interesting change.  There's no test that triggers
> > > this case, but I guess you ran into this at some point.  Could you
> > > explain more about what's going on here?
> >
> > This can happen if gdb mistakenly tries to read debug info that
> > has been deferred and not yet downloaded.  For example if gdb attempted
> > to read symbols from a debuginfo-stripped parent objfile without first
> > downloading the full separate debug info, then this error would trigger.
> >
> > > > @@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> > > >          && objfile->separate_debug_objfile_backlink == NULL)
> > > >      {
> > > >        if (objfile->find_and_add_separate_symbol_file (symfile_flags))
> > > > -     gdb_assert (objfile->separate_debug_objfile != nullptr);
> > > > +     gdb_assert (objfile->separate_debug_objfile != nullptr
> > > > +                 || objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> > > >        else
> > > >       has_dwarf2 = false;
> > > >      }
> > > > diff --git a/gdb/frame.c b/gdb/frame.c
> > > > index 41003cc7771..d3409c8fd97 100644
> > > > --- a/gdb/frame.c
> > > > +++ b/gdb/frame.c
> > > > @@ -1889,6 +1889,13 @@ get_selected_frame (const char *message)
> > > >       error (("%s"), message);
> > > >
> > > >        lookup_selected_frame (selected_frame_id, selected_frame_level);
> > > > +
> > > > +      /* It is possible for lookup_selected_frame to cause a new objfile
> > > > +      to be loaded.  Some objfile observers may choose to clear
> > > > +      selected_frame when an objfile is loaded.  Work around this by
> > > > +      calling lookup_selected_frame again if the first try failed.  */
> > > > +      if (selected_frame == nullptr)
> > > > +     lookup_selected_frame (selected_frame_id, selected_frame_level);
> > >
> > > I notice this case wasn't tested -- I added an abort here and reran the
> > > test, and didn't see it hit.
> >
> > With the 2nd call to lookup_selected_frame removed, the test
> > "gdb.debuginfod/section.exp: local_url: line thread" becomes unresolved
> > when testing with --target_board=native-gdbserver.
> >
> > >
> > > I ask because this seems both interesting, and this doesn't feel like
> > > the right solution... As a minimum you'd need a comment on
> > > lookup_selected_frame that says "hey, you will need to call this
> > > function twice to guarantee that it did what it promised".  So then I'd
> > > suggest we should just rename lookup_selected_frame to
> > > lookup_selected_frame_1, and have lookup_selected_frame be a wrapper
> > > which does the double lookup.
> >
> > This is a better solution. Fixed.
> >
> > > But I was kind of hoping that we'd have a test that hit this case so I
> > > could look at the stack trace and better understand what exactly is
> > > happening when the selected frame is cleared.
> > >
> > > > diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > > new file mode 100644
> > > > index 00000000000..7ad19c276c4
> > > > --- /dev/null
> > > > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > > @@ -0,0 +1,43 @@
> > > > +/* This testcase is part of GDB, the GNU debugger.
> > > > +
> > > > +   Copyright 2023 Free Software Foundation, Inc.
> > >
> > > Remember to update your copyright dates to '2023-2024' throughout.
> >
> > Fixed.
> >
> > > > +load_lib debuginfod-support.exp
> > > > +
> > > > +require allow_debuginfod_tests
> > > > +
> > > > +set sourcetmp [standard_output_file tmp-${srcfile}]
> > > > +set outputdir [standard_output_file {}]
> > >
> > > Not sure what these two are for.  Maybe a copy&paste issue?
> >
> > Fixed.
> >
> > >
> > > > +
> > > > +# SECTEXEC is an executable which calls a function from LIB_SL1.
> > > > +set sectfile "section"
> > > > +set sectsrc $srcdir/$subdir/section.c
> > > > +set sectexec [standard_output_file $sectfile]
> > >
> > > I'd have expected you to use ${srcfile} and ${binfile} here which are
> > > setup by `standard_testfile` based on the name of the test script.  So
> > > section.exp will setup `section.c` and `/path/to/.../section`
> > > respectively.
> > >
> > > The source file doesn't have the directory prefix, but the compile procs
> > > know how to find the source file, so that's OK.
> > >
> > > standard_testfile also gives you `testfile` which is $binfile without
> > > the path prefix, if that's needed.
> > >
> > > It's good practice to use $binfile throughout the test unless there's
> > > some compelling reason not too everyone knows what $binfile is.
> >
> > Fixed.
> >
> > > > +if { [gdb_compile $sectsrc $sectexec executable $exec_opts] != "" } {
> > > > +    untested "failed to compile $sectfile"
> > > > +    return -1
> > > > +}
> > >
> > > Instead of calling all these compile worker functions directly, the
> > > better approach is to call build_executable and pass through the correct
> > > flags.  This seems to work for me:
> > >
> > >   if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
> > >           {debug build-id shlib}] != 0 } {
> > >       return -1
> > >   }
> >
> > Fixed.
> >
> > > > +    gdb_test "set debuginfod enabled on" "" "clean_restart enable $testname"
> > >
> > > You can use gdb_test_no_output here rather than passing an empty pattern.
> >
> > Fixed.
> >
> > > > +# Return 1 if gdb is configured to download ELF/DWARF sections from
> > > > +# debuginfod servers.  Otherwise return 0.
> > >
> > > We can use 'true' and 'false' in TCL code, see allow_debuginfod_tests as
> > > an example.
> > >
> > > > +proc enable_section_downloads { } {
> > >
> > > I think this should be renamed to something like allow_section_downloads
> > > (though I about allow_debuginfod_section_downloads to make it clear it's
> > > a debuginfod function).  Then, in the test script you would say:
> > >
> > >   require allow_debuginfod_section_downloads
> > >
> > > Rather than the `if` check that you have right now.
> > >
> > >
> > > > +    global gdb_prompt
> > >
> > > This line isn't needed, gdb_prompt isn't used.
> >
> > Fixed.  I've attached the updated patch below.
> >
> > Aaron
> >
> > Commit message:
> >
> > At the beginning of a session, gdb may attempt to download debuginfo
> > for all shared libraries associated with the process or core file
> > being debugged.  This can be a waste of time and storage space when much
> > of the debuginfo ends up not being used during the session.
> >
> > To reduce the gdb's startup latency and to download only the debuginfo
> > that is really needed, this patch adds on-demand downloading of debuginfo.
> >
> > 'set debuginfo enabled on' now causes gdb to attempt to download a .gdb_index
> > for each shared library instead of its full debuginfo.  Each corresponding
> > separate debuginfo will be deferred until gdb needs to expand symtabs
> > associated with the debuginfo's index.
> >
> > Because these indices are significantly smaller than their corresponding
> > debuginfo, this generally reduces the total amount of data gdb downloads.
> > Reductions of 80%-95% have been observed when debugging large GUI programs.
> >
> >     (gdb) set debuginfod enabled on
> >     (gdb) start
> >     Downloading section .gdb_index for /lib64/libcurl.so.4
> >     [...]
> >     1826        client->server_mhandle = curl_multi_init ();
> >     (gdb) step
> >     Downloading separate debug info for /lib64/libcurl.so.4
> >     Downloading separate debug info for [libcurl dwz]
> >     Downloading source file /usr/src/debug/curl-7.85.0-6.fc37.x86_64/build-full/lib/../../lib/multi.c
> >     curl_multi_init () at ../../lib/multi.c:457
> >     457     {
> >     (gdb)
> >
> > Some of the key functions below include dwarf2_has_separate_index which
> > downloads the separate .gdb_index.  If successful, the shared library
> > objfile owns the index until the separate debug objfile is downloaded
> > or confirmed to not be available.
> >
> > read_full_dwarf_from_debuginfod downloads the full debuginfo and
> > initializes the separate debug objfile.  It is called by functions
> > such as dwarf2_gdb_index::expand_symtabs_matching and
> > dwarf2_base_index_functions::find_pc_sect_compunit_symtab when symtab
> > expansion is required.
> > ---
> >  gdb/dwarf2/frame.c                         |  13 ++
> >  gdb/dwarf2/frame.h                         |   4 +
> >  gdb/dwarf2/index-cache.c                   |  33 +++++
> >  gdb/dwarf2/index-cache.h                   |  13 ++
> >  gdb/dwarf2/public.h                        |   7 +
> >  gdb/dwarf2/read-gdb-index.c                | 125 +++++++++++++---
> >  gdb/dwarf2/read.c                          | 145 ++++++++++++++++++-
> >  gdb/dwarf2/read.h                          |  10 ++
> >  gdb/dwarf2/section.c                       |   3 +-
> >  gdb/elfread.c                              |   3 +-
> >  gdb/frame.c                                |  27 +++-
> >  gdb/objfile-flags.h                        |   4 +
> >  gdb/objfiles.h                             |  20 +++
> >  gdb/quick-symbol.h                         |   4 +
> >  gdb/symfile-debug.c                        |  11 ++
> >  gdb/symfile.c                              |  12 +-
> >  gdb/symtab.c                               |  18 ++-
> >  gdb/testsuite/gdb.debuginfod/libsection1.c |  43 ++++++
> >  gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
> >  gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
> >  gdb/testsuite/gdb.debuginfod/section.exp   | 157 +++++++++++++++++++++
> >  gdb/testsuite/lib/debuginfod-support.exp   |  25 +++-
> >  gdb/testsuite/lib/gdb.exp                  |   8 +-
> >  23 files changed, 719 insertions(+), 32 deletions(-)
> >  create mode 100644 gdb/testsuite/gdb.debuginfod/libsection1.c
> >  create mode 100644 gdb/testsuite/gdb.debuginfod/libsection2.c
> >  create mode 100644 gdb/testsuite/gdb.debuginfod/section.c
> >  create mode 100644 gdb/testsuite/gdb.debuginfod/section.exp
> >
> > diff --git a/gdb/dwarf2/frame.c b/gdb/dwarf2/frame.c
> > index d3d1ecdf1f5..9e290af79f7 100644
> > --- a/gdb/dwarf2/frame.c
> > +++ b/gdb/dwarf2/frame.c
> > @@ -1620,6 +1620,19 @@ set_comp_unit (struct objfile *objfile, struct comp_unit *unit)
> >    return dwarf2_frame_bfd_data.set (abfd, unit);
> >  }
> >
> > +/* See frame.h.  */
> > +
> > +void
> > +dwarf2_clear_frame_data (struct objfile *objfile)
> > +{
> > +  bfd *abfd = objfile->obfd.get ();
> > +
> > +  if (gdb_bfd_requires_relocations (abfd))
> > +    dwarf2_frame_objfile_data.clear (objfile);
> > +  else
> > +    dwarf2_frame_bfd_data.clear (abfd);
> > +}
> > +
> >  /* Find the FDE for *PC.  Return a pointer to the FDE, and store the
> >     initial location associated with it into *PC.  */
> >
> > diff --git a/gdb/dwarf2/frame.h b/gdb/dwarf2/frame.h
> > index 5643e557513..2391e313e7c 100644
> > --- a/gdb/dwarf2/frame.h
> > +++ b/gdb/dwarf2/frame.h
> > @@ -238,6 +238,10 @@ void dwarf2_append_unwinders (struct gdbarch *gdbarch);
> >  extern const struct frame_base *
> >    dwarf2_frame_base_sniffer (frame_info_ptr this_frame);
> >
> > +/* Delete OBJFILEs comp_unit.  */
> > +
> > +extern void dwarf2_clear_frame_data (struct objfile * objfile);
> > +
> >  /* Compute the DWARF CFA for a frame.  */
> >
> >  CORE_ADDR dwarf2_frame_cfa (frame_info_ptr this_frame);
> > diff --git a/gdb/dwarf2/index-cache.c b/gdb/dwarf2/index-cache.c
> > index 69f70642dc6..8c969ecd590 100644
> > --- a/gdb/dwarf2/index-cache.c
> > +++ b/gdb/dwarf2/index-cache.c
> > @@ -240,6 +240,33 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
> >    return {};
> >  }
> >
> > +/* See index-cache.h.  */
> > +
> > +gdb::array_view<const gdb_byte>
> > +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> > +                                         std::unique_ptr<index_cache_resource> *resource)
> > +{
> > +  try
> > +    {
> > +      /* Try to map that file.  */
> > +      index_cache_resource_mmap *mmap_resource
> > +       = new index_cache_resource_mmap (index_path);
> > +
> > +      /* Hand the resource to the caller.  */
> > +      resource->reset (mmap_resource);
> > +
> > +      return gdb::array_view<const gdb_byte>
> > +         ((const gdb_byte *) mmap_resource->mapping.get (),
> > +          mmap_resource->mapping.size ());
> > +    }
> > +  catch (const gdb_exception_error &except)
> > +    {
> > +      warning (_("Unable to read %s: %s"), index_path, except.what ());
> > +    }
> > +
> > +  return {};
> > +}
> > +
> >  #else /* !HAVE_SYS_MMAN_H */
> >
> >  /* See dwarf-index-cache.h.  This is a no-op on unsupported systems.  */
> > @@ -251,6 +278,12 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
> >    return {};
> >  }
> >
> > +gdb::array_view<const gdb_byte>
> > +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> > +                                         std::unique_ptr<index_cache_resource> *resource)
> > +{
> > +  return {};
> > +}
> >  #endif
> >
> >  /* See dwarf-index-cache.h.  */
> > diff --git a/gdb/dwarf2/index-cache.h b/gdb/dwarf2/index-cache.h
> > index 023fc86fc89..0d845ebcadd 100644
> > --- a/gdb/dwarf2/index-cache.h
> > +++ b/gdb/dwarf2/index-cache.h
> > @@ -90,6 +90,19 @@ class index_cache
> >    lookup_gdb_index (const bfd_build_id *build_id,
> >                     std::unique_ptr<index_cache_resource> *resource);
> >
> > +  /* Look for an index file located at INDEX_PATH in the debuginfod cache.
> > +     Unlike lookup_gdb_index, this function does not exit early if the
> > +     index cache has not been enabled.
> > +
> > +     If found, return the contents as an array_view and store the underlying
> > +     resources (allocated memory, mapped file, etc) in RESOURCE.  The returned
> > +     array_view is valid as long as RESOURCE is not destroyed.
> > +
> > +     If no matching index file is found, return an empty array view.  */
> > +  gdb::array_view<const gdb_byte>
> > +  lookup_gdb_index_debuginfod (const char *index_path,
> > +                              std::unique_ptr<index_cache_resource> *resource);
> > +
> >    /* Return the number of cache hits.  */
> >    unsigned int n_hits () const
> >    { return m_n_hits; }
> > diff --git a/gdb/dwarf2/public.h b/gdb/dwarf2/public.h
> > index efb754b5fe8..4d3776465c3 100644
> > --- a/gdb/dwarf2/public.h
> > +++ b/gdb/dwarf2/public.h
> > @@ -44,4 +44,11 @@ extern bool dwarf2_initialize_objfile
> >
> >  extern void dwarf2_build_frame_info (struct objfile *);
> >
> > +/* Query debuginfod for the .gdb_index associated with OBJFILE.  If
> > +   successful, create an objfile to hold the .gdb_index information
> > +   and act as a placeholder until the full debuginfo needs to be
> > +   downloaded.  */
> > +
> > +extern bool dwarf2_has_separate_index (struct objfile *);
> > +
> >  #endif /* DWARF2_PUBLIC_H */
> > diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
> > index 4ca44540296..f4d77ed2d04 100644
> > --- a/gdb/dwarf2/read-gdb-index.c
> > +++ b/gdb/dwarf2/read-gdb-index.c
> > @@ -139,6 +139,8 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> >       gdb.dwarf2/gdb-index.exp testcase.  */
> >    void dump (struct objfile *objfile) override;
> >
> > +  /* Calls do_expand_symtabs_matching and triggers debuginfo downloading
> > +     if necessary.  */
> >    bool expand_symtabs_matching
> >      (struct objfile *objfile,
> >       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > @@ -148,8 +150,60 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> >       block_search_flags search_flags,
> >       domain_enum domain,
> >       enum search_domain kind) override;
> > +
> > +  /* Expand symtabs matching a given symbol or file.  */
> > +  bool do_expand_symtabs_matching
> > +    (struct objfile *objfile,
> > +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > +     const lookup_name_info *lookup_name,
> > +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> > +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> > +     block_search_flags search_flags,
> > +     domain_enum domain,
> > +     enum search_domain kind);
> > +
> > +  /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
> > +     debuginfo if necessary.  */
> > +  void expand_all_symtabs (struct objfile *objfile) override;
> > +
> > +  /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
> > +     debuginfo if necessary.  */
> > +  struct symtab *find_last_source_symtab (struct objfile *objfile) override;
> >  };
> >
> > +void
> > +dwarf2_gdb_index::expand_all_symtabs (struct objfile *objfile)
> > +{
> > +  try
> > +    {
> > +      dwarf2_base_index_functions::expand_all_symtabs (objfile);
> > +    }
> > +  catch (const gdb_exception &e)
> > +    {
> > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > +       exception_print (gdb_stderr, e);
> > +      else
> > +       read_full_dwarf_from_debuginfod (objfile, this);
> > +    }
> > +}
> > +
> > +struct symtab *
> > +dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
> > +{
> > +  try
> > +    {
> > +      return dwarf2_base_index_functions::find_last_source_symtab (objfile);
> > +    }
> > +  catch (const gdb_exception &e)
> > +    {
> > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > +       exception_print (gdb_stderr, e);
> > +      else
> > +       read_full_dwarf_from_debuginfod (objfile, this);
> > +      return nullptr;
> > +    }
> > +}
> > +
> >  /* This dumps minimal information about the index.
> >     It is called via "mt print objfiles".
> >     One use is to verify .gdb_index has been loaded by the
> > @@ -268,7 +322,7 @@ dw2_expand_marked_cus
> >  }
> >
> >  bool
> > -dwarf2_gdb_index::expand_symtabs_matching
> > +dwarf2_gdb_index::do_expand_symtabs_matching
> >      (struct objfile *objfile,
> >       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> >       const lookup_name_info *lookup_name,
> > @@ -317,6 +371,39 @@ dwarf2_gdb_index::expand_symtabs_matching
> >    return result;
> >  }
> >
> > +bool
> > +dwarf2_gdb_index::expand_symtabs_matching
> > +    (struct objfile *objfile,
> > +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > +     const lookup_name_info *lookup_name,
> > +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> > +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> > +     block_search_flags search_flags,
> > +     domain_enum domain,
> > +     enum search_domain kind)
> > +{
> > +  if ((objfile->flags & OBJF_READNEVER) != 0)
> > +    return false;
> > +
> > +  try
> > +    {
> > +      return do_expand_symtabs_matching (objfile, file_matcher, lookup_name,
> > +                                        symbol_matcher, expansion_notify,
> > +                                        search_flags, domain, kind);
> > +    }
> > +  catch (const gdb_exception &e)
> > +    {
> > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > +       {
> > +         exception_print (gdb_stderr, e);
> > +         return false;
> > +       }
> > +
> > +      read_full_dwarf_from_debuginfod (objfile, this);
> > +      return true;
> > +    }
> > +}
> > +
> >  quick_symbol_functions_up
> >  mapped_gdb_index::make_quick_functions () const
> >  {
> > @@ -652,28 +739,32 @@ dwarf2_read_gdb_index
> >
> >    /* If there is a .dwz file, read it so we can get its CU list as
> >       well.  */
> > -  dwz = dwarf2_get_dwz_file (per_bfd);
> > -  if (dwz != NULL)
> > +  if (get_gdb_index_contents_dwz != nullptr)
> >      {
> >        mapped_gdb_index dwz_map;
> >        const gdb_byte *dwz_types_ignore;
> >        offset_type dwz_types_elements_ignore;
> > +      dwz = dwarf2_get_dwz_file (per_bfd);
> >
> > -      gdb::array_view<const gdb_byte> dwz_index_content
> > -       = get_gdb_index_contents_dwz (objfile, dwz);
> > -
> > -      if (dwz_index_content.empty ())
> > -       return 0;
> > -
> > -      if (!read_gdb_index_from_buffer (bfd_get_filename (dwz->dwz_bfd.get ()),
> > -                                      1, dwz_index_content, &dwz_map,
> > -                                      &dwz_list, &dwz_list_elements,
> > -                                      &dwz_types_ignore,
> > -                                      &dwz_types_elements_ignore))
> > +      if (dwz != nullptr)
> >         {
> > -         warning (_("could not read '.gdb_index' section from %s; skipping"),
> > -                  bfd_get_filename (dwz->dwz_bfd.get ()));
> > -         return 0;
> > +         gdb::array_view<const gdb_byte> dwz_index_content
> > +           = get_gdb_index_contents_dwz (objfile, dwz);
> > +
> > +         if (dwz_index_content.empty ())
> > +           return 0;
> > +
> > +         if (!read_gdb_index_from_buffer (bfd_get_filename
> > +                                            (dwz->dwz_bfd.get ()),
> > +                                          1, dwz_index_content, &dwz_map,
> > +                                          &dwz_list, &dwz_list_elements,
> > +                                          &dwz_types_ignore,
> > +                                          &dwz_types_elements_ignore))
> > +           {
> > +             warning (_("could not read '.gdb_index' section from %s; skipping"),
> > +                      bfd_get_filename (dwz->dwz_bfd.get ()));
> > +               return 0;
> > +           }
> >         }
> >      }
> >
> > diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> > index 76dbeb8d536..062f0e34938 100644
> > --- a/gdb/dwarf2/read.c
> > +++ b/gdb/dwarf2/read.c
> > @@ -35,6 +35,7 @@
> >  #include "dwarf2/attribute.h"
> >  #include "dwarf2/comp-unit-head.h"
> >  #include "dwarf2/cu.h"
> > +#include "dwarf2/frame.h"
> >  #include "dwarf2/index-cache.h"
> >  #include "dwarf2/index-common.h"
> >  #include "dwarf2/leb.h"
> > @@ -96,6 +97,8 @@
> >  #include "split-name.h"
> >  #include "gdbsupport/thread-pool.h"
> >  #include "run-on-main-thread.h"
> > +#include "inferior.h"
> > +#include "debuginfod-support.h"
> >
> >  /* When == 1, print basic high level tracing messages.
> >     When > 1, be more verbose.
> > @@ -3003,7 +3006,7 @@ dwarf2_base_index_functions::find_per_cu (dwarf2_per_bfd *per_bfd,
> >  }
> >
> >  struct compunit_symtab *
> > -dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> > +dwarf2_base_index_functions::do_find_pc_sect_compunit_symtab
> >       (struct objfile *objfile,
> >        struct bound_minimal_symbol msymbol,
> >        CORE_ADDR pc,
> > @@ -3034,6 +3037,32 @@ dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> >    return result;
> >  }
> >
> > +struct compunit_symtab *
> > +dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> > +     (struct objfile *objfile,
> > +      struct bound_minimal_symbol msymbol,
> > +      CORE_ADDR pc,
> > +      struct obj_section *section,
> > +      int warn_if_readin)
> > +{
> > +  if ((objfile->flags & OBJF_READNEVER) != 0)
> > +    return nullptr;
> > +
> > +  try
> > +    {
> > +      return do_find_pc_sect_compunit_symtab (objfile, msymbol, pc,
> > +                                             section, warn_if_readin);
> > +    }
> > +  catch (const gdb_exception &e)
> > +    {
> > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > +       exception_print (gdb_stderr, e);
> > +      else
> > +       read_full_dwarf_from_debuginfod (objfile, this);
> > +      return nullptr;
> > +    }
> > +}
> > +
> >  void
> >  dwarf2_base_index_functions::map_symbol_filenames
> >       (struct objfile *objfile,
> > @@ -3190,6 +3219,29 @@ get_gdb_index_contents_from_cache_dwz (objfile *obj, dwz_file *dwz)
> >    return global_index_cache.lookup_gdb_index (build_id, &dwz->index_cache_res);
> >  }
> >
> > +/* Query debuginfod for the .gdb_index matching OBJFILE's build-id.  Return the
> > +   contents if successful.  */
> > +
> > +static gdb::array_view<const gdb_byte>
> > +get_gdb_index_contents_from_debuginfod (objfile *objfile, dwarf2_per_bfd *per_bfd)
> > +{
> > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > +  if (build_id == nullptr)
> > +    return {};
> > +
> > +  gdb::unique_xmalloc_ptr<char> index_path;
> > +  scoped_fd fd = debuginfod_section_query (build_id->data, build_id->size,
> > +                                          bfd_get_filename
> > +                                            (objfile->obfd.get ()),
> > +                                          ".gdb_index",
> > +                                          &index_path);
> > +  if (fd.get () < 0)
> > +    return {};
> > +
> > +  return global_index_cache.lookup_gdb_index_debuginfod
> > +    (index_path.get (), &per_bfd->index_cache_res);
> > +}
> > +
> >  static quick_symbol_functions_up make_cooked_index_funcs
> >       (dwarf2_per_objfile *);
> >
> > @@ -3200,7 +3252,8 @@ dwarf2_initialize_objfile (struct objfile *objfile,
> >                            const struct dwarf2_debug_sections *names,
> >                            bool can_copy)
> >  {
> > -  if (!dwarf2_has_info (objfile, names, can_copy))
> > +  if (!dwarf2_has_info (objfile, names, can_copy)
> > +      && (objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> >      return false;
> >
> >    dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile);
> > @@ -3262,6 +3315,16 @@ dwarf2_initialize_objfile (struct objfile *objfile,
> >        global_index_cache.hit ();
> >        objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
> >      }
> > +  /* Try to read just a separately downloaded gdb index.  */
> > +  else if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0
> > +          && dwarf2_read_gdb_index (per_objfile,
> > +                                    get_gdb_index_contents_from_debuginfod,
> > +                                    nullptr))
> > +    {
> > +      dwarf_read_debug_printf ("found .gdb_index from debuginfod");
> > +      objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
> > +      objfile->qf.begin ()->get ()->from_separate_index = true;
> > +    }
> >    else
> >      {
> >        global_index_cache.miss ();
> > @@ -3270,6 +3333,84 @@ dwarf2_initialize_objfile (struct objfile *objfile,
> >    return true;
> >  }
> >
> > +/* See read.h.  */
> > +
> > +void
> > +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> > +                                dwarf2_base_index_functions *fncs)
> > +{
> > +  gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > +
> > +  SCOPE_EXIT { objfile->remove_deferred_status (); };
> > +
> > +  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > +  if (build_id == nullptr)
> > +    return;
> > +
> > +  const char *filename = bfd_get_filename (objfile->obfd.get ());
> > +  gdb::unique_xmalloc_ptr<char> symfile_path;
> > +  scoped_fd fd;
> > +
> > +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> > +                                  filename, &symfile_path);
> > +  if (fd.get () < 0)
> > +    return;
> > +
> > +  /* Separate debuginfo successfully retrieved from server.  */
> > +  gdb_bfd_ref_ptr debug_bfd = symfile_bfd_open (symfile_path.get ());
> > +  if (debug_bfd == nullptr
> > +      || !build_id_verify (debug_bfd.get (), build_id->size, build_id->data))
> > +    {
> > +      warning (_("File \"%s\" from debuginfod cannot be opened as bfd"),
> > +              filename);
> > +      return;
> > +    }
> > +
> > +  /* Clear frame data so it can be recalculated using DWARF.  */
> > +  dwarf2_clear_frame_data (objfile);
> > +
> > +  /* This may also trigger a dwz download.  */
> > +  symbol_file_add_separate (debug_bfd, symfile_path.get (),
> > +                           current_inferior ()->symfile_flags, objfile);
> > +}
> > +
> > +/* See public.h.  */
> > +
> > +bool
> > +dwarf2_has_separate_index (struct objfile *objfile)
> > +{
> > +  if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0)
> > +    return true;
> > +  if ((objfile->flags & OBJF_MAINLINE) != 0)
> > +    return false;
> > +  if (!IS_DIR_SEPARATOR (*objfile_filename (objfile)))
> > +    return false;
> > +
> > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > +
> > +  if (build_id == nullptr)
> > +    return false;
> > +
> > +  gdb::unique_xmalloc_ptr<char> index_path;
> > +  scoped_fd fd = debuginfod_section_query (build_id->data,
> > +                                          build_id->size,
> > +                                          bfd_get_filename
> > +                                            (objfile->obfd.get ()),
> > +                                          ".gdb_index",
> > +                                          &index_path);
> > +
> > +  if (fd.get () < 0)
> > +    return false;
> > +
> > +  /* We found a separate .gdb_index file so a separate debuginfo file
> > +     should exist, but we don't want to download it until necessary.
> > +     Attach the index to this objfile and defer the debuginfo download
> > +     until gdb needs to expand symtabs referenced by the index.  */
> > +  objfile->flags |= OBJF_DOWNLOAD_DEFERRED;
> > +  dwarf2_initialize_objfile (objfile);
> > +  return true;
> > +}
> > +
> >
> >
> >  /* Find the base address of the compilation unit for range lists and
> > diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> > index d6631d45a54..8b6f1864458 100644
> > --- a/gdb/dwarf2/read.h
> > +++ b/gdb/dwarf2/read.h
> > @@ -900,6 +900,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
> >       CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
> >         override;
> >
> > +  struct compunit_symtab *do_find_pc_sect_compunit_symtab
> > +    (struct objfile *objfile, struct bound_minimal_symbol msymbol,
> > +     CORE_ADDR pc, struct obj_section *section, int warn_if_readin);
> > +
> >    struct compunit_symtab *find_compunit_symtab_by_address
> >      (struct objfile *objfile, CORE_ADDR address) override
> >    {
> > @@ -972,4 +976,10 @@ extern void create_all_units (dwarf2_per_objfile *per_objfile);
> >
> >  extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
> >
> > +/* If OBJFILE contains information from a separately downloaded .gdb_index,
> > +   attempt to download the full debuginfo.  */
> > +
> > +extern void read_full_dwarf_from_debuginfod (struct objfile *,
> > +                                            dwarf2_base_index_functions *);
> > +
> >  #endif /* DWARF2READ_H */
> > diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
> > index 1235f293f45..b674103c72f 100644
> > --- a/gdb/dwarf2/section.c
> > +++ b/gdb/dwarf2/section.c
> > @@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
> >        section = get_containing_section ();
> >        gdb_assert (!section->is_virtual);
> >      }
> > -  gdb_assert (section->s.section != nullptr);
> > +  if (section->s.section == nullptr)
> > +    error (_("Can't find owner of DWARF section."));
> >    return section->s.section->owner;
> >  }
> >
> > diff --git a/gdb/elfread.c b/gdb/elfread.c
> > index c5ba8837217..f14dc1bf80b 100644
> > --- a/gdb/elfread.c
> > +++ b/gdb/elfread.c
> > @@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> >            && objfile->separate_debug_objfile_backlink == NULL)
> >      {
> >        if (objfile->find_and_add_separate_symbol_file (symfile_flags))
> > -       gdb_assert (objfile->separate_debug_objfile != nullptr);
> > +       gdb_assert (objfile->separate_debug_objfile != nullptr
> > +                   || (objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> >        else
> >         has_dwarf2 = false;
> >      }
> > diff --git a/gdb/frame.c b/gdb/frame.c
> > index 41003cc7771..7f57cf80a1b 100644
> > --- a/gdb/frame.c
> > +++ b/gdb/frame.c
> > @@ -1772,14 +1772,10 @@ restore_selected_frame (frame_id frame_id, int frame_level)
> >    selected_frame = nullptr;
> >  }
> >
> > -/* Lookup the frame_info object for the selected frame FRAME_ID /
> > -   FRAME_LEVEL and cache the result.
> > -
> > -   If FRAME_LEVEL > 0 and the originally selected frame isn't found,
> > -   warn and select the innermost (current) frame.  */
> > +/* See lookup_selected_frame.  */
> >
> >  static void
> > -lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> > +lookup_selected_frame_1 (struct frame_id &a_frame_id, int frame_level)
> >  {
> >    frame_info_ptr frame = NULL;
> >    int count;
> > @@ -1851,6 +1847,25 @@ lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> >      }
> >  }
> >
> > +/* Lookup the frame_info object for the selected frame FRAME_ID /
> > +   FRAME_LEVEL and cache the result.
> > +
> > +   If FRAME_LEVEL > 0 and the originally selected frame isn't found,
> > +   warn and select the innermost (current) frame.  */
> > +
> > +static void
> > +lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> > +{
> > +  lookup_selected_frame_1 (selected_frame_id, selected_frame_level);
> > +
> > +  /* It is possible for lookup_selected_frame_1 to cause a new objfile
> > +     to be loaded.  However some objfile observers may choose to clear
> > +     selected_frame when an objfile is loaded.  Work around this by
> > +     calling lookup_selected_frame_1 again if the first call failed.  */
> > +  if (selected_frame == nullptr)
> > +    lookup_selected_frame_1 (selected_frame_id, selected_frame_level);
> > +}
> > +
> >  bool
> >  has_stack_frames ()
> >  {
> > diff --git a/gdb/objfile-flags.h b/gdb/objfile-flags.h
> > index 74aea1a88d3..3f922d9e84f 100644
> > --- a/gdb/objfile-flags.h
> > +++ b/gdb/objfile-flags.h
> > @@ -56,6 +56,10 @@ enum objfile_flag : unsigned
> >      /* User requested that we do not read this objfile's symbolic
> >         information.  */
> >      OBJF_READNEVER = 1 << 6,
> > +
> > +    /* A separate .gdb_index has been downloaded for this objfile.
> > +       Debuginfo for this objfile can be downloaded when required.  */
> > +    OBJF_DOWNLOAD_DEFERRED = 1 << 7,
> >    };
> >
> >  DEF_ENUM_FLAGS_TYPE (enum objfile_flag, objfile_flags);
> > diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> > index b917886988d..4a4db1e7058 100644
> > --- a/gdb/objfiles.h
> > +++ b/gdb/objfiles.h
> > @@ -622,6 +622,26 @@ struct objfile
> >                                                domain_enum domain,
> >                                                bool *symbol_found_p);
> >
> > +  /* Indicate that the acquisition of this objfile's separate debug objfile
> > +     is no longer deferred.  Used when the debug objfile has been acquired
> > +     or could not be found.  */
> > +  void remove_deferred_status ()
> > +  {
> > +    flags &= ~OBJF_DOWNLOAD_DEFERRED;
> > +
> > +    /* Remove quick_symbol_functions derived from a separately downloaded
> > +       index.  If available the separate debug objfile's index will be used
> > +       instead, since that objfile actually contains the symbols and CUs
> > +       referenced in the index.
> > +
> > +       No more than one element of qf should have from_separate_index set
> > +       to true.  */
> > +    qf.remove_if ([&] (const quick_symbol_functions_up &qf_up)
> > +      {
> > +       return qf_up->from_separate_index;
> > +      });
> > +  }
> > +
> >    /* Return the relocation offset applied to SECTION.  */
> >    CORE_ADDR section_offset (bfd_section *section) const
> >    {
> > diff --git a/gdb/quick-symbol.h b/gdb/quick-symbol.h
> > index 4cd234162ae..01a228b446c 100644
> > --- a/gdb/quick-symbol.h
> > +++ b/gdb/quick-symbol.h
> > @@ -193,6 +193,10 @@ struct quick_symbol_functions
> >    virtual void compute_main_name (struct objfile *objfile)
> >    {
> >    }
> > +
> > +  /* True if this quick_symbol_functions is derived from a separately
> > +     downloaded index.  */
> > +  bool from_separate_index = false;
> >  };
> >
> >  typedef std::unique_ptr<quick_symbol_functions> quick_symbol_functions_up;
> > diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
> > index c684f9cdd43..3ed31661300 100644
> > --- a/gdb/symfile-debug.c
> > +++ b/gdb/symfile-debug.c
> > @@ -37,6 +37,7 @@
> >  #include "cli/cli-style.h"
> >  #include "build-id.h"
> >  #include "debuginfod-support.h"
> > +#include "dwarf2/public.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
> > @@ -617,6 +618,16 @@ objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
> >           = simple_find_and_open_separate_symbol_file
> >               (this, find_separate_debug_file_by_debuglink, &warnings);
> >
> > +      /* Attempt to download only an index from the separate debug info.
> > +        As with debuginfod_find_and_open_separate_symbol_file, only attempt
> > +        this once.  */
> > +      if (debug_bfd == nullptr && attempt == 0
> > +         && dwarf2_has_separate_index (this))
> > +       {
> > +         has_dwarf2 = true;
> > +         break;
> > +       }
> > +
> >        /* 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
> > diff --git a/gdb/symfile.c b/gdb/symfile.c
> > index bdcc27e7ee5..820eb061aca 100644
> > --- a/gdb/symfile.c
> > +++ b/gdb/symfile.c
> > @@ -989,6 +989,10 @@ syms_from_objfile (struct objfile *objfile,
> >  static void
> >  finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
> >  {
> > +  struct objfile *parent = objfile->separate_debug_objfile_backlink;
> > +  bool was_deferred
> > +    = (parent != nullptr) && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > +
> >    /* If this is the main symbol file we have to clean up all users of the
> >       old main symbol file.  Otherwise it is sufficient to fixup all the
> >       breakpoints that may have been redefined by this symbol file.  */
> > @@ -999,7 +1003,7 @@ finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
> >
> >        clear_symtab_users (add_flags);
> >      }
> > -  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0)
> > +  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0 && !was_deferred)
> >      {
> >        breakpoint_re_set ();
> >      }
> > @@ -1120,6 +1124,12 @@ symbol_file_add_with_addrs (const gdb_bfd_ref_ptr &abfd, const char *name,
> >    if (objfile->sf != nullptr)
> >      finish_new_objfile (objfile, add_flags);
> >
> > +  /* Remove deferred status now in case any observers trigger symtab
> > +     expansion.  Otherwise gdb might try to read parent for psymbols
> > +     when it should read the separate debug objfile instead.  */
> > +  if (parent != nullptr && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0))
> > +    parent->remove_deferred_status ();
> > +
> >    gdb::observers::new_objfile.notify (objfile);
> >
> >    return objfile;
> > diff --git a/gdb/symtab.c b/gdb/symtab.c
> > index cc0bdb80c7c..d0219da6317 100644
> > --- a/gdb/symtab.c
> > +++ b/gdb/symtab.c
> > @@ -2926,14 +2926,30 @@ find_pc_sect_compunit_symtab (CORE_ADDR pc, struct obj_section *section)
> >    if (best_cust != NULL)
> >      return best_cust;
> >
> > +  int warn_if_readin = 1;
> > +
> >    /* Not found in symtabs, search the "quick" symtabs (e.g. psymtabs).  */
> >
> >    for (objfile *objf : current_program_space->objfiles ())
> >      {
> > +      bool was_deferred = (objf->flags & OBJF_DOWNLOAD_DEFERRED) != 0;
> > +
> >        struct compunit_symtab *result
> > -       = objf->find_pc_sect_compunit_symtab (msymbol, pc, section, 1);
> > +       = objf->find_pc_sect_compunit_symtab (msymbol, pc, section,
> > +                                             warn_if_readin);
> > +
> >        if (result != NULL)
> >         return result;
> > +
> > +      /* If OBJF's separate debug info was just acquired, disable
> > +        warn_if_readin for the next iteration of this loop.  This prevents
> > +        a spurious warning in case an observer already triggered expansion
> > +        of the separate debug objfile's symtabs.  */
> > +      if (was_deferred && objf->separate_debug_objfile != nullptr
> > +         && (objf->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > +       warn_if_readin = 0;
> > +      else if (warn_if_readin == 0)
> > +       warn_if_readin = 1;
> >      }
> >
> >    return NULL;
> > diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > new file mode 100644
> > index 00000000000..e4f05db0a79
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > @@ -0,0 +1,43 @@
> > +/* This testcase is part of GDB, the GNU debugger.
> > +
> > +   Copyright 2023-2024 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/>.  */
> > +
> > +#include <stdio.h>
> > +#include <pthread.h>
> > +#include <unistd.h>
> > +
> > +extern void libsection2_test ();
> > +extern void *libsection2_thread_test (void *);
> > +
> > +static volatile int flag = 0;
> > +
> > +void
> > +libsection1_test ()
> > +{
> > +  pthread_t thr;
> > +
> > +  printf ("In libsection1\n");
> > +  libsection2_test ();
> > +
> > +  pthread_create (&thr, NULL, libsection2_thread_test, (void *) &flag);
> > +
> > +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> > +  while (!flag)
> > +    ;
> > +
> > +  printf ("Cancelling thread\n");
> > +  pthread_cancel (thr);
> > +}
> > diff --git a/gdb/testsuite/gdb.debuginfod/libsection2.c b/gdb/testsuite/gdb.debuginfod/libsection2.c
> > new file mode 100644
> > index 00000000000..3c82808e5fe
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.debuginfod/libsection2.c
> > @@ -0,0 +1,37 @@
> > +/* This testcase is part of GDB, the GNU debugger.
> > +
> > +   Copyright 2023-2024 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/>.  */
> > +
> > +#include <stdio.h>
> > +#include <pthread.h>
> > +
> > +void
> > +libsection2_test ()
> > +{
> > +  printf ("In libsection2\n");
> > +}
> > +
> > +void *
> > +libsection2_thread_test (void *arg)
> > +{
> > +  int *flag = (int *) arg;
> > +  printf ("In thread test\n");
> > +
> > +  while (1)
> > +    *flag = 1;
> > +
> > +  return NULL;
> > +}
> > diff --git a/gdb/testsuite/gdb.debuginfod/section.c b/gdb/testsuite/gdb.debuginfod/section.c
> > new file mode 100644
> > index 00000000000..fa9ac536a6e
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.debuginfod/section.c
> > @@ -0,0 +1,29 @@
> > +/* This testcase is part of GDB, the GNU debugger.
> > +
> > +   Copyright 2023-2024 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/>.  */
> > +
> > +#include <stdio.h>
> > +
> > +extern void libsection1_test ();
> > +
> > +int
> > +main ()
> > +{
> > +  libsection1_test ();
> > +  printf ("in section exec\n");
> > +
> > +  return 0;
> > +}
> > diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
> > new file mode 100644
> > index 00000000000..35086d15705
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > @@ -0,0 +1,157 @@
> > +# Copyright 2023-2024 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/>.
> > +
> > +# Test debuginfod functionality
> > +
> > +standard_testfile
> > +
> > +load_lib debuginfod-support.exp
> > +
> > +clean_restart
> > +require allow_debuginfod_tests
> > +require allow_debuginfod_section_downloads
> > +
> > +# BINFILE calls a function from LIB_SL1.
> > +set libfile1 "libsection1"
> > +set libsrc1 $srcdir/$subdir/$libfile1.c
> > +set lib_sl1 [standard_output_file $libfile1.sl]
> > +
> > +# LIB_SL1 calls functions from LIB_SL2.
> > +set libfile2 "libsection2"
> > +set libsrc2 $srcdir/$subdir/$libfile2.c
> > +set lib_sl2 [standard_output_file $libfile2.sl]
> > +
> > +# Build LIB_SL2, LIB_SL1 and BINFILE.
> > +if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
> > +       {debug build-id shlib}] != 0 } {
> > +    return -1
> > +}
> > +
> > +if { [build_executable "build $libfile1" $lib_sl1 $libsrc1 \
> > +       [list debug build-id shlib_pthreads shlib=$lib_sl2]] != 0 } {
> > +    return -1
> > +}
> > +
> > +if { [build_executable "build executable" $binfile $srcfile \
> > +       [list debug build-id shlib=$lib_sl1 shlib=$lib_sl2]] != 0 } {
> > +    return -1
> > +}
> > +
> > +# Make sure libsection1 and libsection2 contain .gdb_index.
> > +if { [ensure_gdb_index $lib_sl1 "" "libsection1"] != 1 } {
> > +    untested "failed to add .gdb_index to $libfile1"
> > +    return -1
> > +}
> > +
> > +if { [ensure_gdb_index $lib_sl2 "" "libsection2"] != 1 } {
> > +    untested "failed to add .gdb_index to $libfile2"
> > +    return -1
> > +}
> > +
> > +# Strip solib debuginfo into separate files.
> > +if { [gdb_gnu_strip_debug $lib_sl1 ""] != 0} {
> > +   fail "strip $lib_sl1 debuginfo"
> > +   return -1
> > +}
> > +
> > +if { [gdb_gnu_strip_debug $lib_sl2 ""] != 0} {
> > +   fail "strip $lib_sl2 debuginfo"
> > +   return -1
> > +}
> > +
> > +# Move debuginfo files into directory that debuginfod will serve from.
> > +set debugdir [standard_output_file "debug"]
> > +set debuginfo_sl1 [standard_output_file $libfile1.sl.debug]
> > +set debuginfo_sl2 [standard_output_file $libfile2.sl.debug]
> > +
> > +file mkdir $debugdir
> > +file rename -force $debuginfo_sl1 $debugdir
> > +file rename -force $debuginfo_sl2 $debugdir
> > +
> > +# Restart GDB and clear the debuginfod client cache.  Then load BINFILE into
> > +# GDB and start running it.  Match output with pattern RES and use TESTNAME
> > +# as the test name.
> > +proc_with_prefix clean_restart_with_prompt { binfile testname } {
> > +    global cache
> > +
> > +    # Delete client cache so debuginfo downloads again.
> > +    file delete -force $cache
> > +    clean_restart
> > +
> > +    gdb_test_no_output "set debuginfod enabled on" \
> > +                      "clean_restart enable $testname"
> > +    gdb_load $binfile
> > +
> > +    runto_main
> > +}
> > +
> > +# Tests with no debuginfod server running.
> > +proc_with_prefix no_url { } {
> > +    global binfile libfile1 libfile2
> > +
> > +    gdb_load $binfile
> > +    if {![runto_main]} {
> > +       return
> > +    }
> > +
> > +    # Check that no section is downloaded and no debuginfo is found.
> > +    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile1.*" \
> > +            "found no url lib1"
> > +    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile2.*" \
> > +            "found no url lib2"
> > +}
> > +
> > +# Tests with a debuginfod server running.
> > +proc_with_prefix local_url { } {
> > +    global binfile
> > +    global libsrc1 lib_sl1 libfile1
> > +    global libsrc2 lib_sl2 libfile2
> > +    global debugdir db
> > +
> > +    set url [start_debuginfod $db $debugdir]
> > +    if { $url == "" } {
> > +       unresolved "failed to start debuginfod server"
> > +       return
> > +    }
> > +
> > +    # Point GDB to the server.
> > +    setenv DEBUGINFOD_URLS $url
> > +
> > +    clean_restart_with_prompt $binfile "index"
> > +
> > +    # Download debuginfo when stepping into a function.
> > +    set res ".*separate debug info for $lib_sl1.*\"In ${libfile1}\\\\n\".*"
> > +    gdb_test "step" $res "step"
> > +
> > +    clean_restart_with_prompt $binfile "break"
> > +
> > +    # Download debuginfo when setting a breakpoint.
> > +    set res "Download.*separate debug info for $lib_sl2.*"
> > +    gdb_test "br libsection2_test" $res "break set"
> > +
> > +    # Hit the breakpoint.
> > +    set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
> > +    gdb_test "c" $res "break continue"
> > +}
> > +
> > +# Create CACHE and DB directories ready for debuginfod to use.
> > +prepare_for_debuginfod cache db
> > +
> > +with_debuginfod_env $cache {
> > +    no_url
> > +    local_url
> > +}
> > +
> > +stop_debuginfod
> > diff --git a/gdb/testsuite/lib/debuginfod-support.exp b/gdb/testsuite/lib/debuginfod-support.exp
> > index 50a8b512a4a..71964a90564 100644
> > --- a/gdb/testsuite/lib/debuginfod-support.exp
> > +++ b/gdb/testsuite/lib/debuginfod-support.exp
> > @@ -113,6 +113,8 @@ proc with_debuginfod_env { cache body } {
> >  proc start_debuginfod { db debugdir } {
> >      global debuginfod_spawn_id spawn_id
> >
> > +    set logfile [standard_output_file "server_log"]
> > +
> >      # Find an unused port.
> >      set port 7999
> >      set found false
> > @@ -127,7 +129,8 @@ proc start_debuginfod { db debugdir } {
> >             set old_spawn_id $spawn_id
> >         }
> >
> > -       spawn debuginfod -vvvv -d $db -p $port -F $debugdir
> > +       spawn sh -c "debuginfod -vvvv -d $db -p $port -F $debugdir 2>&1 \
> > +               | tee $logfile"
> >         set debuginfod_spawn_id $spawn_id
> >
> >         if { [info exists old_spawn_id] } {
> > @@ -194,3 +197,23 @@ proc stop_debuginfod { } {
> >         unset debuginfod_spawn_id
> >      }
> >  }
> > +
> > +# Return true if gdb is configured to download ELF/DWARF sections from
> > +# debuginfod servers.  Otherwise return false.
> > +proc allow_debuginfod_section_downloads { } {
> > +    set cmd "maint set debuginfod download-sections on"
> > +    set msg "enable section downloads"
> > +
> > +    gdb_test_multiple $cmd $msg {
> > +       -re -wrap ".*not compiled into GDB.*" {
> > +           return false
> > +       }
> > +       -re -wrap "^" {
> > +           return true
> > +       }
> > +       -re -wrap "" {
> > +           fail "$gdb_test_name (unexpected output)"
> > +           return false
> > +       }
> > +    }
> > +}
> > diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
> > index 9ac707be696..9b3c7e6a02c 100644
> > --- a/gdb/testsuite/lib/gdb.exp
> > +++ b/gdb/testsuite/lib/gdb.exp
> > @@ -9414,10 +9414,14 @@ proc get_index_type { objfile { testname "" } } {
> >  # STYLE controls which style of index to add, if needed.  The empty
> >  # string (the default) means .gdb_index; "-dwarf-5" means .debug_names.
> >
> > -proc ensure_gdb_index { binfile {style ""} } {
> > +proc ensure_gdb_index { binfile {style ""} {testname_prefix ""} } {
> >      set testfile [file tail $binfile]
> > -
> >      set test "check if index present"
> > +
> > +    if { $testname_prefix != "" } {
> > +      set test "$testname_prefix $test"
> > +    }
> > +
> >      set index_type [get_index_type $testfile $test]
> >
> >      if { $index_type eq "gdb" || $index_type eq "dwarf5" } {
> > --
> > 2.43.0
> >
  
Aaron Merey March 1, 2024, 12:09 a.m. UTC | #5
Ping.

Thanks,
Aaron

On Thu, Feb 22, 2024 at 5:23 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Wed, Feb 7, 2024 at 4:25 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Fri, Jan 19, 2024 at 12:12 AM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Hi Andrew,
> > >
> > > Thanks for the review.
> > >
> > > On Thu, Jan 18, 2024 at 10:34 AM Andrew Burgess <aburgess@redhat.com> wrote:
> > > >
> > > > Should be:
> > > >
> > > >   gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > > >
> > > > OK, I'm going to stop commenting on these now, but there's a few more of
> > > > them in the rest of the patch.  GDB style is not to rely on implicit
> > > > int -> bool conversion.
> > >
> > > Fixed.
> > >
> > > >
> > > > > +
> > > > > +  SCOPE_EXIT { objfile->remove_deferred_status (); };
> > > > > +
> > > > > +  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > > > +  const char *filename;
> > > > > +  gdb_bfd_ref_ptr debug_bfd;
> > > > > +  gdb::unique_xmalloc_ptr<char> symfile_path;
> > > > > +  scoped_fd fd;
> > > >
> > > > Of these, filename, debug_bfd, and fd can be declared where they are
> > > > first defined.  You can probably move symfile_path closer to where it's
> > > > used too.
> > > >
> > >
> > > Fixed.
> > >
> > > > > diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
> > > > > index 1235f293f45..b674103c72f 100644
> > > > > --- a/gdb/dwarf2/section.c
> > > > > +++ b/gdb/dwarf2/section.c
> > > > > @@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
> > > > >        section = get_containing_section ();
> > > > >        gdb_assert (!section->is_virtual);
> > > > >      }
> > > > > -  gdb_assert (section->s.section != nullptr);
> > > > > +  if (section->s.section == nullptr)
> > > > > +    error (_("Can't find owner of DWARF section."));
> > > >
> > > > This seems like an interesting change.  There's no test that triggers
> > > > this case, but I guess you ran into this at some point.  Could you
> > > > explain more about what's going on here?
> > >
> > > This can happen if gdb mistakenly tries to read debug info that
> > > has been deferred and not yet downloaded.  For example if gdb attempted
> > > to read symbols from a debuginfo-stripped parent objfile without first
> > > downloading the full separate debug info, then this error would trigger.
> > >
> > > > > @@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> > > > >          && objfile->separate_debug_objfile_backlink == NULL)
> > > > >      {
> > > > >        if (objfile->find_and_add_separate_symbol_file (symfile_flags))
> > > > > -     gdb_assert (objfile->separate_debug_objfile != nullptr);
> > > > > +     gdb_assert (objfile->separate_debug_objfile != nullptr
> > > > > +                 || objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> > > > >        else
> > > > >       has_dwarf2 = false;
> > > > >      }
> > > > > diff --git a/gdb/frame.c b/gdb/frame.c
> > > > > index 41003cc7771..d3409c8fd97 100644
> > > > > --- a/gdb/frame.c
> > > > > +++ b/gdb/frame.c
> > > > > @@ -1889,6 +1889,13 @@ get_selected_frame (const char *message)
> > > > >       error (("%s"), message);
> > > > >
> > > > >        lookup_selected_frame (selected_frame_id, selected_frame_level);
> > > > > +
> > > > > +      /* It is possible for lookup_selected_frame to cause a new objfile
> > > > > +      to be loaded.  Some objfile observers may choose to clear
> > > > > +      selected_frame when an objfile is loaded.  Work around this by
> > > > > +      calling lookup_selected_frame again if the first try failed.  */
> > > > > +      if (selected_frame == nullptr)
> > > > > +     lookup_selected_frame (selected_frame_id, selected_frame_level);
> > > >
> > > > I notice this case wasn't tested -- I added an abort here and reran the
> > > > test, and didn't see it hit.
> > >
> > > With the 2nd call to lookup_selected_frame removed, the test
> > > "gdb.debuginfod/section.exp: local_url: line thread" becomes unresolved
> > > when testing with --target_board=native-gdbserver.
> > >
> > > >
> > > > I ask because this seems both interesting, and this doesn't feel like
> > > > the right solution... As a minimum you'd need a comment on
> > > > lookup_selected_frame that says "hey, you will need to call this
> > > > function twice to guarantee that it did what it promised".  So then I'd
> > > > suggest we should just rename lookup_selected_frame to
> > > > lookup_selected_frame_1, and have lookup_selected_frame be a wrapper
> > > > which does the double lookup.
> > >
> > > This is a better solution. Fixed.
> > >
> > > > But I was kind of hoping that we'd have a test that hit this case so I
> > > > could look at the stack trace and better understand what exactly is
> > > > happening when the selected frame is cleared.
> > > >
> > > > > diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > > > new file mode 100644
> > > > > index 00000000000..7ad19c276c4
> > > > > --- /dev/null
> > > > > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > > > @@ -0,0 +1,43 @@
> > > > > +/* This testcase is part of GDB, the GNU debugger.
> > > > > +
> > > > > +   Copyright 2023 Free Software Foundation, Inc.
> > > >
> > > > Remember to update your copyright dates to '2023-2024' throughout.
> > >
> > > Fixed.
> > >
> > > > > +load_lib debuginfod-support.exp
> > > > > +
> > > > > +require allow_debuginfod_tests
> > > > > +
> > > > > +set sourcetmp [standard_output_file tmp-${srcfile}]
> > > > > +set outputdir [standard_output_file {}]
> > > >
> > > > Not sure what these two are for.  Maybe a copy&paste issue?
> > >
> > > Fixed.
> > >
> > > >
> > > > > +
> > > > > +# SECTEXEC is an executable which calls a function from LIB_SL1.
> > > > > +set sectfile "section"
> > > > > +set sectsrc $srcdir/$subdir/section.c
> > > > > +set sectexec [standard_output_file $sectfile]
> > > >
> > > > I'd have expected you to use ${srcfile} and ${binfile} here which are
> > > > setup by `standard_testfile` based on the name of the test script.  So
> > > > section.exp will setup `section.c` and `/path/to/.../section`
> > > > respectively.
> > > >
> > > > The source file doesn't have the directory prefix, but the compile procs
> > > > know how to find the source file, so that's OK.
> > > >
> > > > standard_testfile also gives you `testfile` which is $binfile without
> > > > the path prefix, if that's needed.
> > > >
> > > > It's good practice to use $binfile throughout the test unless there's
> > > > some compelling reason not too everyone knows what $binfile is.
> > >
> > > Fixed.
> > >
> > > > > +if { [gdb_compile $sectsrc $sectexec executable $exec_opts] != "" } {
> > > > > +    untested "failed to compile $sectfile"
> > > > > +    return -1
> > > > > +}
> > > >
> > > > Instead of calling all these compile worker functions directly, the
> > > > better approach is to call build_executable and pass through the correct
> > > > flags.  This seems to work for me:
> > > >
> > > >   if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
> > > >           {debug build-id shlib}] != 0 } {
> > > >       return -1
> > > >   }
> > >
> > > Fixed.
> > >
> > > > > +    gdb_test "set debuginfod enabled on" "" "clean_restart enable $testname"
> > > >
> > > > You can use gdb_test_no_output here rather than passing an empty pattern.
> > >
> > > Fixed.
> > >
> > > > > +# Return 1 if gdb is configured to download ELF/DWARF sections from
> > > > > +# debuginfod servers.  Otherwise return 0.
> > > >
> > > > We can use 'true' and 'false' in TCL code, see allow_debuginfod_tests as
> > > > an example.
> > > >
> > > > > +proc enable_section_downloads { } {
> > > >
> > > > I think this should be renamed to something like allow_section_downloads
> > > > (though I about allow_debuginfod_section_downloads to make it clear it's
> > > > a debuginfod function).  Then, in the test script you would say:
> > > >
> > > >   require allow_debuginfod_section_downloads
> > > >
> > > > Rather than the `if` check that you have right now.
> > > >
> > > >
> > > > > +    global gdb_prompt
> > > >
> > > > This line isn't needed, gdb_prompt isn't used.
> > >
> > > Fixed.  I've attached the updated patch below.
> > >
> > > Aaron
> > >
> > > Commit message:
> > >
> > > At the beginning of a session, gdb may attempt to download debuginfo
> > > for all shared libraries associated with the process or core file
> > > being debugged.  This can be a waste of time and storage space when much
> > > of the debuginfo ends up not being used during the session.
> > >
> > > To reduce the gdb's startup latency and to download only the debuginfo
> > > that is really needed, this patch adds on-demand downloading of debuginfo.
> > >
> > > 'set debuginfo enabled on' now causes gdb to attempt to download a .gdb_index
> > > for each shared library instead of its full debuginfo.  Each corresponding
> > > separate debuginfo will be deferred until gdb needs to expand symtabs
> > > associated with the debuginfo's index.
> > >
> > > Because these indices are significantly smaller than their corresponding
> > > debuginfo, this generally reduces the total amount of data gdb downloads.
> > > Reductions of 80%-95% have been observed when debugging large GUI programs.
> > >
> > >     (gdb) set debuginfod enabled on
> > >     (gdb) start
> > >     Downloading section .gdb_index for /lib64/libcurl.so.4
> > >     [...]
> > >     1826        client->server_mhandle = curl_multi_init ();
> > >     (gdb) step
> > >     Downloading separate debug info for /lib64/libcurl.so.4
> > >     Downloading separate debug info for [libcurl dwz]
> > >     Downloading source file /usr/src/debug/curl-7.85.0-6.fc37.x86_64/build-full/lib/../../lib/multi.c
> > >     curl_multi_init () at ../../lib/multi.c:457
> > >     457     {
> > >     (gdb)
> > >
> > > Some of the key functions below include dwarf2_has_separate_index which
> > > downloads the separate .gdb_index.  If successful, the shared library
> > > objfile owns the index until the separate debug objfile is downloaded
> > > or confirmed to not be available.
> > >
> > > read_full_dwarf_from_debuginfod downloads the full debuginfo and
> > > initializes the separate debug objfile.  It is called by functions
> > > such as dwarf2_gdb_index::expand_symtabs_matching and
> > > dwarf2_base_index_functions::find_pc_sect_compunit_symtab when symtab
> > > expansion is required.
> > > ---
> > >  gdb/dwarf2/frame.c                         |  13 ++
> > >  gdb/dwarf2/frame.h                         |   4 +
> > >  gdb/dwarf2/index-cache.c                   |  33 +++++
> > >  gdb/dwarf2/index-cache.h                   |  13 ++
> > >  gdb/dwarf2/public.h                        |   7 +
> > >  gdb/dwarf2/read-gdb-index.c                | 125 +++++++++++++---
> > >  gdb/dwarf2/read.c                          | 145 ++++++++++++++++++-
> > >  gdb/dwarf2/read.h                          |  10 ++
> > >  gdb/dwarf2/section.c                       |   3 +-
> > >  gdb/elfread.c                              |   3 +-
> > >  gdb/frame.c                                |  27 +++-
> > >  gdb/objfile-flags.h                        |   4 +
> > >  gdb/objfiles.h                             |  20 +++
> > >  gdb/quick-symbol.h                         |   4 +
> > >  gdb/symfile-debug.c                        |  11 ++
> > >  gdb/symfile.c                              |  12 +-
> > >  gdb/symtab.c                               |  18 ++-
> > >  gdb/testsuite/gdb.debuginfod/libsection1.c |  43 ++++++
> > >  gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
> > >  gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
> > >  gdb/testsuite/gdb.debuginfod/section.exp   | 157 +++++++++++++++++++++
> > >  gdb/testsuite/lib/debuginfod-support.exp   |  25 +++-
> > >  gdb/testsuite/lib/gdb.exp                  |   8 +-
> > >  23 files changed, 719 insertions(+), 32 deletions(-)
> > >  create mode 100644 gdb/testsuite/gdb.debuginfod/libsection1.c
> > >  create mode 100644 gdb/testsuite/gdb.debuginfod/libsection2.c
> > >  create mode 100644 gdb/testsuite/gdb.debuginfod/section.c
> > >  create mode 100644 gdb/testsuite/gdb.debuginfod/section.exp
> > >
> > > diff --git a/gdb/dwarf2/frame.c b/gdb/dwarf2/frame.c
> > > index d3d1ecdf1f5..9e290af79f7 100644
> > > --- a/gdb/dwarf2/frame.c
> > > +++ b/gdb/dwarf2/frame.c
> > > @@ -1620,6 +1620,19 @@ set_comp_unit (struct objfile *objfile, struct comp_unit *unit)
> > >    return dwarf2_frame_bfd_data.set (abfd, unit);
> > >  }
> > >
> > > +/* See frame.h.  */
> > > +
> > > +void
> > > +dwarf2_clear_frame_data (struct objfile *objfile)
> > > +{
> > > +  bfd *abfd = objfile->obfd.get ();
> > > +
> > > +  if (gdb_bfd_requires_relocations (abfd))
> > > +    dwarf2_frame_objfile_data.clear (objfile);
> > > +  else
> > > +    dwarf2_frame_bfd_data.clear (abfd);
> > > +}
> > > +
> > >  /* Find the FDE for *PC.  Return a pointer to the FDE, and store the
> > >     initial location associated with it into *PC.  */
> > >
> > > diff --git a/gdb/dwarf2/frame.h b/gdb/dwarf2/frame.h
> > > index 5643e557513..2391e313e7c 100644
> > > --- a/gdb/dwarf2/frame.h
> > > +++ b/gdb/dwarf2/frame.h
> > > @@ -238,6 +238,10 @@ void dwarf2_append_unwinders (struct gdbarch *gdbarch);
> > >  extern const struct frame_base *
> > >    dwarf2_frame_base_sniffer (frame_info_ptr this_frame);
> > >
> > > +/* Delete OBJFILEs comp_unit.  */
> > > +
> > > +extern void dwarf2_clear_frame_data (struct objfile * objfile);
> > > +
> > >  /* Compute the DWARF CFA for a frame.  */
> > >
> > >  CORE_ADDR dwarf2_frame_cfa (frame_info_ptr this_frame);
> > > diff --git a/gdb/dwarf2/index-cache.c b/gdb/dwarf2/index-cache.c
> > > index 69f70642dc6..8c969ecd590 100644
> > > --- a/gdb/dwarf2/index-cache.c
> > > +++ b/gdb/dwarf2/index-cache.c
> > > @@ -240,6 +240,33 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
> > >    return {};
> > >  }
> > >
> > > +/* See index-cache.h.  */
> > > +
> > > +gdb::array_view<const gdb_byte>
> > > +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> > > +                                         std::unique_ptr<index_cache_resource> *resource)
> > > +{
> > > +  try
> > > +    {
> > > +      /* Try to map that file.  */
> > > +      index_cache_resource_mmap *mmap_resource
> > > +       = new index_cache_resource_mmap (index_path);
> > > +
> > > +      /* Hand the resource to the caller.  */
> > > +      resource->reset (mmap_resource);
> > > +
> > > +      return gdb::array_view<const gdb_byte>
> > > +         ((const gdb_byte *) mmap_resource->mapping.get (),
> > > +          mmap_resource->mapping.size ());
> > > +    }
> > > +  catch (const gdb_exception_error &except)
> > > +    {
> > > +      warning (_("Unable to read %s: %s"), index_path, except.what ());
> > > +    }
> > > +
> > > +  return {};
> > > +}
> > > +
> > >  #else /* !HAVE_SYS_MMAN_H */
> > >
> > >  /* See dwarf-index-cache.h.  This is a no-op on unsupported systems.  */
> > > @@ -251,6 +278,12 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
> > >    return {};
> > >  }
> > >
> > > +gdb::array_view<const gdb_byte>
> > > +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> > > +                                         std::unique_ptr<index_cache_resource> *resource)
> > > +{
> > > +  return {};
> > > +}
> > >  #endif
> > >
> > >  /* See dwarf-index-cache.h.  */
> > > diff --git a/gdb/dwarf2/index-cache.h b/gdb/dwarf2/index-cache.h
> > > index 023fc86fc89..0d845ebcadd 100644
> > > --- a/gdb/dwarf2/index-cache.h
> > > +++ b/gdb/dwarf2/index-cache.h
> > > @@ -90,6 +90,19 @@ class index_cache
> > >    lookup_gdb_index (const bfd_build_id *build_id,
> > >                     std::unique_ptr<index_cache_resource> *resource);
> > >
> > > +  /* Look for an index file located at INDEX_PATH in the debuginfod cache.
> > > +     Unlike lookup_gdb_index, this function does not exit early if the
> > > +     index cache has not been enabled.
> > > +
> > > +     If found, return the contents as an array_view and store the underlying
> > > +     resources (allocated memory, mapped file, etc) in RESOURCE.  The returned
> > > +     array_view is valid as long as RESOURCE is not destroyed.
> > > +
> > > +     If no matching index file is found, return an empty array view.  */
> > > +  gdb::array_view<const gdb_byte>
> > > +  lookup_gdb_index_debuginfod (const char *index_path,
> > > +                              std::unique_ptr<index_cache_resource> *resource);
> > > +
> > >    /* Return the number of cache hits.  */
> > >    unsigned int n_hits () const
> > >    { return m_n_hits; }
> > > diff --git a/gdb/dwarf2/public.h b/gdb/dwarf2/public.h
> > > index efb754b5fe8..4d3776465c3 100644
> > > --- a/gdb/dwarf2/public.h
> > > +++ b/gdb/dwarf2/public.h
> > > @@ -44,4 +44,11 @@ extern bool dwarf2_initialize_objfile
> > >
> > >  extern void dwarf2_build_frame_info (struct objfile *);
> > >
> > > +/* Query debuginfod for the .gdb_index associated with OBJFILE.  If
> > > +   successful, create an objfile to hold the .gdb_index information
> > > +   and act as a placeholder until the full debuginfo needs to be
> > > +   downloaded.  */
> > > +
> > > +extern bool dwarf2_has_separate_index (struct objfile *);
> > > +
> > >  #endif /* DWARF2_PUBLIC_H */
> > > diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
> > > index 4ca44540296..f4d77ed2d04 100644
> > > --- a/gdb/dwarf2/read-gdb-index.c
> > > +++ b/gdb/dwarf2/read-gdb-index.c
> > > @@ -139,6 +139,8 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >       gdb.dwarf2/gdb-index.exp testcase.  */
> > >    void dump (struct objfile *objfile) override;
> > >
> > > +  /* Calls do_expand_symtabs_matching and triggers debuginfo downloading
> > > +     if necessary.  */
> > >    bool expand_symtabs_matching
> > >      (struct objfile *objfile,
> > >       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > > @@ -148,8 +150,60 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >       block_search_flags search_flags,
> > >       domain_enum domain,
> > >       enum search_domain kind) override;
> > > +
> > > +  /* Expand symtabs matching a given symbol or file.  */
> > > +  bool do_expand_symtabs_matching
> > > +    (struct objfile *objfile,
> > > +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > > +     const lookup_name_info *lookup_name,
> > > +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> > > +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> > > +     block_search_flags search_flags,
> > > +     domain_enum domain,
> > > +     enum search_domain kind);
> > > +
> > > +  /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
> > > +     debuginfo if necessary.  */
> > > +  void expand_all_symtabs (struct objfile *objfile) override;
> > > +
> > > +  /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
> > > +     debuginfo if necessary.  */
> > > +  struct symtab *find_last_source_symtab (struct objfile *objfile) override;
> > >  };
> > >
> > > +void
> > > +dwarf2_gdb_index::expand_all_symtabs (struct objfile *objfile)
> > > +{
> > > +  try
> > > +    {
> > > +      dwarf2_base_index_functions::expand_all_symtabs (objfile);
> > > +    }
> > > +  catch (const gdb_exception &e)
> > > +    {
> > > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       exception_print (gdb_stderr, e);
> > > +      else
> > > +       read_full_dwarf_from_debuginfod (objfile, this);
> > > +    }
> > > +}
> > > +
> > > +struct symtab *
> > > +dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
> > > +{
> > > +  try
> > > +    {
> > > +      return dwarf2_base_index_functions::find_last_source_symtab (objfile);
> > > +    }
> > > +  catch (const gdb_exception &e)
> > > +    {
> > > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       exception_print (gdb_stderr, e);
> > > +      else
> > > +       read_full_dwarf_from_debuginfod (objfile, this);
> > > +      return nullptr;
> > > +    }
> > > +}
> > > +
> > >  /* This dumps minimal information about the index.
> > >     It is called via "mt print objfiles".
> > >     One use is to verify .gdb_index has been loaded by the
> > > @@ -268,7 +322,7 @@ dw2_expand_marked_cus
> > >  }
> > >
> > >  bool
> > > -dwarf2_gdb_index::expand_symtabs_matching
> > > +dwarf2_gdb_index::do_expand_symtabs_matching
> > >      (struct objfile *objfile,
> > >       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > >       const lookup_name_info *lookup_name,
> > > @@ -317,6 +371,39 @@ dwarf2_gdb_index::expand_symtabs_matching
> > >    return result;
> > >  }
> > >
> > > +bool
> > > +dwarf2_gdb_index::expand_symtabs_matching
> > > +    (struct objfile *objfile,
> > > +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > > +     const lookup_name_info *lookup_name,
> > > +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> > > +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> > > +     block_search_flags search_flags,
> > > +     domain_enum domain,
> > > +     enum search_domain kind)
> > > +{
> > > +  if ((objfile->flags & OBJF_READNEVER) != 0)
> > > +    return false;
> > > +
> > > +  try
> > > +    {
> > > +      return do_expand_symtabs_matching (objfile, file_matcher, lookup_name,
> > > +                                        symbol_matcher, expansion_notify,
> > > +                                        search_flags, domain, kind);
> > > +    }
> > > +  catch (const gdb_exception &e)
> > > +    {
> > > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       {
> > > +         exception_print (gdb_stderr, e);
> > > +         return false;
> > > +       }
> > > +
> > > +      read_full_dwarf_from_debuginfod (objfile, this);
> > > +      return true;
> > > +    }
> > > +}
> > > +
> > >  quick_symbol_functions_up
> > >  mapped_gdb_index::make_quick_functions () const
> > >  {
> > > @@ -652,28 +739,32 @@ dwarf2_read_gdb_index
> > >
> > >    /* If there is a .dwz file, read it so we can get its CU list as
> > >       well.  */
> > > -  dwz = dwarf2_get_dwz_file (per_bfd);
> > > -  if (dwz != NULL)
> > > +  if (get_gdb_index_contents_dwz != nullptr)
> > >      {
> > >        mapped_gdb_index dwz_map;
> > >        const gdb_byte *dwz_types_ignore;
> > >        offset_type dwz_types_elements_ignore;
> > > +      dwz = dwarf2_get_dwz_file (per_bfd);
> > >
> > > -      gdb::array_view<const gdb_byte> dwz_index_content
> > > -       = get_gdb_index_contents_dwz (objfile, dwz);
> > > -
> > > -      if (dwz_index_content.empty ())
> > > -       return 0;
> > > -
> > > -      if (!read_gdb_index_from_buffer (bfd_get_filename (dwz->dwz_bfd.get ()),
> > > -                                      1, dwz_index_content, &dwz_map,
> > > -                                      &dwz_list, &dwz_list_elements,
> > > -                                      &dwz_types_ignore,
> > > -                                      &dwz_types_elements_ignore))
> > > +      if (dwz != nullptr)
> > >         {
> > > -         warning (_("could not read '.gdb_index' section from %s; skipping"),
> > > -                  bfd_get_filename (dwz->dwz_bfd.get ()));
> > > -         return 0;
> > > +         gdb::array_view<const gdb_byte> dwz_index_content
> > > +           = get_gdb_index_contents_dwz (objfile, dwz);
> > > +
> > > +         if (dwz_index_content.empty ())
> > > +           return 0;
> > > +
> > > +         if (!read_gdb_index_from_buffer (bfd_get_filename
> > > +                                            (dwz->dwz_bfd.get ()),
> > > +                                          1, dwz_index_content, &dwz_map,
> > > +                                          &dwz_list, &dwz_list_elements,
> > > +                                          &dwz_types_ignore,
> > > +                                          &dwz_types_elements_ignore))
> > > +           {
> > > +             warning (_("could not read '.gdb_index' section from %s; skipping"),
> > > +                      bfd_get_filename (dwz->dwz_bfd.get ()));
> > > +               return 0;
> > > +           }
> > >         }
> > >      }
> > >
> > > diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> > > index 76dbeb8d536..062f0e34938 100644
> > > --- a/gdb/dwarf2/read.c
> > > +++ b/gdb/dwarf2/read.c
> > > @@ -35,6 +35,7 @@
> > >  #include "dwarf2/attribute.h"
> > >  #include "dwarf2/comp-unit-head.h"
> > >  #include "dwarf2/cu.h"
> > > +#include "dwarf2/frame.h"
> > >  #include "dwarf2/index-cache.h"
> > >  #include "dwarf2/index-common.h"
> > >  #include "dwarf2/leb.h"
> > > @@ -96,6 +97,8 @@
> > >  #include "split-name.h"
> > >  #include "gdbsupport/thread-pool.h"
> > >  #include "run-on-main-thread.h"
> > > +#include "inferior.h"
> > > +#include "debuginfod-support.h"
> > >
> > >  /* When == 1, print basic high level tracing messages.
> > >     When > 1, be more verbose.
> > > @@ -3003,7 +3006,7 @@ dwarf2_base_index_functions::find_per_cu (dwarf2_per_bfd *per_bfd,
> > >  }
> > >
> > >  struct compunit_symtab *
> > > -dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> > > +dwarf2_base_index_functions::do_find_pc_sect_compunit_symtab
> > >       (struct objfile *objfile,
> > >        struct bound_minimal_symbol msymbol,
> > >        CORE_ADDR pc,
> > > @@ -3034,6 +3037,32 @@ dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> > >    return result;
> > >  }
> > >
> > > +struct compunit_symtab *
> > > +dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> > > +     (struct objfile *objfile,
> > > +      struct bound_minimal_symbol msymbol,
> > > +      CORE_ADDR pc,
> > > +      struct obj_section *section,
> > > +      int warn_if_readin)
> > > +{
> > > +  if ((objfile->flags & OBJF_READNEVER) != 0)
> > > +    return nullptr;
> > > +
> > > +  try
> > > +    {
> > > +      return do_find_pc_sect_compunit_symtab (objfile, msymbol, pc,
> > > +                                             section, warn_if_readin);
> > > +    }
> > > +  catch (const gdb_exception &e)
> > > +    {
> > > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       exception_print (gdb_stderr, e);
> > > +      else
> > > +       read_full_dwarf_from_debuginfod (objfile, this);
> > > +      return nullptr;
> > > +    }
> > > +}
> > > +
> > >  void
> > >  dwarf2_base_index_functions::map_symbol_filenames
> > >       (struct objfile *objfile,
> > > @@ -3190,6 +3219,29 @@ get_gdb_index_contents_from_cache_dwz (objfile *obj, dwz_file *dwz)
> > >    return global_index_cache.lookup_gdb_index (build_id, &dwz->index_cache_res);
> > >  }
> > >
> > > +/* Query debuginfod for the .gdb_index matching OBJFILE's build-id.  Return the
> > > +   contents if successful.  */
> > > +
> > > +static gdb::array_view<const gdb_byte>
> > > +get_gdb_index_contents_from_debuginfod (objfile *objfile, dwarf2_per_bfd *per_bfd)
> > > +{
> > > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > +  if (build_id == nullptr)
> > > +    return {};
> > > +
> > > +  gdb::unique_xmalloc_ptr<char> index_path;
> > > +  scoped_fd fd = debuginfod_section_query (build_id->data, build_id->size,
> > > +                                          bfd_get_filename
> > > +                                            (objfile->obfd.get ()),
> > > +                                          ".gdb_index",
> > > +                                          &index_path);
> > > +  if (fd.get () < 0)
> > > +    return {};
> > > +
> > > +  return global_index_cache.lookup_gdb_index_debuginfod
> > > +    (index_path.get (), &per_bfd->index_cache_res);
> > > +}
> > > +
> > >  static quick_symbol_functions_up make_cooked_index_funcs
> > >       (dwarf2_per_objfile *);
> > >
> > > @@ -3200,7 +3252,8 @@ dwarf2_initialize_objfile (struct objfile *objfile,
> > >                            const struct dwarf2_debug_sections *names,
> > >                            bool can_copy)
> > >  {
> > > -  if (!dwarf2_has_info (objfile, names, can_copy))
> > > +  if (!dwarf2_has_info (objfile, names, can_copy)
> > > +      && (objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > >      return false;
> > >
> > >    dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile);
> > > @@ -3262,6 +3315,16 @@ dwarf2_initialize_objfile (struct objfile *objfile,
> > >        global_index_cache.hit ();
> > >        objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
> > >      }
> > > +  /* Try to read just a separately downloaded gdb index.  */
> > > +  else if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0
> > > +          && dwarf2_read_gdb_index (per_objfile,
> > > +                                    get_gdb_index_contents_from_debuginfod,
> > > +                                    nullptr))
> > > +    {
> > > +      dwarf_read_debug_printf ("found .gdb_index from debuginfod");
> > > +      objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
> > > +      objfile->qf.begin ()->get ()->from_separate_index = true;
> > > +    }
> > >    else
> > >      {
> > >        global_index_cache.miss ();
> > > @@ -3270,6 +3333,84 @@ dwarf2_initialize_objfile (struct objfile *objfile,
> > >    return true;
> > >  }
> > >
> > > +/* See read.h.  */
> > > +
> > > +void
> > > +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> > > +                                dwarf2_base_index_functions *fncs)
> > > +{
> > > +  gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > > +
> > > +  SCOPE_EXIT { objfile->remove_deferred_status (); };
> > > +
> > > +  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > +  if (build_id == nullptr)
> > > +    return;
> > > +
> > > +  const char *filename = bfd_get_filename (objfile->obfd.get ());
> > > +  gdb::unique_xmalloc_ptr<char> symfile_path;
> > > +  scoped_fd fd;
> > > +
> > > +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> > > +                                  filename, &symfile_path);
> > > +  if (fd.get () < 0)
> > > +    return;
> > > +
> > > +  /* Separate debuginfo successfully retrieved from server.  */
> > > +  gdb_bfd_ref_ptr debug_bfd = symfile_bfd_open (symfile_path.get ());
> > > +  if (debug_bfd == nullptr
> > > +      || !build_id_verify (debug_bfd.get (), build_id->size, build_id->data))
> > > +    {
> > > +      warning (_("File \"%s\" from debuginfod cannot be opened as bfd"),
> > > +              filename);
> > > +      return;
> > > +    }
> > > +
> > > +  /* Clear frame data so it can be recalculated using DWARF.  */
> > > +  dwarf2_clear_frame_data (objfile);
> > > +
> > > +  /* This may also trigger a dwz download.  */
> > > +  symbol_file_add_separate (debug_bfd, symfile_path.get (),
> > > +                           current_inferior ()->symfile_flags, objfile);
> > > +}
> > > +
> > > +/* See public.h.  */
> > > +
> > > +bool
> > > +dwarf2_has_separate_index (struct objfile *objfile)
> > > +{
> > > +  if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0)
> > > +    return true;
> > > +  if ((objfile->flags & OBJF_MAINLINE) != 0)
> > > +    return false;
> > > +  if (!IS_DIR_SEPARATOR (*objfile_filename (objfile)))
> > > +    return false;
> > > +
> > > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > +
> > > +  if (build_id == nullptr)
> > > +    return false;
> > > +
> > > +  gdb::unique_xmalloc_ptr<char> index_path;
> > > +  scoped_fd fd = debuginfod_section_query (build_id->data,
> > > +                                          build_id->size,
> > > +                                          bfd_get_filename
> > > +                                            (objfile->obfd.get ()),
> > > +                                          ".gdb_index",
> > > +                                          &index_path);
> > > +
> > > +  if (fd.get () < 0)
> > > +    return false;
> > > +
> > > +  /* We found a separate .gdb_index file so a separate debuginfo file
> > > +     should exist, but we don't want to download it until necessary.
> > > +     Attach the index to this objfile and defer the debuginfo download
> > > +     until gdb needs to expand symtabs referenced by the index.  */
> > > +  objfile->flags |= OBJF_DOWNLOAD_DEFERRED;
> > > +  dwarf2_initialize_objfile (objfile);
> > > +  return true;
> > > +}
> > > +
> > >
> > >
> > >  /* Find the base address of the compilation unit for range lists and
> > > diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> > > index d6631d45a54..8b6f1864458 100644
> > > --- a/gdb/dwarf2/read.h
> > > +++ b/gdb/dwarf2/read.h
> > > @@ -900,6 +900,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
> > >       CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
> > >         override;
> > >
> > > +  struct compunit_symtab *do_find_pc_sect_compunit_symtab
> > > +    (struct objfile *objfile, struct bound_minimal_symbol msymbol,
> > > +     CORE_ADDR pc, struct obj_section *section, int warn_if_readin);
> > > +
> > >    struct compunit_symtab *find_compunit_symtab_by_address
> > >      (struct objfile *objfile, CORE_ADDR address) override
> > >    {
> > > @@ -972,4 +976,10 @@ extern void create_all_units (dwarf2_per_objfile *per_objfile);
> > >
> > >  extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
> > >
> > > +/* If OBJFILE contains information from a separately downloaded .gdb_index,
> > > +   attempt to download the full debuginfo.  */
> > > +
> > > +extern void read_full_dwarf_from_debuginfod (struct objfile *,
> > > +                                            dwarf2_base_index_functions *);
> > > +
> > >  #endif /* DWARF2READ_H */
> > > diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
> > > index 1235f293f45..b674103c72f 100644
> > > --- a/gdb/dwarf2/section.c
> > > +++ b/gdb/dwarf2/section.c
> > > @@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
> > >        section = get_containing_section ();
> > >        gdb_assert (!section->is_virtual);
> > >      }
> > > -  gdb_assert (section->s.section != nullptr);
> > > +  if (section->s.section == nullptr)
> > > +    error (_("Can't find owner of DWARF section."));
> > >    return section->s.section->owner;
> > >  }
> > >
> > > diff --git a/gdb/elfread.c b/gdb/elfread.c
> > > index c5ba8837217..f14dc1bf80b 100644
> > > --- a/gdb/elfread.c
> > > +++ b/gdb/elfread.c
> > > @@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> > >            && objfile->separate_debug_objfile_backlink == NULL)
> > >      {
> > >        if (objfile->find_and_add_separate_symbol_file (symfile_flags))
> > > -       gdb_assert (objfile->separate_debug_objfile != nullptr);
> > > +       gdb_assert (objfile->separate_debug_objfile != nullptr
> > > +                   || (objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > >        else
> > >         has_dwarf2 = false;
> > >      }
> > > diff --git a/gdb/frame.c b/gdb/frame.c
> > > index 41003cc7771..7f57cf80a1b 100644
> > > --- a/gdb/frame.c
> > > +++ b/gdb/frame.c
> > > @@ -1772,14 +1772,10 @@ restore_selected_frame (frame_id frame_id, int frame_level)
> > >    selected_frame = nullptr;
> > >  }
> > >
> > > -/* Lookup the frame_info object for the selected frame FRAME_ID /
> > > -   FRAME_LEVEL and cache the result.
> > > -
> > > -   If FRAME_LEVEL > 0 and the originally selected frame isn't found,
> > > -   warn and select the innermost (current) frame.  */
> > > +/* See lookup_selected_frame.  */
> > >
> > >  static void
> > > -lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> > > +lookup_selected_frame_1 (struct frame_id &a_frame_id, int frame_level)
> > >  {
> > >    frame_info_ptr frame = NULL;
> > >    int count;
> > > @@ -1851,6 +1847,25 @@ lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> > >      }
> > >  }
> > >
> > > +/* Lookup the frame_info object for the selected frame FRAME_ID /
> > > +   FRAME_LEVEL and cache the result.
> > > +
> > > +   If FRAME_LEVEL > 0 and the originally selected frame isn't found,
> > > +   warn and select the innermost (current) frame.  */
> > > +
> > > +static void
> > > +lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> > > +{
> > > +  lookup_selected_frame_1 (selected_frame_id, selected_frame_level);
> > > +
> > > +  /* It is possible for lookup_selected_frame_1 to cause a new objfile
> > > +     to be loaded.  However some objfile observers may choose to clear
> > > +     selected_frame when an objfile is loaded.  Work around this by
> > > +     calling lookup_selected_frame_1 again if the first call failed.  */
> > > +  if (selected_frame == nullptr)
> > > +    lookup_selected_frame_1 (selected_frame_id, selected_frame_level);
> > > +}
> > > +
> > >  bool
> > >  has_stack_frames ()
> > >  {
> > > diff --git a/gdb/objfile-flags.h b/gdb/objfile-flags.h
> > > index 74aea1a88d3..3f922d9e84f 100644
> > > --- a/gdb/objfile-flags.h
> > > +++ b/gdb/objfile-flags.h
> > > @@ -56,6 +56,10 @@ enum objfile_flag : unsigned
> > >      /* User requested that we do not read this objfile's symbolic
> > >         information.  */
> > >      OBJF_READNEVER = 1 << 6,
> > > +
> > > +    /* A separate .gdb_index has been downloaded for this objfile.
> > > +       Debuginfo for this objfile can be downloaded when required.  */
> > > +    OBJF_DOWNLOAD_DEFERRED = 1 << 7,
> > >    };
> > >
> > >  DEF_ENUM_FLAGS_TYPE (enum objfile_flag, objfile_flags);
> > > diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> > > index b917886988d..4a4db1e7058 100644
> > > --- a/gdb/objfiles.h
> > > +++ b/gdb/objfiles.h
> > > @@ -622,6 +622,26 @@ struct objfile
> > >                                                domain_enum domain,
> > >                                                bool *symbol_found_p);
> > >
> > > +  /* Indicate that the acquisition of this objfile's separate debug objfile
> > > +     is no longer deferred.  Used when the debug objfile has been acquired
> > > +     or could not be found.  */
> > > +  void remove_deferred_status ()
> > > +  {
> > > +    flags &= ~OBJF_DOWNLOAD_DEFERRED;
> > > +
> > > +    /* Remove quick_symbol_functions derived from a separately downloaded
> > > +       index.  If available the separate debug objfile's index will be used
> > > +       instead, since that objfile actually contains the symbols and CUs
> > > +       referenced in the index.
> > > +
> > > +       No more than one element of qf should have from_separate_index set
> > > +       to true.  */
> > > +    qf.remove_if ([&] (const quick_symbol_functions_up &qf_up)
> > > +      {
> > > +       return qf_up->from_separate_index;
> > > +      });
> > > +  }
> > > +
> > >    /* Return the relocation offset applied to SECTION.  */
> > >    CORE_ADDR section_offset (bfd_section *section) const
> > >    {
> > > diff --git a/gdb/quick-symbol.h b/gdb/quick-symbol.h
> > > index 4cd234162ae..01a228b446c 100644
> > > --- a/gdb/quick-symbol.h
> > > +++ b/gdb/quick-symbol.h
> > > @@ -193,6 +193,10 @@ struct quick_symbol_functions
> > >    virtual void compute_main_name (struct objfile *objfile)
> > >    {
> > >    }
> > > +
> > > +  /* True if this quick_symbol_functions is derived from a separately
> > > +     downloaded index.  */
> > > +  bool from_separate_index = false;
> > >  };
> > >
> > >  typedef std::unique_ptr<quick_symbol_functions> quick_symbol_functions_up;
> > > diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
> > > index c684f9cdd43..3ed31661300 100644
> > > --- a/gdb/symfile-debug.c
> > > +++ b/gdb/symfile-debug.c
> > > @@ -37,6 +37,7 @@
> > >  #include "cli/cli-style.h"
> > >  #include "build-id.h"
> > >  #include "debuginfod-support.h"
> > > +#include "dwarf2/public.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
> > > @@ -617,6 +618,16 @@ objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
> > >           = simple_find_and_open_separate_symbol_file
> > >               (this, find_separate_debug_file_by_debuglink, &warnings);
> > >
> > > +      /* Attempt to download only an index from the separate debug info.
> > > +        As with debuginfod_find_and_open_separate_symbol_file, only attempt
> > > +        this once.  */
> > > +      if (debug_bfd == nullptr && attempt == 0
> > > +         && dwarf2_has_separate_index (this))
> > > +       {
> > > +         has_dwarf2 = true;
> > > +         break;
> > > +       }
> > > +
> > >        /* 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
> > > diff --git a/gdb/symfile.c b/gdb/symfile.c
> > > index bdcc27e7ee5..820eb061aca 100644
> > > --- a/gdb/symfile.c
> > > +++ b/gdb/symfile.c
> > > @@ -989,6 +989,10 @@ syms_from_objfile (struct objfile *objfile,
> > >  static void
> > >  finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
> > >  {
> > > +  struct objfile *parent = objfile->separate_debug_objfile_backlink;
> > > +  bool was_deferred
> > > +    = (parent != nullptr) && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > > +
> > >    /* If this is the main symbol file we have to clean up all users of the
> > >       old main symbol file.  Otherwise it is sufficient to fixup all the
> > >       breakpoints that may have been redefined by this symbol file.  */
> > > @@ -999,7 +1003,7 @@ finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
> > >
> > >        clear_symtab_users (add_flags);
> > >      }
> > > -  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0)
> > > +  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0 && !was_deferred)
> > >      {
> > >        breakpoint_re_set ();
> > >      }
> > > @@ -1120,6 +1124,12 @@ symbol_file_add_with_addrs (const gdb_bfd_ref_ptr &abfd, const char *name,
> > >    if (objfile->sf != nullptr)
> > >      finish_new_objfile (objfile, add_flags);
> > >
> > > +  /* Remove deferred status now in case any observers trigger symtab
> > > +     expansion.  Otherwise gdb might try to read parent for psymbols
> > > +     when it should read the separate debug objfile instead.  */
> > > +  if (parent != nullptr && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0))
> > > +    parent->remove_deferred_status ();
> > > +
> > >    gdb::observers::new_objfile.notify (objfile);
> > >
> > >    return objfile;
> > > diff --git a/gdb/symtab.c b/gdb/symtab.c
> > > index cc0bdb80c7c..d0219da6317 100644
> > > --- a/gdb/symtab.c
> > > +++ b/gdb/symtab.c
> > > @@ -2926,14 +2926,30 @@ find_pc_sect_compunit_symtab (CORE_ADDR pc, struct obj_section *section)
> > >    if (best_cust != NULL)
> > >      return best_cust;
> > >
> > > +  int warn_if_readin = 1;
> > > +
> > >    /* Not found in symtabs, search the "quick" symtabs (e.g. psymtabs).  */
> > >
> > >    for (objfile *objf : current_program_space->objfiles ())
> > >      {
> > > +      bool was_deferred = (objf->flags & OBJF_DOWNLOAD_DEFERRED) != 0;
> > > +
> > >        struct compunit_symtab *result
> > > -       = objf->find_pc_sect_compunit_symtab (msymbol, pc, section, 1);
> > > +       = objf->find_pc_sect_compunit_symtab (msymbol, pc, section,
> > > +                                             warn_if_readin);
> > > +
> > >        if (result != NULL)
> > >         return result;
> > > +
> > > +      /* If OBJF's separate debug info was just acquired, disable
> > > +        warn_if_readin for the next iteration of this loop.  This prevents
> > > +        a spurious warning in case an observer already triggered expansion
> > > +        of the separate debug objfile's symtabs.  */
> > > +      if (was_deferred && objf->separate_debug_objfile != nullptr
> > > +         && (objf->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       warn_if_readin = 0;
> > > +      else if (warn_if_readin == 0)
> > > +       warn_if_readin = 1;
> > >      }
> > >
> > >    return NULL;
> > > diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > new file mode 100644
> > > index 00000000000..e4f05db0a79
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > @@ -0,0 +1,43 @@
> > > +/* This testcase is part of GDB, the GNU debugger.
> > > +
> > > +   Copyright 2023-2024 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/>.  */
> > > +
> > > +#include <stdio.h>
> > > +#include <pthread.h>
> > > +#include <unistd.h>
> > > +
> > > +extern void libsection2_test ();
> > > +extern void *libsection2_thread_test (void *);
> > > +
> > > +static volatile int flag = 0;
> > > +
> > > +void
> > > +libsection1_test ()
> > > +{
> > > +  pthread_t thr;
> > > +
> > > +  printf ("In libsection1\n");
> > > +  libsection2_test ();
> > > +
> > > +  pthread_create (&thr, NULL, libsection2_thread_test, (void *) &flag);
> > > +
> > > +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> > > +  while (!flag)
> > > +    ;
> > > +
> > > +  printf ("Cancelling thread\n");
> > > +  pthread_cancel (thr);
> > > +}
> > > diff --git a/gdb/testsuite/gdb.debuginfod/libsection2.c b/gdb/testsuite/gdb.debuginfod/libsection2.c
> > > new file mode 100644
> > > index 00000000000..3c82808e5fe
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/libsection2.c
> > > @@ -0,0 +1,37 @@
> > > +/* This testcase is part of GDB, the GNU debugger.
> > > +
> > > +   Copyright 2023-2024 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/>.  */
> > > +
> > > +#include <stdio.h>
> > > +#include <pthread.h>
> > > +
> > > +void
> > > +libsection2_test ()
> > > +{
> > > +  printf ("In libsection2\n");
> > > +}
> > > +
> > > +void *
> > > +libsection2_thread_test (void *arg)
> > > +{
> > > +  int *flag = (int *) arg;
> > > +  printf ("In thread test\n");
> > > +
> > > +  while (1)
> > > +    *flag = 1;
> > > +
> > > +  return NULL;
> > > +}
> > > diff --git a/gdb/testsuite/gdb.debuginfod/section.c b/gdb/testsuite/gdb.debuginfod/section.c
> > > new file mode 100644
> > > index 00000000000..fa9ac536a6e
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/section.c
> > > @@ -0,0 +1,29 @@
> > > +/* This testcase is part of GDB, the GNU debugger.
> > > +
> > > +   Copyright 2023-2024 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/>.  */
> > > +
> > > +#include <stdio.h>
> > > +
> > > +extern void libsection1_test ();
> > > +
> > > +int
> > > +main ()
> > > +{
> > > +  libsection1_test ();
> > > +  printf ("in section exec\n");
> > > +
> > > +  return 0;
> > > +}
> > > diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
> > > new file mode 100644
> > > index 00000000000..35086d15705
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > > @@ -0,0 +1,157 @@
> > > +# Copyright 2023-2024 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/>.
> > > +
> > > +# Test debuginfod functionality
> > > +
> > > +standard_testfile
> > > +
> > > +load_lib debuginfod-support.exp
> > > +
> > > +clean_restart
> > > +require allow_debuginfod_tests
> > > +require allow_debuginfod_section_downloads
> > > +
> > > +# BINFILE calls a function from LIB_SL1.
> > > +set libfile1 "libsection1"
> > > +set libsrc1 $srcdir/$subdir/$libfile1.c
> > > +set lib_sl1 [standard_output_file $libfile1.sl]
> > > +
> > > +# LIB_SL1 calls functions from LIB_SL2.
> > > +set libfile2 "libsection2"
> > > +set libsrc2 $srcdir/$subdir/$libfile2.c
> > > +set lib_sl2 [standard_output_file $libfile2.sl]
> > > +
> > > +# Build LIB_SL2, LIB_SL1 and BINFILE.
> > > +if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
> > > +       {debug build-id shlib}] != 0 } {
> > > +    return -1
> > > +}
> > > +
> > > +if { [build_executable "build $libfile1" $lib_sl1 $libsrc1 \
> > > +       [list debug build-id shlib_pthreads shlib=$lib_sl2]] != 0 } {
> > > +    return -1
> > > +}
> > > +
> > > +if { [build_executable "build executable" $binfile $srcfile \
> > > +       [list debug build-id shlib=$lib_sl1 shlib=$lib_sl2]] != 0 } {
> > > +    return -1
> > > +}
> > > +
> > > +# Make sure libsection1 and libsection2 contain .gdb_index.
> > > +if { [ensure_gdb_index $lib_sl1 "" "libsection1"] != 1 } {
> > > +    untested "failed to add .gdb_index to $libfile1"
> > > +    return -1
> > > +}
> > > +
> > > +if { [ensure_gdb_index $lib_sl2 "" "libsection2"] != 1 } {
> > > +    untested "failed to add .gdb_index to $libfile2"
> > > +    return -1
> > > +}
> > > +
> > > +# Strip solib debuginfo into separate files.
> > > +if { [gdb_gnu_strip_debug $lib_sl1 ""] != 0} {
> > > +   fail "strip $lib_sl1 debuginfo"
> > > +   return -1
> > > +}
> > > +
> > > +if { [gdb_gnu_strip_debug $lib_sl2 ""] != 0} {
> > > +   fail "strip $lib_sl2 debuginfo"
> > > +   return -1
> > > +}
> > > +
> > > +# Move debuginfo files into directory that debuginfod will serve from.
> > > +set debugdir [standard_output_file "debug"]
> > > +set debuginfo_sl1 [standard_output_file $libfile1.sl.debug]
> > > +set debuginfo_sl2 [standard_output_file $libfile2.sl.debug]
> > > +
> > > +file mkdir $debugdir
> > > +file rename -force $debuginfo_sl1 $debugdir
> > > +file rename -force $debuginfo_sl2 $debugdir
> > > +
> > > +# Restart GDB and clear the debuginfod client cache.  Then load BINFILE into
> > > +# GDB and start running it.  Match output with pattern RES and use TESTNAME
> > > +# as the test name.
> > > +proc_with_prefix clean_restart_with_prompt { binfile testname } {
> > > +    global cache
> > > +
> > > +    # Delete client cache so debuginfo downloads again.
> > > +    file delete -force $cache
> > > +    clean_restart
> > > +
> > > +    gdb_test_no_output "set debuginfod enabled on" \
> > > +                      "clean_restart enable $testname"
> > > +    gdb_load $binfile
> > > +
> > > +    runto_main
> > > +}
> > > +
> > > +# Tests with no debuginfod server running.
> > > +proc_with_prefix no_url { } {
> > > +    global binfile libfile1 libfile2
> > > +
> > > +    gdb_load $binfile
> > > +    if {![runto_main]} {
> > > +       return
> > > +    }
> > > +
> > > +    # Check that no section is downloaded and no debuginfo is found.
> > > +    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile1.*" \
> > > +            "found no url lib1"
> > > +    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile2.*" \
> > > +            "found no url lib2"
> > > +}
> > > +
> > > +# Tests with a debuginfod server running.
> > > +proc_with_prefix local_url { } {
> > > +    global binfile
> > > +    global libsrc1 lib_sl1 libfile1
> > > +    global libsrc2 lib_sl2 libfile2
> > > +    global debugdir db
> > > +
> > > +    set url [start_debuginfod $db $debugdir]
> > > +    if { $url == "" } {
> > > +       unresolved "failed to start debuginfod server"
> > > +       return
> > > +    }
> > > +
> > > +    # Point GDB to the server.
> > > +    setenv DEBUGINFOD_URLS $url
> > > +
> > > +    clean_restart_with_prompt $binfile "index"
> > > +
> > > +    # Download debuginfo when stepping into a function.
> > > +    set res ".*separate debug info for $lib_sl1.*\"In ${libfile1}\\\\n\".*"
> > > +    gdb_test "step" $res "step"
> > > +
> > > +    clean_restart_with_prompt $binfile "break"
> > > +
> > > +    # Download debuginfo when setting a breakpoint.
> > > +    set res "Download.*separate debug info for $lib_sl2.*"
> > > +    gdb_test "br libsection2_test" $res "break set"
> > > +
> > > +    # Hit the breakpoint.
> > > +    set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
> > > +    gdb_test "c" $res "break continue"
> > > +}
> > > +
> > > +# Create CACHE and DB directories ready for debuginfod to use.
> > > +prepare_for_debuginfod cache db
> > > +
> > > +with_debuginfod_env $cache {
> > > +    no_url
> > > +    local_url
> > > +}
> > > +
> > > +stop_debuginfod
> > > diff --git a/gdb/testsuite/lib/debuginfod-support.exp b/gdb/testsuite/lib/debuginfod-support.exp
> > > index 50a8b512a4a..71964a90564 100644
> > > --- a/gdb/testsuite/lib/debuginfod-support.exp
> > > +++ b/gdb/testsuite/lib/debuginfod-support.exp
> > > @@ -113,6 +113,8 @@ proc with_debuginfod_env { cache body } {
> > >  proc start_debuginfod { db debugdir } {
> > >      global debuginfod_spawn_id spawn_id
> > >
> > > +    set logfile [standard_output_file "server_log"]
> > > +
> > >      # Find an unused port.
> > >      set port 7999
> > >      set found false
> > > @@ -127,7 +129,8 @@ proc start_debuginfod { db debugdir } {
> > >             set old_spawn_id $spawn_id
> > >         }
> > >
> > > -       spawn debuginfod -vvvv -d $db -p $port -F $debugdir
> > > +       spawn sh -c "debuginfod -vvvv -d $db -p $port -F $debugdir 2>&1 \
> > > +               | tee $logfile"
> > >         set debuginfod_spawn_id $spawn_id
> > >
> > >         if { [info exists old_spawn_id] } {
> > > @@ -194,3 +197,23 @@ proc stop_debuginfod { } {
> > >         unset debuginfod_spawn_id
> > >      }
> > >  }
> > > +
> > > +# Return true if gdb is configured to download ELF/DWARF sections from
> > > +# debuginfod servers.  Otherwise return false.
> > > +proc allow_debuginfod_section_downloads { } {
> > > +    set cmd "maint set debuginfod download-sections on"
> > > +    set msg "enable section downloads"
> > > +
> > > +    gdb_test_multiple $cmd $msg {
> > > +       -re -wrap ".*not compiled into GDB.*" {
> > > +           return false
> > > +       }
> > > +       -re -wrap "^" {
> > > +           return true
> > > +       }
> > > +       -re -wrap "" {
> > > +           fail "$gdb_test_name (unexpected output)"
> > > +           return false
> > > +       }
> > > +    }
> > > +}
> > > diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
> > > index 9ac707be696..9b3c7e6a02c 100644
> > > --- a/gdb/testsuite/lib/gdb.exp
> > > +++ b/gdb/testsuite/lib/gdb.exp
> > > @@ -9414,10 +9414,14 @@ proc get_index_type { objfile { testname "" } } {
> > >  # STYLE controls which style of index to add, if needed.  The empty
> > >  # string (the default) means .gdb_index; "-dwarf-5" means .debug_names.
> > >
> > > -proc ensure_gdb_index { binfile {style ""} } {
> > > +proc ensure_gdb_index { binfile {style ""} {testname_prefix ""} } {
> > >      set testfile [file tail $binfile]
> > > -
> > >      set test "check if index present"
> > > +
> > > +    if { $testname_prefix != "" } {
> > > +      set test "$testname_prefix $test"
> > > +    }
> > > +
> > >      set index_type [get_index_type $testfile $test]
> > >
> > >      if { $index_type eq "gdb" || $index_type eq "dwarf5" } {
> > > --
> > > 2.43.0
> > >
  
Aaron Merey March 12, 2024, 10:15 p.m. UTC | #6
Ping

Thanks,
Aaron

On Thu, Feb 22, 2024 at 5:23 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Wed, Feb 7, 2024 at 4:25 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Fri, Jan 19, 2024 at 12:12 AM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Hi Andrew,
> > >
> > > Thanks for the review.
> > >
> > > On Thu, Jan 18, 2024 at 10:34 AM Andrew Burgess <aburgess@redhat.com> wrote:
> > > >
> > > > Should be:
> > > >
> > > >   gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > > >
> > > > OK, I'm going to stop commenting on these now, but there's a few more of
> > > > them in the rest of the patch.  GDB style is not to rely on implicit
> > > > int -> bool conversion.
> > >
> > > Fixed.
> > >
> > > >
> > > > > +
> > > > > +  SCOPE_EXIT { objfile->remove_deferred_status (); };
> > > > > +
> > > > > +  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > > > +  const char *filename;
> > > > > +  gdb_bfd_ref_ptr debug_bfd;
> > > > > +  gdb::unique_xmalloc_ptr<char> symfile_path;
> > > > > +  scoped_fd fd;
> > > >
> > > > Of these, filename, debug_bfd, and fd can be declared where they are
> > > > first defined.  You can probably move symfile_path closer to where it's
> > > > used too.
> > > >
> > >
> > > Fixed.
> > >
> > > > > diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
> > > > > index 1235f293f45..b674103c72f 100644
> > > > > --- a/gdb/dwarf2/section.c
> > > > > +++ b/gdb/dwarf2/section.c
> > > > > @@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
> > > > >        section = get_containing_section ();
> > > > >        gdb_assert (!section->is_virtual);
> > > > >      }
> > > > > -  gdb_assert (section->s.section != nullptr);
> > > > > +  if (section->s.section == nullptr)
> > > > > +    error (_("Can't find owner of DWARF section."));
> > > >
> > > > This seems like an interesting change.  There's no test that triggers
> > > > this case, but I guess you ran into this at some point.  Could you
> > > > explain more about what's going on here?
> > >
> > > This can happen if gdb mistakenly tries to read debug info that
> > > has been deferred and not yet downloaded.  For example if gdb attempted
> > > to read symbols from a debuginfo-stripped parent objfile without first
> > > downloading the full separate debug info, then this error would trigger.
> > >
> > > > > @@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> > > > >          && objfile->separate_debug_objfile_backlink == NULL)
> > > > >      {
> > > > >        if (objfile->find_and_add_separate_symbol_file (symfile_flags))
> > > > > -     gdb_assert (objfile->separate_debug_objfile != nullptr);
> > > > > +     gdb_assert (objfile->separate_debug_objfile != nullptr
> > > > > +                 || objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> > > > >        else
> > > > >       has_dwarf2 = false;
> > > > >      }
> > > > > diff --git a/gdb/frame.c b/gdb/frame.c
> > > > > index 41003cc7771..d3409c8fd97 100644
> > > > > --- a/gdb/frame.c
> > > > > +++ b/gdb/frame.c
> > > > > @@ -1889,6 +1889,13 @@ get_selected_frame (const char *message)
> > > > >       error (("%s"), message);
> > > > >
> > > > >        lookup_selected_frame (selected_frame_id, selected_frame_level);
> > > > > +
> > > > > +      /* It is possible for lookup_selected_frame to cause a new objfile
> > > > > +      to be loaded.  Some objfile observers may choose to clear
> > > > > +      selected_frame when an objfile is loaded.  Work around this by
> > > > > +      calling lookup_selected_frame again if the first try failed.  */
> > > > > +      if (selected_frame == nullptr)
> > > > > +     lookup_selected_frame (selected_frame_id, selected_frame_level);
> > > >
> > > > I notice this case wasn't tested -- I added an abort here and reran the
> > > > test, and didn't see it hit.
> > >
> > > With the 2nd call to lookup_selected_frame removed, the test
> > > "gdb.debuginfod/section.exp: local_url: line thread" becomes unresolved
> > > when testing with --target_board=native-gdbserver.
> > >
> > > >
> > > > I ask because this seems both interesting, and this doesn't feel like
> > > > the right solution... As a minimum you'd need a comment on
> > > > lookup_selected_frame that says "hey, you will need to call this
> > > > function twice to guarantee that it did what it promised".  So then I'd
> > > > suggest we should just rename lookup_selected_frame to
> > > > lookup_selected_frame_1, and have lookup_selected_frame be a wrapper
> > > > which does the double lookup.
> > >
> > > This is a better solution. Fixed.
> > >
> > > > But I was kind of hoping that we'd have a test that hit this case so I
> > > > could look at the stack trace and better understand what exactly is
> > > > happening when the selected frame is cleared.
> > > >
> > > > > diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > > > new file mode 100644
> > > > > index 00000000000..7ad19c276c4
> > > > > --- /dev/null
> > > > > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > > > @@ -0,0 +1,43 @@
> > > > > +/* This testcase is part of GDB, the GNU debugger.
> > > > > +
> > > > > +   Copyright 2023 Free Software Foundation, Inc.
> > > >
> > > > Remember to update your copyright dates to '2023-2024' throughout.
> > >
> > > Fixed.
> > >
> > > > > +load_lib debuginfod-support.exp
> > > > > +
> > > > > +require allow_debuginfod_tests
> > > > > +
> > > > > +set sourcetmp [standard_output_file tmp-${srcfile}]
> > > > > +set outputdir [standard_output_file {}]
> > > >
> > > > Not sure what these two are for.  Maybe a copy&paste issue?
> > >
> > > Fixed.
> > >
> > > >
> > > > > +
> > > > > +# SECTEXEC is an executable which calls a function from LIB_SL1.
> > > > > +set sectfile "section"
> > > > > +set sectsrc $srcdir/$subdir/section.c
> > > > > +set sectexec [standard_output_file $sectfile]
> > > >
> > > > I'd have expected you to use ${srcfile} and ${binfile} here which are
> > > > setup by `standard_testfile` based on the name of the test script.  So
> > > > section.exp will setup `section.c` and `/path/to/.../section`
> > > > respectively.
> > > >
> > > > The source file doesn't have the directory prefix, but the compile procs
> > > > know how to find the source file, so that's OK.
> > > >
> > > > standard_testfile also gives you `testfile` which is $binfile without
> > > > the path prefix, if that's needed.
> > > >
> > > > It's good practice to use $binfile throughout the test unless there's
> > > > some compelling reason not too everyone knows what $binfile is.
> > >
> > > Fixed.
> > >
> > > > > +if { [gdb_compile $sectsrc $sectexec executable $exec_opts] != "" } {
> > > > > +    untested "failed to compile $sectfile"
> > > > > +    return -1
> > > > > +}
> > > >
> > > > Instead of calling all these compile worker functions directly, the
> > > > better approach is to call build_executable and pass through the correct
> > > > flags.  This seems to work for me:
> > > >
> > > >   if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
> > > >           {debug build-id shlib}] != 0 } {
> > > >       return -1
> > > >   }
> > >
> > > Fixed.
> > >
> > > > > +    gdb_test "set debuginfod enabled on" "" "clean_restart enable $testname"
> > > >
> > > > You can use gdb_test_no_output here rather than passing an empty pattern.
> > >
> > > Fixed.
> > >
> > > > > +# Return 1 if gdb is configured to download ELF/DWARF sections from
> > > > > +# debuginfod servers.  Otherwise return 0.
> > > >
> > > > We can use 'true' and 'false' in TCL code, see allow_debuginfod_tests as
> > > > an example.
> > > >
> > > > > +proc enable_section_downloads { } {
> > > >
> > > > I think this should be renamed to something like allow_section_downloads
> > > > (though I about allow_debuginfod_section_downloads to make it clear it's
> > > > a debuginfod function).  Then, in the test script you would say:
> > > >
> > > >   require allow_debuginfod_section_downloads
> > > >
> > > > Rather than the `if` check that you have right now.
> > > >
> > > >
> > > > > +    global gdb_prompt
> > > >
> > > > This line isn't needed, gdb_prompt isn't used.
> > >
> > > Fixed.  I've attached the updated patch below.
> > >
> > > Aaron
> > >
> > > Commit message:
> > >
> > > At the beginning of a session, gdb may attempt to download debuginfo
> > > for all shared libraries associated with the process or core file
> > > being debugged.  This can be a waste of time and storage space when much
> > > of the debuginfo ends up not being used during the session.
> > >
> > > To reduce the gdb's startup latency and to download only the debuginfo
> > > that is really needed, this patch adds on-demand downloading of debuginfo.
> > >
> > > 'set debuginfo enabled on' now causes gdb to attempt to download a .gdb_index
> > > for each shared library instead of its full debuginfo.  Each corresponding
> > > separate debuginfo will be deferred until gdb needs to expand symtabs
> > > associated with the debuginfo's index.
> > >
> > > Because these indices are significantly smaller than their corresponding
> > > debuginfo, this generally reduces the total amount of data gdb downloads.
> > > Reductions of 80%-95% have been observed when debugging large GUI programs.
> > >
> > >     (gdb) set debuginfod enabled on
> > >     (gdb) start
> > >     Downloading section .gdb_index for /lib64/libcurl.so.4
> > >     [...]
> > >     1826        client->server_mhandle = curl_multi_init ();
> > >     (gdb) step
> > >     Downloading separate debug info for /lib64/libcurl.so.4
> > >     Downloading separate debug info for [libcurl dwz]
> > >     Downloading source file /usr/src/debug/curl-7.85.0-6.fc37.x86_64/build-full/lib/../../lib/multi.c
> > >     curl_multi_init () at ../../lib/multi.c:457
> > >     457     {
> > >     (gdb)
> > >
> > > Some of the key functions below include dwarf2_has_separate_index which
> > > downloads the separate .gdb_index.  If successful, the shared library
> > > objfile owns the index until the separate debug objfile is downloaded
> > > or confirmed to not be available.
> > >
> > > read_full_dwarf_from_debuginfod downloads the full debuginfo and
> > > initializes the separate debug objfile.  It is called by functions
> > > such as dwarf2_gdb_index::expand_symtabs_matching and
> > > dwarf2_base_index_functions::find_pc_sect_compunit_symtab when symtab
> > > expansion is required.
> > > ---
> > >  gdb/dwarf2/frame.c                         |  13 ++
> > >  gdb/dwarf2/frame.h                         |   4 +
> > >  gdb/dwarf2/index-cache.c                   |  33 +++++
> > >  gdb/dwarf2/index-cache.h                   |  13 ++
> > >  gdb/dwarf2/public.h                        |   7 +
> > >  gdb/dwarf2/read-gdb-index.c                | 125 +++++++++++++---
> > >  gdb/dwarf2/read.c                          | 145 ++++++++++++++++++-
> > >  gdb/dwarf2/read.h                          |  10 ++
> > >  gdb/dwarf2/section.c                       |   3 +-
> > >  gdb/elfread.c                              |   3 +-
> > >  gdb/frame.c                                |  27 +++-
> > >  gdb/objfile-flags.h                        |   4 +
> > >  gdb/objfiles.h                             |  20 +++
> > >  gdb/quick-symbol.h                         |   4 +
> > >  gdb/symfile-debug.c                        |  11 ++
> > >  gdb/symfile.c                              |  12 +-
> > >  gdb/symtab.c                               |  18 ++-
> > >  gdb/testsuite/gdb.debuginfod/libsection1.c |  43 ++++++
> > >  gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
> > >  gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
> > >  gdb/testsuite/gdb.debuginfod/section.exp   | 157 +++++++++++++++++++++
> > >  gdb/testsuite/lib/debuginfod-support.exp   |  25 +++-
> > >  gdb/testsuite/lib/gdb.exp                  |   8 +-
> > >  23 files changed, 719 insertions(+), 32 deletions(-)
> > >  create mode 100644 gdb/testsuite/gdb.debuginfod/libsection1.c
> > >  create mode 100644 gdb/testsuite/gdb.debuginfod/libsection2.c
> > >  create mode 100644 gdb/testsuite/gdb.debuginfod/section.c
> > >  create mode 100644 gdb/testsuite/gdb.debuginfod/section.exp
> > >
> > > diff --git a/gdb/dwarf2/frame.c b/gdb/dwarf2/frame.c
> > > index d3d1ecdf1f5..9e290af79f7 100644
> > > --- a/gdb/dwarf2/frame.c
> > > +++ b/gdb/dwarf2/frame.c
> > > @@ -1620,6 +1620,19 @@ set_comp_unit (struct objfile *objfile, struct comp_unit *unit)
> > >    return dwarf2_frame_bfd_data.set (abfd, unit);
> > >  }
> > >
> > > +/* See frame.h.  */
> > > +
> > > +void
> > > +dwarf2_clear_frame_data (struct objfile *objfile)
> > > +{
> > > +  bfd *abfd = objfile->obfd.get ();
> > > +
> > > +  if (gdb_bfd_requires_relocations (abfd))
> > > +    dwarf2_frame_objfile_data.clear (objfile);
> > > +  else
> > > +    dwarf2_frame_bfd_data.clear (abfd);
> > > +}
> > > +
> > >  /* Find the FDE for *PC.  Return a pointer to the FDE, and store the
> > >     initial location associated with it into *PC.  */
> > >
> > > diff --git a/gdb/dwarf2/frame.h b/gdb/dwarf2/frame.h
> > > index 5643e557513..2391e313e7c 100644
> > > --- a/gdb/dwarf2/frame.h
> > > +++ b/gdb/dwarf2/frame.h
> > > @@ -238,6 +238,10 @@ void dwarf2_append_unwinders (struct gdbarch *gdbarch);
> > >  extern const struct frame_base *
> > >    dwarf2_frame_base_sniffer (frame_info_ptr this_frame);
> > >
> > > +/* Delete OBJFILEs comp_unit.  */
> > > +
> > > +extern void dwarf2_clear_frame_data (struct objfile * objfile);
> > > +
> > >  /* Compute the DWARF CFA for a frame.  */
> > >
> > >  CORE_ADDR dwarf2_frame_cfa (frame_info_ptr this_frame);
> > > diff --git a/gdb/dwarf2/index-cache.c b/gdb/dwarf2/index-cache.c
> > > index 69f70642dc6..8c969ecd590 100644
> > > --- a/gdb/dwarf2/index-cache.c
> > > +++ b/gdb/dwarf2/index-cache.c
> > > @@ -240,6 +240,33 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
> > >    return {};
> > >  }
> > >
> > > +/* See index-cache.h.  */
> > > +
> > > +gdb::array_view<const gdb_byte>
> > > +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> > > +                                         std::unique_ptr<index_cache_resource> *resource)
> > > +{
> > > +  try
> > > +    {
> > > +      /* Try to map that file.  */
> > > +      index_cache_resource_mmap *mmap_resource
> > > +       = new index_cache_resource_mmap (index_path);
> > > +
> > > +      /* Hand the resource to the caller.  */
> > > +      resource->reset (mmap_resource);
> > > +
> > > +      return gdb::array_view<const gdb_byte>
> > > +         ((const gdb_byte *) mmap_resource->mapping.get (),
> > > +          mmap_resource->mapping.size ());
> > > +    }
> > > +  catch (const gdb_exception_error &except)
> > > +    {
> > > +      warning (_("Unable to read %s: %s"), index_path, except.what ());
> > > +    }
> > > +
> > > +  return {};
> > > +}
> > > +
> > >  #else /* !HAVE_SYS_MMAN_H */
> > >
> > >  /* See dwarf-index-cache.h.  This is a no-op on unsupported systems.  */
> > > @@ -251,6 +278,12 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
> > >    return {};
> > >  }
> > >
> > > +gdb::array_view<const gdb_byte>
> > > +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> > > +                                         std::unique_ptr<index_cache_resource> *resource)
> > > +{
> > > +  return {};
> > > +}
> > >  #endif
> > >
> > >  /* See dwarf-index-cache.h.  */
> > > diff --git a/gdb/dwarf2/index-cache.h b/gdb/dwarf2/index-cache.h
> > > index 023fc86fc89..0d845ebcadd 100644
> > > --- a/gdb/dwarf2/index-cache.h
> > > +++ b/gdb/dwarf2/index-cache.h
> > > @@ -90,6 +90,19 @@ class index_cache
> > >    lookup_gdb_index (const bfd_build_id *build_id,
> > >                     std::unique_ptr<index_cache_resource> *resource);
> > >
> > > +  /* Look for an index file located at INDEX_PATH in the debuginfod cache.
> > > +     Unlike lookup_gdb_index, this function does not exit early if the
> > > +     index cache has not been enabled.
> > > +
> > > +     If found, return the contents as an array_view and store the underlying
> > > +     resources (allocated memory, mapped file, etc) in RESOURCE.  The returned
> > > +     array_view is valid as long as RESOURCE is not destroyed.
> > > +
> > > +     If no matching index file is found, return an empty array view.  */
> > > +  gdb::array_view<const gdb_byte>
> > > +  lookup_gdb_index_debuginfod (const char *index_path,
> > > +                              std::unique_ptr<index_cache_resource> *resource);
> > > +
> > >    /* Return the number of cache hits.  */
> > >    unsigned int n_hits () const
> > >    { return m_n_hits; }
> > > diff --git a/gdb/dwarf2/public.h b/gdb/dwarf2/public.h
> > > index efb754b5fe8..4d3776465c3 100644
> > > --- a/gdb/dwarf2/public.h
> > > +++ b/gdb/dwarf2/public.h
> > > @@ -44,4 +44,11 @@ extern bool dwarf2_initialize_objfile
> > >
> > >  extern void dwarf2_build_frame_info (struct objfile *);
> > >
> > > +/* Query debuginfod for the .gdb_index associated with OBJFILE.  If
> > > +   successful, create an objfile to hold the .gdb_index information
> > > +   and act as a placeholder until the full debuginfo needs to be
> > > +   downloaded.  */
> > > +
> > > +extern bool dwarf2_has_separate_index (struct objfile *);
> > > +
> > >  #endif /* DWARF2_PUBLIC_H */
> > > diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
> > > index 4ca44540296..f4d77ed2d04 100644
> > > --- a/gdb/dwarf2/read-gdb-index.c
> > > +++ b/gdb/dwarf2/read-gdb-index.c
> > > @@ -139,6 +139,8 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >       gdb.dwarf2/gdb-index.exp testcase.  */
> > >    void dump (struct objfile *objfile) override;
> > >
> > > +  /* Calls do_expand_symtabs_matching and triggers debuginfo downloading
> > > +     if necessary.  */
> > >    bool expand_symtabs_matching
> > >      (struct objfile *objfile,
> > >       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > > @@ -148,8 +150,60 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >       block_search_flags search_flags,
> > >       domain_enum domain,
> > >       enum search_domain kind) override;
> > > +
> > > +  /* Expand symtabs matching a given symbol or file.  */
> > > +  bool do_expand_symtabs_matching
> > > +    (struct objfile *objfile,
> > > +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > > +     const lookup_name_info *lookup_name,
> > > +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> > > +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> > > +     block_search_flags search_flags,
> > > +     domain_enum domain,
> > > +     enum search_domain kind);
> > > +
> > > +  /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
> > > +     debuginfo if necessary.  */
> > > +  void expand_all_symtabs (struct objfile *objfile) override;
> > > +
> > > +  /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
> > > +     debuginfo if necessary.  */
> > > +  struct symtab *find_last_source_symtab (struct objfile *objfile) override;
> > >  };
> > >
> > > +void
> > > +dwarf2_gdb_index::expand_all_symtabs (struct objfile *objfile)
> > > +{
> > > +  try
> > > +    {
> > > +      dwarf2_base_index_functions::expand_all_symtabs (objfile);
> > > +    }
> > > +  catch (const gdb_exception &e)
> > > +    {
> > > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       exception_print (gdb_stderr, e);
> > > +      else
> > > +       read_full_dwarf_from_debuginfod (objfile, this);
> > > +    }
> > > +}
> > > +
> > > +struct symtab *
> > > +dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
> > > +{
> > > +  try
> > > +    {
> > > +      return dwarf2_base_index_functions::find_last_source_symtab (objfile);
> > > +    }
> > > +  catch (const gdb_exception &e)
> > > +    {
> > > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       exception_print (gdb_stderr, e);
> > > +      else
> > > +       read_full_dwarf_from_debuginfod (objfile, this);
> > > +      return nullptr;
> > > +    }
> > > +}
> > > +
> > >  /* This dumps minimal information about the index.
> > >     It is called via "mt print objfiles".
> > >     One use is to verify .gdb_index has been loaded by the
> > > @@ -268,7 +322,7 @@ dw2_expand_marked_cus
> > >  }
> > >
> > >  bool
> > > -dwarf2_gdb_index::expand_symtabs_matching
> > > +dwarf2_gdb_index::do_expand_symtabs_matching
> > >      (struct objfile *objfile,
> > >       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > >       const lookup_name_info *lookup_name,
> > > @@ -317,6 +371,39 @@ dwarf2_gdb_index::expand_symtabs_matching
> > >    return result;
> > >  }
> > >
> > > +bool
> > > +dwarf2_gdb_index::expand_symtabs_matching
> > > +    (struct objfile *objfile,
> > > +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > > +     const lookup_name_info *lookup_name,
> > > +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> > > +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> > > +     block_search_flags search_flags,
> > > +     domain_enum domain,
> > > +     enum search_domain kind)
> > > +{
> > > +  if ((objfile->flags & OBJF_READNEVER) != 0)
> > > +    return false;
> > > +
> > > +  try
> > > +    {
> > > +      return do_expand_symtabs_matching (objfile, file_matcher, lookup_name,
> > > +                                        symbol_matcher, expansion_notify,
> > > +                                        search_flags, domain, kind);
> > > +    }
> > > +  catch (const gdb_exception &e)
> > > +    {
> > > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       {
> > > +         exception_print (gdb_stderr, e);
> > > +         return false;
> > > +       }
> > > +
> > > +      read_full_dwarf_from_debuginfod (objfile, this);
> > > +      return true;
> > > +    }
> > > +}
> > > +
> > >  quick_symbol_functions_up
> > >  mapped_gdb_index::make_quick_functions () const
> > >  {
> > > @@ -652,28 +739,32 @@ dwarf2_read_gdb_index
> > >
> > >    /* If there is a .dwz file, read it so we can get its CU list as
> > >       well.  */
> > > -  dwz = dwarf2_get_dwz_file (per_bfd);
> > > -  if (dwz != NULL)
> > > +  if (get_gdb_index_contents_dwz != nullptr)
> > >      {
> > >        mapped_gdb_index dwz_map;
> > >        const gdb_byte *dwz_types_ignore;
> > >        offset_type dwz_types_elements_ignore;
> > > +      dwz = dwarf2_get_dwz_file (per_bfd);
> > >
> > > -      gdb::array_view<const gdb_byte> dwz_index_content
> > > -       = get_gdb_index_contents_dwz (objfile, dwz);
> > > -
> > > -      if (dwz_index_content.empty ())
> > > -       return 0;
> > > -
> > > -      if (!read_gdb_index_from_buffer (bfd_get_filename (dwz->dwz_bfd.get ()),
> > > -                                      1, dwz_index_content, &dwz_map,
> > > -                                      &dwz_list, &dwz_list_elements,
> > > -                                      &dwz_types_ignore,
> > > -                                      &dwz_types_elements_ignore))
> > > +      if (dwz != nullptr)
> > >         {
> > > -         warning (_("could not read '.gdb_index' section from %s; skipping"),
> > > -                  bfd_get_filename (dwz->dwz_bfd.get ()));
> > > -         return 0;
> > > +         gdb::array_view<const gdb_byte> dwz_index_content
> > > +           = get_gdb_index_contents_dwz (objfile, dwz);
> > > +
> > > +         if (dwz_index_content.empty ())
> > > +           return 0;
> > > +
> > > +         if (!read_gdb_index_from_buffer (bfd_get_filename
> > > +                                            (dwz->dwz_bfd.get ()),
> > > +                                          1, dwz_index_content, &dwz_map,
> > > +                                          &dwz_list, &dwz_list_elements,
> > > +                                          &dwz_types_ignore,
> > > +                                          &dwz_types_elements_ignore))
> > > +           {
> > > +             warning (_("could not read '.gdb_index' section from %s; skipping"),
> > > +                      bfd_get_filename (dwz->dwz_bfd.get ()));
> > > +               return 0;
> > > +           }
> > >         }
> > >      }
> > >
> > > diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> > > index 76dbeb8d536..062f0e34938 100644
> > > --- a/gdb/dwarf2/read.c
> > > +++ b/gdb/dwarf2/read.c
> > > @@ -35,6 +35,7 @@
> > >  #include "dwarf2/attribute.h"
> > >  #include "dwarf2/comp-unit-head.h"
> > >  #include "dwarf2/cu.h"
> > > +#include "dwarf2/frame.h"
> > >  #include "dwarf2/index-cache.h"
> > >  #include "dwarf2/index-common.h"
> > >  #include "dwarf2/leb.h"
> > > @@ -96,6 +97,8 @@
> > >  #include "split-name.h"
> > >  #include "gdbsupport/thread-pool.h"
> > >  #include "run-on-main-thread.h"
> > > +#include "inferior.h"
> > > +#include "debuginfod-support.h"
> > >
> > >  /* When == 1, print basic high level tracing messages.
> > >     When > 1, be more verbose.
> > > @@ -3003,7 +3006,7 @@ dwarf2_base_index_functions::find_per_cu (dwarf2_per_bfd *per_bfd,
> > >  }
> > >
> > >  struct compunit_symtab *
> > > -dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> > > +dwarf2_base_index_functions::do_find_pc_sect_compunit_symtab
> > >       (struct objfile *objfile,
> > >        struct bound_minimal_symbol msymbol,
> > >        CORE_ADDR pc,
> > > @@ -3034,6 +3037,32 @@ dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> > >    return result;
> > >  }
> > >
> > > +struct compunit_symtab *
> > > +dwarf2_base_index_functions::find_pc_sect_compunit_symtab
> > > +     (struct objfile *objfile,
> > > +      struct bound_minimal_symbol msymbol,
> > > +      CORE_ADDR pc,
> > > +      struct obj_section *section,
> > > +      int warn_if_readin)
> > > +{
> > > +  if ((objfile->flags & OBJF_READNEVER) != 0)
> > > +    return nullptr;
> > > +
> > > +  try
> > > +    {
> > > +      return do_find_pc_sect_compunit_symtab (objfile, msymbol, pc,
> > > +                                             section, warn_if_readin);
> > > +    }
> > > +  catch (const gdb_exception &e)
> > > +    {
> > > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       exception_print (gdb_stderr, e);
> > > +      else
> > > +       read_full_dwarf_from_debuginfod (objfile, this);
> > > +      return nullptr;
> > > +    }
> > > +}
> > > +
> > >  void
> > >  dwarf2_base_index_functions::map_symbol_filenames
> > >       (struct objfile *objfile,
> > > @@ -3190,6 +3219,29 @@ get_gdb_index_contents_from_cache_dwz (objfile *obj, dwz_file *dwz)
> > >    return global_index_cache.lookup_gdb_index (build_id, &dwz->index_cache_res);
> > >  }
> > >
> > > +/* Query debuginfod for the .gdb_index matching OBJFILE's build-id.  Return the
> > > +   contents if successful.  */
> > > +
> > > +static gdb::array_view<const gdb_byte>
> > > +get_gdb_index_contents_from_debuginfod (objfile *objfile, dwarf2_per_bfd *per_bfd)
> > > +{
> > > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > +  if (build_id == nullptr)
> > > +    return {};
> > > +
> > > +  gdb::unique_xmalloc_ptr<char> index_path;
> > > +  scoped_fd fd = debuginfod_section_query (build_id->data, build_id->size,
> > > +                                          bfd_get_filename
> > > +                                            (objfile->obfd.get ()),
> > > +                                          ".gdb_index",
> > > +                                          &index_path);
> > > +  if (fd.get () < 0)
> > > +    return {};
> > > +
> > > +  return global_index_cache.lookup_gdb_index_debuginfod
> > > +    (index_path.get (), &per_bfd->index_cache_res);
> > > +}
> > > +
> > >  static quick_symbol_functions_up make_cooked_index_funcs
> > >       (dwarf2_per_objfile *);
> > >
> > > @@ -3200,7 +3252,8 @@ dwarf2_initialize_objfile (struct objfile *objfile,
> > >                            const struct dwarf2_debug_sections *names,
> > >                            bool can_copy)
> > >  {
> > > -  if (!dwarf2_has_info (objfile, names, can_copy))
> > > +  if (!dwarf2_has_info (objfile, names, can_copy)
> > > +      && (objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > >      return false;
> > >
> > >    dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile);
> > > @@ -3262,6 +3315,16 @@ dwarf2_initialize_objfile (struct objfile *objfile,
> > >        global_index_cache.hit ();
> > >        objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
> > >      }
> > > +  /* Try to read just a separately downloaded gdb index.  */
> > > +  else if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0
> > > +          && dwarf2_read_gdb_index (per_objfile,
> > > +                                    get_gdb_index_contents_from_debuginfod,
> > > +                                    nullptr))
> > > +    {
> > > +      dwarf_read_debug_printf ("found .gdb_index from debuginfod");
> > > +      objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
> > > +      objfile->qf.begin ()->get ()->from_separate_index = true;
> > > +    }
> > >    else
> > >      {
> > >        global_index_cache.miss ();
> > > @@ -3270,6 +3333,84 @@ dwarf2_initialize_objfile (struct objfile *objfile,
> > >    return true;
> > >  }
> > >
> > > +/* See read.h.  */
> > > +
> > > +void
> > > +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> > > +                                dwarf2_base_index_functions *fncs)
> > > +{
> > > +  gdb_assert ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > > +
> > > +  SCOPE_EXIT { objfile->remove_deferred_status (); };
> > > +
> > > +  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > +  if (build_id == nullptr)
> > > +    return;
> > > +
> > > +  const char *filename = bfd_get_filename (objfile->obfd.get ());
> > > +  gdb::unique_xmalloc_ptr<char> symfile_path;
> > > +  scoped_fd fd;
> > > +
> > > +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> > > +                                  filename, &symfile_path);
> > > +  if (fd.get () < 0)
> > > +    return;
> > > +
> > > +  /* Separate debuginfo successfully retrieved from server.  */
> > > +  gdb_bfd_ref_ptr debug_bfd = symfile_bfd_open (symfile_path.get ());
> > > +  if (debug_bfd == nullptr
> > > +      || !build_id_verify (debug_bfd.get (), build_id->size, build_id->data))
> > > +    {
> > > +      warning (_("File \"%s\" from debuginfod cannot be opened as bfd"),
> > > +              filename);
> > > +      return;
> > > +    }
> > > +
> > > +  /* Clear frame data so it can be recalculated using DWARF.  */
> > > +  dwarf2_clear_frame_data (objfile);
> > > +
> > > +  /* This may also trigger a dwz download.  */
> > > +  symbol_file_add_separate (debug_bfd, symfile_path.get (),
> > > +                           current_inferior ()->symfile_flags, objfile);
> > > +}
> > > +
> > > +/* See public.h.  */
> > > +
> > > +bool
> > > +dwarf2_has_separate_index (struct objfile *objfile)
> > > +{
> > > +  if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0)
> > > +    return true;
> > > +  if ((objfile->flags & OBJF_MAINLINE) != 0)
> > > +    return false;
> > > +  if (!IS_DIR_SEPARATOR (*objfile_filename (objfile)))
> > > +    return false;
> > > +
> > > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > +
> > > +  if (build_id == nullptr)
> > > +    return false;
> > > +
> > > +  gdb::unique_xmalloc_ptr<char> index_path;
> > > +  scoped_fd fd = debuginfod_section_query (build_id->data,
> > > +                                          build_id->size,
> > > +                                          bfd_get_filename
> > > +                                            (objfile->obfd.get ()),
> > > +                                          ".gdb_index",
> > > +                                          &index_path);
> > > +
> > > +  if (fd.get () < 0)
> > > +    return false;
> > > +
> > > +  /* We found a separate .gdb_index file so a separate debuginfo file
> > > +     should exist, but we don't want to download it until necessary.
> > > +     Attach the index to this objfile and defer the debuginfo download
> > > +     until gdb needs to expand symtabs referenced by the index.  */
> > > +  objfile->flags |= OBJF_DOWNLOAD_DEFERRED;
> > > +  dwarf2_initialize_objfile (objfile);
> > > +  return true;
> > > +}
> > > +
> > >
> > >
> > >  /* Find the base address of the compilation unit for range lists and
> > > diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> > > index d6631d45a54..8b6f1864458 100644
> > > --- a/gdb/dwarf2/read.h
> > > +++ b/gdb/dwarf2/read.h
> > > @@ -900,6 +900,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
> > >       CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
> > >         override;
> > >
> > > +  struct compunit_symtab *do_find_pc_sect_compunit_symtab
> > > +    (struct objfile *objfile, struct bound_minimal_symbol msymbol,
> > > +     CORE_ADDR pc, struct obj_section *section, int warn_if_readin);
> > > +
> > >    struct compunit_symtab *find_compunit_symtab_by_address
> > >      (struct objfile *objfile, CORE_ADDR address) override
> > >    {
> > > @@ -972,4 +976,10 @@ extern void create_all_units (dwarf2_per_objfile *per_objfile);
> > >
> > >  extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
> > >
> > > +/* If OBJFILE contains information from a separately downloaded .gdb_index,
> > > +   attempt to download the full debuginfo.  */
> > > +
> > > +extern void read_full_dwarf_from_debuginfod (struct objfile *,
> > > +                                            dwarf2_base_index_functions *);
> > > +
> > >  #endif /* DWARF2READ_H */
> > > diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
> > > index 1235f293f45..b674103c72f 100644
> > > --- a/gdb/dwarf2/section.c
> > > +++ b/gdb/dwarf2/section.c
> > > @@ -54,7 +54,8 @@ dwarf2_section_info::get_bfd_owner () const
> > >        section = get_containing_section ();
> > >        gdb_assert (!section->is_virtual);
> > >      }
> > > -  gdb_assert (section->s.section != nullptr);
> > > +  if (section->s.section == nullptr)
> > > +    error (_("Can't find owner of DWARF section."));
> > >    return section->s.section->owner;
> > >  }
> > >
> > > diff --git a/gdb/elfread.c b/gdb/elfread.c
> > > index c5ba8837217..f14dc1bf80b 100644
> > > --- a/gdb/elfread.c
> > > +++ b/gdb/elfread.c
> > > @@ -1219,7 +1219,8 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> > >            && objfile->separate_debug_objfile_backlink == NULL)
> > >      {
> > >        if (objfile->find_and_add_separate_symbol_file (symfile_flags))
> > > -       gdb_assert (objfile->separate_debug_objfile != nullptr);
> > > +       gdb_assert (objfile->separate_debug_objfile != nullptr
> > > +                   || (objfile->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > >        else
> > >         has_dwarf2 = false;
> > >      }
> > > diff --git a/gdb/frame.c b/gdb/frame.c
> > > index 41003cc7771..7f57cf80a1b 100644
> > > --- a/gdb/frame.c
> > > +++ b/gdb/frame.c
> > > @@ -1772,14 +1772,10 @@ restore_selected_frame (frame_id frame_id, int frame_level)
> > >    selected_frame = nullptr;
> > >  }
> > >
> > > -/* Lookup the frame_info object for the selected frame FRAME_ID /
> > > -   FRAME_LEVEL and cache the result.
> > > -
> > > -   If FRAME_LEVEL > 0 and the originally selected frame isn't found,
> > > -   warn and select the innermost (current) frame.  */
> > > +/* See lookup_selected_frame.  */
> > >
> > >  static void
> > > -lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> > > +lookup_selected_frame_1 (struct frame_id &a_frame_id, int frame_level)
> > >  {
> > >    frame_info_ptr frame = NULL;
> > >    int count;
> > > @@ -1851,6 +1847,25 @@ lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> > >      }
> > >  }
> > >
> > > +/* Lookup the frame_info object for the selected frame FRAME_ID /
> > > +   FRAME_LEVEL and cache the result.
> > > +
> > > +   If FRAME_LEVEL > 0 and the originally selected frame isn't found,
> > > +   warn and select the innermost (current) frame.  */
> > > +
> > > +static void
> > > +lookup_selected_frame (struct frame_id a_frame_id, int frame_level)
> > > +{
> > > +  lookup_selected_frame_1 (selected_frame_id, selected_frame_level);
> > > +
> > > +  /* It is possible for lookup_selected_frame_1 to cause a new objfile
> > > +     to be loaded.  However some objfile observers may choose to clear
> > > +     selected_frame when an objfile is loaded.  Work around this by
> > > +     calling lookup_selected_frame_1 again if the first call failed.  */
> > > +  if (selected_frame == nullptr)
> > > +    lookup_selected_frame_1 (selected_frame_id, selected_frame_level);
> > > +}
> > > +
> > >  bool
> > >  has_stack_frames ()
> > >  {
> > > diff --git a/gdb/objfile-flags.h b/gdb/objfile-flags.h
> > > index 74aea1a88d3..3f922d9e84f 100644
> > > --- a/gdb/objfile-flags.h
> > > +++ b/gdb/objfile-flags.h
> > > @@ -56,6 +56,10 @@ enum objfile_flag : unsigned
> > >      /* User requested that we do not read this objfile's symbolic
> > >         information.  */
> > >      OBJF_READNEVER = 1 << 6,
> > > +
> > > +    /* A separate .gdb_index has been downloaded for this objfile.
> > > +       Debuginfo for this objfile can be downloaded when required.  */
> > > +    OBJF_DOWNLOAD_DEFERRED = 1 << 7,
> > >    };
> > >
> > >  DEF_ENUM_FLAGS_TYPE (enum objfile_flag, objfile_flags);
> > > diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> > > index b917886988d..4a4db1e7058 100644
> > > --- a/gdb/objfiles.h
> > > +++ b/gdb/objfiles.h
> > > @@ -622,6 +622,26 @@ struct objfile
> > >                                                domain_enum domain,
> > >                                                bool *symbol_found_p);
> > >
> > > +  /* Indicate that the acquisition of this objfile's separate debug objfile
> > > +     is no longer deferred.  Used when the debug objfile has been acquired
> > > +     or could not be found.  */
> > > +  void remove_deferred_status ()
> > > +  {
> > > +    flags &= ~OBJF_DOWNLOAD_DEFERRED;
> > > +
> > > +    /* Remove quick_symbol_functions derived from a separately downloaded
> > > +       index.  If available the separate debug objfile's index will be used
> > > +       instead, since that objfile actually contains the symbols and CUs
> > > +       referenced in the index.
> > > +
> > > +       No more than one element of qf should have from_separate_index set
> > > +       to true.  */
> > > +    qf.remove_if ([&] (const quick_symbol_functions_up &qf_up)
> > > +      {
> > > +       return qf_up->from_separate_index;
> > > +      });
> > > +  }
> > > +
> > >    /* Return the relocation offset applied to SECTION.  */
> > >    CORE_ADDR section_offset (bfd_section *section) const
> > >    {
> > > diff --git a/gdb/quick-symbol.h b/gdb/quick-symbol.h
> > > index 4cd234162ae..01a228b446c 100644
> > > --- a/gdb/quick-symbol.h
> > > +++ b/gdb/quick-symbol.h
> > > @@ -193,6 +193,10 @@ struct quick_symbol_functions
> > >    virtual void compute_main_name (struct objfile *objfile)
> > >    {
> > >    }
> > > +
> > > +  /* True if this quick_symbol_functions is derived from a separately
> > > +     downloaded index.  */
> > > +  bool from_separate_index = false;
> > >  };
> > >
> > >  typedef std::unique_ptr<quick_symbol_functions> quick_symbol_functions_up;
> > > diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
> > > index c684f9cdd43..3ed31661300 100644
> > > --- a/gdb/symfile-debug.c
> > > +++ b/gdb/symfile-debug.c
> > > @@ -37,6 +37,7 @@
> > >  #include "cli/cli-style.h"
> > >  #include "build-id.h"
> > >  #include "debuginfod-support.h"
> > > +#include "dwarf2/public.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
> > > @@ -617,6 +618,16 @@ objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
> > >           = simple_find_and_open_separate_symbol_file
> > >               (this, find_separate_debug_file_by_debuglink, &warnings);
> > >
> > > +      /* Attempt to download only an index from the separate debug info.
> > > +        As with debuginfod_find_and_open_separate_symbol_file, only attempt
> > > +        this once.  */
> > > +      if (debug_bfd == nullptr && attempt == 0
> > > +         && dwarf2_has_separate_index (this))
> > > +       {
> > > +         has_dwarf2 = true;
> > > +         break;
> > > +       }
> > > +
> > >        /* 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
> > > diff --git a/gdb/symfile.c b/gdb/symfile.c
> > > index bdcc27e7ee5..820eb061aca 100644
> > > --- a/gdb/symfile.c
> > > +++ b/gdb/symfile.c
> > > @@ -989,6 +989,10 @@ syms_from_objfile (struct objfile *objfile,
> > >  static void
> > >  finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
> > >  {
> > > +  struct objfile *parent = objfile->separate_debug_objfile_backlink;
> > > +  bool was_deferred
> > > +    = (parent != nullptr) && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0);
> > > +
> > >    /* If this is the main symbol file we have to clean up all users of the
> > >       old main symbol file.  Otherwise it is sufficient to fixup all the
> > >       breakpoints that may have been redefined by this symbol file.  */
> > > @@ -999,7 +1003,7 @@ finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
> > >
> > >        clear_symtab_users (add_flags);
> > >      }
> > > -  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0)
> > > +  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0 && !was_deferred)
> > >      {
> > >        breakpoint_re_set ();
> > >      }
> > > @@ -1120,6 +1124,12 @@ symbol_file_add_with_addrs (const gdb_bfd_ref_ptr &abfd, const char *name,
> > >    if (objfile->sf != nullptr)
> > >      finish_new_objfile (objfile, add_flags);
> > >
> > > +  /* Remove deferred status now in case any observers trigger symtab
> > > +     expansion.  Otherwise gdb might try to read parent for psymbols
> > > +     when it should read the separate debug objfile instead.  */
> > > +  if (parent != nullptr && ((parent->flags & OBJF_DOWNLOAD_DEFERRED) != 0))
> > > +    parent->remove_deferred_status ();
> > > +
> > >    gdb::observers::new_objfile.notify (objfile);
> > >
> > >    return objfile;
> > > diff --git a/gdb/symtab.c b/gdb/symtab.c
> > > index cc0bdb80c7c..d0219da6317 100644
> > > --- a/gdb/symtab.c
> > > +++ b/gdb/symtab.c
> > > @@ -2926,14 +2926,30 @@ find_pc_sect_compunit_symtab (CORE_ADDR pc, struct obj_section *section)
> > >    if (best_cust != NULL)
> > >      return best_cust;
> > >
> > > +  int warn_if_readin = 1;
> > > +
> > >    /* Not found in symtabs, search the "quick" symtabs (e.g. psymtabs).  */
> > >
> > >    for (objfile *objf : current_program_space->objfiles ())
> > >      {
> > > +      bool was_deferred = (objf->flags & OBJF_DOWNLOAD_DEFERRED) != 0;
> > > +
> > >        struct compunit_symtab *result
> > > -       = objf->find_pc_sect_compunit_symtab (msymbol, pc, section, 1);
> > > +       = objf->find_pc_sect_compunit_symtab (msymbol, pc, section,
> > > +                                             warn_if_readin);
> > > +
> > >        if (result != NULL)
> > >         return result;
> > > +
> > > +      /* If OBJF's separate debug info was just acquired, disable
> > > +        warn_if_readin for the next iteration of this loop.  This prevents
> > > +        a spurious warning in case an observer already triggered expansion
> > > +        of the separate debug objfile's symtabs.  */
> > > +      if (was_deferred && objf->separate_debug_objfile != nullptr
> > > +         && (objf->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       warn_if_readin = 0;
> > > +      else if (warn_if_readin == 0)
> > > +       warn_if_readin = 1;
> > >      }
> > >
> > >    return NULL;
> > > diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > new file mode 100644
> > > index 00000000000..e4f05db0a79
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > @@ -0,0 +1,43 @@
> > > +/* This testcase is part of GDB, the GNU debugger.
> > > +
> > > +   Copyright 2023-2024 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/>.  */
> > > +
> > > +#include <stdio.h>
> > > +#include <pthread.h>
> > > +#include <unistd.h>
> > > +
> > > +extern void libsection2_test ();
> > > +extern void *libsection2_thread_test (void *);
> > > +
> > > +static volatile int flag = 0;
> > > +
> > > +void
> > > +libsection1_test ()
> > > +{
> > > +  pthread_t thr;
> > > +
> > > +  printf ("In libsection1\n");
> > > +  libsection2_test ();
> > > +
> > > +  pthread_create (&thr, NULL, libsection2_thread_test, (void *) &flag);
> > > +
> > > +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> > > +  while (!flag)
> > > +    ;
> > > +
> > > +  printf ("Cancelling thread\n");
> > > +  pthread_cancel (thr);
> > > +}
> > > diff --git a/gdb/testsuite/gdb.debuginfod/libsection2.c b/gdb/testsuite/gdb.debuginfod/libsection2.c
> > > new file mode 100644
> > > index 00000000000..3c82808e5fe
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/libsection2.c
> > > @@ -0,0 +1,37 @@
> > > +/* This testcase is part of GDB, the GNU debugger.
> > > +
> > > +   Copyright 2023-2024 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/>.  */
> > > +
> > > +#include <stdio.h>
> > > +#include <pthread.h>
> > > +
> > > +void
> > > +libsection2_test ()
> > > +{
> > > +  printf ("In libsection2\n");
> > > +}
> > > +
> > > +void *
> > > +libsection2_thread_test (void *arg)
> > > +{
> > > +  int *flag = (int *) arg;
> > > +  printf ("In thread test\n");
> > > +
> > > +  while (1)
> > > +    *flag = 1;
> > > +
> > > +  return NULL;
> > > +}
> > > diff --git a/gdb/testsuite/gdb.debuginfod/section.c b/gdb/testsuite/gdb.debuginfod/section.c
> > > new file mode 100644
> > > index 00000000000..fa9ac536a6e
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/section.c
> > > @@ -0,0 +1,29 @@
> > > +/* This testcase is part of GDB, the GNU debugger.
> > > +
> > > +   Copyright 2023-2024 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/>.  */
> > > +
> > > +#include <stdio.h>
> > > +
> > > +extern void libsection1_test ();
> > > +
> > > +int
> > > +main ()
> > > +{
> > > +  libsection1_test ();
> > > +  printf ("in section exec\n");
> > > +
> > > +  return 0;
> > > +}
> > > diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
> > > new file mode 100644
> > > index 00000000000..35086d15705
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > > @@ -0,0 +1,157 @@
> > > +# Copyright 2023-2024 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/>.
> > > +
> > > +# Test debuginfod functionality
> > > +
> > > +standard_testfile
> > > +
> > > +load_lib debuginfod-support.exp
> > > +
> > > +clean_restart
> > > +require allow_debuginfod_tests
> > > +require allow_debuginfod_section_downloads
> > > +
> > > +# BINFILE calls a function from LIB_SL1.
> > > +set libfile1 "libsection1"
> > > +set libsrc1 $srcdir/$subdir/$libfile1.c
> > > +set lib_sl1 [standard_output_file $libfile1.sl]
> > > +
> > > +# LIB_SL1 calls functions from LIB_SL2.
> > > +set libfile2 "libsection2"
> > > +set libsrc2 $srcdir/$subdir/$libfile2.c
> > > +set lib_sl2 [standard_output_file $libfile2.sl]
> > > +
> > > +# Build LIB_SL2, LIB_SL1 and BINFILE.
> > > +if { [build_executable "build $libfile2" $lib_sl2 $libsrc2 \
> > > +       {debug build-id shlib}] != 0 } {
> > > +    return -1
> > > +}
> > > +
> > > +if { [build_executable "build $libfile1" $lib_sl1 $libsrc1 \
> > > +       [list debug build-id shlib_pthreads shlib=$lib_sl2]] != 0 } {
> > > +    return -1
> > > +}
> > > +
> > > +if { [build_executable "build executable" $binfile $srcfile \
> > > +       [list debug build-id shlib=$lib_sl1 shlib=$lib_sl2]] != 0 } {
> > > +    return -1
> > > +}
> > > +
> > > +# Make sure libsection1 and libsection2 contain .gdb_index.
> > > +if { [ensure_gdb_index $lib_sl1 "" "libsection1"] != 1 } {
> > > +    untested "failed to add .gdb_index to $libfile1"
> > > +    return -1
> > > +}
> > > +
> > > +if { [ensure_gdb_index $lib_sl2 "" "libsection2"] != 1 } {
> > > +    untested "failed to add .gdb_index to $libfile2"
> > > +    return -1
> > > +}
> > > +
> > > +# Strip solib debuginfo into separate files.
> > > +if { [gdb_gnu_strip_debug $lib_sl1 ""] != 0} {
> > > +   fail "strip $lib_sl1 debuginfo"
> > > +   return -1
> > > +}
> > > +
> > > +if { [gdb_gnu_strip_debug $lib_sl2 ""] != 0} {
> > > +   fail "strip $lib_sl2 debuginfo"
> > > +   return -1
> > > +}
> > > +
> > > +# Move debuginfo files into directory that debuginfod will serve from.
> > > +set debugdir [standard_output_file "debug"]
> > > +set debuginfo_sl1 [standard_output_file $libfile1.sl.debug]
> > > +set debuginfo_sl2 [standard_output_file $libfile2.sl.debug]
> > > +
> > > +file mkdir $debugdir
> > > +file rename -force $debuginfo_sl1 $debugdir
> > > +file rename -force $debuginfo_sl2 $debugdir
> > > +
> > > +# Restart GDB and clear the debuginfod client cache.  Then load BINFILE into
> > > +# GDB and start running it.  Match output with pattern RES and use TESTNAME
> > > +# as the test name.
> > > +proc_with_prefix clean_restart_with_prompt { binfile testname } {
> > > +    global cache
> > > +
> > > +    # Delete client cache so debuginfo downloads again.
> > > +    file delete -force $cache
> > > +    clean_restart
> > > +
> > > +    gdb_test_no_output "set debuginfod enabled on" \
> > > +                      "clean_restart enable $testname"
> > > +    gdb_load $binfile
> > > +
> > > +    runto_main
> > > +}
> > > +
> > > +# Tests with no debuginfod server running.
> > > +proc_with_prefix no_url { } {
> > > +    global binfile libfile1 libfile2
> > > +
> > > +    gdb_load $binfile
> > > +    if {![runto_main]} {
> > > +       return
> > > +    }
> > > +
> > > +    # Check that no section is downloaded and no debuginfo is found.
> > > +    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile1.*" \
> > > +            "found no url lib1"
> > > +    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile2.*" \
> > > +            "found no url lib2"
> > > +}
> > > +
> > > +# Tests with a debuginfod server running.
> > > +proc_with_prefix local_url { } {
> > > +    global binfile
> > > +    global libsrc1 lib_sl1 libfile1
> > > +    global libsrc2 lib_sl2 libfile2
> > > +    global debugdir db
> > > +
> > > +    set url [start_debuginfod $db $debugdir]
> > > +    if { $url == "" } {
> > > +       unresolved "failed to start debuginfod server"
> > > +       return
> > > +    }
> > > +
> > > +    # Point GDB to the server.
> > > +    setenv DEBUGINFOD_URLS $url
> > > +
> > > +    clean_restart_with_prompt $binfile "index"
> > > +
> > > +    # Download debuginfo when stepping into a function.
> > > +    set res ".*separate debug info for $lib_sl1.*\"In ${libfile1}\\\\n\".*"
> > > +    gdb_test "step" $res "step"
> > > +
> > > +    clean_restart_with_prompt $binfile "break"
> > > +
> > > +    # Download debuginfo when setting a breakpoint.
> > > +    set res "Download.*separate debug info for $lib_sl2.*"
> > > +    gdb_test "br libsection2_test" $res "break set"
> > > +
> > > +    # Hit the breakpoint.
> > > +    set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
> > > +    gdb_test "c" $res "break continue"
> > > +}
> > > +
> > > +# Create CACHE and DB directories ready for debuginfod to use.
> > > +prepare_for_debuginfod cache db
> > > +
> > > +with_debuginfod_env $cache {
> > > +    no_url
> > > +    local_url
> > > +}
> > > +
> > > +stop_debuginfod
> > > diff --git a/gdb/testsuite/lib/debuginfod-support.exp b/gdb/testsuite/lib/debuginfod-support.exp
> > > index 50a8b512a4a..71964a90564 100644
> > > --- a/gdb/testsuite/lib/debuginfod-support.exp
> > > +++ b/gdb/testsuite/lib/debuginfod-support.exp
> > > @@ -113,6 +113,8 @@ proc with_debuginfod_env { cache body } {
> > >  proc start_debuginfod { db debugdir } {
> > >      global debuginfod_spawn_id spawn_id
> > >
> > > +    set logfile [standard_output_file "server_log"]
> > > +
> > >      # Find an unused port.
> > >      set port 7999
> > >      set found false
> > > @@ -127,7 +129,8 @@ proc start_debuginfod { db debugdir } {
> > >             set old_spawn_id $spawn_id
> > >         }
> > >
> > > -       spawn debuginfod -vvvv -d $db -p $port -F $debugdir
> > > +       spawn sh -c "debuginfod -vvvv -d $db -p $port -F $debugdir 2>&1 \
> > > +               | tee $logfile"
> > >         set debuginfod_spawn_id $spawn_id
> > >
> > >         if { [info exists old_spawn_id] } {
> > > @@ -194,3 +197,23 @@ proc stop_debuginfod { } {
> > >         unset debuginfod_spawn_id
> > >      }
> > >  }
> > > +
> > > +# Return true if gdb is configured to download ELF/DWARF sections from
> > > +# debuginfod servers.  Otherwise return false.
> > > +proc allow_debuginfod_section_downloads { } {
> > > +    set cmd "maint set debuginfod download-sections on"
> > > +    set msg "enable section downloads"
> > > +
> > > +    gdb_test_multiple $cmd $msg {
> > > +       -re -wrap ".*not compiled into GDB.*" {
> > > +           return false
> > > +       }
> > > +       -re -wrap "^" {
> > > +           return true
> > > +       }
> > > +       -re -wrap "" {
> > > +           fail "$gdb_test_name (unexpected output)"
> > > +           return false
> > > +       }
> > > +    }
> > > +}
> > > diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
> > > index 9ac707be696..9b3c7e6a02c 100644
> > > --- a/gdb/testsuite/lib/gdb.exp
> > > +++ b/gdb/testsuite/lib/gdb.exp
> > > @@ -9414,10 +9414,14 @@ proc get_index_type { objfile { testname "" } } {
> > >  # STYLE controls which style of index to add, if needed.  The empty
> > >  # string (the default) means .gdb_index; "-dwarf-5" means .debug_names.
> > >
> > > -proc ensure_gdb_index { binfile {style ""} } {
> > > +proc ensure_gdb_index { binfile {style ""} {testname_prefix ""} } {
> > >      set testfile [file tail $binfile]
> > > -
> > >      set test "check if index present"
> > > +
> > > +    if { $testname_prefix != "" } {
> > > +      set test "$testname_prefix $test"
> > > +    }
> > > +
> > >      set index_type [get_index_type $testfile $test]
> > >
> > >      if { $index_type eq "gdb" || $index_type eq "dwarf5" } {
> > > --
> > > 2.43.0
> > >
  
Andrew Burgess March 27, 2024, 10:58 a.m. UTC | #7
Aaron Merey <amerey@redhat.com> writes:

> diff --git a/gdb/dwarf2/frame.h b/gdb/dwarf2/frame.h
> index 5643e557513..2391e313e7c 100644
> --- a/gdb/dwarf2/frame.h
> +++ b/gdb/dwarf2/frame.h
> @@ -238,6 +238,10 @@ void dwarf2_append_unwinders (struct gdbarch *gdbarch);
>  extern const struct frame_base *
>    dwarf2_frame_base_sniffer (frame_info_ptr this_frame);
>  
> +/* Delete OBJFILEs comp_unit.  */
> +
> +extern void dwarf2_clear_frame_data (struct objfile * objfile);
> +
>  /* Compute the DWARF CFA for a frame.  */
>  
>  CORE_ADDR dwarf2_frame_cfa (frame_info_ptr this_frame);
> diff --git a/gdb/dwarf2/index-cache.c b/gdb/dwarf2/index-cache.c
> index 69f70642dc6..8c969ecd590 100644
> --- a/gdb/dwarf2/index-cache.c
> +++ b/gdb/dwarf2/index-cache.c
> @@ -240,6 +240,33 @@ index_cache::lookup_gdb_index (const bfd_build_id *build_id,
>    return {};
>  }
>  
> +/* See index-cache.h.  */
> +
> +gdb::array_view<const gdb_byte>
> +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> +					  std::unique_ptr<index_cache_resource> *resource)
> +{
> +  try
> +    {
> +      /* Try to map that file.  */
> +      index_cache_resource_mmap *mmap_resource
> +	= new index_cache_resource_mmap (index_path);
> +
> +      /* Hand the resource to the caller.  */
> +      resource->reset (mmap_resource);
> +
> +      return gdb::array_view<const gdb_byte>
> +	  ((const gdb_byte *) mmap_resource->mapping.get (),
> +	   mmap_resource->mapping.size ());
> +    }
> +  catch (const gdb_exception_error &except)
> +    {
> +      warning (_("Unable to read %s: %s"), index_path, except.what ());
> +    }
> +
> +  return {};
> +}

I wonder if the core of this function should be merged with
lookup_gdb_index?  The core functionality should be identical, but
there's already a little divergence.

I'd be tempted to just rename lookup_gdb_index_debuginfod to
lookup_gdb_index and have it be an overload, one that finds the index in
a named file, and the other that takes a build-id, creates a filename,
and then calls your new version.


> diff --git a/gdb/dwarf2/public.h b/gdb/dwarf2/public.h
> index efb754b5fe8..4d3776465c3 100644
> --- a/gdb/dwarf2/public.h
> +++ b/gdb/dwarf2/public.h
> @@ -44,4 +44,11 @@ extern bool dwarf2_initialize_objfile
>  
>  extern void dwarf2_build_frame_info (struct objfile *);
>  
> +/* Query debuginfod for the .gdb_index associated with OBJFILE.  If
> +   successful, create an objfile to hold the .gdb_index information
> +   and act as a placeholder until the full debuginfo needs to be
> +   downloaded.  */
> +
> +extern bool dwarf2_has_separate_index (struct objfile *);

Unless I'm missing something then this comment is not correct.  This
function doesn't create an objfile to held the .gdb_index does it?

> +
>  #endif /* DWARF2_PUBLIC_H */
> diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
> index 4ca44540296..f4d77ed2d04 100644
> --- a/gdb/dwarf2/read-gdb-index.c
> +++ b/gdb/dwarf2/read-gdb-index.c
> @@ -139,6 +139,8 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>       gdb.dwarf2/gdb-index.exp testcase.  */
>    void dump (struct objfile *objfile) override;
>  
> +  /* Calls do_expand_symtabs_matching and triggers debuginfo downloading
> +     if necessary.  */
>    bool expand_symtabs_matching
>      (struct objfile *objfile,
>       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> @@ -148,8 +150,60 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>       block_search_flags search_flags,
>       domain_enum domain,
>       enum search_domain kind) override;
> +
> +  /* Expand symtabs matching a given symbol or file.  */
> +  bool do_expand_symtabs_matching
> +    (struct objfile *objfile,
> +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> +     const lookup_name_info *lookup_name,
> +     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
> +     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
> +     block_search_flags search_flags,
> +     domain_enum domain,
> +     enum search_domain kind);
> +
> +  /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
> +     debuginfo if necessary.  */
> +  void expand_all_symtabs (struct objfile *objfile) override;
> +
> +  /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
> +     debuginfo if necessary.  */
> +  struct symtab *find_last_source_symtab (struct objfile *objfile) override;
>  };
>  
> +void
> +dwarf2_gdb_index::expand_all_symtabs (struct objfile *objfile)
> +{
> +  try
> +    {
> +      dwarf2_base_index_functions::expand_all_symtabs (objfile);
> +    }
> +  catch (const gdb_exception &e)
> +    {
> +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +	exception_print (gdb_stderr, e);
> +      else
> +	read_full_dwarf_from_debuginfod (objfile, this);

This is the part that I don't really understand about this patch, why is
the download and read of the full dwarf done in the exception path?

How do we know that the exception that was thrown indicates we should go
fetch the dwarf and not that some other error has occurred?

I couldn't find any obvious path from which
read_full_dwarf_from_debuginfod might throw an exception, but if it does
then I guess we're not going to get the behaviour we expect.

I guess my expectation, going into this patch, was that you would have
generalised the index checking code in some way so that when we check
the index and find a match, instead of reading the full DWARF, we'd
download the info, and then read the full DWARF.

I'm not saying that what you have is wrong, or that it needs to change,
just that I found this approach surprising, and I don't really
understand the design -- but I'm open to being convinced.

Thanks,
Andrew
  
Aaron Merey March 27, 2024, 11:56 p.m. UTC | #8
Hi Andrew,

On Wed, Mar 27, 2024 at 6:58 AM Andrew Burgess <aburgess@redhat.com> wrote:
> > +gdb::array_view<const gdb_byte>
> > +index_cache::lookup_gdb_index_debuginfod (const char *index_path,
> > +                                       std::unique_ptr<index_cache_resource> *resource)
> > +{
> > +  try
> > +    {
> > +      /* Try to map that file.  */
> > +      index_cache_resource_mmap *mmap_resource
> > +     = new index_cache_resource_mmap (index_path);
> > +
> > +      /* Hand the resource to the caller.  */
> > +      resource->reset (mmap_resource);
> > +
> > +      return gdb::array_view<const gdb_byte>
> > +       ((const gdb_byte *) mmap_resource->mapping.get (),
> > +        mmap_resource->mapping.size ());
> > +    }
> > +  catch (const gdb_exception_error &except)
> > +    {
> > +      warning (_("Unable to read %s: %s"), index_path, except.what ());
> > +    }
> > +
> > +  return {};
> > +}
>
> I wonder if the core of this function should be merged with
> lookup_gdb_index?  The core functionality should be identical, but
> there's already a little divergence.
>
> I'd be tempted to just rename lookup_gdb_index_debuginfod to
> lookup_gdb_index and have it be an overload, one that finds the index in
> a named file, and the other that takes a build-id, creates a filename,
> and then calls your new version.

Ok I'll change this.

> > diff --git a/gdb/dwarf2/public.h b/gdb/dwarf2/public.h
> > index efb754b5fe8..4d3776465c3 100644
> > --- a/gdb/dwarf2/public.h
> > +++ b/gdb/dwarf2/public.h
> > @@ -44,4 +44,11 @@ extern bool dwarf2_initialize_objfile
> >
> >  extern void dwarf2_build_frame_info (struct objfile *);
> >
> > +/* Query debuginfod for the .gdb_index associated with OBJFILE.  If
> > +   successful, create an objfile to hold the .gdb_index information
> > +   and act as a placeholder until the full debuginfo needs to be
> > +   downloaded.  */
> > +
> > +extern bool dwarf2_has_separate_index (struct objfile *);
>
> Unless I'm missing something then this comment is not correct.  This
> function doesn't create an objfile to held the .gdb_index does it?

My mistake, that comment should instead say something like "associate
the .gdb_index information with OBJFILE until the full debuginfo is
downloaded".  In earlier revisions of the patch, this function did
create a new objfile but the comment wasn't updated when the function
changed.

> > +void
> > +dwarf2_gdb_index::expand_all_symtabs (struct objfile *objfile)
> > +{
> > +  try
> > +    {
> > +      dwarf2_base_index_functions::expand_all_symtabs (objfile);
> > +    }
> > +  catch (const gdb_exception &e)
> > +    {
> > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > +     exception_print (gdb_stderr, e);
> > +      else
> > +     read_full_dwarf_from_debuginfod (objfile, this);
>
> This is the part that I don't really understand about this patch, why is
> the download and read of the full dwarf done in the exception path?
>
> How do we know that the exception that was thrown indicates we should go
> fetch the dwarf and not that some other error has occurred?
>
> I couldn't find any obvious path from which
> read_full_dwarf_from_debuginfod might throw an exception, but if it does
> then I guess we're not going to get the behaviour we expect.
>
> I guess my expectation, going into this patch, was that you would have
> generalised the index checking code in some way so that when we check
> the index and find a match, instead of reading the full DWARF, we'd
> download the info, and then read the full DWARF.
>
> I'm not saying that what you have is wrong, or that it needs to change,
> just that I found this approach surprising, and I don't really
> understand the design -- but I'm open to being convinced.

Performing the download and read in the exception path helps decouple
dwarf2_base_index_functions and dwarf2_gdb_index functions from
the lazy debuginfo logic.  This design also facilitates an early return
from these functions so that the next objfile in the progspace objfile
list can be searched for a match.  If the deferred download was
successful, this next objfile holds the actual DWARF that gdb is looking for.

It is possible that some unrelated error during the initial index search
triggers an unnecessary debuginfo download.  FWIW I have not come across
such a case.  If this turned out to be an issue later on, we could for
instance introduce a special exception type indicating whether or not
gdb should attempt to download the deferred debuginfo.

In any case, the chance of an unnecessary download with this patch is
vastly lower than the current approach of immediately downloading all
debuginfo.

Aaron
  

Patch

diff --git a/gdb/dwarf2/frame.c b/gdb/dwarf2/frame.c
index d3d1ecdf1f5..9e290af79f7 100644
--- a/gdb/dwarf2/frame.c
+++ b/gdb/dwarf2/frame.c
@@ -1620,6 +1620,19 @@  set_comp_unit (struct objfile *objfile, struct comp_unit *unit)
   return dwarf2_frame_bfd_data.set (abfd, unit);
 }
 
+/* See frame.h.  */
+
+void
+dwarf2_clear_frame_data (struct objfile *objfile)
+{
+  bfd *abfd = objfile->obfd.get ();
+
+  if (gdb_bfd_requires_relocations (abfd))
+    dwarf2_frame_objfile_data.clear (objfile);
+  else
+    dwarf2_frame_bfd_data.clear (abfd);
+}
+
 /* Find the FDE for *PC.  Return a pointer to the FDE, and store the
    initial location associated with it into *PC.  */
 
diff --git a/gdb/dwarf2/frame.h b/gdb/dwarf2/frame.h
index 5643e557513..2391e313e7c 100644
--- a/gdb/dwarf2/frame.h
+++ b/gdb/dwarf2/frame.h
@@ -238,6 +238,10 @@  void dwarf2_append_unwinders (struct gdbarch *gdbarch);
 extern const struct frame_base *
   dwarf2_frame_base_sniffer (frame_info_ptr this_frame);
 
+/* Delete OBJFILEs comp_unit.  */
+
+extern void dwarf2_clear_frame_data (struct objfile * objfile);
+
 /* Compute the DWARF CFA for a frame.  */
 
 CORE_ADDR dwarf2_frame_cfa (frame_info_ptr this_frame);
diff --git a/gdb/dwarf2/index-cache.c b/gdb/dwarf2/index-cache.c
index 69f70642dc6..8c969ecd590 100644
--- a/gdb/dwarf2/index-cache.c
+++ b/gdb/dwarf2/index-cache.c
@@ -240,6 +240,33 @@  index_cache::lookup_gdb_index (const bfd_build_id *build_id,
   return {};
 }
 
+/* See index-cache.h.  */
+
+gdb::array_view<const gdb_byte>
+index_cache::lookup_gdb_index_debuginfod (const char *index_path,
+					  std::unique_ptr<index_cache_resource> *resource)
+{
+  try
+    {
+      /* Try to map that file.  */
+      index_cache_resource_mmap *mmap_resource
+	= new index_cache_resource_mmap (index_path);
+
+      /* Hand the resource to the caller.  */
+      resource->reset (mmap_resource);
+
+      return gdb::array_view<const gdb_byte>
+	  ((const gdb_byte *) mmap_resource->mapping.get (),
+	   mmap_resource->mapping.size ());
+    }
+  catch (const gdb_exception_error &except)
+    {
+      warning (_("Unable to read %s: %s"), index_path, except.what ());
+    }
+
+  return {};
+}
+
 #else /* !HAVE_SYS_MMAN_H */
 
 /* See dwarf-index-cache.h.  This is a no-op on unsupported systems.  */
@@ -251,6 +278,12 @@  index_cache::lookup_gdb_index (const bfd_build_id *build_id,
   return {};
 }
 
+gdb::array_view<const gdb_byte>
+index_cache::lookup_gdb_index_debuginfod (const char *index_path,
+					  std::unique_ptr<index_cache_resource> *resource)
+{
+  return {};
+}
 #endif
 
 /* See dwarf-index-cache.h.  */
diff --git a/gdb/dwarf2/index-cache.h b/gdb/dwarf2/index-cache.h
index 023fc86fc89..0d845ebcadd 100644
--- a/gdb/dwarf2/index-cache.h
+++ b/gdb/dwarf2/index-cache.h
@@ -90,6 +90,19 @@  class index_cache
   lookup_gdb_index (const bfd_build_id *build_id,
 		    std::unique_ptr<index_cache_resource> *resource);
 
+  /* Look for an index file located at INDEX_PATH in the debuginfod cache.
+     Unlike lookup_gdb_index, this function does not exit early if the
+     index cache has not been enabled.
+
+     If found, return the contents as an array_view and store the underlying
+     resources (allocated memory, mapped file, etc) in RESOURCE.  The returned
+     array_view is valid as long as RESOURCE is not destroyed.
+
+     If no matching index file is found, return an empty array view.  */
+  gdb::array_view<const gdb_byte>
+  lookup_gdb_index_debuginfod (const char *index_path,
+			       std::unique_ptr<index_cache_resource> *resource);
+
   /* Return the number of cache hits.  */
   unsigned int n_hits () const
   { return m_n_hits; }
diff --git a/gdb/dwarf2/public.h b/gdb/dwarf2/public.h
index efb754b5fe8..4d3776465c3 100644
--- a/gdb/dwarf2/public.h
+++ b/gdb/dwarf2/public.h
@@ -44,4 +44,11 @@  extern bool dwarf2_initialize_objfile
 
 extern void dwarf2_build_frame_info (struct objfile *);
 
+/* Query debuginfod for the .gdb_index associated with OBJFILE.  If
+   successful, create an objfile to hold the .gdb_index information
+   and act as a placeholder until the full debuginfo needs to be
+   downloaded.  */
+
+extern bool dwarf2_has_separate_index (struct objfile *);
+
 #endif /* DWARF2_PUBLIC_H */
diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
index 4ca44540296..55b04391eb5 100644
--- a/gdb/dwarf2/read-gdb-index.c
+++ b/gdb/dwarf2/read-gdb-index.c
@@ -139,6 +139,8 @@  struct dwarf2_gdb_index : public dwarf2_base_index_functions
      gdb.dwarf2/gdb-index.exp testcase.  */
   void dump (struct objfile *objfile) override;
 
+  /* Calls do_expand_symtabs_matching and triggers debuginfo downloading
+     if necessary.  */
   bool expand_symtabs_matching
     (struct objfile *objfile,
      gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
@@ -148,8 +150,60 @@  struct dwarf2_gdb_index : public dwarf2_base_index_functions
      block_search_flags search_flags,
      domain_enum domain,
      enum search_domain kind) override;
+
+  /* Expand symtabs matching a given symbol or file.  */
+  bool do_expand_symtabs_matching
+    (struct objfile *objfile,
+     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
+     const lookup_name_info *lookup_name,
+     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
+     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
+     block_search_flags search_flags,
+     domain_enum domain,
+     enum search_domain kind);
+
+  /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
+     debuginfo if necessary.  */
+  void expand_all_symtabs (struct objfile *objfile) override;
+
+  /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
+     debuginfo if necessary.  */
+  struct symtab *find_last_source_symtab (struct objfile *objfile) override;
 };
 
+void
+dwarf2_gdb_index::expand_all_symtabs (struct objfile *objfile)
+{
+  try
+    {
+      dwarf2_base_index_functions::expand_all_symtabs (objfile);
+    }
+  catch (const gdb_exception &e)
+    {
+      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	exception_print (gdb_stderr, e);
+      else
+	read_full_dwarf_from_debuginfod (objfile, this);
+    }
+}
+
+struct symtab *
+dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
+{
+  try
+    {
+      return dwarf2_base_index_functions::find_last_source_symtab (objfile);
+    }
+  catch (const gdb_exception &e)
+    {
+      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	exception_print (gdb_stderr, e);
+      else
+	read_full_dwarf_from_debuginfod (objfile, this);
+      return nullptr;
+    }
+}
+
 /* This dumps minimal information about the index.
    It is called via "mt print objfiles".
    One use is to verify .gdb_index has been loaded by the
@@ -268,7 +322,7 @@  dw2_expand_marked_cus
 }
 
 bool
-dwarf2_gdb_index::expand_symtabs_matching
+dwarf2_gdb_index::do_expand_symtabs_matching
     (struct objfile *objfile,
      gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
      const lookup_name_info *lookup_name,
@@ -317,6 +371,39 @@  dwarf2_gdb_index::expand_symtabs_matching
   return result;
 }
 
+bool
+dwarf2_gdb_index::expand_symtabs_matching
+    (struct objfile *objfile,
+     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
+     const lookup_name_info *lookup_name,
+     gdb::function_view<expand_symtabs_symbol_matcher_ftype> symbol_matcher,
+     gdb::function_view<expand_symtabs_exp_notify_ftype> expansion_notify,
+     block_search_flags search_flags,
+     domain_enum domain,
+     enum search_domain kind)
+{
+  if (objfile->flags & OBJF_READNEVER)
+    return false;
+
+  try
+    {
+      return do_expand_symtabs_matching (objfile, file_matcher, lookup_name,
+					 symbol_matcher, expansion_notify,
+					 search_flags, domain, kind);
+    }
+  catch (const gdb_exception &e)
+    {
+      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	{
+	  exception_print (gdb_stderr, e);
+	  return false;
+	}
+
+      read_full_dwarf_from_debuginfod (objfile, this);
+      return true;
+    }
+}
+
 quick_symbol_functions_up
 mapped_gdb_index::make_quick_functions () const
 {
@@ -652,28 +739,32 @@  dwarf2_read_gdb_index
 
   /* If there is a .dwz file, read it so we can get its CU list as
      well.  */
-  dwz = dwarf2_get_dwz_file (per_bfd);
-  if (dwz != NULL)
+  if (get_gdb_index_contents_dwz != nullptr)
     {
       mapped_gdb_index dwz_map;
       const gdb_byte *dwz_types_ignore;
       offset_type dwz_types_elements_ignore;
+      dwz = dwarf2_get_dwz_file (per_bfd);
 
-      gdb::array_view<const gdb_byte> dwz_index_content
-	= get_gdb_index_contents_dwz (objfile, dwz);
-
-      if (dwz_index_content.empty ())
-	return 0;
-
-      if (!read_gdb_index_from_buffer (bfd_get_filename (dwz->dwz_bfd.get ()),
-				       1, dwz_index_content, &dwz_map,
-				       &dwz_list, &dwz_list_elements,
-				       &dwz_types_ignore,
-				       &dwz_types_elements_ignore))
+      if (dwz != nullptr)
 	{
-	  warning (_("could not read '.gdb_index' section from %s; skipping"),
-		   bfd_get_filename (dwz->dwz_bfd.get ()));
-	  return 0;
+	  gdb::array_view<const gdb_byte> dwz_index_content
+	    = get_gdb_index_contents_dwz (objfile, dwz);
+
+	  if (dwz_index_content.empty ())
+	    return 0;
+
+	  if (!read_gdb_index_from_buffer (bfd_get_filename
+					     (dwz->dwz_bfd.get ()),
+					   1, dwz_index_content, &dwz_map,
+					   &dwz_list, &dwz_list_elements,
+					   &dwz_types_ignore,
+					   &dwz_types_elements_ignore))
+	    {
+	      warning (_("could not read '.gdb_index' section from %s; skipping"),
+		       bfd_get_filename (dwz->dwz_bfd.get ()));
+		return 0;
+	    }
 	}
     }
 
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 76dbeb8d536..8725a2fbcdb 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -35,6 +35,7 @@ 
 #include "dwarf2/attribute.h"
 #include "dwarf2/comp-unit-head.h"
 #include "dwarf2/cu.h"
+#include "dwarf2/frame.h"
 #include "dwarf2/index-cache.h"
 #include "dwarf2/index-common.h"
 #include "dwarf2/leb.h"
@@ -96,6 +97,8 @@ 
 #include "split-name.h"
 #include "gdbsupport/thread-pool.h"
 #include "run-on-main-thread.h"
+#include "inferior.h"
+#include "debuginfod-support.h"
 
 /* When == 1, print basic high level tracing messages.
    When > 1, be more verbose.
@@ -3003,7 +3006,7 @@  dwarf2_base_index_functions::find_per_cu (dwarf2_per_bfd *per_bfd,
 }
 
 struct compunit_symtab *
-dwarf2_base_index_functions::find_pc_sect_compunit_symtab
+dwarf2_base_index_functions::do_find_pc_sect_compunit_symtab
      (struct objfile *objfile,
       struct bound_minimal_symbol msymbol,
       CORE_ADDR pc,
@@ -3034,6 +3037,32 @@  dwarf2_base_index_functions::find_pc_sect_compunit_symtab
   return result;
 }
 
+struct compunit_symtab *
+dwarf2_base_index_functions::find_pc_sect_compunit_symtab
+     (struct objfile *objfile,
+      struct bound_minimal_symbol msymbol,
+      CORE_ADDR pc,
+      struct obj_section *section,
+      int warn_if_readin)
+{
+  if (objfile->flags & OBJF_READNEVER)
+    return nullptr;
+
+  try
+    {
+      return do_find_pc_sect_compunit_symtab (objfile, msymbol, pc,
+					      section, warn_if_readin);
+    }
+  catch (const gdb_exception &e)
+    {
+      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	exception_print (gdb_stderr, e);
+      else
+	read_full_dwarf_from_debuginfod (objfile, this);
+      return nullptr;
+    }
+}
+
 void
 dwarf2_base_index_functions::map_symbol_filenames
      (struct objfile *objfile,
@@ -3190,6 +3219,29 @@  get_gdb_index_contents_from_cache_dwz (objfile *obj, dwz_file *dwz)
   return global_index_cache.lookup_gdb_index (build_id, &dwz->index_cache_res);
 }
 
+/* Query debuginfod for the .gdb_index matching OBJFILE's build-id.  Return the
+   contents if successful.  */
+
+static gdb::array_view<const gdb_byte>
+get_gdb_index_contents_from_debuginfod (objfile *objfile, dwarf2_per_bfd *per_bfd)
+{
+  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
+  if (build_id == nullptr)
+    return {};
+
+  gdb::unique_xmalloc_ptr<char> index_path;
+  scoped_fd fd = debuginfod_section_query (build_id->data, build_id->size,
+					   bfd_get_filename
+					     (objfile->obfd.get ()),
+					   ".gdb_index",
+					   &index_path);
+  if (fd.get () < 0)
+    return {};
+
+  return global_index_cache.lookup_gdb_index_debuginfod
+    (index_path.get (), &per_bfd->index_cache_res);
+}
+
 static quick_symbol_functions_up make_cooked_index_funcs
      (dwarf2_per_objfile *);
 
@@ -3200,7 +3252,8 @@  dwarf2_initialize_objfile (struct objfile *objfile,
 			   const struct dwarf2_debug_sections *names,
 			   bool can_copy)
 {
-  if (!dwarf2_has_info (objfile, names, can_copy))
+  if (!dwarf2_has_info (objfile, names, can_copy)
+      && (objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
     return false;
 
   dwarf2_per_objfile *per_objfile = get_dwarf2_per_objfile (objfile);
@@ -3262,6 +3315,16 @@  dwarf2_initialize_objfile (struct objfile *objfile,
       global_index_cache.hit ();
       objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
     }
+  /* Try to read just a separately downloaded gdb index.  */
+  else if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED)
+	   && dwarf2_read_gdb_index (per_objfile,
+				     get_gdb_index_contents_from_debuginfod,
+				     nullptr))
+    {
+      dwarf_read_debug_printf ("found .gdb_index from debuginfod");
+      objfile->qf.push_front (per_bfd->index_table->make_quick_functions ());
+      objfile->qf.begin ()->get ()->from_separate_index = true;
+    }
   else
     {
       global_index_cache.miss ();
@@ -3270,6 +3333,86 @@  dwarf2_initialize_objfile (struct objfile *objfile,
   return true;
 }
 
+/* See read.h.  */
+
+void
+read_full_dwarf_from_debuginfod (struct objfile *objfile,
+				 dwarf2_base_index_functions *fncs)
+{
+  gdb_assert (objfile->flags & OBJF_DOWNLOAD_DEFERRED);
+
+  SCOPE_EXIT { objfile->remove_deferred_status (); };
+
+  const struct bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
+  const char *filename;
+  gdb_bfd_ref_ptr debug_bfd;
+  gdb::unique_xmalloc_ptr<char> symfile_path;
+  scoped_fd fd;
+
+  if (build_id == nullptr)
+    return;
+
+  filename = bfd_get_filename (objfile->obfd.get ());
+  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
+				   filename, &symfile_path);
+  if (fd.get () < 0)
+    return;
+
+  /* Separate debuginfo successfully retrieved from server.  */
+  debug_bfd = symfile_bfd_open (symfile_path.get ());
+  if (debug_bfd == nullptr
+      || !build_id_verify (debug_bfd.get (), build_id->size, build_id->data))
+    {
+      warning (_("File \"%s\" from debuginfod cannot be opened as bfd"),
+	       filename);
+      return;
+    }
+
+  /* Clear frame data so it can be recalculated using DWARF.  */
+  dwarf2_clear_frame_data (objfile);
+
+  /* This may also trigger a dwz download.  */
+  symbol_file_add_separate (debug_bfd, symfile_path.get (),
+			    current_inferior ()->symfile_flags, objfile);
+}
+
+/* See public.h.  */
+
+bool
+dwarf2_has_separate_index (struct objfile *objfile)
+{
+  if (objfile->flags & OBJF_DOWNLOAD_DEFERRED)
+    return true;
+  if (objfile->flags & OBJF_MAINLINE)
+    return false;
+  if (!IS_DIR_SEPARATOR (*objfile_filename (objfile)))
+    return false;
+
+  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
+
+  if (build_id == nullptr)
+    return false;
+
+  gdb::unique_xmalloc_ptr<char> index_path;
+  scoped_fd fd = debuginfod_section_query (build_id->data,
+					   build_id->size,
+					   bfd_get_filename
+					     (objfile->obfd.get ()),
+					   ".gdb_index",
+					   &index_path);
+
+  if (fd.get () < 0)
+    return false;
+
+  /* We found a separate .gdb_index file so a separate debuginfo file
+     should exist, but we don't want to download it until necessary.
+     Attach the index to this objfile and defer the debuginfo download
+     until gdb needs to expand symtabs referenced by the index.  */
+  objfile->flags |= OBJF_DOWNLOAD_DEFERRED;
+  dwarf2_initialize_objfile (objfile);
+  return true;
+}
+
 
 
 /* Find the base address of the compilation unit for range lists and
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index d6631d45a54..8b6f1864458 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -900,6 +900,10 @@  struct dwarf2_base_index_functions : public quick_symbol_functions
      CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
        override;
 
+  struct compunit_symtab *do_find_pc_sect_compunit_symtab
+    (struct objfile *objfile, struct bound_minimal_symbol msymbol,
+     CORE_ADDR pc, struct obj_section *section, int warn_if_readin);
+
   struct compunit_symtab *find_compunit_symtab_by_address
     (struct objfile *objfile, CORE_ADDR address) override
   {
@@ -972,4 +976,10 @@  extern void create_all_units (dwarf2_per_objfile *per_objfile);
 
 extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
 
+/* If OBJFILE contains information from a separately downloaded .gdb_index,
+   attempt to download the full debuginfo.  */
+
+extern void read_full_dwarf_from_debuginfod (struct objfile *,
+					     dwarf2_base_index_functions *);
+
 #endif /* DWARF2READ_H */
diff --git a/gdb/dwarf2/section.c b/gdb/dwarf2/section.c
index 1235f293f45..b674103c72f 100644
--- a/gdb/dwarf2/section.c
+++ b/gdb/dwarf2/section.c
@@ -54,7 +54,8 @@  dwarf2_section_info::get_bfd_owner () const
       section = get_containing_section ();
       gdb_assert (!section->is_virtual);
     }
-  gdb_assert (section->s.section != nullptr);
+  if (section->s.section == nullptr)
+    error (_("Can't find owner of DWARF section."));
   return section->s.section->owner;
 }
 
diff --git a/gdb/elfread.c b/gdb/elfread.c
index c5ba8837217..764e991dfc2 100644
--- a/gdb/elfread.c
+++ b/gdb/elfread.c
@@ -1219,7 +1219,8 @@  elf_symfile_read_dwarf2 (struct objfile *objfile,
 	   && objfile->separate_debug_objfile_backlink == NULL)
     {
       if (objfile->find_and_add_separate_symbol_file (symfile_flags))
-	gdb_assert (objfile->separate_debug_objfile != nullptr);
+	gdb_assert (objfile->separate_debug_objfile != nullptr
+		    || objfile->flags & OBJF_DOWNLOAD_DEFERRED);
       else
 	has_dwarf2 = false;
     }
diff --git a/gdb/frame.c b/gdb/frame.c
index 41003cc7771..d3409c8fd97 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1889,6 +1889,13 @@  get_selected_frame (const char *message)
 	error (("%s"), message);
 
       lookup_selected_frame (selected_frame_id, selected_frame_level);
+
+      /* It is possible for lookup_selected_frame to cause a new objfile
+	 to be loaded.  Some objfile observers may choose to clear
+	 selected_frame when an objfile is loaded.  Work around this by
+	 calling lookup_selected_frame again if the first try failed.  */
+      if (selected_frame == nullptr)
+	lookup_selected_frame (selected_frame_id, selected_frame_level);
     }
   /* There is always a frame.  */
   gdb_assert (selected_frame != NULL);
diff --git a/gdb/objfile-flags.h b/gdb/objfile-flags.h
index 74aea1a88d3..3f922d9e84f 100644
--- a/gdb/objfile-flags.h
+++ b/gdb/objfile-flags.h
@@ -56,6 +56,10 @@  enum objfile_flag : unsigned
     /* User requested that we do not read this objfile's symbolic
        information.  */
     OBJF_READNEVER = 1 << 6,
+
+    /* A separate .gdb_index has been downloaded for this objfile.
+       Debuginfo for this objfile can be downloaded when required.  */
+    OBJF_DOWNLOAD_DEFERRED = 1 << 7,
   };
 
 DEF_ENUM_FLAGS_TYPE (enum objfile_flag, objfile_flags);
diff --git a/gdb/objfiles.h b/gdb/objfiles.h
index 34b3dac0b26..69222092b61 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -622,6 +622,26 @@  struct objfile
 					       domain_enum domain,
 					       bool *symbol_found_p);
 
+  /* Indicate that the acquisition of this objfile's separate debug objfile
+     is no longer deferred.  Used when the debug objfile has been acquired
+     or could not be found.  */
+  void remove_deferred_status ()
+  {
+    flags &= ~OBJF_DOWNLOAD_DEFERRED;
+
+    /* Remove quick_symbol_functions derived from a separately downloaded
+       index.  If available the separate debug objfile's index will be used
+       instead, since that objfile actually contains the symbols and CUs
+       referenced in the index.
+
+       No more than one element of qf should have from_separate_index set
+       to true.  */
+    qf.remove_if ([&] (const quick_symbol_functions_up &qf_up)
+      {
+	return qf_up->from_separate_index;
+      });
+  }
+
   /* Return the relocation offset applied to SECTION.  */
   CORE_ADDR section_offset (bfd_section *section) const
   {
diff --git a/gdb/quick-symbol.h b/gdb/quick-symbol.h
index 4cd234162ae..01a228b446c 100644
--- a/gdb/quick-symbol.h
+++ b/gdb/quick-symbol.h
@@ -193,6 +193,10 @@  struct quick_symbol_functions
   virtual void compute_main_name (struct objfile *objfile)
   {
   }
+
+  /* True if this quick_symbol_functions is derived from a separately
+     downloaded index.  */
+  bool from_separate_index = false;
 };
 
 typedef std::unique_ptr<quick_symbol_functions> quick_symbol_functions_up;
diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index c684f9cdd43..3ed31661300 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -37,6 +37,7 @@ 
 #include "cli/cli-style.h"
 #include "build-id.h"
 #include "debuginfod-support.h"
+#include "dwarf2/public.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
@@ -617,6 +618,16 @@  objfile::find_and_add_separate_symbol_file (symfile_add_flags symfile_flags)
 	  = simple_find_and_open_separate_symbol_file
 	      (this, find_separate_debug_file_by_debuglink, &warnings);
 
+      /* Attempt to download only an index from the separate debug info.
+	 As with debuginfod_find_and_open_separate_symbol_file, only attempt
+	 this once.  */
+      if (debug_bfd == nullptr && attempt == 0
+	  && dwarf2_has_separate_index (this))
+	{
+	  has_dwarf2 = true;
+	  break;
+	}
+
       /* 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
diff --git a/gdb/symfile.c b/gdb/symfile.c
index bdcc27e7ee5..ec4b4da12ed 100644
--- a/gdb/symfile.c
+++ b/gdb/symfile.c
@@ -989,6 +989,10 @@  syms_from_objfile (struct objfile *objfile,
 static void
 finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
 {
+  struct objfile *parent = objfile->separate_debug_objfile_backlink;
+  bool was_deferred
+    = (parent != nullptr) && (parent->flags & OBJF_DOWNLOAD_DEFERRED);
+
   /* If this is the main symbol file we have to clean up all users of the
      old main symbol file.  Otherwise it is sufficient to fixup all the
      breakpoints that may have been redefined by this symbol file.  */
@@ -999,7 +1003,8 @@  finish_new_objfile (struct objfile *objfile, symfile_add_flags add_flags)
 
       clear_symtab_users (add_flags);
     }
-  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0)
+  else if ((add_flags & SYMFILE_DEFER_BP_RESET) == 0
+	   && !was_deferred)
     {
       breakpoint_re_set ();
     }
@@ -1120,6 +1125,12 @@  symbol_file_add_with_addrs (const gdb_bfd_ref_ptr &abfd, const char *name,
   if (objfile->sf != nullptr)
     finish_new_objfile (objfile, add_flags);
 
+  /* Remove deferred status now in case any observers trigger symtab
+     expansion.  Otherwise gdb might try to read parent for psymbols
+     when it should read the separate debug objfile instead.  */
+  if (parent != nullptr && (parent->flags & OBJF_DOWNLOAD_DEFERRED))
+    parent->remove_deferred_status ();
+
   gdb::observers::new_objfile.notify (objfile);
 
   return objfile;
diff --git a/gdb/symtab.c b/gdb/symtab.c
index cc0bdb80c7c..ac9513608d7 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -2926,14 +2926,30 @@  find_pc_sect_compunit_symtab (CORE_ADDR pc, struct obj_section *section)
   if (best_cust != NULL)
     return best_cust;
 
+  int warn_if_readin = 1;
+
   /* Not found in symtabs, search the "quick" symtabs (e.g. psymtabs).  */
 
   for (objfile *objf : current_program_space->objfiles ())
     {
+      bool was_deferred = objf->flags & OBJF_DOWNLOAD_DEFERRED;
+
       struct compunit_symtab *result
-	= objf->find_pc_sect_compunit_symtab (msymbol, pc, section, 1);
+	= objf->find_pc_sect_compunit_symtab (msymbol, pc, section,
+					      warn_if_readin);
+
       if (result != NULL)
 	return result;
+
+      /* If OBJF's separate debug info was just acquired, disable
+	 warn_if_readin for the next iteration of this loop.  This prevents
+	 a spurious warning in case an observer already triggered expansion
+	 of the separate debug objfile's symtabs.  */
+      if (was_deferred && objf->separate_debug_objfile != nullptr
+	  && (objf->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	warn_if_readin = 0;
+      else if (warn_if_readin == 0)
+	warn_if_readin = 1;
     }
 
   return NULL;
diff --git a/gdb/testsuite/gdb.debuginfod/libsection1.c b/gdb/testsuite/gdb.debuginfod/libsection1.c
new file mode 100644
index 00000000000..7ad19c276c4
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
@@ -0,0 +1,43 @@ 
+/* This testcase 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/>.  */
+
+#include <stdio.h>
+#include <pthread.h>
+#include <unistd.h>
+
+extern void libsection2_test ();
+extern void *libsection2_thread_test (void *);
+
+static volatile int flag = 0;
+
+void
+libsection1_test ()
+{
+  pthread_t thr;
+
+  printf ("In libsection1\n");
+  libsection2_test ();
+
+  pthread_create (&thr, NULL, libsection2_thread_test, (void *) &flag);
+
+  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
+  while (!flag)
+    ;
+
+  printf ("Cancelling thread\n");
+  pthread_cancel (thr);
+}
diff --git a/gdb/testsuite/gdb.debuginfod/libsection2.c b/gdb/testsuite/gdb.debuginfod/libsection2.c
new file mode 100644
index 00000000000..20edd68166b
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/libsection2.c
@@ -0,0 +1,37 @@ 
+/* This testcase 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/>.  */
+
+#include <stdio.h>
+#include <pthread.h>
+
+void
+libsection2_test ()
+{
+  printf ("In libsection2\n");
+}
+
+void *
+libsection2_thread_test (void *arg)
+{
+  int *flag = (int *) arg;
+  printf ("In thread test\n");
+
+  while (1)
+    *flag = 1;
+
+  return NULL;
+}
diff --git a/gdb/testsuite/gdb.debuginfod/section.c b/gdb/testsuite/gdb.debuginfod/section.c
new file mode 100644
index 00000000000..33e4faf2cac
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/section.c
@@ -0,0 +1,29 @@ 
+/* This testcase 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/>.  */
+
+#include <stdio.h>
+
+extern void libsection1_test ();
+
+int
+main ()
+{
+  libsection1_test ();
+  printf ("in section exec\n");
+
+  return 0;
+}
diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
new file mode 100644
index 00000000000..c3872ecdfc9
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/section.exp
@@ -0,0 +1,172 @@ 
+# 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/>.
+
+# Test debuginfod functionality
+
+standard_testfile
+
+load_lib debuginfod-support.exp
+
+require allow_debuginfod_tests
+
+set sourcetmp [standard_output_file tmp-${srcfile}]
+set outputdir [standard_output_file {}]
+
+# SECTEXEC is an executable which calls a function from LIB_SL1.
+set sectfile "section"
+set sectsrc $srcdir/$subdir/section.c
+set sectexec [standard_output_file $sectfile]
+
+# Solib LIB_SL1 calls functions from LIB_SL2.
+set libfile1 "libsection1"
+set libsrc1 $srcdir/$subdir/$libfile1.c
+set lib_sl1 [standard_output_file $libfile1.sl]
+
+set libfile2 "libsection2"
+set libsrc2 $srcdir/$subdir/$libfile2.c
+set lib_sl2 [standard_output_file $libfile2.sl]
+
+set lib_opts1 [list debug build-id shlib=$lib_sl2]
+set lib_opts2 [list debug build-id]
+set exec_opts [list debug build-id shlib=$lib_sl1 shlib=$lib_sl2]
+
+clean_restart
+
+if {[enable_section_downloads] == 0} {
+    untested "GDB does not support debuginfod section downloads"
+    return -1
+}
+
+# Compile SECTEXEC, LIB_SL1 and LIB_SL2.
+if { [gdb_compile_shlib $libsrc2 $lib_sl2 $lib_opts2] != "" } {
+    untested "failed to compile $libfile2"
+    return -1
+}
+
+if { [gdb_compile_shlib_pthreads $libsrc1 $lib_sl1 $lib_opts1] != "" } {
+    untested "failed to compile $libfile1"
+    return -1
+}
+
+if { [gdb_compile $sectsrc $sectexec executable $exec_opts] != "" } {
+    untested "failed to compile $sectfile"
+    return -1
+}
+
+# Add .gdb_index to solibs.
+if { [ensure_gdb_index $lib_sl1 "" "libsection1"] != 1 } {
+    untested "failed to add .gdb_index to $libfile1"
+    return -1
+}
+
+if { [ensure_gdb_index $lib_sl2 "" "libsection2"] != 1 } {
+    untested "failed to add .gdb_index to $libfile2"
+    return -1
+}
+
+# Strip solib debuginfo into separate files.
+if { [gdb_gnu_strip_debug $lib_sl1 ""] != 0} {
+   fail "strip $lib_sl1 debuginfo"
+   return -1
+}
+
+if { [gdb_gnu_strip_debug $lib_sl2 ""] != 0} {
+   fail "strip $lib_sl2 debuginfo"
+   return -1
+}
+
+# Move debuginfo files into directory that debuginfod will serve from.
+set debugdir [standard_output_file "debug"]
+set debuginfo_sl1 [standard_output_file $libfile1.sl.debug]
+set debuginfo_sl2 [standard_output_file $libfile2.sl.debug]
+
+file mkdir $debugdir
+file rename -force $debuginfo_sl1 $debugdir
+file rename -force $debuginfo_sl2 $debugdir
+
+# Restart GDB and clear the debuginfod client cache.  Then load BINFILE into
+# GDB and start running it.  Match output with pattern RES and use TESTNAME
+# as the test name.
+proc_with_prefix clean_restart_with_prompt { binfile testname } {
+    global cache
+
+    # Delete client cache so debuginfo downloads again.
+    file delete -force $cache
+    clean_restart
+
+    gdb_test "set debuginfod enabled on" "" "clean_restart enable $testname"
+    gdb_load $binfile
+
+    runto_main
+}
+
+# Tests with no debuginfod server running.
+proc_with_prefix no_url { } {
+    global sectexec libfile1 libfile2
+
+    gdb_load $sectexec
+    if {![runto_main]} {
+       return
+    }
+
+    # Check that no section is downloaded and no debuginfo is found.
+    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile1.*" \
+	     "found no url lib1"
+    gdb_test "info sharedlibrary" ".*Yes \\(\\*\\).*$libfile2.*" \
+	     "found no url lib2"
+}
+
+# Tests with a debuginfod server running.
+proc_with_prefix local_url { } {
+    global sectexec
+    global libsrc1 lib_sl1 libfile1
+    global libsrc2 lib_sl2 libfile2
+    global debugdir db
+
+    set url [start_debuginfod $db $debugdir]
+    if { $url == "" } {
+	unresolved "failed to start debuginfod server"
+	return
+    }
+
+    # Point GDB to the server.
+    setenv DEBUGINFOD_URLS $url
+
+    clean_restart_with_prompt $sectexec "index"
+
+    # Download debuginfo when stepping into a function.
+    set res ".*separate debug info for $lib_sl1.*\"In ${libfile1}\\\\n\".*"
+    gdb_test "step" $res "step"
+
+    clean_restart_with_prompt $sectexec "break"
+
+    # Download debuginfo when setting a breakpoint.
+    set res "Download.*separate debug info for $lib_sl2.*"
+    gdb_test "br libsection2_test" $res "break set"
+
+    # Hit the breakpoint.
+    set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
+    gdb_test "c" $res "break continue"
+}
+
+# Create CACHE and DB directories ready for debuginfod to use.
+prepare_for_debuginfod cache db
+
+with_debuginfod_env $cache {
+    no_url
+    local_url
+}
+
+stop_debuginfod
diff --git a/gdb/testsuite/lib/debuginfod-support.exp b/gdb/testsuite/lib/debuginfod-support.exp
index 50a8b512a4a..e0b3dc39f51 100644
--- a/gdb/testsuite/lib/debuginfod-support.exp
+++ b/gdb/testsuite/lib/debuginfod-support.exp
@@ -113,6 +113,8 @@  proc with_debuginfod_env { cache body } {
 proc start_debuginfod { db debugdir } {
     global debuginfod_spawn_id spawn_id
 
+    set logfile [standard_output_file "server_log"]
+
     # Find an unused port.
     set port 7999
     set found false
@@ -127,7 +129,8 @@  proc start_debuginfod { db debugdir } {
 	    set old_spawn_id $spawn_id
 	}
 
-	spawn debuginfod -vvvv -d $db -p $port -F $debugdir
+	spawn sh -c "debuginfod -vvvv -d $db -p $port -F $debugdir 2>&1 \
+		| tee $logfile"
 	set debuginfod_spawn_id $spawn_id
 
 	if { [info exists old_spawn_id] } {
@@ -194,3 +197,25 @@  proc stop_debuginfod { } {
 	unset debuginfod_spawn_id
     }
 }
+
+# Return 1 if gdb is configured to download ELF/DWARF sections from
+# debuginfod servers.  Otherwise return 0.
+proc enable_section_downloads { } {
+    global gdb_prompt
+
+    set cmd "maint set debuginfod download-sections on"
+    set msg "enable section downloads"
+
+    gdb_test_multiple $cmd $msg {
+	-re -wrap ".*not compiled into GDB.*" {
+	    return 0
+	}
+	-re -wrap "^" {
+	    return 1
+	}
+	-re -wrap "" {
+	    fail "$gdb_test_name (unexpected output)"
+	    return 0
+	}
+    }
+}
diff --git a/gdb/testsuite/lib/gdb.exp b/gdb/testsuite/lib/gdb.exp
index 9ac707be696..9b3c7e6a02c 100644
--- a/gdb/testsuite/lib/gdb.exp
+++ b/gdb/testsuite/lib/gdb.exp
@@ -9414,10 +9414,14 @@  proc get_index_type { objfile { testname "" } } {
 # STYLE controls which style of index to add, if needed.  The empty
 # string (the default) means .gdb_index; "-dwarf-5" means .debug_names.
 
-proc ensure_gdb_index { binfile {style ""} } {
+proc ensure_gdb_index { binfile {style ""} {testname_prefix ""} } {
     set testfile [file tail $binfile]
-
     set test "check if index present"
+
+    if { $testname_prefix != "" } {
+      set test "$testname_prefix $test"
+    }
+
     set index_type [get_index_type $testfile $test]
 
     if { $index_type eq "gdb" || $index_type eq "dwarf5" } {