Mirror of the gdb-patches mailing list
 help / color / mirror / Atom feed
* [PATCH 0/4] On-demand debuginfo downloading
@ 2024-01-17 15:48 Aaron Merey
  2024-01-17 15:48 ` [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
                   ` (3 more replies)
  0 siblings, 4 replies; 29+ messages in thread
From: Aaron Merey @ 2024-01-17 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, thiago.bauermann, Aaron Merey

Patch set that adds support for lazy/on-demand downloading of debuginfo
as well as .gdb_index, .debug_line and .debug_line_str ELF section
downloading.

Previous version can be found here:
https://sourceware.org/pipermail/gdb-patches/2023-October/203589.html

Aaron Merey (4):
  gdb: Buffer output streams during events that might download debuginfo
  gdb/progspace: Add reverse safe iterator
  gdb/debuginfod: Support on-demand debuginfo downloading
  gdb/debuginfod: Add .debug_line downloading

 gdb/cli-out.c                              |  21 +-
 gdb/cli-out.h                              |   3 +
 gdb/completer.c                            |  18 +-
 gdb/debuginfod-support.c                   |  15 +-
 gdb/dwarf2/frame.c                         |  13 +
 gdb/dwarf2/frame.h                         |   4 +
 gdb/dwarf2/index-cache.c                   |  33 ++
 gdb/dwarf2/index-cache.h                   |  13 +
 gdb/dwarf2/line-header.c                   | 161 +++++++---
 gdb/dwarf2/line-header.h                   |  10 +
 gdb/dwarf2/public.h                        |   7 +
 gdb/dwarf2/read-gdb-index.c                | 185 ++++++++++-
 gdb/dwarf2/read.c                          | 355 ++++++++++++++++++++-
 gdb/dwarf2/read.h                          |  47 +++
 gdb/dwarf2/section.c                       |   3 +-
 gdb/elfread.c                              |   3 +-
 gdb/frame.c                                |   7 +
 gdb/infrun.c                               |  16 +-
 gdb/jit.c                                  |   7 +-
 gdb/mi/mi-out.c                            |   9 +-
 gdb/mi/mi-out.h                            |   3 +
 gdb/objfile-flags.h                        |   4 +
 gdb/objfiles.c                             |  14 +-
 gdb/objfiles.h                             |  30 ++
 gdb/progspace.c                            |  19 +-
 gdb/progspace.h                            |  31 +-
 gdb/python/py-uiout.h                      |   3 +
 gdb/quick-symbol.h                         |   4 +
 gdb/stack.c                                |  35 +-
 gdb/symfile-debug.c                        |  45 ++-
 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   | 199 ++++++++++++
 gdb/testsuite/gdb.python/py-objfile.exp    |   2 +-
 gdb/testsuite/lib/debuginfod-support.exp   |  27 +-
 gdb/testsuite/lib/gdb.exp                  |   8 +-
 gdb/thread.c                               | 180 ++++++-----
 gdb/ui-file.h                              |   2 +-
 gdb/ui-out.c                               | 144 +++++++++
 gdb/ui-out.h                               | 204 ++++++++++++
 gdbsupport/safe-iterator.h                 | 106 ++++++
 44 files changed, 1910 insertions(+), 220 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

-- 
2.43.0


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

* [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo
  2024-01-17 15:48 [PATCH 0/4] On-demand debuginfo downloading Aaron Merey
@ 2024-01-17 15:48 ` Aaron Merey
  2024-01-18 11:22   ` Andrew Burgess
  2024-01-17 15:48 ` [PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator Aaron Merey
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 29+ messages in thread
From: Aaron Merey @ 2024-01-17 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, thiago.bauermann, Aaron Merey

v8 fixes a few style issues suggested by Thiago:
https://sourceware.org/pipermail/gdb-patches/2023-December/205504.html

Introduce new ui_file buffering_file to temporarily collect output
written to gdb_std* output streams during print_thread, print_frame_info
and print_stop_event.

This ensures that output during these functions is not interrupted
by debuginfod progress messages.

With the addition of deferred debuginfo downloading it is possible
for download progress messages to print during these events.
Without any intervention we can end up with poorly formatted output:

    (gdb) backtrace
    [...]
    #8  0x00007fbe8af7d7cf in pygi_invoke_c_callable (Downloading separate debug info for /lib64/libpython3.11.so.1.0
    function_cache=0x561221b224d0, state=<optimized out>...

To fix this we buffer writes to gdb_std* output streams while allowing
debuginfod progress messages to skip the buffers and print to the
underlying output streams immediately.  Buffered output is then written
to the output streams.  This ensures that progress messages print first,
followed by uninterrupted frame/thread/stop info:

    (gdb) backtrace
    [...]
    Downloading separate debug info for /lib64/libpython3.11.so.1.0
    #8  0x00007fbe8af7d7cf in pygi_invoke_c_callable (function_cache=0x561221b224d0, state=<optimized out>...

Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
---
 gdb/cli-out.c            |  10 ++-
 gdb/cli-out.h            |   3 +
 gdb/debuginfod-support.c |  15 ++--
 gdb/infrun.c             |  16 +++-
 gdb/mi/mi-out.h          |   3 +
 gdb/python/py-uiout.h    |   3 +
 gdb/stack.c              |  35 +++++---
 gdb/thread.c             | 180 ++++++++++++++++++++++----------------
 gdb/ui-file.h            |   2 +-
 gdb/ui-out.c             | 141 ++++++++++++++++++++++++++++++
 gdb/ui-out.h             | 184 +++++++++++++++++++++++++++++++++++++++
 11 files changed, 493 insertions(+), 99 deletions(-)

diff --git a/gdb/cli-out.c b/gdb/cli-out.c
index a1cde54feda..b4c7a657423 100644
--- a/gdb/cli-out.c
+++ b/gdb/cli-out.c
@@ -299,7 +299,7 @@ cli_ui_out::do_progress_notify (const std::string &msg,
 				double howmuch, double total)
 {
   int chars_per_line = get_chars_per_line ();
-  struct ui_file *stream = m_streams.back ();
+  struct ui_file *stream = get_unbuffered (m_streams.back ());
   cli_progress_info &info (m_progress_info.back ());
 
   if (chars_per_line > MAX_CHARS_PER_LINE)
@@ -384,7 +384,7 @@ cli_ui_out::do_progress_notify (const std::string &msg,
 void
 cli_ui_out::clear_progress_notify ()
 {
-  struct ui_file *stream = m_streams.back ();
+  struct ui_file *stream = get_unbuffered (m_streams.back ());
   int chars_per_line = get_chars_per_line ();
 
   scoped_restore save_pagination
@@ -413,10 +413,12 @@ void
 cli_ui_out::do_progress_end ()
 {
   struct ui_file *stream = m_streams.back ();
-  m_progress_info.pop_back ();
+  cli_progress_info &info (m_progress_info.back ());
 
-  if (stream->isatty ())
+  if (stream->isatty () && info.state != progress_update::START)
     clear_progress_notify ();
+
+  m_progress_info.pop_back ();
 }
 
 /* local functions */
diff --git a/gdb/cli-out.h b/gdb/cli-out.h
index 34016182269..89b4aa40870 100644
--- a/gdb/cli-out.h
+++ b/gdb/cli-out.h
@@ -35,6 +35,9 @@ class cli_ui_out : public ui_out
 
   bool can_emit_style_escape () const override;
 
+  ui_file *current_stream () const override
+  { return m_streams.back (); }
+
 protected:
 
   virtual void do_table_begin (int nbrofcols, int nr_rows,
diff --git a/gdb/debuginfod-support.c b/gdb/debuginfod-support.c
index eb88c406ad6..35dcf56cb7e 100644
--- a/gdb/debuginfod-support.c
+++ b/gdb/debuginfod-support.c
@@ -155,7 +155,8 @@ progressfn (debuginfod_client *c, long cur, long total)
 
   if (check_quit_flag ())
     {
-      gdb_printf ("Cancelling download of %s %s...\n",
+      ui_file *outstream = get_unbuffered (gdb_stdout);
+      gdb_printf (outstream, _("Cancelling download of %s %s...\n"),
 		  data->desc, styled_fname.c_str ());
       return 1;
     }
@@ -295,10 +296,14 @@ static void
 print_outcome (int fd, const char *desc, const char *fname)
 {
   if (fd < 0 && fd != -ENOENT)
-    gdb_printf (_("Download failed: %s.  Continuing without %s %ps.\n"),
-		safe_strerror (-fd),
-		desc,
-		styled_string (file_name_style.style (), fname));
+    {
+      ui_file *outstream = get_unbuffered (gdb_stdout);
+      gdb_printf (outstream,
+		  _("Download failed: %s.  Continuing without %s %ps.\n"),
+		  safe_strerror (-fd),
+		  desc,
+		  styled_string (file_name_style.style (), fname));
+    }
 }
 
 /* See debuginfod-support.h  */
diff --git a/gdb/infrun.c b/gdb/infrun.c
index c769e972f49..b977ba815ff 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -9277,10 +9277,10 @@ print_stop_location (const target_waitstatus &ws)
     print_stack_frame (get_selected_frame (nullptr), 0, source_flag, 1);
 }
 
-/* See infrun.h.  */
+/* See `print_stop_event` in infrun.h.  */
 
-void
-print_stop_event (struct ui_out *uiout, bool displays)
+static void
+do_print_stop_event (struct ui_out *uiout, bool displays)
 {
   struct target_waitstatus last;
   struct thread_info *tp;
@@ -9309,6 +9309,16 @@ print_stop_event (struct ui_out *uiout, bool displays)
     }
 }
 
+/* See infrun.h.  This function itself sets up buffered output for the
+   duration of do_print_stop_event, which performs the actual event
+   printing.  */
+
+void
+print_stop_event (struct ui_out *uiout, bool displays)
+{
+  do_with_buffered_output (do_print_stop_event, uiout, displays);
+}
+
 /* See infrun.h.  */
 
 void
diff --git a/gdb/mi/mi-out.h b/gdb/mi/mi-out.h
index 0dd7479a52f..68ff5faf632 100644
--- a/gdb/mi/mi-out.h
+++ b/gdb/mi/mi-out.h
@@ -45,6 +45,9 @@ class mi_ui_out : public ui_out
     return false;
   }
 
+  ui_file *current_stream () const override
+  { return m_streams.back (); }
+
 protected:
 
   virtual void do_table_begin (int nbrofcols, int nr_rows, const char *tblid)
diff --git a/gdb/python/py-uiout.h b/gdb/python/py-uiout.h
index 9c9617ea831..5944d1b6e34 100644
--- a/gdb/python/py-uiout.h
+++ b/gdb/python/py-uiout.h
@@ -56,6 +56,9 @@ class py_ui_out : public ui_out
     return std::move (current ().obj);
   }
 
+  ui_file *current_stream () const override
+  { return nullptr; }
+
 protected:
 
   void do_progress_end () override { }
diff --git a/gdb/stack.c b/gdb/stack.c
index fad4b62c6f7..42e919e1078 100644
--- a/gdb/stack.c
+++ b/gdb/stack.c
@@ -220,7 +220,8 @@ static void print_frame_local_vars (frame_info_ptr frame,
 				    const char *regexp, const char *t_regexp,
 				    int num_tabs, struct ui_file *stream);
 
-static void print_frame (const frame_print_options &opts,
+static void print_frame (struct ui_out *uiout,
+			 const frame_print_options &opts,
 			 frame_info_ptr frame, int print_level,
 			 enum print_what print_what,  int print_args,
 			 struct symtab_and_line sal);
@@ -1020,16 +1021,15 @@ get_user_print_what_frame_info (std::optional<enum print_what> *what)
    Used in "where" output, and to emit breakpoint or step
    messages.  */
 
-void
-print_frame_info (const frame_print_options &fp_opts,
-		  frame_info_ptr frame, int print_level,
-		  enum print_what print_what, int print_args,
-		  int set_current_sal)
+static void
+do_print_frame_info (struct ui_out *uiout, const frame_print_options &fp_opts,
+		     frame_info_ptr frame, int print_level,
+		     enum print_what print_what, int print_args,
+		     int set_current_sal)
 {
   struct gdbarch *gdbarch = get_frame_arch (frame);
   int source_print;
   int location_print;
-  struct ui_out *uiout = current_uiout;
 
   if (!current_uiout->is_mi_like_p ()
       && fp_opts.print_frame_info != print_frame_info_auto)
@@ -1105,7 +1105,8 @@ print_frame_info (const frame_print_options &fp_opts,
 		    || print_what == LOC_AND_ADDRESS
 		    || print_what == SHORT_LOCATION);
   if (location_print || !sal.symtab)
-    print_frame (fp_opts, frame, print_level, print_what, print_args, sal);
+    print_frame (uiout, fp_opts, frame, print_level,
+		 print_what, print_args, sal);
 
   source_print = (print_what == SRC_LINE || print_what == SRC_AND_LOC);
 
@@ -1185,6 +1186,20 @@ print_frame_info (const frame_print_options &fp_opts,
   gdb_flush (gdb_stdout);
 }
 
+/* Redirect output to a temporary buffer for the duration
+   of do_print_frame_info.  */
+
+void
+print_frame_info (const frame_print_options &fp_opts,
+		  frame_info_ptr frame, int print_level,
+		  enum print_what print_what, int print_args,
+		  int set_current_sal)
+{
+  do_with_buffered_output (do_print_frame_info, current_uiout,
+			   fp_opts, frame, print_level, print_what,
+			   print_args, set_current_sal);
+}
+
 /* See stack.h.  */
 
 void
@@ -1309,13 +1324,13 @@ find_frame_funname (frame_info_ptr frame, enum language *funlang,
 }
 
 static void
-print_frame (const frame_print_options &fp_opts,
+print_frame (struct ui_out *uiout,
+	     const frame_print_options &fp_opts,
 	     frame_info_ptr frame, int print_level,
 	     enum print_what print_what, int print_args,
 	     struct symtab_and_line sal)
 {
   struct gdbarch *gdbarch = get_frame_arch (frame);
-  struct ui_out *uiout = current_uiout;
   enum language funlang = language_unknown;
   struct value_print_options opts;
   struct symbol *func;
diff --git a/gdb/thread.c b/gdb/thread.c
index f03a60c4d01..08c156d6687 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -1089,6 +1089,107 @@ thread_target_id_str (thread_info *tp)
     return target_id;
 }
 
+/* Print thread TP.  GLOBAL_IDS indicates whether REQUESTED_THREADS
+   is a list of global or per-inferior thread ids.  */
+
+static void
+do_print_thread (ui_out *uiout, const char *requested_threads,
+		 int global_ids, int pid, int show_global_ids,
+		 int default_inf_num, thread_info *tp,
+		 thread_info *current_thread)
+{
+  int core;
+
+  /* In case REQUESTED_THREADS contains $_thread.  */
+  if (current_thread != nullptr)
+    switch_to_thread (current_thread);
+
+  if (!should_print_thread (requested_threads, default_inf_num,
+			    global_ids, pid, tp))
+    return;
+
+  ui_out_emit_tuple tuple_emitter (uiout, NULL);
+
+  if (!uiout->is_mi_like_p ())
+    {
+      if (tp == current_thread)
+	uiout->field_string ("current", "*");
+      else
+	uiout->field_skip ("current");
+
+      uiout->field_string ("id-in-tg", print_thread_id (tp));
+    }
+
+  if (show_global_ids || uiout->is_mi_like_p ())
+    uiout->field_signed ("id", tp->global_num);
+
+  /* Switch to the thread (and inferior / target).  */
+  switch_to_thread (tp);
+
+  /* For the CLI, we stuff everything into the target-id field.
+     This is a gross hack to make the output come out looking
+     correct.  The underlying problem here is that ui-out has no
+     way to specify that a field's space allocation should be
+     shared by several fields.  For MI, we do the right thing
+     instead.  */
+
+  if (uiout->is_mi_like_p ())
+    {
+      uiout->field_string ("target-id", target_pid_to_str (tp->ptid));
+
+      const char *extra_info = target_extra_thread_info (tp);
+      if (extra_info != nullptr)
+	uiout->field_string ("details", extra_info);
+
+      const char *name = thread_name (tp);
+      if (name != NULL)
+	uiout->field_string ("name", name);
+    }
+  else
+    {
+      uiout->field_string ("target-id", thread_target_id_str (tp));
+    }
+
+  if (tp->state == THREAD_RUNNING)
+    uiout->text ("(running)\n");
+  else
+    {
+      /* The switch above put us at the top of the stack (leaf
+	 frame).  */
+      print_stack_frame (get_selected_frame (NULL),
+			 /* For MI output, print frame level.  */
+			 uiout->is_mi_like_p (),
+			 LOCATION, 0);
+    }
+
+  if (uiout->is_mi_like_p ())
+    {
+      const char *state = "stopped";
+
+      if (tp->state == THREAD_RUNNING)
+	state = "running";
+      uiout->field_string ("state", state);
+    }
+
+  core = target_core_of_thread (tp->ptid);
+  if (uiout->is_mi_like_p () && core != -1)
+    uiout->field_signed ("core", core);
+}
+
+/* Redirect output to a temporary buffer for the duration
+   of do_print_thread.  */
+
+static void
+print_thread (ui_out *uiout, const char *requested_threads,
+	      int global_ids, int pid, int show_global_ids,
+	      int default_inf_num, thread_info *tp, thread_info *current_thread)
+
+{
+  do_with_buffered_output (do_print_thread, uiout, requested_threads,
+			   global_ids, pid, show_global_ids,
+			   default_inf_num, tp, current_thread);
+}
+
 /* Like print_thread_info, but in addition, GLOBAL_IDS indicates
    whether REQUESTED_THREADS is a list of global or per-inferior
    thread ids.  */
@@ -1176,86 +1277,13 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
     for (inferior *inf : all_inferiors ())
       for (thread_info *tp : inf->threads ())
 	{
-	  int core;
-
 	  any_thread = true;
+
 	  if (tp == current_thread && tp->state == THREAD_EXITED)
 	    current_exited = true;
 
-	  /* In case REQUESTED_THREADS contains $_thread.  */
-	  if (current_thread != nullptr)
-	    switch_to_thread (current_thread);
-
-	  if (!should_print_thread (requested_threads, default_inf_num,
-				    global_ids, pid, tp))
-	    continue;
-
-	  ui_out_emit_tuple tuple_emitter (uiout, NULL);
-
-	  if (!uiout->is_mi_like_p ())
-	    {
-	      if (tp == current_thread)
-		uiout->field_string ("current", "*");
-	      else
-		uiout->field_skip ("current");
-
-	      uiout->field_string ("id-in-tg", print_thread_id (tp));
-	    }
-
-	  if (show_global_ids || uiout->is_mi_like_p ())
-	    uiout->field_signed ("id", tp->global_num);
-
-	  /* Switch to the thread (and inferior / target).  */
-	  switch_to_thread (tp);
-
-	  /* For the CLI, we stuff everything into the target-id field.
-	     This is a gross hack to make the output come out looking
-	     correct.  The underlying problem here is that ui-out has no
-	     way to specify that a field's space allocation should be
-	     shared by several fields.  For MI, we do the right thing
-	     instead.  */
-
-	  if (uiout->is_mi_like_p ())
-	    {
-	      uiout->field_string ("target-id", target_pid_to_str (tp->ptid));
-
-	      const char *extra_info = target_extra_thread_info (tp);
-	      if (extra_info != nullptr)
-		uiout->field_string ("details", extra_info);
-
-	      const char *name = thread_name (tp);
-	      if (name != NULL)
-		uiout->field_string ("name", name);
-	    }
-	  else
-	    {
-	      uiout->field_string ("target-id", thread_target_id_str (tp));
-	    }
-
-	  if (tp->state == THREAD_RUNNING)
-	    uiout->text ("(running)\n");
-	  else
-	    {
-	      /* The switch above put us at the top of the stack (leaf
-		 frame).  */
-	      print_stack_frame (get_selected_frame (NULL),
-				 /* For MI output, print frame level.  */
-				 uiout->is_mi_like_p (),
-				 LOCATION, 0);
-	    }
-
-	  if (uiout->is_mi_like_p ())
-	    {
-	      const char *state = "stopped";
-
-	      if (tp->state == THREAD_RUNNING)
-		state = "running";
-	      uiout->field_string ("state", state);
-	    }
-
-	  core = target_core_of_thread (tp->ptid);
-	  if (uiout->is_mi_like_p () && core != -1)
-	    uiout->field_signed ("core", core);
+	  print_thread (uiout, requested_threads, global_ids, pid,
+			show_global_ids, default_inf_num, tp, current_thread);
 	}
 
     /* This end scope restores the current thread and the frame
diff --git a/gdb/ui-file.h b/gdb/ui-file.h
index 31f87ffd51d..8385033b441 100644
--- a/gdb/ui-file.h
+++ b/gdb/ui-file.h
@@ -224,7 +224,7 @@ class string_file : public ui_file
   bool empty () const { return m_string.empty (); }
   void clear () { return m_string.clear (); }
 
-private:
+protected:
   /* The internal buffer.  */
   std::string m_string;
 
diff --git a/gdb/ui-out.c b/gdb/ui-out.c
index e8653c6c127..75f9f1612cb 100644
--- a/gdb/ui-out.c
+++ b/gdb/ui-out.c
@@ -871,3 +871,144 @@ ui_out::ui_out (ui_out_flags flags)
 ui_out::~ui_out ()
 {
 }
+
+/* See ui-out.h.  */
+
+void
+buffer_group::output_unit::flush () const
+{
+  if (!m_msg.empty ())
+    m_stream->puts (m_msg.c_str ());
+
+  if (m_wrap_hint >= 0)
+    m_stream->wrap_here (m_wrap_hint);
+
+  if (m_flush)
+    m_stream->flush ();
+}
+
+/* See ui-out.h.  */
+
+void
+buffer_group::write (const char *buf, long length_buf, ui_file *stream)
+{
+  /* Record each line separately.  */
+  for (size_t prev = 0, cur = 0; cur < length_buf; ++cur)
+    if (buf[cur] == '\n' || cur == length_buf - 1)
+      {
+	std::string msg (buf + prev, cur - prev + 1);
+
+	if (m_buffered_output.size () > 0
+	    && m_buffered_output.back ().m_wrap_hint == -1
+	    && m_buffered_output.back ().m_stream == stream
+	    && m_buffered_output.back ().m_msg.size () > 0
+	    && m_buffered_output.back ().m_msg.back () != '\n')
+	  m_buffered_output.back ().m_msg.append (msg);
+	else
+	  {
+	    m_buffered_output.emplace_back (msg);
+	    m_buffered_output.back ().m_stream = stream;
+	  }
+	prev = cur + 1;
+      }
+}
+
+/* See ui-out.h.  */
+
+void
+buffer_group::wrap_here (int indent, ui_file *stream)
+{
+  m_buffered_output.emplace_back ("", indent);
+  m_buffered_output.back ().m_stream = stream;
+}
+
+/* See ui-out.h.  */
+
+void
+buffer_group::flush_here (ui_file *stream)
+{
+  m_buffered_output.emplace_back ("", -1, true);
+  m_buffered_output.back ().m_stream = stream;
+}
+
+/* See ui-out.h.  */
+
+ui_file *
+get_unbuffered (ui_file * stream)
+{
+  buffering_file *buf = dynamic_cast<buffering_file *> (stream);
+
+  if (buf == nullptr)
+    return stream;
+
+  return get_unbuffered (buf->stream ());
+}
+
+buffered_streams::buffered_streams (buffer_group *group, ui_out *uiout)
+  : m_buffered_stdout (group, gdb_stdout),
+    m_buffered_stderr (group, gdb_stderr),
+    m_buffered_stdlog (group, gdb_stdlog),
+    m_buffered_stdtarg (group, gdb_stdtarg),
+    m_buffered_stdtargerr (group, gdb_stdtargerr),
+    m_uiout (uiout)
+{
+  gdb_stdout = &m_buffered_stdout;
+  gdb_stderr = &m_buffered_stderr;
+  gdb_stdlog = &m_buffered_stdlog;
+  gdb_stdtarg = &m_buffered_stdtarg;
+  gdb_stdtargerr = &m_buffered_stdtargerr;
+
+  ui_file *stream = current_uiout->current_stream ();
+  if (stream != nullptr)
+    {
+      m_buffered_current_uiout.emplace (group, stream);
+      current_uiout->redirect (&(*m_buffered_current_uiout));
+    }
+
+  stream = m_uiout->current_stream ();
+  if (stream != nullptr && current_uiout != m_uiout)
+    {
+      m_buffered_uiout.emplace (group, stream);
+      m_uiout->redirect (&(*m_buffered_uiout));
+    }
+
+  m_buffers_in_place = true;
+}
+
+/* See ui-out.h.  */
+
+void
+buffered_streams::remove_buffers ()
+{
+  if (!m_buffers_in_place)
+    return;
+
+  m_buffers_in_place = false;
+
+  gdb_stdout = m_buffered_stdout.stream ();
+  gdb_stderr = m_buffered_stderr.stream ();
+  gdb_stdlog = m_buffered_stdlog.stream ();
+  gdb_stdtarg = m_buffered_stdtarg.stream ();
+  gdb_stdtargerr = m_buffered_stdtargerr.stream ();
+
+  if (m_buffered_current_uiout.has_value ())
+    current_uiout->redirect (nullptr);
+
+  if (m_buffered_uiout.has_value ())
+    m_uiout->redirect (nullptr);
+}
+
+buffer_group::buffer_group (ui_out *uiout)
+  : m_buffered_streams (new buffered_streams (this, uiout))
+{ /* Nothing.  */ }
+
+/* See ui-out.h.  */
+
+void
+buffer_group::flush () const
+{
+  m_buffered_streams->remove_buffers ();
+
+  for (const output_unit &ou : m_buffered_output)
+    ou.flush ();
+}
diff --git a/gdb/ui-out.h b/gdb/ui-out.h
index 07567a1df35..2934d3a6b34 100644
--- a/gdb/ui-out.h
+++ b/gdb/ui-out.h
@@ -278,6 +278,9 @@ class ui_out
      escapes.  */
   virtual bool can_emit_style_escape () const = 0;
 
+  /* Return the ui_file currently used for output.  */
+  virtual ui_file *current_stream () const = 0;
+
   /* An object that starts and finishes displaying progress updates.  */
   class progress_update
   {
@@ -470,4 +473,185 @@ class ui_out_redirect_pop
   struct ui_out *m_uiout;
 };
 
+struct buffered_streams;
+
+/* Organizes writes to a collection of buffered output streams
+   so that when flushed, output is written to all streams in
+   chronological order.  */
+
+struct buffer_group
+{
+  buffer_group (ui_out *uiout);
+
+  /* Flush all buffered writes to the underlying output streams.  */
+  void flush () const;
+
+  /* Record contents of BUF and associate it with STREAM.  */
+  void write (const char *buf, long length_buf, ui_file *stream);
+
+  /* Record a wrap_here and associate it with STREAM.  */
+  void wrap_here (int indent, ui_file *stream);
+
+  /* Record a call to flush and associate it with STREAM.  */
+  void flush_here (ui_file *stream);
+
+private:
+
+  struct output_unit
+  {
+    output_unit (std::string msg, int wrap_hint = -1, bool flush = false)
+      : m_msg (msg), m_wrap_hint (wrap_hint), m_flush (flush)
+    {}
+
+    /* Write contents of this output_unit to the underlying stream.  */
+    void flush () const;
+
+    /* Underlying stream for which this output unit will be written to.  */
+    ui_file *m_stream;
+
+    /* String to be written to underlying buffer.  */
+    std::string m_msg;
+
+    /* Argument to wrap_here.  -1 indicates no wrap.  Used to call wrap_here
+       during buffer flush.  */
+    int m_wrap_hint;
+
+    /* Indicate that the underlying output stream's flush should be called.  */
+    bool m_flush;
+  };
+
+  /* Output_units to be written to buffered output streams.  */
+  std::vector<output_unit> m_buffered_output;
+
+  /* Buffered output streams.  */
+  std::unique_ptr<buffered_streams> m_buffered_streams;
+};
+
+/* If FILE is a buffering_file, return it's underlying stream.  */
+
+extern ui_file *get_unbuffered (ui_file *file);
+
+/* Buffer output to gdb_stdout and gdb_stderr for the duration of FUNC.  */
+
+template<typename F, typename... Arg>
+void
+do_with_buffered_output (F func, ui_out *uiout, Arg... args)
+{
+  buffer_group g (uiout);
+
+  try
+    {
+      func (uiout, std::forward<Arg> (args)...);
+    }
+  catch (gdb_exception &ex)
+    {
+      /* Ideally flush would be called in the destructor of buffer_group,
+	 however flushing might cause an exception to be thrown.  Catch it
+	 and ensure the first exception propagates.  */
+      try
+	{
+	  g.flush ();
+	}
+      catch (const gdb_exception &)
+	{
+	}
+
+      throw_exception (std::move (ex));
+    }
+
+  /* Try was successful.  Let any further exceptions propagate.  */
+  g.flush ();
+}
+
+/* Accumulate writes to an underlying ui_file.  Output to the
+   underlying file is deferred until required.  */
+
+struct buffering_file : public ui_file
+{
+  buffering_file (buffer_group *group, ui_file *stream)
+    : m_group (group),
+      m_stream (stream)
+  { /* Nothing.  */ }
+
+  /* Return the underlying output stream.  */
+  ui_file *stream () const
+  {
+    return m_stream;
+  }
+
+  /* Record the contents of BUF.  */
+  void write (const char *buf, long length_buf) override
+  {
+    m_group->write (buf, length_buf, m_stream);
+  }
+
+  /* Record a wrap_here call with argument INDENT.  */
+  void wrap_here (int indent) override
+  {
+    m_group->wrap_here (indent, m_stream);
+  }
+
+  /* Return true if the underlying stream is a tty.  */
+  bool isatty () override
+  {
+    return m_stream->isatty ();
+  }
+
+  /* Return true if ANSI escapes can be used on the underlying stream.  */
+  bool can_emit_style_escape () override
+  {
+    return m_stream->can_emit_style_escape ();
+  }
+
+  /* Flush the underlying output stream.  */
+  void flush () override
+  {
+    return m_group->flush_here (m_stream);
+  }
+
+private:
+
+  /* Coordinates buffering across multiple buffering_files.  */
+  buffer_group *m_group;
+
+  /* The underlying output stream.  */
+  ui_file *m_stream;
+};
+
+/* Attaches and detaches buffers for each of the gdb_std* streams.  */
+
+struct buffered_streams
+{
+  buffered_streams (buffer_group *group, ui_out *uiout);
+
+  ~buffered_streams ()
+  {
+    this->remove_buffers ();
+  }
+
+  /* Remove buffering_files from all underlying streams.  */
+  void remove_buffers ();
+
+private:
+
+  /* True if buffers are still attached to each underlying output stream.  */
+  bool m_buffers_in_place;
+
+  /* Buffers for each gdb_std* output stream.  */
+  buffering_file m_buffered_stdout;
+  buffering_file m_buffered_stderr;
+  buffering_file m_buffered_stdlog;
+  buffering_file m_buffered_stdtarg;
+  buffering_file m_buffered_stdtargerr;
+
+  /* Buffer for current_uiout's output stream.  */
+  std::optional<buffering_file> m_buffered_current_uiout;
+
+  /* Additional ui_out being buffered.  */
+  ui_out *m_uiout;
+
+  /* Buffer for m_uiout's output stream.  */
+  std::optional<buffering_file> m_buffered_uiout;
+};
+
 #endif /* UI_OUT_H */
-- 
2.43.0


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

* [PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator
  2024-01-17 15:48 [PATCH 0/4] On-demand debuginfo downloading Aaron Merey
  2024-01-17 15:48 ` [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
@ 2024-01-17 15:48 ` Aaron Merey
  2024-01-18 12:02   ` Andrew Burgess
  2024-01-17 15:48 ` [PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading Aaron Merey
  2024-01-17 15:48 ` [PATCH 4/4 v6] gdb/debuginfod: Add .debug_line downloading Aaron Merey
  3 siblings, 1 reply; 29+ messages in thread
From: Aaron Merey @ 2024-01-17 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, thiago.bauermann, Aaron Merey

v3 tweaks qf iteration in symfile-debug.c to account for the removal of
qf_require_partial_symbols.

Commit message:

This patch changes progspace objfile_list insertion so that separate
debug objfiles are placed into the list after the parent objfile,
instead of before.

These changes are intended to prepare gdb for on-demand debuginfo
downloading and the downloading of .gdb_index sections.

With on-demand downloading enabled, gdb might need to delete a
.gdb_index quick_symbol_functions from a parent objfile while looping
the objfile's list of quick_symbol_functions because the separate
debug objfile has just been downloaded.  The use of a safe_range
prevents qf removal from causing iterator invalidation.

gdb might also download a debuginfo file during symtab expansion.
In this case an objfile will be added to the current progspace's
objfiles_list during iteration over the list (for example, in
iterate_over_symtabs).  We want these loops to also iterate over
newly downloaded objfiles.  So objfiles need to be inserted into
objfiles_list after their parent since it is during the search of
the parent objfile for some symbol or filename that the separate
debug objfile might be downloaded.

To facilitate the safe deletion of objfiles, this patch also adds
basic_safe_reverse_range and basic_safe_reverse_iterator.  This allows
objfiles to be removed from the objfiles_list in a loop without iterator
invalidation.

If a forward safe iterator were to be used, the deletion of an
objfile could invalidate the safe iterator's reference to the next
objfile in the objfiles_list.  This can happen when the deletion
of an objfile causes the deletion of a separate debug objfile that
happens to the be next element in the objfiles_list.

The standard reverse iterator is not suitable for safe objfile deletion.
In order to safely delete the first objfile in the objfiles_list, the
standard reverse iterator's underlying begin iterator would have to be
decremented, resulting in undefined behavior.

A small change was also made to a testcase in py-objfile.exp to
account for the new placement of separate debug objfiles in
objfiles_list.
---
 gdb/jit.c                               |   7 +-
 gdb/objfiles.c                          |  14 ++--
 gdb/objfiles.h                          |  10 +++
 gdb/progspace.c                         |  19 ++++-
 gdb/progspace.h                         |  31 ++++---
 gdb/symfile-debug.c                     |  34 ++++----
 gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
 gdbsupport/safe-iterator.h              | 106 ++++++++++++++++++++++++
 8 files changed, 181 insertions(+), 42 deletions(-)

diff --git a/gdb/jit.c b/gdb/jit.c
index 85a10be3055..10ee5f9b749 100644
--- a/gdb/jit.c
+++ b/gdb/jit.c
@@ -1240,11 +1240,10 @@ jit_breakpoint_re_set (void)
 static void
 jit_inferior_exit_hook (struct inferior *inf)
 {
-  for (objfile *objf : current_program_space->objfiles_safe ())
+  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
     {
-      if (objf->jited_data != nullptr && objf->jited_data->addr != 0)
-	objf->unlink ();
-    }
+      return (objf->jited_data != nullptr) && (objf->jited_data->addr != 0);
+    });
 }
 
 void
diff --git a/gdb/objfiles.c b/gdb/objfiles.c
index 8f085b1bb7c..d53dfaca90b 100644
--- a/gdb/objfiles.c
+++ b/gdb/objfiles.c
@@ -470,6 +470,12 @@ objfile::unlink ()
   current_program_space->remove_objfile (this);
 }
 
+qf_safe_range
+objfile::qf_safe ()
+{
+  return qf_safe_range (qf_range (qf.begin (), qf.end ()));
+}
+
 /* Free all separate debug objfile of OBJFILE, but don't free OBJFILE
    itself.  */
 
@@ -793,14 +799,12 @@ have_full_symbols (void)
 void
 objfile_purge_solibs (void)
 {
-  for (objfile *objf : current_program_space->objfiles_safe ())
+  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
     {
       /* We assume that the solib package has been purged already, or will
 	 be soon.  */
-
-      if (!(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED))
-	objf->unlink ();
-    }
+      return !(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED);
+    });
 }
 
 
diff --git a/gdb/objfiles.h b/gdb/objfiles.h
index 621342d22b7..34b3dac0b26 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -370,6 +370,12 @@ class separate_debug_iterator
 
 typedef iterator_range<separate_debug_iterator> separate_debug_range;
 
+/* See objfile::qf_safe.  */
+
+using qf_list = std::forward_list<quick_symbol_functions_up>;
+using qf_range = iterator_range<qf_list::iterator>;
+using qf_safe_range = basic_safe_range<qf_range>;
+
 /* Sections in an objfile.  The section offsets are stored in the
    OBJFILE.  */
 
@@ -764,6 +770,10 @@ struct objfile
      reader.  */
   std::forward_list<quick_symbol_functions_up> qf;
 
+  /* Returns iterable object that allows for safe deletion during
+     iteration.  */
+  qf_safe_range qf_safe ();
+
   /* Per objfile data-pointers required by other GDB modules.  */
 
   registry<objfile> registry_fields;
diff --git a/gdb/progspace.c b/gdb/progspace.c
index 0fc1fdd57ab..c4cc2e2e8af 100644
--- a/gdb/progspace.c
+++ b/gdb/progspace.c
@@ -141,19 +141,19 @@ program_space::free_all_objfiles ()
 
 void
 program_space::add_objfile (std::unique_ptr<objfile> &&objfile,
-			    struct objfile *before)
+			    struct objfile *after)
 {
-  if (before == nullptr)
+  if (after == nullptr)
     objfiles_list.push_back (std::move (objfile));
   else
     {
       auto iter = std::find_if (objfiles_list.begin (), objfiles_list.end (),
 				[=] (const std::unique_ptr<::objfile> &objf)
 				{
-				  return objf.get () == before;
+				  return objf.get () == after;
 				});
       gdb_assert (iter != objfiles_list.end ());
-      objfiles_list.insert (iter, std::move (objfile));
+      objfiles_list.insert (++iter, std::move (objfile));
     }
 }
 
@@ -182,6 +182,17 @@ program_space::remove_objfile (struct objfile *objfile)
 
 /* See progspace.h.  */
 
+void
+program_space::unlink_objfiles_if
+  (gdb::function_view<bool (const objfile *objfile)> predicate)
+{
+  for (auto &it : objfiles_safe ())
+    if (predicate (it.get ()))
+      it->unlink ();
+}
+
+/* See progspace.h.  */
+
 struct objfile *
 program_space::objfile_for_address (CORE_ADDR address)
 {
diff --git a/gdb/progspace.h b/gdb/progspace.h
index 163cbf6a0f8..f23a57dc842 100644
--- a/gdb/progspace.h
+++ b/gdb/progspace.h
@@ -250,28 +250,32 @@ struct program_space
        unwrapping_objfile_iterator (objfiles_list.end ()));
   }
 
-  using objfiles_safe_range = basic_safe_range<objfiles_range>;
+  using objfiles_it_range = iterator_range<objfile_list::iterator>;
+  using objfiles_safe_reverse_range
+    = basic_safe_reverse_range<objfiles_it_range>;
 
   /* An iterable object that can be used to iterate over all objfiles.
      The basic use is in a foreach, like:
 
      for (objfile *objf : pspace->objfiles_safe ()) { ... }
 
-     This variant uses a basic_safe_iterator so that objfiles can be
-     deleted during iteration.  */
-  objfiles_safe_range objfiles_safe ()
+     This variant uses a basic_safe_reverse_iterator so that objfiles
+     can be deleted during iteration.
+
+     The use of a reverse iterator helps ensure that separate debug
+     objfiles are deleted before their parent objfile.  This prevents
+     iterator invalidation due to the deletion of a parent objfile.  */
+ objfiles_safe_reverse_range objfiles_safe ()
   {
-    return objfiles_safe_range
-      (objfiles_range
-	 (unwrapping_objfile_iterator (objfiles_list.begin ()),
-	  unwrapping_objfile_iterator (objfiles_list.end ())));
+    return objfiles_safe_reverse_range
+      (objfiles_it_range (objfiles_list.begin (), objfiles_list.end ()));
   }
 
-  /* Add OBJFILE to the list of objfiles, putting it just before
-     BEFORE.  If BEFORE is nullptr, it will go at the end of the
+  /* Add OBJFILE to the list of objfiles, putting it just after
+     AFTER.  If AFTER is nullptr, it will go at the end of the
      list.  */
   void add_objfile (std::unique_ptr<objfile> &&objfile,
-		    struct objfile *before);
+		    struct objfile *after);
 
   /* Remove OBJFILE from the list of objfiles.  */
   void remove_objfile (struct objfile *objfile);
@@ -286,6 +290,11 @@ struct program_space
   /* Free all the objfiles associated with this program space.  */
   void free_all_objfiles ();
 
+  /* Unlink all objfiles associated with this program space for which
+     PREDICATE evaluates to true.  */
+  void unlink_objfiles_if
+    (gdb::function_view<bool (const objfile *objfile)> predicate);
+
   /* Return the objfile containing ADDRESS, or nullptr if the address
      is outside all objfiles in this progspace.  */
   struct objfile *objfile_for_address (CORE_ADDR address);
diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index 32c3ed9d349..c684f9cdd43 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
      them, then that is an indication that they are in fact available.  Without
      this function the symbols may have been already read in but they also may
      not be present in this objfile.  */
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       retval = iter->has_symbols (this);
       if (retval)
@@ -108,7 +108,7 @@ objfile::has_unexpanded_symtabs ()
 		objfile_debug_name (this));
 
   bool result = false;
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       if (iter->has_unexpanded_symtabs (this))
 	{
@@ -133,7 +133,7 @@ objfile::find_last_source_symtab ()
     gdb_printf (gdb_stdlog, "qf->find_last_source_symtab (%s)\n",
 		objfile_debug_name (this));
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       retval = iter->find_last_source_symtab (this);
       if (retval != nullptr)
@@ -166,7 +166,7 @@ objfile::forget_cached_source_info ()
 	}
     }
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->forget_cached_source_info (this);
 }
 
@@ -213,7 +213,7 @@ objfile::map_symtabs_matching_filename
     return result;
   };
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       if (!iter->expand_symtabs_matching (this,
 					  match_one_filename,
@@ -278,7 +278,7 @@ objfile::lookup_symbol (block_enum kind, const char *name, domain_enum domain)
     return true;
   };
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       if (!iter->expand_symtabs_matching (this,
 					  nullptr,
@@ -309,7 +309,7 @@ objfile::print_stats (bool print_bcache)
     gdb_printf (gdb_stdlog, "qf->print_stats (%s, %d)\n",
 		objfile_debug_name (this), print_bcache);
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->print_stats (this, print_bcache);
 }
 
@@ -320,7 +320,7 @@ objfile::dump ()
     gdb_printf (gdb_stdlog, "qf->dump (%s)\n",
 		objfile_debug_name (this));
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->dump (this);
 }
 
@@ -335,7 +335,7 @@ objfile::expand_symtabs_for_function (const char *func_name)
   lookup_name_info base_lookup (func_name, symbol_name_match_type::FULL);
   lookup_name_info lookup_name = base_lookup.make_ignore_params ();
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->expand_symtabs_matching (this,
 				   nullptr,
 				   &lookup_name,
@@ -354,7 +354,7 @@ objfile::expand_all_symtabs ()
     gdb_printf (gdb_stdlog, "qf->expand_all_symtabs (%s)\n",
 		objfile_debug_name (this));
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->expand_all_symtabs (this);
 }
 
@@ -372,7 +372,7 @@ objfile::expand_symtabs_with_fullname (const char *fullname)
     return filename_cmp (basenames ? basename : fullname, filename) == 0;
   };
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->expand_symtabs_matching (this,
 				   file_matcher,
 				   nullptr,
@@ -406,7 +406,7 @@ objfile::expand_symtabs_matching
 		host_address_to_string (&expansion_notify),
 		search_domain_name (kind));
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     if (!iter->expand_symtabs_matching (this, file_matcher, lookup_name,
 					symbol_matcher, expansion_notify,
 					search_flags, domain, kind))
@@ -431,7 +431,7 @@ objfile::find_pc_sect_compunit_symtab (struct bound_minimal_symbol msymbol,
 		host_address_to_string (section),
 		warn_if_readin);
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       retval = iter->find_pc_sect_compunit_symtab (this, msymbol, pc, section,
 						   warn_if_readin);
@@ -459,7 +459,7 @@ objfile::map_symbol_filenames (gdb::function_view<symbol_filename_ftype> fun,
 		objfile_debug_name (this),
 		need_fullname);
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->map_symbol_filenames (this, fun, need_fullname);
 }
 
@@ -471,7 +471,7 @@ objfile::compute_main_name ()
 		"qf->compute_main_name (%s)\n",
 		objfile_debug_name (this));
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->compute_main_name (this);
 }
 
@@ -485,7 +485,7 @@ objfile::find_compunit_symtab_by_address (CORE_ADDR address)
 		hex_string (address));
 
   struct compunit_symtab *result = NULL;
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       result = iter->find_compunit_symtab_by_address (this, address);
       if (result != nullptr)
@@ -510,7 +510,7 @@ objfile::lookup_global_symbol_language (const char *name,
   enum language result = language_unknown;
   *symbol_found_p = false;
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       result = iter->lookup_global_symbol_language (this, name, domain,
 						    symbol_found_p);
diff --git a/gdb/testsuite/gdb.python/py-objfile.exp b/gdb/testsuite/gdb.python/py-objfile.exp
index 61b9942de79..0bf49976b73 100644
--- a/gdb/testsuite/gdb.python/py-objfile.exp
+++ b/gdb/testsuite/gdb.python/py-objfile.exp
@@ -135,7 +135,7 @@ gdb_test "p main" "= {<text variable, no debug info>} $hex <main>" \
 gdb_py_test_silent_cmd "python objfile.add_separate_debug_file(\"${binfile}\")" \
     "Add separate debug file file" 1
 
-gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[0\]" \
+gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[1\]" \
     "Get separate debug info objfile" 1
 
 gdb_test "python print (sep_objfile.owner.filename)" "${testfile}2" \
diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h
index b4891328f1a..8bb80729bbd 100644
--- a/gdbsupport/safe-iterator.h
+++ b/gdbsupport/safe-iterator.h
@@ -136,4 +136,110 @@ class basic_safe_range
   Range m_range;
 };
 
+/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
+
+template<typename Iterator>
+class basic_safe_reverse_iterator
+{
+public:
+  typedef basic_safe_reverse_iterator self_type;
+  typedef typename Iterator::value_type value_type;
+  typedef typename Iterator::reference reference;
+  typedef typename Iterator::pointer pointer;
+  typedef typename Iterator::iterator_category iterator_category;
+  typedef typename Iterator::difference_type difference_type;
+
+  /* Construct the iterator using ARG, and construct the end iterator
+     using ARG2.  */
+  template<typename Arg>
+  explicit basic_safe_reverse_iterator (Arg &&arg, Arg &&arg2)
+    : m_begin (std::forward<Arg> (arg)),
+      m_end (std::forward<Arg> (arg2)),
+      m_it (m_end),
+      m_next (m_end)
+  {
+    /* M_IT and M_NEXT are initialized as one-past-end.  Set M_IT to point
+       to the last element and set M_NEXT to point to the second last element,
+       if such elements exist.  */
+    if (m_it != m_begin)
+      {
+	--m_it;
+
+	if (m_it != m_begin)
+	  {
+	    --m_next;
+	    --m_next;
+	  }
+      }
+  }
+
+  typename std::invoke_result<decltype(&Iterator::operator*), Iterator>::type
+    operator* () const
+  { return *m_it; }
+
+  self_type &operator++ ()
+  {
+    m_it = m_next;
+
+    if (m_it != m_end)
+      {
+	/* Use M_BEGIN only if we sure that it is valid.  */
+	if (m_it == m_begin)
+	  m_next = m_end;
+	else
+	  --m_next;
+      }
+
+    return *this;
+  }
+
+  bool operator== (const self_type &other) const
+  { return m_it == other.m_it; }
+
+  bool operator!= (const self_type &other) const
+  { return m_it != other.m_it; }
+
+private:
+  /* The first element.  */
+  Iterator m_begin {};
+
+  /* A one-past-end iterator.  */
+  Iterator m_end {};
+
+  /* The current element.  */
+  Iterator m_it {};
+
+  /* The next element.  Always one element ahead of M_IT.  */
+  Iterator m_next {};
+};
+
+/* A range adapter that wraps a forward range, and then returns
+   safe reverse iterators wrapping the original range's iterators.  */
+
+template<typename Range>
+class basic_safe_reverse_range
+{
+public:
+
+  typedef basic_safe_reverse_iterator<typename Range::iterator> iterator;
+
+  explicit basic_safe_reverse_range (Range range)
+    : m_range (range)
+  {
+  }
+
+  iterator begin ()
+  {
+    return iterator (m_range.begin (), m_range.end ());
+  }
+
+  iterator end ()
+  {
+    return iterator (m_range.end (), m_range.end ());
+  }
+
+private:
+
+  Range m_range;
+};
 #endif /* COMMON_SAFE_ITERATOR_H */
-- 
2.43.0


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

* [PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading
  2024-01-17 15:48 [PATCH 0/4] On-demand debuginfo downloading Aaron Merey
  2024-01-17 15:48 ` [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
  2024-01-17 15:48 ` [PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator Aaron Merey
@ 2024-01-17 15:48 ` Aaron Merey
  2024-01-18 15:34   ` Andrew Burgess
  2024-01-17 15:48 ` [PATCH 4/4 v6] gdb/debuginfod: Add .debug_line downloading Aaron Merey
  3 siblings, 1 reply; 29+ messages in thread
From: Aaron Merey @ 2024-01-17 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, thiago.bauermann, Aaron Merey

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)
+    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;
+}
+
 \f
 
 /* 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" } {
-- 
2.43.0


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

* [PATCH 4/4 v6] gdb/debuginfod: Add .debug_line downloading
  2024-01-17 15:48 [PATCH 0/4] On-demand debuginfo downloading Aaron Merey
                   ` (2 preceding siblings ...)
  2024-01-17 15:48 ` [PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading Aaron Merey
@ 2024-01-17 15:48 ` Aaron Merey
  2024-02-07 21:25   ` [PING][PATCH " Aaron Merey
  3 siblings, 1 reply; 29+ messages in thread
From: Aaron Merey @ 2024-01-17 15:48 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, thiago.bauermann, Aaron Merey

v6 adds a testcase for downloads during autocompletion.  It also
implements the suggestions from Thiago's review:
https://sourceware.org/pipermail/gdb-patches/2023-December/205507.html

ELF/DWARF section downloading allows gdb to download .gdb_index files in
order to defer full debuginfo downloads.  However .gdb_index does not
contain any information regarding source filenames.  When a gdb command
includes a filename argument (ex. 'break main.c:50'), this results in
the mass downloading of all deferred debuginfo so that gdb can search the
debuginfo for matching source filenames.  This can result in unnecessary
downloads.

To improve this, have gdb instead download each debuginfo's .debug_line
(and .debug_line_str if using DWARF5) when executing these commands.
Download full debuginfo only when its .debug_line contains a matching
filename.

Since the combined size of .debug_line and .debug_line_str is only about
1% the size of the corresponding debuginfo, significant time can be saved
by checking these sections before choosing to download an entire debuginfo.

This patch also redirects stdout and stderr of the debuginfod server
used by testsuite/gdb.debuginfod tests to a server_log standard output
file.  While adding tests for this patch I ran into an issue where the
test server would block when logging to stderr, presumably because the
stderr buffer filled up and wasn't being read from.  Redirecting the
log to a file fixes this and also makes the server log more accessible
when debugging test failures.
---
 gdb/cli-out.c                            |  11 +-
 gdb/completer.c                          |  18 +-
 gdb/dwarf2/line-header.c                 | 161 ++++++++++++------
 gdb/dwarf2/line-header.h                 |  10 ++
 gdb/dwarf2/read-gdb-index.c              |  60 +++++++
 gdb/dwarf2/read.c                        | 208 +++++++++++++++++++++++
 gdb/dwarf2/read.h                        |  37 ++++
 gdb/mi/mi-out.c                          |   9 +-
 gdb/testsuite/gdb.debuginfod/section.exp |  27 +++
 gdb/ui-out.c                             |   3 +
 gdb/ui-out.h                             |  20 +++
 11 files changed, 511 insertions(+), 53 deletions(-)

diff --git a/gdb/cli-out.c b/gdb/cli-out.c
index b4c7a657423..79f61eed62c 100644
--- a/gdb/cli-out.c
+++ b/gdb/cli-out.c
@@ -307,16 +307,23 @@ cli_ui_out::do_progress_notify (const std::string &msg,
 
   if (info.state == progress_update::START)
     {
+      std::string prefix;
+      if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED)
+	{
+	  prefix = "\n";
+	  cur_prefix_state = prefix_state_t::NEWLINE_PRINTED;
+	}
+
       if (stream->isatty ()
 	  && current_ui->input_interactive_p ()
 	  && chars_per_line >= MIN_CHARS_PER_LINE)
 	{
-	  gdb_printf (stream, "%s\n", msg.c_str ());
+	  gdb_printf (stream, "%s\n", (prefix + msg).c_str ());
 	  info.state = progress_update::BAR;
 	}
       else
 	{
-	  gdb_printf (stream, "%s...\n", msg.c_str ());
+	  gdb_printf (stream, "%s...\n", (prefix + msg).c_str ());
 	  info.state = progress_update::WORKING;
 	}
     }
diff --git a/gdb/completer.c b/gdb/completer.c
index 8e2f031f13a..6d453d0d197 100644
--- a/gdb/completer.c
+++ b/gdb/completer.c
@@ -1362,6 +1362,10 @@ complete_line_internal_1 (completion_tracker &tracker,
     {
       /* We've recognized a full command.  */
 
+      /* Disable pagination since responding to the pagination prompt
+	 overwrites rl_line_buffer.  */
+      scoped_restore pag_restore = make_scoped_restore (&pagination_enabled, false);
+
       if (p == tmp_command + point)
 	{
 	  /* There is no non-whitespace in the line beyond the
@@ -1461,7 +1465,8 @@ complete_line_internal_1 (completion_tracker &tracker,
 }
 
 /* Wrapper around complete_line_internal_1 to handle
-   MAX_COMPLETIONS_REACHED_ERROR.  */
+   MAX_COMPLETIONS_REACHED_ERROR and possible progress update
+   interactions.  */
 
 static void
 complete_line_internal (completion_tracker &tracker,
@@ -1469,6 +1474,11 @@ complete_line_internal (completion_tracker &tracker,
 			const char *line_buffer, int point,
 			complete_line_internal_reason reason)
 {
+  scoped_restore restore_prefix_state
+    = make_scoped_restore
+      (&cur_prefix_state,
+       ui_out::progress_update::prefix_state::NEWLINE_NEEDED);
+
   try
     {
       complete_line_internal_1 (tracker, text, line_buffer, point, reason);
@@ -1478,6 +1488,12 @@ complete_line_internal (completion_tracker &tracker,
       if (except.error != MAX_COMPLETIONS_REACHED_ERROR)
 	throw;
     }
+
+  /* If progress update messages printed, then the text being completed
+     needs to be printed again.  */
+  if (cur_prefix_state
+      == ui_out::progress_update::prefix_state::NEWLINE_PRINTED)
+    rl_forced_update_display ();
 }
 
 /* See completer.h.  */
diff --git a/gdb/dwarf2/line-header.c b/gdb/dwarf2/line-header.c
index ddaf7e4a527..aec02b3e1f7 100644
--- a/gdb/dwarf2/line-header.c
+++ b/gdb/dwarf2/line-header.c
@@ -102,12 +102,15 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
 {
   LONGEST length = read_initial_length (abfd, buf, bytes_read);
 
-  gdb_assert (cu_header->initial_length_size == 4
-	      || cu_header->initial_length_size == 8
-	      || cu_header->initial_length_size == 12);
+  if (cu_header != nullptr)
+    {
+      gdb_assert (cu_header->initial_length_size == 4
+		  || cu_header->initial_length_size == 8
+		  || cu_header->initial_length_size == 12);
 
-  if (cu_header->initial_length_size != *bytes_read)
-    complaint (_("intermixed 32-bit and 64-bit DWARF sections"));
+      if (cu_header->initial_length_size != *bytes_read)
+	complaint (_("intermixed 32-bit and 64-bit DWARF sections"));
+    }
 
   *offset_size = (*bytes_read == 4) ? 4 : 8;
   return length;
@@ -116,21 +119,27 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
 /* Read directory or file name entry format, starting with byte of
    format count entries, ULEB128 pairs of entry formats, ULEB128 of
    entries count and the entries themselves in the described entry
-   format.  */
+   format.
+
+   .debug_line and .debug_line_str sections are stored in LINE_BUFP
+   and LINE_STR_DATA respectively.  */
 
 static void
-read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
-			const gdb_byte **bufp, struct line_header *lh,
-			unsigned int offset_size,
-			void (*callback) (struct line_header *lh,
-					  const char *name,
-					  dir_index d_index,
-					  unsigned int mod_time,
-					  unsigned int length))
+read_formatted_entries
+  (bfd *abfd, const gdb_byte **line_bufp,
+   const gdb::array_view<const gdb_byte> line_str_data,
+   struct line_header *lh,
+   unsigned int offset_size,
+   void (*callback) (struct line_header *lh,
+		     const char *name,
+		     dir_index d_index,
+		     unsigned int mod_time,
+		     unsigned int length))
 {
   gdb_byte format_count, formati;
   ULONGEST data_count, datai;
-  const gdb_byte *buf = *bufp;
+  const gdb_byte *buf = *line_bufp;
+  const gdb_byte *str_buf = line_str_data.data ();
   const gdb_byte *format_header_data;
   unsigned int bytes_read;
 
@@ -157,7 +166,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
 	  ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
 	  format += bytes_read;
 
-	  ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
+	  ULONGEST form = read_unsigned_leb128 (abfd, format, &bytes_read);
 	  format += bytes_read;
 
 	  std::optional<const char *> string;
@@ -172,12 +181,24 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
 
 	    case DW_FORM_line_strp:
 	      {
-		const char *str
-		  = per_objfile->read_line_string (buf, offset_size);
+		if (line_str_data.empty ())
+		  warning (_("Dwarf Error: DW_FORM_line_strp used without " \
+			     "required section"));
+
+		if (line_str_data.size () <= offset_size)
+		  warning (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
+			     "of section .debug_line_str"));
+
+		ULONGEST str_offset = read_offset (abfd, buf, offset_size);
+		const char *str;
+		if (str_buf[str_offset] == '\0')
+		  str = nullptr;
+		else
+		  str = (const char *) (str_buf + str_offset);
 		string.emplace (str);
 		buf += offset_size;
+		break;
 	      }
-	      break;
 
 	    case DW_FORM_data1:
 	      uint.emplace (read_1_byte (abfd, buf));
@@ -248,28 +269,30 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
       callback (lh, fe.name, fe.d_index, fe.mod_time, fe.length);
     }
 
-  *bufp = buf;
+  *line_bufp = buf;
 }
 
 /* See line-header.h.  */
 
 line_header_up
-dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
-			   dwarf2_per_objfile *per_objfile,
-			   struct dwarf2_section_info *section,
-			   const struct comp_unit_head *cu_header,
-			   const char *comp_dir)
+dwarf_decode_line_header (bfd *abfd,
+			  gdb::array_view<const gdb_byte> line_data,
+			  gdb::array_view<const gdb_byte> line_str_data,
+			  const gdb_byte **debug_line_ptr,
+			  bool is_dwz,
+			  const struct comp_unit_head *cu_header,
+			  const char *comp_dir)
 {
-  const gdb_byte *line_ptr;
+  const gdb_byte *line_ptr, *buf;
   unsigned int bytes_read, offset_size;
   int i;
   const char *cur_dir, *cur_file;
 
-  bfd *abfd = section->get_bfd_owner ();
+  buf = *debug_line_ptr;
 
   /* Make sure that at least there's room for the total_length field.
      That could be 12 bytes long, but we're just going to fudge that.  */
-  if (to_underlying (sect_off) + 4 >= section->size)
+  if (buf + 4 >= line_data.data () + line_data.size ())
     {
       dwarf2_statement_list_fits_in_line_number_section_complaint ();
       return 0;
@@ -277,24 +300,26 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
 
   line_header_up lh (new line_header (comp_dir));
 
-  lh->sect_off = sect_off;
+  lh->sect_off = (sect_offset) (buf - line_data.data ());
   lh->offset_in_dwz = is_dwz;
 
-  line_ptr = section->buffer + to_underlying (sect_off);
+  line_ptr = buf;
 
   /* Read in the header.  */
   LONGEST unit_length
-    = read_checked_initial_length_and_offset (abfd, line_ptr, cu_header,
+    = read_checked_initial_length_and_offset (abfd, buf, cu_header,
 					      &bytes_read, &offset_size);
-  line_ptr += bytes_read;
 
-  const gdb_byte *start_here = line_ptr;
+  line_ptr += bytes_read;
 
-  if (line_ptr + unit_length > (section->buffer + section->size))
+  if (line_ptr + unit_length > buf + line_data.size ())
     {
       dwarf2_statement_list_fits_in_line_number_section_complaint ();
       return 0;
     }
+
+  const gdb_byte *start_here = line_ptr;
+
   lh->statement_program_end = start_here + unit_length;
   lh->version = read_2_bytes (abfd, line_ptr);
   line_ptr += 2;
@@ -303,7 +328,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
       /* This is a version we don't understand.  The format could have
 	 changed in ways we don't handle properly so just punt.  */
       complaint (_("unsupported version in .debug_line section"));
-      return NULL;
+      return nullptr;
     }
   if (lh->version >= 5)
     {
@@ -320,13 +345,14 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
 	  complaint (_("unsupported segment selector size %u "
 		       "in .debug_line section"),
 		     segment_selector_size);
-	  return NULL;
+	  return nullptr;
 	}
     }
 
   LONGEST header_length = read_offset (abfd, line_ptr, offset_size);
   line_ptr += offset_size;
   lh->statement_program_start = line_ptr + header_length;
+
   lh->minimum_instruction_length = read_1_byte (abfd, line_ptr);
   line_ptr += 1;
 
@@ -347,12 +373,16 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
 
   lh->default_is_stmt = read_1_byte (abfd, line_ptr);
   line_ptr += 1;
+
   lh->line_base = read_1_signed_byte (abfd, line_ptr);
   line_ptr += 1;
+
   lh->line_range = read_1_byte (abfd, line_ptr);
   line_ptr += 1;
+
   lh->opcode_base = read_1_byte (abfd, line_ptr);
   line_ptr += 1;
+
   lh->standard_opcode_lengths.reset (new unsigned char[lh->opcode_base]);
 
   lh->standard_opcode_lengths[0] = 1;  /* This should never be used anyway.  */
@@ -365,21 +395,23 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
   if (lh->version >= 5)
     {
       /* Read directory table.  */
-      read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
-			      offset_size,
-			      [] (struct line_header *header, const char *name,
-				  dir_index d_index, unsigned int mod_time,
-				  unsigned int length)
+      read_formatted_entries
+	(abfd, &line_ptr, line_str_data,
+	 lh.get (), offset_size,
+	 [] (struct line_header *header, const char *name,
+	     dir_index d_index, unsigned int mod_time,
+	     unsigned int length)
 	{
 	  header->add_include_dir (name);
 	});
 
       /* Read file name table.  */
-      read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
-			      offset_size,
-			      [] (struct line_header *header, const char *name,
-				  dir_index d_index, unsigned int mod_time,
-				  unsigned int length)
+      read_formatted_entries
+	(abfd, &line_ptr, line_str_data,
+	 lh.get (), offset_size,
+	 [] (struct line_header *header, const char *name,
+	     dir_index d_index, unsigned int mod_time,
+	     unsigned int length)
 	{
 	  header->add_file_name (name, d_index, mod_time, length);
 	});
@@ -387,7 +419,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
   else
     {
       /* Read directory table.  */
-      while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL)
+      while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr)
 	{
 	  line_ptr += bytes_read;
 	  lh->add_include_dir (cur_dir);
@@ -395,7 +427,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
       line_ptr += bytes_read;
 
       /* Read file name table.  */
-      while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL)
+      while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr)
 	{
 	  unsigned int mod_time, length;
 	  dir_index d_index;
@@ -413,9 +445,40 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
       line_ptr += bytes_read;
     }
 
-  if (line_ptr > (section->buffer + section->size))
+  if (line_ptr > (buf + line_data.size ()))
     complaint (_("line number info header doesn't "
 		 "fit in `.debug_line' section"));
 
+  *debug_line_ptr += unit_length + offset_size;
   return lh;
 }
+
+line_header_up
+dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
+			   dwarf2_per_objfile *per_objfile,
+			   struct dwarf2_section_info *section,
+			   const struct comp_unit_head *cu_header,
+			   const char *comp_dir)
+{
+  struct objfile *objfile = per_objfile->objfile;
+  struct dwarf2_per_bfd *per_bfd = per_objfile->per_bfd;
+
+  /* Read .debug_line.  */
+  dwarf2_section_info *line_sec = &per_bfd->line;
+  bfd_size_type line_size = line_sec->get_size (objfile);
+
+  gdb::array_view<const gdb_byte> line (line_sec->buffer, line_size);
+
+  /* Read .debug_line_str.  */
+  dwarf2_section_info *line_str_sec = &per_bfd->line_str;
+  bfd_size_type line_str_size = line_str_sec->get_size (objfile);
+
+  gdb::array_view<const gdb_byte> line_str (line_str_sec->buffer,
+					    line_str_size);
+
+  const gdb_byte *line_ptr = line.data () + to_underlying (sect_off);
+
+  return dwarf_decode_line_header
+    (per_bfd->obfd, line, line_str, &line_ptr,
+     is_dwz, cu_header, comp_dir);
+}
diff --git a/gdb/dwarf2/line-header.h b/gdb/dwarf2/line-header.h
index 06d2eec573b..1dffb7c7e5e 100644
--- a/gdb/dwarf2/line-header.h
+++ b/gdb/dwarf2/line-header.h
@@ -217,4 +217,14 @@ extern line_header_up dwarf_decode_line_header
    struct dwarf2_section_info *section, const struct comp_unit_head *cu_header,
    const char *comp_dir);
 
+/* Like above but the .debug_line and .debug_line_str are stored in
+   LINE_DATA and LINE_STR_DATA.  *DEBUG_LINE_PTR should point to a
+   statement program header within LINE_DATA.  */
+
+extern line_header_up dwarf_decode_line_header
+  (bfd *abfd, gdb::array_view<const gdb_byte> line_data,
+   gdb::array_view<const gdb_byte> line_str_data,
+   const gdb_byte **debug_line_ptr, bool is_dwz,
+  const comp_unit_head *cu_header, const char *comp_dir);
+
 #endif /* DWARF2_LINE_HEADER_H */
diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
index 55b04391eb5..3d75678d7dc 100644
--- a/gdb/dwarf2/read-gdb-index.c
+++ b/gdb/dwarf2/read-gdb-index.c
@@ -131,6 +131,9 @@ struct mapped_gdb_index final : public mapped_index_base
   }
 };
 
+struct mapped_debug_line;
+typedef std::unique_ptr<mapped_debug_line> mapped_debug_line_up;
+
 struct dwarf2_gdb_index : public dwarf2_base_index_functions
 {
   /* This dumps minimal information about the index.
@@ -162,6 +165,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
      domain_enum domain,
      enum search_domain kind);
 
+ /* If OBJFILE's debuginfo download has been deferred, use a mapped_debug_line
+    to generate filenames.
+
+    Otherwise call dwarf2_base_index_functions::map_symbol_filenames.  */
+
+  void map_symbol_filenames (struct objfile *objfile,
+			     gdb::function_view<symbol_filename_ftype> fun,
+			     bool need_fullname) override;
+
   /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
      debuginfo if necessary.  */
   void expand_all_symtabs (struct objfile *objfile) override;
@@ -169,6 +181,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
   /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
      debuginfo if necessary.  */
   struct symtab *find_last_source_symtab (struct objfile *objfile) override;
+
+  /* Filename information related to this .gdb_index.  */
+  mapped_debug_line_up mdl;
+
+  /* Return true if any of the filenames in this .gdb_index's .debug_line
+     mapping match FILE_MATCHER.  Initializes the mapping if necessary.  */
+  bool filename_in_debug_line
+    (objfile *objfile,
+     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher);
 };
 
 void
@@ -204,6 +225,30 @@ dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
     }
 }
 
+void
+dwarf2_gdb_index::map_symbol_filenames
+     (struct objfile *objfile,
+      gdb::function_view<symbol_filename_ftype> fun,
+      bool need_fullname)
+{
+  try
+    {
+      dwarf2_base_index_functions::map_symbol_filenames (objfile, fun,
+							 need_fullname);
+    }
+  catch (const gdb_exception &e)
+    {
+      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
+	exception_print (gdb_stderr, e);
+      else
+	{
+	  if (mdl == nullptr)
+	    mdl.reset (new mapped_debug_line (objfile));
+	  mdl->map_filenames (fun, need_fullname);
+	}
+    }
+}
+
 /* 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
@@ -371,6 +416,17 @@ dwarf2_gdb_index::do_expand_symtabs_matching
   return result;
 }
 
+bool
+dwarf2_gdb_index::filename_in_debug_line
+  (objfile *objfile,
+   gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
+{
+  if (mdl == nullptr)
+    mdl.reset (new mapped_debug_line (objfile));
+
+  return mdl->contains_matching_filename (file_matcher);
+}
+
 bool
 dwarf2_gdb_index::expand_symtabs_matching
     (struct objfile *objfile,
@@ -399,6 +455,10 @@ dwarf2_gdb_index::expand_symtabs_matching
 	  return false;
 	}
 
+      if (file_matcher != nullptr
+	  && !filename_in_debug_line (objfile, file_matcher))
+	return true;
+
       read_full_dwarf_from_debuginfod (objfile, this);
       return true;
     }
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 8725a2fbcdb..4af83728c63 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -82,6 +82,7 @@
 #include <optional>
 #include "gdbsupport/underlying.h"
 #include "gdbsupport/hash_enum.h"
+#include "gdbsupport/scoped_mmap.h"
 #include "filename-seen-cache.h"
 #include "producer.h"
 #include <fcntl.h>
@@ -1950,6 +1951,213 @@ dw2_get_file_names (dwarf2_per_cu_data *this_cu,
   return this_cu->file_names;
 }
 
+#if !HAVE_SYS_MMAN_H
+
+bool
+mapped_debug_line::contains_matching_filename
+  (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
+{
+  return false;
+}
+
+gdb::array_view<const gdb_byte>
+mapped_debug_line::read_debug_line_separate
+  (const char *filename, std::unique_ptr<index_cache_resource> *resource)
+{
+  return {};
+}
+
+bool
+mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile)
+{
+  return false;
+}
+
+void
+mapped_debug_line::map_filenames
+  (gdb::function_view<symbol_filename_ftype> fun,
+   bool need_fullname)
+{
+  return;
+}
+
+#else /* !HAVE_SYS_MMAN_H */
+
+struct line_resource_mmap final : public index_cache_resource
+{
+  /* Try to mmap FILENAME.  Throw an exception on failure, including if the
+     file doesn't exist.  */
+  line_resource_mmap (const char *filename)
+    : mapping (mmap_file (filename))
+  {}
+
+  scoped_mmap mapping;
+};
+
+/* See read.h.  */
+
+bool
+mapped_debug_line::contains_matching_filename
+  (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
+{
+  for (line_header_up &lh : line_headers)
+    for (file_entry &fe : lh->file_names ())
+      {
+	const char *filename = fe.name;
+
+	if (file_matcher (fe.name, false))
+	  return true;
+
+	bool basename_match = file_matcher (lbasename (fe.name), true);
+
+	if (!basenames_may_differ && !basename_match)
+	  continue;
+
+	/* DW_AT_comp_dir is not explicitly mentioned in the .debug_line
+	   until DWARF5.  Since we don't have access to the CU at this
+	   point we just check for a partial match on the filename.
+	   If there is a match, the full debuginfo will be downloaded
+	   ane the match will be re-evalute with DW_AT_comp_dir.  */
+	if (lh->version < 5 && fe.d_index == 0)
+	  return basename_match;
+
+	const char *dirname = fe.include_dir (&*lh);
+	std::string fullname;
+
+	if (dirname == nullptr || IS_ABSOLUTE_PATH (filename))
+	  fullname = filename;
+	else
+	  fullname = std::string (dirname) + SLASH_STRING + filename;
+
+	gdb::unique_xmalloc_ptr<char> rewritten
+	  = rewrite_source_path (fullname.c_str ());
+	if (rewritten != nullptr)
+	  fullname = rewritten.release ();
+
+	if (file_matcher (fullname.c_str (), false))
+	  return true;
+      }
+
+  return false;
+}
+
+/* See read.h.  */
+
+void
+mapped_debug_line::map_filenames
+  (gdb::function_view<symbol_filename_ftype> fun,
+   bool need_fullname)
+{
+  for (line_header_up &lh : line_headers)
+    for (file_entry &fe : lh->file_names ())
+      {
+	const char *filename = fe.name;
+
+	if (!need_fullname)
+	  {
+	    fun (filename, nullptr);
+	    continue;
+	  }
+
+	const char *dirname = fe.include_dir (&*lh);
+	std::string fullname;
+
+	if (dirname == nullptr || IS_ABSOLUTE_PATH (filename))
+	  fullname = filename;
+	else
+	  fullname = std::string (dirname) + SLASH_STRING + filename;
+
+	gdb::unique_xmalloc_ptr<char> rewritten
+	  = rewrite_source_path (fullname.c_str ());
+	if (rewritten != nullptr)
+	  fullname = rewritten.release ();
+
+	fun (filename, fullname.c_str ());
+      }
+}
+
+/* See read.h.  */
+
+gdb::array_view<const gdb_byte>
+mapped_debug_line::read_debug_line_separate
+  (const char *filename, std::unique_ptr<index_cache_resource> *resource)
+{
+  if (filename == nullptr)
+    return {};
+
+  try
+  {
+    line_resource_mmap *mmap_resource
+      = new line_resource_mmap (filename);
+
+    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 &except)
+  {
+    exception_print (gdb_stderr, except);
+  }
+
+  return {};
+}
+
+/* See read.h.  */
+
+bool
+mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile)
+{
+  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
+  if (build_id == nullptr)
+    return false;
+
+  gdb::unique_xmalloc_ptr<char> line_path;
+  scoped_fd line_fd = debuginfod_section_query (build_id->data,
+						build_id->size,
+						bfd_get_filename
+						  (objfile->obfd.get ()),
+						".debug_line",
+						&line_path);
+
+  if (line_fd.get () < 0)
+    return false;
+
+  gdb::unique_xmalloc_ptr<char> line_str_path;
+  scoped_fd line_str_fd = debuginfod_section_query (build_id->data,
+						    build_id->size,
+						    bfd_get_filename
+						      (objfile->obfd.get ()),
+						    ".debug_line_str",
+						    &line_str_path);
+
+  line_data = read_debug_line_separate (line_path.get (), &line_resource);
+  line_str_data = read_debug_line_separate (line_str_path.get (),
+					    &line_str_resource);
+
+  const gdb_byte *line_ptr = line_data.data ();
+
+  while (line_ptr < line_data.data () + line_data.size ())
+    {
+      line_header_up lh
+	= dwarf_decode_line_header (objfile->obfd.get (),
+				    line_data, line_str_data,
+				    &line_ptr, false,
+				    nullptr, nullptr);
+      line_headers.emplace_back (std::move (lh));
+    }
+
+  return true;
+}
+#endif /* !HAVE_SYS_MMAN_H */
+
+mapped_debug_line::mapped_debug_line (objfile *objfile)
+{
+  if (!read_debug_line_from_debuginfod (objfile))
+    line_headers.clear ();
+}
+
 /* A helper for the "quick" functions which computes and caches the
    real path for a given file name from the line table.  */
 
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index 8b6f1864458..db16160b43e 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -33,6 +33,7 @@
 #include "gdbsupport/hash_enum.h"
 #include "gdbsupport/function-view.h"
 #include "gdbsupport/packed.h"
+#include "dwarf2/line-header.h"
 
 /* Hold 'maintenance (set|show) dwarf' commands.  */
 extern struct cmd_list_element *set_dwarf_cmdlist;
@@ -982,4 +983,40 @@ extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
 extern void read_full_dwarf_from_debuginfod (struct objfile *,
 					     dwarf2_base_index_functions *);
 
+struct mapped_debug_line
+{
+  mapped_debug_line (objfile *objfile);
+
+  /* Return true if any of the mapped .debug_line's filenames match
+     FILE_MATCHER.  */
+
+  bool contains_matching_filename
+    (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher);
+
+  /* Call FUN with each filename in this mapped .debug_line.  Include
+     each file's fullname if NEED_FULLNAME is true.  */
+
+  void map_filenames (gdb::function_view<symbol_filename_ftype> fun,
+		      bool need_fullname);
+
+private:
+  std::vector<line_header_up> line_headers;
+
+  gdb::array_view<const gdb_byte> line_data;
+  gdb::array_view<const gdb_byte> line_str_data;
+
+  std::unique_ptr<index_cache_resource> line_resource;
+  std::unique_ptr<index_cache_resource> line_str_resource;
+
+  /* Download the .debug_line and .debug_line_str associated with OBJFILE
+     and populate line_headers.  */
+
+  bool read_debug_line_from_debuginfod (objfile *objfile);
+
+  /* Initialize line_data and line_str_data with the .debug_line and
+    .debug_line_str downloaded read_debug_line_from_debuginfod.  */
+
+  gdb::array_view<const gdb_byte> read_debug_line_separate
+    (const char *filename, std::unique_ptr<index_cache_resource> *resource);
+};
 #endif /* DWARF2READ_H */
diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c
index 0bc13f963bc..ab955a0fc05 100644
--- a/gdb/mi/mi-out.c
+++ b/gdb/mi/mi-out.c
@@ -278,7 +278,14 @@ mi_ui_out::do_progress_notify (const std::string &msg, const char *unit,
 
   if (info.state == progress_update::START)
     {
-      gdb_printf ("%s...\n", msg.c_str ());
+      std::string prefix;
+      if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED)
+	{
+	  prefix = "\n";
+	  cur_prefix_state = prefix_state_t::NEWLINE_PRINTED;
+	}
+
+      gdb_printf ("%s...\n", (prefix + msg).c_str ());
       info.state = progress_update::WORKING;
     }
 }
diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
index c3872ecdfc9..3ac11152daa 100644
--- a/gdb/testsuite/gdb.debuginfod/section.exp
+++ b/gdb/testsuite/gdb.debuginfod/section.exp
@@ -18,6 +18,7 @@
 standard_testfile
 
 load_lib debuginfod-support.exp
+load_lib completion-support.exp
 
 require allow_debuginfod_tests
 
@@ -159,6 +160,32 @@ proc_with_prefix local_url { } {
     # Hit the breakpoint.
     set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
     gdb_test "c" $res "break continue"
+
+    clean_restart_with_prompt $sectexec "line 1"
+
+    # List source file using .debug_line download.
+    set res ".*\.debug_line.*$lib_sl1.*21.*extern void libsection2_test.*"
+    gdb_test "list $libsrc1:21" $res "line 1 list"
+
+    clean_restart_with_prompt $sectexec "line 2"
+
+    # Set breakpoint using .debug_line download.
+    set res ".*section \.debug_line for $lib_sl1.*Breakpoint 2 at.*$libsrc1.*"
+    gdb_test "br $libsrc1:41" $res "line 2 br"
+
+    # Continue to breakpoint.
+    set res "Breakpoint 2, libsection1_test.*\"Cancelling thread\\\\n\".*"
+    gdb_test "c" $res "line 2 continue"
+
+    # Check that download progress message is correctly formatted
+    # when printing threads.
+    set res ".*separate debug info for $lib_sl2\.\.\.\r\n.* 2    Thread.*"
+    gdb_test "info thr" $res "line thread"
+
+    clean_restart_with_prompt $sectexec "autocomplete"
+
+    # Download debuginfo during autocompletion.
+    test_gdb_complete_tab_unique "br lib" ".*Downloading separate debug.*" ""
 }
 
 # Create CACHE and DB directories ready for debuginfod to use.
diff --git a/gdb/ui-out.c b/gdb/ui-out.c
index 75f9f1612cb..d9a620b70e8 100644
--- a/gdb/ui-out.c
+++ b/gdb/ui-out.c
@@ -32,6 +32,9 @@
 #include <memory>
 #include <string>
 
+/* Current state of newline prefixing for progress update messages.  */
+prefix_state_t cur_prefix_state = prefix_state_t::NEWLINE_OFF;
+
 namespace {
 
 /* A header of a ui_out_table.  */
diff --git a/gdb/ui-out.h b/gdb/ui-out.h
index 2934d3a6b34..2c21603f1a7 100644
--- a/gdb/ui-out.h
+++ b/gdb/ui-out.h
@@ -296,6 +296,21 @@ class ui_out
       BAR
     };
 
+    /* Used to communicate the status of a newline prefix for the next progress
+       update message.  */
+    enum prefix_state
+    {
+      /* Do not modify the next progress update message.  */
+      NEWLINE_OFF,
+
+      /* The next progress update message should include a newline prefix.  */
+      NEWLINE_NEEDED,
+
+      /* A newline prefix was included in a debuginfod progress update
+	 message.  */
+      NEWLINE_PRINTED
+    };
+
     /* SHOULD_PRINT indicates whether something should be printed for a tty.  */
     progress_update ()
     {
@@ -393,6 +408,11 @@ class ui_out
   ui_out_level *current_level () const;
 };
 
+typedef ui_out::progress_update::prefix_state prefix_state_t;
+
+/* Current state of the newline prefix.  */
+extern prefix_state_t cur_prefix_state;
+
 /* Start a new tuple or list on construction, and end it on
    destruction.  Normally this is used via the typedefs
    ui_out_emit_tuple and ui_out_emit_list.  */
-- 
2.43.0


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

* Re: [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo
  2024-01-17 15:48 ` [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
@ 2024-01-18 11:22   ` Andrew Burgess
  2024-01-19  5:28     ` Aaron Merey
  0 siblings, 1 reply; 29+ messages in thread
From: Andrew Burgess @ 2024-01-18 11:22 UTC (permalink / raw)
  To: Aaron Merey, gdb-patches; +Cc: thiago.bauermann, Aaron Merey

Aaron Merey <amerey@redhat.com> writes:

> v8 fixes a few style issues suggested by Thiago:
> https://sourceware.org/pipermail/gdb-patches/2023-December/205504.html
>
> Introduce new ui_file buffering_file to temporarily collect output
> written to gdb_std* output streams during print_thread, print_frame_info
> and print_stop_event.
>
> This ensures that output during these functions is not interrupted
> by debuginfod progress messages.
>
> With the addition of deferred debuginfo downloading it is possible
> for download progress messages to print during these events.
> Without any intervention we can end up with poorly formatted output:
>
>     (gdb) backtrace
>     [...]
>     #8  0x00007fbe8af7d7cf in pygi_invoke_c_callable (Downloading separate debug info for /lib64/libpython3.11.so.1.0
>     function_cache=0x561221b224d0, state=<optimized out>...
>
> To fix this we buffer writes to gdb_std* output streams while allowing
> debuginfod progress messages to skip the buffers and print to the
> underlying output streams immediately.  Buffered output is then written
> to the output streams.  This ensures that progress messages print first,
> followed by uninterrupted frame/thread/stop info:
>
>     (gdb) backtrace
>     [...]
>     Downloading separate debug info for /lib64/libpython3.11.so.1.0
>     #8  0x00007fbe8af7d7cf in pygi_invoke_c_callable (function_cache=0x561221b224d0, state=<optimized out>...
>
> Co-Authored-By: Andrew Burgess <aburgess@redhat.com>
> ---
>  gdb/cli-out.c            |  10 ++-
>  gdb/cli-out.h            |   3 +
>  gdb/debuginfod-support.c |  15 ++--
>  gdb/infrun.c             |  16 +++-
>  gdb/mi/mi-out.h          |   3 +
>  gdb/python/py-uiout.h    |   3 +
>  gdb/stack.c              |  35 +++++---
>  gdb/thread.c             | 180 ++++++++++++++++++++++----------------
>  gdb/ui-file.h            |   2 +-
>  gdb/ui-out.c             | 141 ++++++++++++++++++++++++++++++
>  gdb/ui-out.h             | 184 +++++++++++++++++++++++++++++++++++++++
>  11 files changed, 493 insertions(+), 99 deletions(-)
>
> diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> index a1cde54feda..b4c7a657423 100644
> --- a/gdb/cli-out.c
> +++ b/gdb/cli-out.c
> @@ -299,7 +299,7 @@ cli_ui_out::do_progress_notify (const std::string &msg,
>  				double howmuch, double total)
>  {
>    int chars_per_line = get_chars_per_line ();
> -  struct ui_file *stream = m_streams.back ();
> +  struct ui_file *stream = get_unbuffered (m_streams.back ());
>    cli_progress_info &info (m_progress_info.back ());
>  
>    if (chars_per_line > MAX_CHARS_PER_LINE)
> @@ -384,7 +384,7 @@ cli_ui_out::do_progress_notify (const std::string &msg,
>  void
>  cli_ui_out::clear_progress_notify ()
>  {
> -  struct ui_file *stream = m_streams.back ();
> +  struct ui_file *stream = get_unbuffered (m_streams.back ());
>    int chars_per_line = get_chars_per_line ();
>  
>    scoped_restore save_pagination
> @@ -413,10 +413,12 @@ void
>  cli_ui_out::do_progress_end ()
>  {
>    struct ui_file *stream = m_streams.back ();
> -  m_progress_info.pop_back ();
> +  cli_progress_info &info (m_progress_info.back ());
>  
> -  if (stream->isatty ())
> +  if (stream->isatty () && info.state != progress_update::START)
>      clear_progress_notify ();
> +
> +  m_progress_info.pop_back ();
>  }
>  
>  /* local functions */
> diff --git a/gdb/cli-out.h b/gdb/cli-out.h
> index 34016182269..89b4aa40870 100644
> --- a/gdb/cli-out.h
> +++ b/gdb/cli-out.h
> @@ -35,6 +35,9 @@ class cli_ui_out : public ui_out
>  
>    bool can_emit_style_escape () const override;
>  
> +  ui_file *current_stream () const override
> +  { return m_streams.back (); }
> +
>  protected:
>  
>    virtual void do_table_begin (int nbrofcols, int nr_rows,
> diff --git a/gdb/debuginfod-support.c b/gdb/debuginfod-support.c
> index eb88c406ad6..35dcf56cb7e 100644
> --- a/gdb/debuginfod-support.c
> +++ b/gdb/debuginfod-support.c
> @@ -155,7 +155,8 @@ progressfn (debuginfod_client *c, long cur, long total)
>  
>    if (check_quit_flag ())
>      {
> -      gdb_printf ("Cancelling download of %s %s...\n",
> +      ui_file *outstream = get_unbuffered (gdb_stdout);
> +      gdb_printf (outstream, _("Cancelling download of %s %s...\n"),
>  		  data->desc, styled_fname.c_str ());
>        return 1;
>      }
> @@ -295,10 +296,14 @@ static void
>  print_outcome (int fd, const char *desc, const char *fname)
>  {
>    if (fd < 0 && fd != -ENOENT)
> -    gdb_printf (_("Download failed: %s.  Continuing without %s %ps.\n"),
> -		safe_strerror (-fd),
> -		desc,
> -		styled_string (file_name_style.style (), fname));
> +    {
> +      ui_file *outstream = get_unbuffered (gdb_stdout);
> +      gdb_printf (outstream,
> +		  _("Download failed: %s.  Continuing without %s %ps.\n"),
> +		  safe_strerror (-fd),
> +		  desc,
> +		  styled_string (file_name_style.style (), fname));
> +    }
>  }
>  
>  /* See debuginfod-support.h  */
> diff --git a/gdb/infrun.c b/gdb/infrun.c
> index c769e972f49..b977ba815ff 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -9277,10 +9277,10 @@ print_stop_location (const target_waitstatus &ws)
>      print_stack_frame (get_selected_frame (nullptr), 0, source_flag, 1);
>  }
>  
> -/* See infrun.h.  */
> +/* See `print_stop_event` in infrun.h.  */
>  
> -void
> -print_stop_event (struct ui_out *uiout, bool displays)
> +static void
> +do_print_stop_event (struct ui_out *uiout, bool displays)
>  {
>    struct target_waitstatus last;
>    struct thread_info *tp;
> @@ -9309,6 +9309,16 @@ print_stop_event (struct ui_out *uiout, bool displays)
>      }
>  }
>  
> +/* See infrun.h.  This function itself sets up buffered output for the
> +   duration of do_print_stop_event, which performs the actual event
> +   printing.  */
> +
> +void
> +print_stop_event (struct ui_out *uiout, bool displays)
> +{
> +  do_with_buffered_output (do_print_stop_event, uiout, displays);
> +}
> +
>  /* See infrun.h.  */
>  
>  void
> diff --git a/gdb/mi/mi-out.h b/gdb/mi/mi-out.h
> index 0dd7479a52f..68ff5faf632 100644
> --- a/gdb/mi/mi-out.h
> +++ b/gdb/mi/mi-out.h
> @@ -45,6 +45,9 @@ class mi_ui_out : public ui_out
>      return false;
>    }
>  
> +  ui_file *current_stream () const override
> +  { return m_streams.back (); }
> +
>  protected:
>  
>    virtual void do_table_begin (int nbrofcols, int nr_rows, const char *tblid)
> diff --git a/gdb/python/py-uiout.h b/gdb/python/py-uiout.h
> index 9c9617ea831..5944d1b6e34 100644
> --- a/gdb/python/py-uiout.h
> +++ b/gdb/python/py-uiout.h
> @@ -56,6 +56,9 @@ class py_ui_out : public ui_out
>      return std::move (current ().obj);
>    }
>  
> +  ui_file *current_stream () const override
> +  { return nullptr; }
> +
>  protected:
>  
>    void do_progress_end () override { }
> diff --git a/gdb/stack.c b/gdb/stack.c
> index fad4b62c6f7..42e919e1078 100644
> --- a/gdb/stack.c
> +++ b/gdb/stack.c
> @@ -220,7 +220,8 @@ static void print_frame_local_vars (frame_info_ptr frame,
>  				    const char *regexp, const char *t_regexp,
>  				    int num_tabs, struct ui_file *stream);
>  
> -static void print_frame (const frame_print_options &opts,
> +static void print_frame (struct ui_out *uiout,
> +			 const frame_print_options &opts,
>  			 frame_info_ptr frame, int print_level,
>  			 enum print_what print_what,  int print_args,
>  			 struct symtab_and_line sal);
> @@ -1020,16 +1021,15 @@ get_user_print_what_frame_info (std::optional<enum print_what> *what)
>     Used in "where" output, and to emit breakpoint or step
>     messages.  */
>  
> -void
> -print_frame_info (const frame_print_options &fp_opts,
> -		  frame_info_ptr frame, int print_level,
> -		  enum print_what print_what, int print_args,
> -		  int set_current_sal)
> +static void
> +do_print_frame_info (struct ui_out *uiout, const frame_print_options &fp_opts,
> +		     frame_info_ptr frame, int print_level,
> +		     enum print_what print_what, int print_args,
> +		     int set_current_sal)
>  {
>    struct gdbarch *gdbarch = get_frame_arch (frame);
>    int source_print;
>    int location_print;
> -  struct ui_out *uiout = current_uiout;
>  
>    if (!current_uiout->is_mi_like_p ()
>        && fp_opts.print_frame_info != print_frame_info_auto)
> @@ -1105,7 +1105,8 @@ print_frame_info (const frame_print_options &fp_opts,
>  		    || print_what == LOC_AND_ADDRESS
>  		    || print_what == SHORT_LOCATION);
>    if (location_print || !sal.symtab)
> -    print_frame (fp_opts, frame, print_level, print_what, print_args, sal);
> +    print_frame (uiout, fp_opts, frame, print_level,
> +		 print_what, print_args, sal);
>  
>    source_print = (print_what == SRC_LINE || print_what == SRC_AND_LOC);
>  
> @@ -1185,6 +1186,20 @@ print_frame_info (const frame_print_options &fp_opts,
>    gdb_flush (gdb_stdout);
>  }
>  
> +/* Redirect output to a temporary buffer for the duration
> +   of do_print_frame_info.  */
> +
> +void
> +print_frame_info (const frame_print_options &fp_opts,
> +		  frame_info_ptr frame, int print_level,
> +		  enum print_what print_what, int print_args,
> +		  int set_current_sal)
> +{
> +  do_with_buffered_output (do_print_frame_info, current_uiout,
> +			   fp_opts, frame, print_level, print_what,
> +			   print_args, set_current_sal);
> +}
> +
>  /* See stack.h.  */
>  
>  void
> @@ -1309,13 +1324,13 @@ find_frame_funname (frame_info_ptr frame, enum language *funlang,
>  }
>  
>  static void
> -print_frame (const frame_print_options &fp_opts,
> +print_frame (struct ui_out *uiout,
> +	     const frame_print_options &fp_opts,
>  	     frame_info_ptr frame, int print_level,
>  	     enum print_what print_what, int print_args,
>  	     struct symtab_and_line sal)
>  {
>    struct gdbarch *gdbarch = get_frame_arch (frame);
> -  struct ui_out *uiout = current_uiout;
>    enum language funlang = language_unknown;
>    struct value_print_options opts;
>    struct symbol *func;
> diff --git a/gdb/thread.c b/gdb/thread.c
> index f03a60c4d01..08c156d6687 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -1089,6 +1089,107 @@ thread_target_id_str (thread_info *tp)
>      return target_id;
>  }
>  
> +/* Print thread TP.  GLOBAL_IDS indicates whether REQUESTED_THREADS
> +   is a list of global or per-inferior thread ids.  */
> +
> +static void
> +do_print_thread (ui_out *uiout, const char *requested_threads,
> +		 int global_ids, int pid, int show_global_ids,
> +		 int default_inf_num, thread_info *tp,
> +		 thread_info *current_thread)
> +{
> +  int core;
> +
> +  /* In case REQUESTED_THREADS contains $_thread.  */
> +  if (current_thread != nullptr)
> +    switch_to_thread (current_thread);
> +
> +  if (!should_print_thread (requested_threads, default_inf_num,
> +			    global_ids, pid, tp))
> +    return;
> +
> +  ui_out_emit_tuple tuple_emitter (uiout, NULL);
> +
> +  if (!uiout->is_mi_like_p ())
> +    {
> +      if (tp == current_thread)
> +	uiout->field_string ("current", "*");
> +      else
> +	uiout->field_skip ("current");
> +
> +      uiout->field_string ("id-in-tg", print_thread_id (tp));
> +    }
> +
> +  if (show_global_ids || uiout->is_mi_like_p ())
> +    uiout->field_signed ("id", tp->global_num);
> +
> +  /* Switch to the thread (and inferior / target).  */
> +  switch_to_thread (tp);
> +
> +  /* For the CLI, we stuff everything into the target-id field.
> +     This is a gross hack to make the output come out looking
> +     correct.  The underlying problem here is that ui-out has no
> +     way to specify that a field's space allocation should be
> +     shared by several fields.  For MI, we do the right thing
> +     instead.  */
> +
> +  if (uiout->is_mi_like_p ())
> +    {
> +      uiout->field_string ("target-id", target_pid_to_str (tp->ptid));
> +
> +      const char *extra_info = target_extra_thread_info (tp);
> +      if (extra_info != nullptr)
> +	uiout->field_string ("details", extra_info);
> +
> +      const char *name = thread_name (tp);
> +      if (name != NULL)
> +	uiout->field_string ("name", name);
> +    }
> +  else
> +    {
> +      uiout->field_string ("target-id", thread_target_id_str (tp));
> +    }
> +
> +  if (tp->state == THREAD_RUNNING)
> +    uiout->text ("(running)\n");
> +  else
> +    {
> +      /* The switch above put us at the top of the stack (leaf
> +	 frame).  */
> +      print_stack_frame (get_selected_frame (NULL),
> +			 /* For MI output, print frame level.  */
> +			 uiout->is_mi_like_p (),
> +			 LOCATION, 0);
> +    }
> +
> +  if (uiout->is_mi_like_p ())
> +    {
> +      const char *state = "stopped";
> +
> +      if (tp->state == THREAD_RUNNING)
> +	state = "running";
> +      uiout->field_string ("state", state);
> +    }
> +
> +  core = target_core_of_thread (tp->ptid);
> +  if (uiout->is_mi_like_p () && core != -1)
> +    uiout->field_signed ("core", core);
> +}
> +
> +/* Redirect output to a temporary buffer for the duration
> +   of do_print_thread.  */
> +
> +static void
> +print_thread (ui_out *uiout, const char *requested_threads,
> +	      int global_ids, int pid, int show_global_ids,
> +	      int default_inf_num, thread_info *tp, thread_info *current_thread)
> +
> +{
> +  do_with_buffered_output (do_print_thread, uiout, requested_threads,
> +			   global_ids, pid, show_global_ids,
> +			   default_inf_num, tp, current_thread);
> +}
> +
>  /* Like print_thread_info, but in addition, GLOBAL_IDS indicates
>     whether REQUESTED_THREADS is a list of global or per-inferior
>     thread ids.  */
> @@ -1176,86 +1277,13 @@ print_thread_info_1 (struct ui_out *uiout, const char *requested_threads,
>      for (inferior *inf : all_inferiors ())
>        for (thread_info *tp : inf->threads ())
>  	{
> -	  int core;
> -
>  	  any_thread = true;
> +
>  	  if (tp == current_thread && tp->state == THREAD_EXITED)
>  	    current_exited = true;
>  
> -	  /* In case REQUESTED_THREADS contains $_thread.  */
> -	  if (current_thread != nullptr)
> -	    switch_to_thread (current_thread);
> -
> -	  if (!should_print_thread (requested_threads, default_inf_num,
> -				    global_ids, pid, tp))
> -	    continue;
> -
> -	  ui_out_emit_tuple tuple_emitter (uiout, NULL);
> -
> -	  if (!uiout->is_mi_like_p ())
> -	    {
> -	      if (tp == current_thread)
> -		uiout->field_string ("current", "*");
> -	      else
> -		uiout->field_skip ("current");
> -
> -	      uiout->field_string ("id-in-tg", print_thread_id (tp));
> -	    }
> -
> -	  if (show_global_ids || uiout->is_mi_like_p ())
> -	    uiout->field_signed ("id", tp->global_num);
> -
> -	  /* Switch to the thread (and inferior / target).  */
> -	  switch_to_thread (tp);
> -
> -	  /* For the CLI, we stuff everything into the target-id field.
> -	     This is a gross hack to make the output come out looking
> -	     correct.  The underlying problem here is that ui-out has no
> -	     way to specify that a field's space allocation should be
> -	     shared by several fields.  For MI, we do the right thing
> -	     instead.  */
> -
> -	  if (uiout->is_mi_like_p ())
> -	    {
> -	      uiout->field_string ("target-id", target_pid_to_str (tp->ptid));
> -
> -	      const char *extra_info = target_extra_thread_info (tp);
> -	      if (extra_info != nullptr)
> -		uiout->field_string ("details", extra_info);
> -
> -	      const char *name = thread_name (tp);
> -	      if (name != NULL)
> -		uiout->field_string ("name", name);
> -	    }
> -	  else
> -	    {
> -	      uiout->field_string ("target-id", thread_target_id_str (tp));
> -	    }
> -
> -	  if (tp->state == THREAD_RUNNING)
> -	    uiout->text ("(running)\n");
> -	  else
> -	    {
> -	      /* The switch above put us at the top of the stack (leaf
> -		 frame).  */
> -	      print_stack_frame (get_selected_frame (NULL),
> -				 /* For MI output, print frame level.  */
> -				 uiout->is_mi_like_p (),
> -				 LOCATION, 0);
> -	    }
> -
> -	  if (uiout->is_mi_like_p ())
> -	    {
> -	      const char *state = "stopped";
> -
> -	      if (tp->state == THREAD_RUNNING)
> -		state = "running";
> -	      uiout->field_string ("state", state);
> -	    }
> -
> -	  core = target_core_of_thread (tp->ptid);
> -	  if (uiout->is_mi_like_p () && core != -1)
> -	    uiout->field_signed ("core", core);
> +	  print_thread (uiout, requested_threads, global_ids, pid,
> +			show_global_ids, default_inf_num, tp, current_thread);
>  	}
>  
>      /* This end scope restores the current thread and the frame
> diff --git a/gdb/ui-file.h b/gdb/ui-file.h
> index 31f87ffd51d..8385033b441 100644
> --- a/gdb/ui-file.h
> +++ b/gdb/ui-file.h
> @@ -224,7 +224,7 @@ class string_file : public ui_file
>    bool empty () const { return m_string.empty (); }
>    void clear () { return m_string.clear (); }
>  
> -private:
> +protected:

Is this needed?  Everything seems to compile fine if this is left as
private.

>    /* The internal buffer.  */
>    std::string m_string;
>  
> diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> index e8653c6c127..75f9f1612cb 100644
> --- a/gdb/ui-out.c
> +++ b/gdb/ui-out.c
> @@ -871,3 +871,144 @@ ui_out::ui_out (ui_out_flags flags)
>  ui_out::~ui_out ()
>  {
>  }
> +
> +/* See ui-out.h.  */
> +
> +void
> +buffer_group::output_unit::flush () const
> +{
> +  if (!m_msg.empty ())
> +    m_stream->puts (m_msg.c_str ());
> +
> +  if (m_wrap_hint >= 0)
> +    m_stream->wrap_here (m_wrap_hint);
> +
> +  if (m_flush)
> +    m_stream->flush ();
> +}
> +
> +/* See ui-out.h.  */
> +
> +void
> +buffer_group::write (const char *buf, long length_buf, ui_file *stream)
> +{
> +  /* Record each line separately.  */
> +  for (size_t prev = 0, cur = 0; cur < length_buf; ++cur)
> +    if (buf[cur] == '\n' || cur == length_buf - 1)
> +      {
> +	std::string msg (buf + prev, cur - prev + 1);
> +
> +	if (m_buffered_output.size () > 0
> +	    && m_buffered_output.back ().m_wrap_hint == -1
> +	    && m_buffered_output.back ().m_stream == stream
> +	    && m_buffered_output.back ().m_msg.size () > 0
> +	    && m_buffered_output.back ().m_msg.back () != '\n')
> +	  m_buffered_output.back ().m_msg.append (msg);
> +	else
> +	  {
> +	    m_buffered_output.emplace_back (msg);
> +	    m_buffered_output.back ().m_stream = stream;
> +	  }
> +	prev = cur + 1;
> +      }
> +}
> +
> +/* See ui-out.h.  */
> +
> +void
> +buffer_group::wrap_here (int indent, ui_file *stream)
> +{
> +  m_buffered_output.emplace_back ("", indent);
> +  m_buffered_output.back ().m_stream = stream;
> +}
> +
> +/* See ui-out.h.  */
> +
> +void
> +buffer_group::flush_here (ui_file *stream)
> +{
> +  m_buffered_output.emplace_back ("", -1, true);
> +  m_buffered_output.back ().m_stream = stream;
> +}
> +
> +/* See ui-out.h.  */
> +
> +ui_file *
> +get_unbuffered (ui_file * stream)

Should be 'ui_file *stream'.

> +{
> +  buffering_file *buf = dynamic_cast<buffering_file *> (stream);
> +
> +  if (buf == nullptr)
> +    return stream;
> +
> +  return get_unbuffered (buf->stream ());
> +}
> +
> +buffered_streams::buffered_streams (buffer_group *group, ui_out *uiout)
> +  : m_buffered_stdout (group, gdb_stdout),
> +    m_buffered_stderr (group, gdb_stderr),
> +    m_buffered_stdlog (group, gdb_stdlog),
> +    m_buffered_stdtarg (group, gdb_stdtarg),
> +    m_buffered_stdtargerr (group, gdb_stdtargerr),
> +    m_uiout (uiout)
> +{
> +  gdb_stdout = &m_buffered_stdout;
> +  gdb_stderr = &m_buffered_stderr;
> +  gdb_stdlog = &m_buffered_stdlog;
> +  gdb_stdtarg = &m_buffered_stdtarg;
> +  gdb_stdtargerr = &m_buffered_stdtargerr;
> +
> +  ui_file *stream = current_uiout->current_stream ();
> +  if (stream != nullptr)
> +    {
> +      m_buffered_current_uiout.emplace (group, stream);
> +      current_uiout->redirect (&(*m_buffered_current_uiout));
> +    }
> +
> +  stream = m_uiout->current_stream ();
> +  if (stream != nullptr && current_uiout != m_uiout)
> +    {
> +      m_buffered_uiout.emplace (group, stream);
> +      m_uiout->redirect (&(*m_buffered_uiout));
> +    }
> +
> +  m_buffers_in_place = true;
> +}
> +
> +/* See ui-out.h.  */
> +
> +void
> +buffered_streams::remove_buffers ()
> +{
> +  if (!m_buffers_in_place)
> +    return;
> +
> +  m_buffers_in_place = false;
> +
> +  gdb_stdout = m_buffered_stdout.stream ();
> +  gdb_stderr = m_buffered_stderr.stream ();
> +  gdb_stdlog = m_buffered_stdlog.stream ();
> +  gdb_stdtarg = m_buffered_stdtarg.stream ();
> +  gdb_stdtargerr = m_buffered_stdtargerr.stream ();
> +
> +  if (m_buffered_current_uiout.has_value ())
> +    current_uiout->redirect (nullptr);
> +
> +  if (m_buffered_uiout.has_value ())
> +    m_uiout->redirect (nullptr);
> +}
> +
> +buffer_group::buffer_group (ui_out *uiout)
> +  : m_buffered_streams (new buffered_streams (this, uiout))
> +{ /* Nothing.  */ }
> +
> +/* See ui-out.h.  */
> +
> +void
> +buffer_group::flush () const
> +{
> +  m_buffered_streams->remove_buffers ();
> +
> +  for (const output_unit &ou : m_buffered_output)
> +    ou.flush ();
> +}
> diff --git a/gdb/ui-out.h b/gdb/ui-out.h
> index 07567a1df35..2934d3a6b34 100644
> --- a/gdb/ui-out.h
> +++ b/gdb/ui-out.h
> @@ -278,6 +278,9 @@ class ui_out
>       escapes.  */
>    virtual bool can_emit_style_escape () const = 0;
>  
> +  /* Return the ui_file currently used for output.  */
> +  virtual ui_file *current_stream () const = 0;
> +
>    /* An object that starts and finishes displaying progress updates.  */
>    class progress_update
>    {
> @@ -470,4 +473,185 @@ class ui_out_redirect_pop
>    struct ui_out *m_uiout;
>  };
>  
> +struct buffered_streams;
> +
> +/* Organizes writes to a collection of buffered output streams
> +   so that when flushed, output is written to all streams in
> +   chronological order.  */
> +
> +struct buffer_group
> +{
> +  buffer_group (ui_out *uiout);
> +
> +  /* Flush all buffered writes to the underlying output streams.  */
> +  void flush () const;
> +
> +  /* Record contents of BUF and associate it with STREAM.  */
> +  void write (const char *buf, long length_buf, ui_file *stream);
> +
> +  /* Record a wrap_here and associate it with STREAM.  */
> +  void wrap_here (int indent, ui_file *stream);
> +
> +  /* Record a call to flush and associate it with STREAM.  */
> +  void flush_here (ui_file *stream);
> +
> +private:
> +
> +  struct output_unit
> +  {
> +    output_unit (std::string msg, int wrap_hint = -1, bool flush = false)
> +      : m_msg (msg), m_wrap_hint (wrap_hint), m_flush (flush)
> +    {}
> +
> +    /* Write contents of this output_unit to the underlying stream.  */
> +    void flush () const;
> +
> +    /* Underlying stream for which this output unit will be written to.  */
> +    ui_file *m_stream;
> +
> +    /* String to be written to underlying buffer.  */
> +    std::string m_msg;
> +
> +    /* Argument to wrap_here.  -1 indicates no wrap.  Used to call wrap_here
> +       during buffer flush.  */
> +    int m_wrap_hint;
> +
> +    /* Indicate that the underlying output stream's flush should be called.  */
> +    bool m_flush;
> +  };
> +
> +  /* Output_units to be written to buffered output streams.  */
> +  std::vector<output_unit> m_buffered_output;
> +
> +  /* Buffered output streams.  */
> +  std::unique_ptr<buffered_streams> m_buffered_streams;
> +};
> +
> +/* If FILE is a buffering_file, return it's underlying stream.  */
> +
> +extern ui_file *get_unbuffered (ui_file *file);
> +
> +/* Buffer output to gdb_stdout and gdb_stderr for the duration of FUNC.  */
> +
> +template<typename F, typename... Arg>
> +void
> +do_with_buffered_output (F func, ui_out *uiout, Arg... args)
> +{
> +  buffer_group g (uiout);
> +
> +  try
> +    {
> +      func (uiout, std::forward<Arg> (args)...);
> +    }
> +  catch (gdb_exception &ex)

This should be 'const gdb_exception &ex'.

> +    {
> +      /* Ideally flush would be called in the destructor of buffer_group,
> +	 however flushing might cause an exception to be thrown.  Catch it
> +	 and ensure the first exception propagates.  */
> +      try
> +	{
> +	  g.flush ();
> +	}
> +      catch (const gdb_exception &)
> +	{
> +	}
> +
> +      throw_exception (std::move (ex));

This can be replaced with just 'throw;'.  Object slicing has occurred
when we caught the exception as a gdb_exception, but the current
exception will still be the correct, full object, so we're OK to just
rethrow that.

> +    }
> +
> +  /* Try was successful.  Let any further exceptions propagate.  */
> +  g.flush ();
> +}
> +
> +/* Accumulate writes to an underlying ui_file.  Output to the
> +   underlying file is deferred until required.  */
> +
> +struct buffering_file : public ui_file
> +{
> +  buffering_file (buffer_group *group, ui_file *stream)
> +    : m_group (group),
> +      m_stream (stream)
> +  { /* Nothing.  */ }
> +
> +  /* Return the underlying output stream.  */
> +  ui_file *stream () const
> +  {
> +    return m_stream;
> +  }
> +
> +  /* Record the contents of BUF.  */
> +  void write (const char *buf, long length_buf) override
> +  {
> +    m_group->write (buf, length_buf, m_stream);
> +  }
> +
> +  /* Record a wrap_here call with argument INDENT.  */
> +  void wrap_here (int indent) override
> +  {
> +    m_group->wrap_here (indent, m_stream);
> +  }
> +
> +  /* Return true if the underlying stream is a tty.  */
> +  bool isatty () override
> +  {
> +    return m_stream->isatty ();
> +  }
> +
> +  /* Return true if ANSI escapes can be used on the underlying stream.  */
> +  bool can_emit_style_escape () override
> +  {
> +    return m_stream->can_emit_style_escape ();
> +  }
> +
> +  /* Flush the underlying output stream.  */
> +  void flush () override
> +  {
> +    return m_group->flush_here (m_stream);
> +  }
> +
> +private:
> +
> +  /* Coordinates buffering across multiple buffering_files.  */
> +  buffer_group *m_group;
> +
> +  /* The underlying output stream.  */
> +  ui_file *m_stream;
> +};
> +
> +/* Attaches and detaches buffers for each of the gdb_std* streams.  */
> +
> +struct buffered_streams
> +{
> +  buffered_streams (buffer_group *group, ui_out *uiout);
> +
> +  ~buffered_streams ()
> +  {
> +    this->remove_buffers ();
> +  }
> +
> +  /* Remove buffering_files from all underlying streams.  */
> +  void remove_buffers ();
> +
> +private:
> +
> +  /* True if buffers are still attached to each underlying output stream.  */
> +  bool m_buffers_in_place;
> +
> +  /* Buffers for each gdb_std* output stream.  */
> +  buffering_file m_buffered_stdout;
> +  buffering_file m_buffered_stderr;
> +  buffering_file m_buffered_stdlog;
> +  buffering_file m_buffered_stdtarg;
> +  buffering_file m_buffered_stdtargerr;
> +
> +  /* Buffer for current_uiout's output stream.  */
> +  std::optional<buffering_file> m_buffered_current_uiout;
> +
> +  /* Additional ui_out being buffered.  */
> +  ui_out *m_uiout;
> +
> +  /* Buffer for m_uiout's output stream.  */
> +  std::optional<buffering_file> m_buffered_uiout;
> +};

I really dislike GDB's I/O sub-system.  Every time I look at the code it
always feels like it's far too complicated, and there must be a better
way to do things.

But I've not yet managed to come up with a plan that would actually make
things simpler.

This is adding yet more complexity on top, which isn't ideal.  But I
don't have any better suggestions, and I can see why this is needed.
And I know the actual point of this series, the debuginfod work, is
going to be super valuable.

So, given nobody who knows the I/O stuff better has commented, I propose
that, with the fixes I've pointed out above:

Approved-By: Andrew Burgess <aburgess@redhat.com>

Thanks,
Andrew

> +
>  #endif /* UI_OUT_H */
> -- 
> 2.43.0


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

* Re: [PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator
  2024-01-17 15:48 ` [PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator Aaron Merey
@ 2024-01-18 12:02   ` Andrew Burgess
  2024-01-19  5:09     ` Aaron Merey
  0 siblings, 1 reply; 29+ messages in thread
From: Andrew Burgess @ 2024-01-18 12:02 UTC (permalink / raw)
  To: Aaron Merey, gdb-patches; +Cc: thiago.bauermann, Aaron Merey

Aaron Merey <amerey@redhat.com> writes:

> v3 tweaks qf iteration in symfile-debug.c to account for the removal of
> qf_require_partial_symbols.
>
> Commit message:
>
> This patch changes progspace objfile_list insertion so that separate
> debug objfiles are placed into the list after the parent objfile,
> instead of before.
>
> These changes are intended to prepare gdb for on-demand debuginfo
> downloading and the downloading of .gdb_index sections.
>
> With on-demand downloading enabled, gdb might need to delete a
> .gdb_index quick_symbol_functions from a parent objfile while looping
> the objfile's list of quick_symbol_functions because the separate
> debug objfile has just been downloaded.  The use of a safe_range
> prevents qf removal from causing iterator invalidation.
>
> gdb might also download a debuginfo file during symtab expansion.
> In this case an objfile will be added to the current progspace's
> objfiles_list during iteration over the list (for example, in
> iterate_over_symtabs).  We want these loops to also iterate over
> newly downloaded objfiles.  So objfiles need to be inserted into
> objfiles_list after their parent since it is during the search of
> the parent objfile for some symbol or filename that the separate
> debug objfile might be downloaded.
>
> To facilitate the safe deletion of objfiles, this patch also adds
> basic_safe_reverse_range and basic_safe_reverse_iterator.  This allows
> objfiles to be removed from the objfiles_list in a loop without iterator
> invalidation.
>
> If a forward safe iterator were to be used, the deletion of an
> objfile could invalidate the safe iterator's reference to the next
> objfile in the objfiles_list.  This can happen when the deletion
> of an objfile causes the deletion of a separate debug objfile that
> happens to the be next element in the objfiles_list.
>
> The standard reverse iterator is not suitable for safe objfile deletion.
> In order to safely delete the first objfile in the objfiles_list, the
> standard reverse iterator's underlying begin iterator would have to be
> decremented, resulting in undefined behavior.
>
> A small change was also made to a testcase in py-objfile.exp to
> account for the new placement of separate debug objfiles in
> objfiles_list.
> ---
>  gdb/jit.c                               |   7 +-
>  gdb/objfiles.c                          |  14 ++--
>  gdb/objfiles.h                          |  10 +++
>  gdb/progspace.c                         |  19 ++++-
>  gdb/progspace.h                         |  31 ++++---
>  gdb/symfile-debug.c                     |  34 ++++----
>  gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
>  gdbsupport/safe-iterator.h              | 106 ++++++++++++++++++++++++
>  8 files changed, 181 insertions(+), 42 deletions(-)
>
> diff --git a/gdb/jit.c b/gdb/jit.c
> index 85a10be3055..10ee5f9b749 100644
> --- a/gdb/jit.c
> +++ b/gdb/jit.c
> @@ -1240,11 +1240,10 @@ jit_breakpoint_re_set (void)
>  static void
>  jit_inferior_exit_hook (struct inferior *inf)
>  {
> -  for (objfile *objf : current_program_space->objfiles_safe ())
> +  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
>      {
> -      if (objf->jited_data != nullptr && objf->jited_data->addr != 0)
> -	objf->unlink ();
> -    }
> +      return (objf->jited_data != nullptr) && (objf->jited_data->addr != 0);
> +    });
>  }
>  
>  void
> diff --git a/gdb/objfiles.c b/gdb/objfiles.c
> index 8f085b1bb7c..d53dfaca90b 100644
> --- a/gdb/objfiles.c
> +++ b/gdb/objfiles.c
> @@ -470,6 +470,12 @@ objfile::unlink ()
>    current_program_space->remove_objfile (this);
>  }
>  
> +qf_safe_range
> +objfile::qf_safe ()
> +{
> +  return qf_safe_range (qf_range (qf.begin (), qf.end ()));
> +}
> +
>  /* Free all separate debug objfile of OBJFILE, but don't free OBJFILE
>     itself.  */
>  
> @@ -793,14 +799,12 @@ have_full_symbols (void)
>  void
>  objfile_purge_solibs (void)
>  {
> -  for (objfile *objf : current_program_space->objfiles_safe ())
> +  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
>      {
>        /* We assume that the solib package has been purged already, or will
>  	 be soon.  */
> -
> -      if (!(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED))
> -	objf->unlink ();
> -    }
> +      return !(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED);
> +    });
>  }
>  
>  
> diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> index 621342d22b7..34b3dac0b26 100644
> --- a/gdb/objfiles.h
> +++ b/gdb/objfiles.h
> @@ -370,6 +370,12 @@ class separate_debug_iterator
>  
>  typedef iterator_range<separate_debug_iterator> separate_debug_range;
>  
> +/* See objfile::qf_safe.  */
> +
> +using qf_list = std::forward_list<quick_symbol_functions_up>;
> +using qf_range = iterator_range<qf_list::iterator>;
> +using qf_safe_range = basic_safe_range<qf_range>;
> +
>  /* Sections in an objfile.  The section offsets are stored in the
>     OBJFILE.  */
>  
> @@ -764,6 +770,10 @@ struct objfile
>       reader.  */
>    std::forward_list<quick_symbol_functions_up> qf;

I think this should be updated to use qf_list.

>  
> +  /* Returns iterable object that allows for safe deletion during
> +     iteration.  */
> +  qf_safe_range qf_safe ();
> +
>    /* Per objfile data-pointers required by other GDB modules.  */
>  
>    registry<objfile> registry_fields;
> diff --git a/gdb/progspace.c b/gdb/progspace.c
> index 0fc1fdd57ab..c4cc2e2e8af 100644
> --- a/gdb/progspace.c
> +++ b/gdb/progspace.c
> @@ -141,19 +141,19 @@ program_space::free_all_objfiles ()
>  
>  void
>  program_space::add_objfile (std::unique_ptr<objfile> &&objfile,
> -			    struct objfile *before)
> +			    struct objfile *after)
>  {
> -  if (before == nullptr)
> +  if (after == nullptr)
>      objfiles_list.push_back (std::move (objfile));
>    else
>      {
>        auto iter = std::find_if (objfiles_list.begin (), objfiles_list.end (),
>  				[=] (const std::unique_ptr<::objfile> &objf)
>  				{
> -				  return objf.get () == before;
> +				  return objf.get () == after;
>  				});
>        gdb_assert (iter != objfiles_list.end ());
> -      objfiles_list.insert (iter, std::move (objfile));
> +      objfiles_list.insert (++iter, std::move (objfile));
>      }
>  }
>  
> @@ -182,6 +182,17 @@ program_space::remove_objfile (struct objfile *objfile)
>  
>  /* See progspace.h.  */
>  
> +void
> +program_space::unlink_objfiles_if
> +  (gdb::function_view<bool (const objfile *objfile)> predicate)
> +{
> +  for (auto &it : objfiles_safe ())
> +    if (predicate (it.get ()))
> +      it->unlink ();
> +}
> +
> +/* See progspace.h.  */
> +
>  struct objfile *
>  program_space::objfile_for_address (CORE_ADDR address)
>  {
> diff --git a/gdb/progspace.h b/gdb/progspace.h
> index 163cbf6a0f8..f23a57dc842 100644
> --- a/gdb/progspace.h
> +++ b/gdb/progspace.h
> @@ -250,28 +250,32 @@ struct program_space
>         unwrapping_objfile_iterator (objfiles_list.end ()));
>    }
>  
> -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> +  using objfiles_it_range = iterator_range<objfile_list::iterator>;
> +  using objfiles_safe_reverse_range
> +    = basic_safe_reverse_range<objfiles_it_range>;
>  
>    /* An iterable object that can be used to iterate over all objfiles.
>       The basic use is in a foreach, like:
>  
>       for (objfile *objf : pspace->objfiles_safe ()) { ... }
>  
> -     This variant uses a basic_safe_iterator so that objfiles can be
> -     deleted during iteration.  */
> -  objfiles_safe_range objfiles_safe ()
> +     This variant uses a basic_safe_reverse_iterator so that objfiles
> +     can be deleted during iteration.
> +
> +     The use of a reverse iterator helps ensure that separate debug
> +     objfiles are deleted before their parent objfile.  This prevents
> +     iterator invalidation due to the deletion of a parent objfile.  */
> + objfiles_safe_reverse_range objfiles_safe ()
>    {
> -    return objfiles_safe_range
> -      (objfiles_range
> -	 (unwrapping_objfile_iterator (objfiles_list.begin ()),
> -	  unwrapping_objfile_iterator (objfiles_list.end ())));
> +    return objfiles_safe_reverse_range
> +      (objfiles_it_range (objfiles_list.begin (), objfiles_list.end ()));
>    }
>  
> -  /* Add OBJFILE to the list of objfiles, putting it just before
> -     BEFORE.  If BEFORE is nullptr, it will go at the end of the
> +  /* Add OBJFILE to the list of objfiles, putting it just after
> +     AFTER.  If AFTER is nullptr, it will go at the end of the
>       list.  */
>    void add_objfile (std::unique_ptr<objfile> &&objfile,
> -		    struct objfile *before);
> +		    struct objfile *after);
>  
>    /* Remove OBJFILE from the list of objfiles.  */
>    void remove_objfile (struct objfile *objfile);
> @@ -286,6 +290,11 @@ struct program_space
>    /* Free all the objfiles associated with this program space.  */
>    void free_all_objfiles ();
>  
> +  /* Unlink all objfiles associated with this program space for which
> +     PREDICATE evaluates to true.  */
> +  void unlink_objfiles_if
> +    (gdb::function_view<bool (const objfile *objfile)> predicate);
> +
>    /* Return the objfile containing ADDRESS, or nullptr if the address
>       is outside all objfiles in this progspace.  */
>    struct objfile *objfile_for_address (CORE_ADDR address);
> diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
> index 32c3ed9d349..c684f9cdd43 100644
> --- a/gdb/symfile-debug.c
> +++ b/gdb/symfile-debug.c
> @@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
>       them, then that is an indication that they are in fact available.  Without
>       this function the symbols may have been already read in but they also may
>       not be present in this objfile.  */
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())

I read the commit message a couple of times, but I'm still not 100% sure
I understand what's happening here.  Is the change from qf -> qf_safe()
because calling (in this case has_symbols()) might download the full
debug info, and in doing do, might delete a previously downloaded
.debug_info section?

If this is the case then is it true that we will almost never want to
iterate over `qf` directly as most quick_symbol_functions methods might
result in this download/delete behaviour?

If I'm right, then I think it might be useful to place a warning in the
comment before `qf` explaining this.

>      {
>        retval = iter->has_symbols (this);
>        if (retval)
> @@ -108,7 +108,7 @@ objfile::has_unexpanded_symtabs ()
>  		objfile_debug_name (this));
>  
>    bool result = false;
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        if (iter->has_unexpanded_symtabs (this))
>  	{
> @@ -133,7 +133,7 @@ objfile::find_last_source_symtab ()
>      gdb_printf (gdb_stdlog, "qf->find_last_source_symtab (%s)\n",
>  		objfile_debug_name (this));
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        retval = iter->find_last_source_symtab (this);
>        if (retval != nullptr)
> @@ -166,7 +166,7 @@ objfile::forget_cached_source_info ()
>  	}
>      }
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->forget_cached_source_info (this);
>  }
>  
> @@ -213,7 +213,7 @@ objfile::map_symtabs_matching_filename
>      return result;
>    };
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        if (!iter->expand_symtabs_matching (this,
>  					  match_one_filename,
> @@ -278,7 +278,7 @@ objfile::lookup_symbol (block_enum kind, const char *name, domain_enum domain)
>      return true;
>    };
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        if (!iter->expand_symtabs_matching (this,
>  					  nullptr,
> @@ -309,7 +309,7 @@ objfile::print_stats (bool print_bcache)
>      gdb_printf (gdb_stdlog, "qf->print_stats (%s, %d)\n",
>  		objfile_debug_name (this), print_bcache);
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->print_stats (this, print_bcache);
>  }
>  
> @@ -320,7 +320,7 @@ objfile::dump ()
>      gdb_printf (gdb_stdlog, "qf->dump (%s)\n",
>  		objfile_debug_name (this));
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->dump (this);
>  }
>  
> @@ -335,7 +335,7 @@ objfile::expand_symtabs_for_function (const char *func_name)
>    lookup_name_info base_lookup (func_name, symbol_name_match_type::FULL);
>    lookup_name_info lookup_name = base_lookup.make_ignore_params ();
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->expand_symtabs_matching (this,
>  				   nullptr,
>  				   &lookup_name,
> @@ -354,7 +354,7 @@ objfile::expand_all_symtabs ()
>      gdb_printf (gdb_stdlog, "qf->expand_all_symtabs (%s)\n",
>  		objfile_debug_name (this));
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->expand_all_symtabs (this);
>  }
>  
> @@ -372,7 +372,7 @@ objfile::expand_symtabs_with_fullname (const char *fullname)
>      return filename_cmp (basenames ? basename : fullname, filename) == 0;
>    };
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->expand_symtabs_matching (this,
>  				   file_matcher,
>  				   nullptr,
> @@ -406,7 +406,7 @@ objfile::expand_symtabs_matching
>  		host_address_to_string (&expansion_notify),
>  		search_domain_name (kind));
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      if (!iter->expand_symtabs_matching (this, file_matcher, lookup_name,
>  					symbol_matcher, expansion_notify,
>  					search_flags, domain, kind))
> @@ -431,7 +431,7 @@ objfile::find_pc_sect_compunit_symtab (struct bound_minimal_symbol msymbol,
>  		host_address_to_string (section),
>  		warn_if_readin);
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        retval = iter->find_pc_sect_compunit_symtab (this, msymbol, pc, section,
>  						   warn_if_readin);
> @@ -459,7 +459,7 @@ objfile::map_symbol_filenames (gdb::function_view<symbol_filename_ftype> fun,
>  		objfile_debug_name (this),
>  		need_fullname);
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->map_symbol_filenames (this, fun, need_fullname);
>  }
>  
> @@ -471,7 +471,7 @@ objfile::compute_main_name ()
>  		"qf->compute_main_name (%s)\n",
>  		objfile_debug_name (this));
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->compute_main_name (this);
>  }
>  
> @@ -485,7 +485,7 @@ objfile::find_compunit_symtab_by_address (CORE_ADDR address)
>  		hex_string (address));
>  
>    struct compunit_symtab *result = NULL;
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        result = iter->find_compunit_symtab_by_address (this, address);
>        if (result != nullptr)
> @@ -510,7 +510,7 @@ objfile::lookup_global_symbol_language (const char *name,
>    enum language result = language_unknown;
>    *symbol_found_p = false;
>  
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        result = iter->lookup_global_symbol_language (this, name, domain,
>  						    symbol_found_p);
> diff --git a/gdb/testsuite/gdb.python/py-objfile.exp b/gdb/testsuite/gdb.python/py-objfile.exp
> index 61b9942de79..0bf49976b73 100644
> --- a/gdb/testsuite/gdb.python/py-objfile.exp
> +++ b/gdb/testsuite/gdb.python/py-objfile.exp
> @@ -135,7 +135,7 @@ gdb_test "p main" "= {<text variable, no debug info>} $hex <main>" \
>  gdb_py_test_silent_cmd "python objfile.add_separate_debug_file(\"${binfile}\")" \
>      "Add separate debug file file" 1
>  
> -gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[0\]" \
> +gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[1\]" \
>      "Get separate debug info objfile" 1
>  
>  gdb_test "python print (sep_objfile.owner.filename)" "${testfile}2" \
> diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h
> index b4891328f1a..8bb80729bbd 100644
> --- a/gdbsupport/safe-iterator.h
> +++ b/gdbsupport/safe-iterator.h
> @@ -136,4 +136,110 @@ class basic_safe_range
>    Range m_range;
>  };
>  
> +/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
> +
> +template<typename Iterator>
> +class basic_safe_reverse_iterator
> +{
> +public:
> +  typedef basic_safe_reverse_iterator self_type;
> +  typedef typename Iterator::value_type value_type;
> +  typedef typename Iterator::reference reference;
> +  typedef typename Iterator::pointer pointer;
> +  typedef typename Iterator::iterator_category iterator_category;
> +  typedef typename Iterator::difference_type difference_type;
> +
> +  /* Construct the iterator using ARG, and construct the end iterator
> +     using ARG2.  */

Maybe this is a standard thing, and I'm just showing my lack of
experience, but, given the existence of methods like rbegin() and
rend(), I think the comments here should be doing more to explain what's
expected of this input.

Plus this comment seems confusing, though I can sort of see what it's
trying to say.

We're creating a *_iterator object, which itself contains other
iterators.  We need to be super clear which particular "iterator" we're
talking about in the comments.

Also, I think we need to be explicit that for this reverse iterator, the
first argument to the constructor is going to be the LAST element that
is iterated over.  One might be tempted to think that a safe reverse iterator
should be initialised with rbegin() and rend(), which isn't going to be
correct (unless I'm not understand, please correct me if so).

> +  template<typename Arg>
> +  explicit basic_safe_reverse_iterator (Arg &&arg, Arg &&arg2)
> +    : m_begin (std::forward<Arg> (arg)),
> +      m_end (std::forward<Arg> (arg2)),
> +      m_it (m_end),
> +      m_next (m_end)
> +  {
> +    /* M_IT and M_NEXT are initialized as one-past-end.  Set M_IT to point
> +       to the last element and set M_NEXT to point to the second last element,
> +       if such elements exist.  */
> +    if (m_it != m_begin)
> +      {
> +	--m_it;
> +
> +	if (m_it != m_begin)
> +	  {
> +	    --m_next;
> +	    --m_next;
> +	  }
> +      }
> +  }
> +
> +  typename std::invoke_result<decltype(&Iterator::operator*), Iterator>::type
> +    operator* () const
> +  { return *m_it; }
> +
> +  self_type &operator++ ()
> +  {
> +    m_it = m_next;
> +
> +    if (m_it != m_end)
> +      {
> +	/* Use M_BEGIN only if we sure that it is valid.  */
> +	if (m_it == m_begin)
> +	  m_next = m_end;
> +	else
> +	  --m_next;
> +      }
> +
> +    return *this;
> +  }
> +
> +  bool operator== (const self_type &other) const
> +  { return m_it == other.m_it; }
> +
> +  bool operator!= (const self_type &other) const
> +  { return m_it != other.m_it; }
> +
> +private:
> +  /* The first element.  */
> +  Iterator m_begin {};
> +
> +  /* A one-past-end iterator.  */
> +  Iterator m_end {};
> +
> +  /* The current element.  */
> +  Iterator m_it {};
> +
> +  /* The next element.  Always one element ahead of M_IT.  */
> +  Iterator m_next {};
> +};
> +
> +/* A range adapter that wraps a forward range, and then returns
> +   safe reverse iterators wrapping the original range's iterators.  */

I think the fact that this takes a FORWARD range is really important,
and we should do more to advertise this.  E.g. I think there should be a
comment on the constructor saying that RANGE is a forward range over
which we will iterate backwards.

Thanks,
Andrew
> +
> +template<typename Range>
> +class basic_safe_reverse_range
> +{
> +public:
> +
> +  typedef basic_safe_reverse_iterator<typename Range::iterator> iterator;
> +
> +  explicit basic_safe_reverse_range (Range range)
> +    : m_range (range)
> +  {
> +  }
> +
> +  iterator begin ()
> +  {
> +    return iterator (m_range.begin (), m_range.end ());
> +  }
> +
> +  iterator end ()
> +  {
> +    return iterator (m_range.end (), m_range.end ());
> +  }
> +
> +private:
> +
> +  Range m_range;
> +};
>  #endif /* COMMON_SAFE_ITERATOR_H */
> -- 
> 2.43.0


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

* Re: [PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading
  2024-01-17 15:48 ` [PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading Aaron Merey
@ 2024-01-18 15:34   ` Andrew Burgess
  2024-01-19  5:12     ` Aaron Merey
  0 siblings, 1 reply; 29+ messages in thread
From: Andrew Burgess @ 2024-01-18 15:34 UTC (permalink / raw)
  To: Aaron Merey, gdb-patches; +Cc: thiago.bauermann, Aaron Merey

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;
> +}
> +
>  \f
>  
>  /* 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


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

* Re: [PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator
  2024-01-18 12:02   ` Andrew Burgess
@ 2024-01-19  5:09     ` Aaron Merey
  2024-02-07 21:24       ` [PING][PATCH " Aaron Merey
  0 siblings, 1 reply; 29+ messages in thread
From: Aaron Merey @ 2024-01-19  5:09 UTC (permalink / raw)
  To: aburgess; +Cc: gdb-patches, thiago.bauermann, Aaron Merey

Hi Andrew,

Thanks for the review.

On Thu, Jan 18, 2024 at 7:03 AM Andrew Burgess <aburgess@redhat.com> wrote:
>
> Aaron Merey <amerey@redhat.com> writes:
> > +using qf_list = std::forward_list<quick_symbol_functions_up>;
> > +using qf_range = iterator_range<qf_list::iterator>;
> > +using qf_safe_range = basic_safe_range<qf_range>;
> > +
> >  /* Sections in an objfile.  The section offsets are stored in the
> >     OBJFILE.  */
> >
> > @@ -764,6 +770,10 @@ struct objfile
> >       reader.  */
> >    std::forward_list<quick_symbol_functions_up> qf;
>
> I think this should be updated to use qf_list.

Done.

> > --- a/gdb/symfile-debug.c
> > +++ b/gdb/symfile-debug.c
> > @@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
> >       them, then that is an indication that they are in fact available.  Without
> >       this function the symbols may have been already read in but they also may
> >       not be present in this objfile.  */
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
>
> I read the commit message a couple of times, but I'm still not 100% sure
> I understand what's happening here.  Is the change from qf -> qf_safe()
> because calling (in this case has_symbols()) might download the full
> debug info, and in doing do, might delete a previously downloaded
> .debug_info section?

quick_functions_up made from a separately downloaded .gdb_index is at
first associated with the parent objfile (i.e. the objfile of the shared
library containing a .gnu_debuglink that refers to the separate debug info
which actually contains this .gdb_index section).

When the separate debuginfo is downloaded (possibly during iteration
over the parent's qf_list), we need to remove the gdb_index qf from
the parent's qf_list.  This prevents gdb from later attempting to read
the parent objfile for debug info that is actually in the corresponding
separate debug objfile.

> If this is the case then is it true that we will almost never want to
> iterate over `qf` directly as most quick_symbol_functions methods might
> result in this download/delete behaviour?
>
> If I'm right, then I think it might be useful to place a warning in the
> comment before `qf` explaining this.

Done.

> > +/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
> > +
> > +template<typename Iterator>
> > +class basic_safe_reverse_iterator
> > +{
> > +public:
> > +  typedef basic_safe_reverse_iterator self_type;
> > +  typedef typename Iterator::value_type value_type;
> > +  typedef typename Iterator::reference reference;
> > +  typedef typename Iterator::pointer pointer;
> > +  typedef typename Iterator::iterator_category iterator_category;
> > +  typedef typename Iterator::difference_type difference_type;
> > +
> > +  /* Construct the iterator using ARG, and construct the end iterator
> > +     using ARG2.  */
>
> Maybe this is a standard thing, and I'm just showing my lack of
> experience, but, given the existence of methods like rbegin() and
> rend(), I think the comments here should be doing more to explain what's
> expected of this input.
>
> Plus this comment seems confusing, though I can sort of see what it's
> trying to say.
>
> We're creating a *_iterator object, which itself contains other
> iterators.  We need to be super clear which particular "iterator" we're
> talking about in the comments.
>
> Also, I think we need to be explicit that for this reverse iterator, the
> first argument to the constructor is going to be the LAST element that
> is iterated over.  One might be tempted to think that a safe reverse iterator
> should be initialised with rbegin() and rend(), which isn't going to be
> correct (unless I'm not understand, please correct me if so).

That's right, the arguments to basic_safe_reverse_iterator should be
forward iterators.  This will not work with reverse iterators.

I added more information to this comment to hopefully clear up any
confusion.

> > +
> > +/* A range adapter that wraps a forward range, and then returns
> > +   safe reverse iterators wrapping the original range's iterators.  */
>
> I think the fact that this takes a FORWARD range is really important,
> and we should do more to advertise this.  E.g. I think there should be a
> comment on the constructor saying that RANGE is a forward range over
> which we will iterate backwards.
>

Done.  The updated patch is included below.

Aaron

Commit message:

This patch changes progspace objfile_list insertion so that separate
debug objfiles are placed into the list after the parent objfile,
instead of before.  Additionally qf_require_partial_symbols now returns
a safe_range.

These changes are intended to prepare gdb for on-demand debuginfo
downloading and the downloading of .gdb_index sections.

With on-demand downloading enabled, gdb might need to delete a
.gdb_index quick_symbol_functions from a parent objfile while looping
the objfile's list of quick_symbol_functions because the separate
debug objfile has just been downloaded.  The use of a safe_range
prevents this removal from causing iterator invalidation.

gdb might also download a debuginfo file during symtab expansion.
In this case an objfile will be added to the current progspace's
objfiles_list during iteration over the list (for example, in
iterate_over_symtabs).  We want these loops to also iterate over
newly downloaded objfiles.  So objfiles need to be inserted into
objfiles_list after their parent since it is during the search of
the parent objfile for some symbol or filename that the separate
debug objfile might be downloaded.

To facilitate the safe deletion of objfiles, this patch also adds
basic_safe_reverse_range and basic_safe_reverse_iterator.  This allows
objfiles to be removed from the objfiles_list in a loop without iterator
invalidation.

If a forward safe iterator were to be used, the deletion of an
objfile could invalidate the safe iterator's reference to the next
objfile in the objfiles_list.  This can happen when the deletion
of an objfile causes the deletion of a separate debug objfile that
happens to the be next element in the objfiles_list.

The standard reverse iterator is not suitable for safe objfile deletion.
In order to safely delete the first objfile in the objfiles_list, the
standard reverse iterator's underlying begin iterator would have to be
decremented, resulting in undefined behavior.

A small change was also made to a testcase in py-objfile.exp to
account for the new placement of separate debug objfiles in
objfiles_list.
---
 gdb/jit.c                               |   7 +-
 gdb/objfiles.c                          |  14 +--
 gdb/objfiles.h                          |  17 +++-
 gdb/progspace.c                         |  19 +++-
 gdb/progspace.h                         |  31 ++++---
 gdb/symfile-debug.c                     |  34 +++----
 gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
 gdbsupport/safe-iterator.h              | 115 ++++++++++++++++++++++++
 8 files changed, 195 insertions(+), 44 deletions(-)

diff --git a/gdb/jit.c b/gdb/jit.c
index 85a10be3055..10ee5f9b749 100644
--- a/gdb/jit.c
+++ b/gdb/jit.c
@@ -1240,11 +1240,10 @@ jit_breakpoint_re_set (void)
 static void
 jit_inferior_exit_hook (struct inferior *inf)
 {
-  for (objfile *objf : current_program_space->objfiles_safe ())
+  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
     {
-      if (objf->jited_data != nullptr && objf->jited_data->addr != 0)
-	objf->unlink ();
-    }
+      return (objf->jited_data != nullptr) && (objf->jited_data->addr != 0);
+    });
 }
 
 void
diff --git a/gdb/objfiles.c b/gdb/objfiles.c
index 8f085b1bb7c..d53dfaca90b 100644
--- a/gdb/objfiles.c
+++ b/gdb/objfiles.c
@@ -470,6 +470,12 @@ objfile::unlink ()
   current_program_space->remove_objfile (this);
 }
 
+qf_safe_range
+objfile::qf_safe ()
+{
+  return qf_safe_range (qf_range (qf.begin (), qf.end ()));
+}
+
 /* Free all separate debug objfile of OBJFILE, but don't free OBJFILE
    itself.  */
 
@@ -793,14 +799,12 @@ have_full_symbols (void)
 void
 objfile_purge_solibs (void)
 {
-  for (objfile *objf : current_program_space->objfiles_safe ())
+  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
     {
       /* We assume that the solib package has been purged already, or will
 	 be soon.  */
-
-      if (!(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED))
-	objf->unlink ();
-    }
+      return !(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED);
+    });
 }
 
 
diff --git a/gdb/objfiles.h b/gdb/objfiles.h
index 621342d22b7..b917886988d 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -370,6 +370,12 @@ class separate_debug_iterator
 
 typedef iterator_range<separate_debug_iterator> separate_debug_range;
 
+/* See objfile::qf_safe.  */
+
+using qf_list = std::forward_list<quick_symbol_functions_up>;
+using qf_range = iterator_range<qf_list::iterator>;
+using qf_safe_range = basic_safe_range<qf_range>;
+
 /* Sections in an objfile.  The section offsets are stored in the
    OBJFILE.  */
 
@@ -761,8 +767,15 @@ struct objfile
   const struct sym_fns *sf = nullptr;
 
   /* The "quick" (aka partial) symbol functions for this symbol
-     reader.  */
-  std::forward_list<quick_symbol_functions_up> qf;
+     reader.  Many quick_symbol_functions methods may result
+     in the deletion of a quick_symbol_functions from this
+     qf_list.  It is recommended that qf_safe be used to iterate
+     over the qf_list.  */
+  qf_list qf;
+
+  /* Returns an iterable object that allows for safe deletion during
+     iteration.  See gdbsupport/safe-iterator.h.  */
+  qf_safe_range qf_safe ();
 
   /* Per objfile data-pointers required by other GDB modules.  */
 
diff --git a/gdb/progspace.c b/gdb/progspace.c
index 0fc1fdd57ab..c4cc2e2e8af 100644
--- a/gdb/progspace.c
+++ b/gdb/progspace.c
@@ -141,19 +141,19 @@ program_space::free_all_objfiles ()
 
 void
 program_space::add_objfile (std::unique_ptr<objfile> &&objfile,
-			    struct objfile *before)
+			    struct objfile *after)
 {
-  if (before == nullptr)
+  if (after == nullptr)
     objfiles_list.push_back (std::move (objfile));
   else
     {
       auto iter = std::find_if (objfiles_list.begin (), objfiles_list.end (),
 				[=] (const std::unique_ptr<::objfile> &objf)
 				{
-				  return objf.get () == before;
+				  return objf.get () == after;
 				});
       gdb_assert (iter != objfiles_list.end ());
-      objfiles_list.insert (iter, std::move (objfile));
+      objfiles_list.insert (++iter, std::move (objfile));
     }
 }
 
@@ -182,6 +182,17 @@ program_space::remove_objfile (struct objfile *objfile)
 
 /* See progspace.h.  */
 
+void
+program_space::unlink_objfiles_if
+  (gdb::function_view<bool (const objfile *objfile)> predicate)
+{
+  for (auto &it : objfiles_safe ())
+    if (predicate (it.get ()))
+      it->unlink ();
+}
+
+/* See progspace.h.  */
+
 struct objfile *
 program_space::objfile_for_address (CORE_ADDR address)
 {
diff --git a/gdb/progspace.h b/gdb/progspace.h
index 163cbf6a0f8..f23a57dc842 100644
--- a/gdb/progspace.h
+++ b/gdb/progspace.h
@@ -250,28 +250,32 @@ struct program_space
        unwrapping_objfile_iterator (objfiles_list.end ()));
   }
 
-  using objfiles_safe_range = basic_safe_range<objfiles_range>;
+  using objfiles_it_range = iterator_range<objfile_list::iterator>;
+  using objfiles_safe_reverse_range
+    = basic_safe_reverse_range<objfiles_it_range>;
 
   /* An iterable object that can be used to iterate over all objfiles.
      The basic use is in a foreach, like:
 
      for (objfile *objf : pspace->objfiles_safe ()) { ... }
 
-     This variant uses a basic_safe_iterator so that objfiles can be
-     deleted during iteration.  */
-  objfiles_safe_range objfiles_safe ()
+     This variant uses a basic_safe_reverse_iterator so that objfiles
+     can be deleted during iteration.
+
+     The use of a reverse iterator helps ensure that separate debug
+     objfiles are deleted before their parent objfile.  This prevents
+     iterator invalidation due to the deletion of a parent objfile.  */
+ objfiles_safe_reverse_range objfiles_safe ()
   {
-    return objfiles_safe_range
-      (objfiles_range
-	 (unwrapping_objfile_iterator (objfiles_list.begin ()),
-	  unwrapping_objfile_iterator (objfiles_list.end ())));
+    return objfiles_safe_reverse_range
+      (objfiles_it_range (objfiles_list.begin (), objfiles_list.end ()));
   }
 
-  /* Add OBJFILE to the list of objfiles, putting it just before
-     BEFORE.  If BEFORE is nullptr, it will go at the end of the
+  /* Add OBJFILE to the list of objfiles, putting it just after
+     AFTER.  If AFTER is nullptr, it will go at the end of the
      list.  */
   void add_objfile (std::unique_ptr<objfile> &&objfile,
-		    struct objfile *before);
+		    struct objfile *after);
 
   /* Remove OBJFILE from the list of objfiles.  */
   void remove_objfile (struct objfile *objfile);
@@ -286,6 +290,11 @@ struct program_space
   /* Free all the objfiles associated with this program space.  */
   void free_all_objfiles ();
 
+  /* Unlink all objfiles associated with this program space for which
+     PREDICATE evaluates to true.  */
+  void unlink_objfiles_if
+    (gdb::function_view<bool (const objfile *objfile)> predicate);
+
   /* Return the objfile containing ADDRESS, or nullptr if the address
      is outside all objfiles in this progspace.  */
   struct objfile *objfile_for_address (CORE_ADDR address);
diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
index 32c3ed9d349..c684f9cdd43 100644
--- a/gdb/symfile-debug.c
+++ b/gdb/symfile-debug.c
@@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
      them, then that is an indication that they are in fact available.  Without
      this function the symbols may have been already read in but they also may
      not be present in this objfile.  */
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       retval = iter->has_symbols (this);
       if (retval)
@@ -108,7 +108,7 @@ objfile::has_unexpanded_symtabs ()
 		objfile_debug_name (this));
 
   bool result = false;
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       if (iter->has_unexpanded_symtabs (this))
 	{
@@ -133,7 +133,7 @@ objfile::find_last_source_symtab ()
     gdb_printf (gdb_stdlog, "qf->find_last_source_symtab (%s)\n",
 		objfile_debug_name (this));
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       retval = iter->find_last_source_symtab (this);
       if (retval != nullptr)
@@ -166,7 +166,7 @@ objfile::forget_cached_source_info ()
 	}
     }
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->forget_cached_source_info (this);
 }
 
@@ -213,7 +213,7 @@ objfile::map_symtabs_matching_filename
     return result;
   };
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       if (!iter->expand_symtabs_matching (this,
 					  match_one_filename,
@@ -278,7 +278,7 @@ objfile::lookup_symbol (block_enum kind, const char *name, domain_enum domain)
     return true;
   };
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       if (!iter->expand_symtabs_matching (this,
 					  nullptr,
@@ -309,7 +309,7 @@ objfile::print_stats (bool print_bcache)
     gdb_printf (gdb_stdlog, "qf->print_stats (%s, %d)\n",
 		objfile_debug_name (this), print_bcache);
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->print_stats (this, print_bcache);
 }
 
@@ -320,7 +320,7 @@ objfile::dump ()
     gdb_printf (gdb_stdlog, "qf->dump (%s)\n",
 		objfile_debug_name (this));
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->dump (this);
 }
 
@@ -335,7 +335,7 @@ objfile::expand_symtabs_for_function (const char *func_name)
   lookup_name_info base_lookup (func_name, symbol_name_match_type::FULL);
   lookup_name_info lookup_name = base_lookup.make_ignore_params ();
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->expand_symtabs_matching (this,
 				   nullptr,
 				   &lookup_name,
@@ -354,7 +354,7 @@ objfile::expand_all_symtabs ()
     gdb_printf (gdb_stdlog, "qf->expand_all_symtabs (%s)\n",
 		objfile_debug_name (this));
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->expand_all_symtabs (this);
 }
 
@@ -372,7 +372,7 @@ objfile::expand_symtabs_with_fullname (const char *fullname)
     return filename_cmp (basenames ? basename : fullname, filename) == 0;
   };
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->expand_symtabs_matching (this,
 				   file_matcher,
 				   nullptr,
@@ -406,7 +406,7 @@ objfile::expand_symtabs_matching
 		host_address_to_string (&expansion_notify),
 		search_domain_name (kind));
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     if (!iter->expand_symtabs_matching (this, file_matcher, lookup_name,
 					symbol_matcher, expansion_notify,
 					search_flags, domain, kind))
@@ -431,7 +431,7 @@ objfile::find_pc_sect_compunit_symtab (struct bound_minimal_symbol msymbol,
 		host_address_to_string (section),
 		warn_if_readin);
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       retval = iter->find_pc_sect_compunit_symtab (this, msymbol, pc, section,
 						   warn_if_readin);
@@ -459,7 +459,7 @@ objfile::map_symbol_filenames (gdb::function_view<symbol_filename_ftype> fun,
 		objfile_debug_name (this),
 		need_fullname);
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->map_symbol_filenames (this, fun, need_fullname);
 }
 
@@ -471,7 +471,7 @@ objfile::compute_main_name ()
 		"qf->compute_main_name (%s)\n",
 		objfile_debug_name (this));
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     iter->compute_main_name (this);
 }
 
@@ -485,7 +485,7 @@ objfile::find_compunit_symtab_by_address (CORE_ADDR address)
 		hex_string (address));
 
   struct compunit_symtab *result = NULL;
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       result = iter->find_compunit_symtab_by_address (this, address);
       if (result != nullptr)
@@ -510,7 +510,7 @@ objfile::lookup_global_symbol_language (const char *name,
   enum language result = language_unknown;
   *symbol_found_p = false;
 
-  for (const auto &iter : qf)
+  for (const auto &iter : qf_safe ())
     {
       result = iter->lookup_global_symbol_language (this, name, domain,
 						    symbol_found_p);
diff --git a/gdb/testsuite/gdb.python/py-objfile.exp b/gdb/testsuite/gdb.python/py-objfile.exp
index 61b9942de79..0bf49976b73 100644
--- a/gdb/testsuite/gdb.python/py-objfile.exp
+++ b/gdb/testsuite/gdb.python/py-objfile.exp
@@ -135,7 +135,7 @@ gdb_test "p main" "= {<text variable, no debug info>} $hex <main>" \
 gdb_py_test_silent_cmd "python objfile.add_separate_debug_file(\"${binfile}\")" \
     "Add separate debug file file" 1
 
-gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[0\]" \
+gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[1\]" \
     "Get separate debug info objfile" 1
 
 gdb_test "python print (sep_objfile.owner.filename)" "${testfile}2" \
diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h
index b4891328f1a..d1b9ce21ffc 100644
--- a/gdbsupport/safe-iterator.h
+++ b/gdbsupport/safe-iterator.h
@@ -136,4 +136,119 @@ class basic_safe_range
   Range m_range;
 };
 
+/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
+
+template<typename Iterator>
+class basic_safe_reverse_iterator
+{
+public:
+  typedef basic_safe_reverse_iterator self_type;
+  typedef typename Iterator::value_type value_type;
+  typedef typename Iterator::reference reference;
+  typedef typename Iterator::pointer pointer;
+  typedef typename Iterator::iterator_category iterator_category;
+  typedef typename Iterator::difference_type difference_type;
+
+  /* Construct the iterator using ARG, and construct the end iterator
+     using ARG2.  ARG and ARG2 should be forward iterators, typically
+     from begin and end methods, respectively.
+
+     For example if ARG1 is created with container.begin and ARG2 is
+     is created with container.end, then the basic_safe_reverse_iterator
+     will traverse from the last element in the container to the first
+     element in the container.  */
+  template<typename Arg>
+  explicit basic_safe_reverse_iterator (Arg &&arg, Arg &&arg2)
+    : m_begin (std::forward<Arg> (arg)),
+      m_end (std::forward<Arg> (arg2)),
+      m_it (m_end),
+      m_next (m_end)
+  {
+    /* M_IT and M_NEXT are initialized as one-past-end.  Set M_IT to point
+       to the last element and set M_NEXT to point to the second last element,
+       if such elements exist.  */
+    if (m_it != m_begin)
+      {
+	--m_it;
+
+	if (m_it != m_begin)
+	  {
+	    --m_next;
+	    --m_next;
+	  }
+      }
+  }
+
+  typename std::invoke_result<decltype(&Iterator::operator*), Iterator>::type
+    operator* () const
+  { return *m_it; }
+
+  self_type &operator++ ()
+  {
+    m_it = m_next;
+
+    if (m_it != m_end)
+      {
+	/* Use M_BEGIN only if we sure that it is valid.  */
+	if (m_it == m_begin)
+	  m_next = m_end;
+	else
+	  --m_next;
+      }
+
+    return *this;
+  }
+
+  bool operator== (const self_type &other) const
+  { return m_it == other.m_it; }
+
+  bool operator!= (const self_type &other) const
+  { return m_it != other.m_it; }
+
+private:
+  /* The first element.  */
+  Iterator m_begin {};
+
+  /* A one-past-end iterator.  */
+  Iterator m_end {};
+
+  /* The current element.  */
+  Iterator m_it {};
+
+  /* The next element.  Always one element ahead of M_IT.  */
+  Iterator m_next {};
+};
+
+/* A range adapter that wraps a forward range, and then returns
+   safe reverse iterators wrapping the original range's iterators.  */
+
+template<typename Range>
+class basic_safe_reverse_range
+{
+public:
+
+  typedef basic_safe_reverse_iterator<typename Range::iterator> iterator;
+
+  /* RANGE must be a forward range.  basic_safe_reverse_iterators
+     will be used to traverse the forward range from the last element
+     to the first.  */
+  explicit basic_safe_reverse_range (Range range)
+    : m_range (range)
+  {
+  }
+
+  iterator begin ()
+  {
+    return iterator (m_range.begin (), m_range.end ());
+  }
+
+  iterator end ()
+  {
+    return iterator (m_range.end (), m_range.end ());
+  }
+
+private:
+
+  Range m_range;
+};
 #endif /* COMMON_SAFE_ITERATOR_H */
-- 
2.43.0


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

* Re: [PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading
  2024-01-18 15:34   ` Andrew Burgess
@ 2024-01-19  5:12     ` Aaron Merey
  2024-02-07 21:25       ` [PING][PATCH " Aaron Merey
  2024-03-27 10:58       ` [PATCH " Andrew Burgess
  0 siblings, 2 replies; 29+ messages in thread
From: Aaron Merey @ 2024-01-19  5:12 UTC (permalink / raw)
  To: aburgess; +Cc: gdb-patches, thiago.bauermann, Aaron Merey

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;
+}
+
 \f
 
 /* 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


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

* Re: [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo
  2024-01-18 11:22   ` Andrew Burgess
@ 2024-01-19  5:28     ` Aaron Merey
  2024-01-19 14:35       ` Luis Machado
  0 siblings, 1 reply; 29+ messages in thread
From: Aaron Merey @ 2024-01-19  5:28 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, thiago.bauermann

On Thu, Jan 18, 2024 at 6:22 AM Andrew Burgess <aburgess@redhat.com> wrote:
>
> So, given nobody who knows the I/O stuff better has commented, I propose
> that, with the fixes I've pointed out above:
>
> Approved-By: Andrew Burgess <aburgess@redhat.com>

Thanks Andrew.  The issues you pointed out were fixed and the patch
was pushed as commit 519d63439.

Aaron


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

* Re: [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo
  2024-01-19  5:28     ` Aaron Merey
@ 2024-01-19 14:35       ` Luis Machado
  2024-01-19 17:13         ` Aaron Merey
  0 siblings, 1 reply; 29+ messages in thread
From: Luis Machado @ 2024-01-19 14:35 UTC (permalink / raw)
  To: Aaron Merey, Andrew Burgess; +Cc: gdb-patches, thiago.bauermann

Hi Aaron,

On 1/19/24 05:28, Aaron Merey wrote:
> On Thu, Jan 18, 2024 at 6:22 AM Andrew Burgess <aburgess@redhat.com> wrote:
>>
>> So, given nobody who knows the I/O stuff better has commented, I propose
>> that, with the fixes I've pointed out above:
>>
>> Approved-By: Andrew Burgess <aburgess@redhat.com>
> 
> Thanks Andrew.  The issues you pointed out were fixed and the patch
> was pushed as commit 519d63439.
> 
> Aaron
> 

I bisected a number of failures in gdb.dwarf2/dw2-dir-file-name.exp to commit 519d634396592c5698add4a327468e6e6920576e.

In particular, my testing was done on aarch64-linux (Ubuntu 20.04).

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

* Re: [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo
  2024-01-19 14:35       ` Luis Machado
@ 2024-01-19 17:13         ` Aaron Merey
  2024-01-19 19:34           ` Tom de Vries
  0 siblings, 1 reply; 29+ messages in thread
From: Aaron Merey @ 2024-01-19 17:13 UTC (permalink / raw)
  To: luis.machado; +Cc: aburgess, thiago.bauermann, gdb-patches, Aaron Merey

Hi Luis,

On Fri, Jan 19, 2024 at 9:35 AM Luis Machado <luis.machado@arm.com> wrote:
>
> I bisected a number of failures in gdb.dwarf2/dw2-dir-file-name.exp to commit 519d634396592c5698add4a327468e6e6920576e.
>
> In particular, my testing was done on aarch64-linux (Ubuntu 20.04).

Thanks for finding this.  These regressions are due to a last-minute
decision to replace throw_exception with throw in one of the functions
added in commit 519d634.

I'm going to merge the patch below that restores throw_exception.
Regression tested on Fedora 39 x86_64.

Aaron

---
gdb/ui-out.h: Fix exception handling in do_with_buffered_output

Replace throw with throw_exeception in do_with_buffered_output.

This patch fixes regressions in gdb.dwarf2/dw2-dir-file-name.exp
caused by commit 519d63439.

do_with_buffered_output needs to use throw_exception instead of
throw to ensure that exceptions of the correct type are thrown.
If using throw, gdb_exception_error may be wrongly converted into
gdb_exception during print_frame_info.  This prevents the exception
from being caught in print_stack_frame.
---
 gdb/ui-out.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gdb/ui-out.h b/gdb/ui-out.h
index 71add908a84..5b6ddd55063 100644
--- a/gdb/ui-out.h
+++ b/gdb/ui-out.h
@@ -543,7 +543,7 @@ do_with_buffered_output (F func, ui_out *uiout, Arg... args)
     {
       func (uiout, std::forward<Arg> (args)...);
     }
-  catch (const gdb_exception &ex)
+  catch (gdb_exception &ex)
     {
       /* Ideally flush would be called in the destructor of buffer_group,
 	 however flushing might cause an exception to be thrown.  Catch it
@@ -556,7 +556,7 @@ do_with_buffered_output (F func, ui_out *uiout, Arg... args)
 	{
 	}
 
-      throw (std::move (ex));
+      throw_exception (std::move (ex));
     }
 
   /* Try was successful.  Let any further exceptions propagate.  */
-- 
2.43.0


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

* Re: [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo
  2024-01-19 17:13         ` Aaron Merey
@ 2024-01-19 19:34           ` Tom de Vries
  2024-01-19 19:52             ` Aaron Merey
  0 siblings, 1 reply; 29+ messages in thread
From: Tom de Vries @ 2024-01-19 19:34 UTC (permalink / raw)
  To: Aaron Merey, luis.machado; +Cc: aburgess, thiago.bauermann, gdb-patches

On 1/19/24 18:13, Aaron Merey wrote:
> Hi Luis,
> 
> On Fri, Jan 19, 2024 at 9:35 AM Luis Machado <luis.machado@arm.com> wrote:
>>
>> I bisected a number of failures in gdb.dwarf2/dw2-dir-file-name.exp to commit 519d634396592c5698add4a327468e6e6920576e.
>>
>> In particular, my testing was done on aarch64-linux (Ubuntu 20.04).
> 
> Thanks for finding this.  These regressions are due to a last-minute
> decision to replace throw_exception with throw in one of the functions
> added in commit 519d634.
> 
> I'm going to merge the patch below that restores throw_exception.
> Regression tested on Fedora 39 x86_64.
> 
> Aaron
> 
> ---
> gdb/ui-out.h: Fix exception handling in do_with_buffered_output
> 
> Replace throw with throw_exeception in do_with_buffered_output.
> 
> This patch fixes regressions in gdb.dwarf2/dw2-dir-file-name.exp
> caused by commit 519d63439.
> 
> do_with_buffered_output needs to use throw_exception instead of
> throw to ensure that exceptions of the correct type are thrown.
> If using throw, gdb_exception_error may be wrongly converted into
> gdb_exception during print_frame_info.  This prevents the exception
> from being caught in print_stack_frame.

Hi Aaron,

what you're describing here sounds like exception slicing.

The easiest way to fix this is by rethrowing using "throw" without argument.

See this ( https://en.cppreference.com/w/cpp/language/throw ):
...
When rethrowing exceptions, the second form must be used to avoid object 
slicing in the (typical) case where exception objects use inheritance: ...

Thanks,
- Tom

> ---
>   gdb/ui-out.h | 4 ++--
>   1 file changed, 2 insertions(+), 2 deletions(-)
> 
> diff --git a/gdb/ui-out.h b/gdb/ui-out.h
> index 71add908a84..5b6ddd55063 100644
> --- a/gdb/ui-out.h
> +++ b/gdb/ui-out.h
> @@ -543,7 +543,7 @@ do_with_buffered_output (F func, ui_out *uiout, Arg... args)
>       {
>         func (uiout, std::forward<Arg> (args)...);
>       }
> -  catch (const gdb_exception &ex)
> +  catch (gdb_exception &ex)
>       {
>         /* Ideally flush would be called in the destructor of buffer_group,
>   	 however flushing might cause an exception to be thrown.  Catch it
> @@ -556,7 +556,7 @@ do_with_buffered_output (F func, ui_out *uiout, Arg... args)
>   	{
>   	}
>   
> -      throw (std::move (ex));
> +      throw_exception (std::move (ex));
>       }
>   
>     /* Try was successful.  Let any further exceptions propagate.  */


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

* Re: [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo
  2024-01-19 19:34           ` Tom de Vries
@ 2024-01-19 19:52             ` Aaron Merey
  0 siblings, 0 replies; 29+ messages in thread
From: Aaron Merey @ 2024-01-19 19:52 UTC (permalink / raw)
  To: Tom de Vries; +Cc: luis.machado, aburgess, thiago.bauermann, gdb-patches

Hi Tom,

On Fri, Jan 19, 2024 at 2:33 PM Tom de Vries <tdevries@suse.de> wrote:
>
> what you're describing here sounds like exception slicing.
>
> The easiest way to fix this is by rethrowing using "throw" without argument.
>
> See this ( https://en.cppreference.com/w/cpp/language/throw ):
> ...
> When rethrowing exceptions, the second form must be used to avoid object
> slicing in the (typical) case where exception objects use inheritance: ...

Gotcha, thanks for clarifying. I wasn't aware of the second form of throw.

Aaron


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

* [PING][PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator
  2024-01-19  5:09     ` Aaron Merey
@ 2024-02-07 21:24       ` Aaron Merey
  2024-02-22 22:22         ` [PING*2][PATCH " Aaron Merey
  0 siblings, 1 reply; 29+ messages in thread
From: Aaron Merey @ 2024-02-07 21:24 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Thiago Jung Bauermann

Ping

Thanks,
Aaron

On Fri, Jan 19, 2024 at 12:09 AM Aaron Merey <amerey@redhat.com> wrote:
>
> Hi Andrew,
>
> Thanks for the review.
>
> On Thu, Jan 18, 2024 at 7:03 AM Andrew Burgess <aburgess@redhat.com> wrote:
> >
> > Aaron Merey <amerey@redhat.com> writes:
> > > +using qf_list = std::forward_list<quick_symbol_functions_up>;
> > > +using qf_range = iterator_range<qf_list::iterator>;
> > > +using qf_safe_range = basic_safe_range<qf_range>;
> > > +
> > >  /* Sections in an objfile.  The section offsets are stored in the
> > >     OBJFILE.  */
> > >
> > > @@ -764,6 +770,10 @@ struct objfile
> > >       reader.  */
> > >    std::forward_list<quick_symbol_functions_up> qf;
> >
> > I think this should be updated to use qf_list.
>
> Done.
>
> > > --- a/gdb/symfile-debug.c
> > > +++ b/gdb/symfile-debug.c
> > > @@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
> > >       them, then that is an indication that they are in fact available.  Without
> > >       this function the symbols may have been already read in but they also may
> > >       not be present in this objfile.  */
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> >
> > I read the commit message a couple of times, but I'm still not 100% sure
> > I understand what's happening here.  Is the change from qf -> qf_safe()
> > because calling (in this case has_symbols()) might download the full
> > debug info, and in doing do, might delete a previously downloaded
> > .debug_info section?
>
> quick_functions_up made from a separately downloaded .gdb_index is at
> first associated with the parent objfile (i.e. the objfile of the shared
> library containing a .gnu_debuglink that refers to the separate debug info
> which actually contains this .gdb_index section).
>
> When the separate debuginfo is downloaded (possibly during iteration
> over the parent's qf_list), we need to remove the gdb_index qf from
> the parent's qf_list.  This prevents gdb from later attempting to read
> the parent objfile for debug info that is actually in the corresponding
> separate debug objfile.
>
> > If this is the case then is it true that we will almost never want to
> > iterate over `qf` directly as most quick_symbol_functions methods might
> > result in this download/delete behaviour?
> >
> > If I'm right, then I think it might be useful to place a warning in the
> > comment before `qf` explaining this.
>
> Done.
>
> > > +/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
> > > +
> > > +template<typename Iterator>
> > > +class basic_safe_reverse_iterator
> > > +{
> > > +public:
> > > +  typedef basic_safe_reverse_iterator self_type;
> > > +  typedef typename Iterator::value_type value_type;
> > > +  typedef typename Iterator::reference reference;
> > > +  typedef typename Iterator::pointer pointer;
> > > +  typedef typename Iterator::iterator_category iterator_category;
> > > +  typedef typename Iterator::difference_type difference_type;
> > > +
> > > +  /* Construct the iterator using ARG, and construct the end iterator
> > > +     using ARG2.  */
> >
> > Maybe this is a standard thing, and I'm just showing my lack of
> > experience, but, given the existence of methods like rbegin() and
> > rend(), I think the comments here should be doing more to explain what's
> > expected of this input.
> >
> > Plus this comment seems confusing, though I can sort of see what it's
> > trying to say.
> >
> > We're creating a *_iterator object, which itself contains other
> > iterators.  We need to be super clear which particular "iterator" we're
> > talking about in the comments.
> >
> > Also, I think we need to be explicit that for this reverse iterator, the
> > first argument to the constructor is going to be the LAST element that
> > is iterated over.  One might be tempted to think that a safe reverse iterator
> > should be initialised with rbegin() and rend(), which isn't going to be
> > correct (unless I'm not understand, please correct me if so).
>
> That's right, the arguments to basic_safe_reverse_iterator should be
> forward iterators.  This will not work with reverse iterators.
>
> I added more information to this comment to hopefully clear up any
> confusion.
>
> > > +
> > > +/* A range adapter that wraps a forward range, and then returns
> > > +   safe reverse iterators wrapping the original range's iterators.  */
> >
> > I think the fact that this takes a FORWARD range is really important,
> > and we should do more to advertise this.  E.g. I think there should be a
> > comment on the constructor saying that RANGE is a forward range over
> > which we will iterate backwards.
> >
>
> Done.  The updated patch is included below.
>
> Aaron
>
> Commit message:
>
> This patch changes progspace objfile_list insertion so that separate
> debug objfiles are placed into the list after the parent objfile,
> instead of before.  Additionally qf_require_partial_symbols now returns
> a safe_range.
>
> These changes are intended to prepare gdb for on-demand debuginfo
> downloading and the downloading of .gdb_index sections.
>
> With on-demand downloading enabled, gdb might need to delete a
> .gdb_index quick_symbol_functions from a parent objfile while looping
> the objfile's list of quick_symbol_functions because the separate
> debug objfile has just been downloaded.  The use of a safe_range
> prevents this removal from causing iterator invalidation.
>
> gdb might also download a debuginfo file during symtab expansion.
> In this case an objfile will be added to the current progspace's
> objfiles_list during iteration over the list (for example, in
> iterate_over_symtabs).  We want these loops to also iterate over
> newly downloaded objfiles.  So objfiles need to be inserted into
> objfiles_list after their parent since it is during the search of
> the parent objfile for some symbol or filename that the separate
> debug objfile might be downloaded.
>
> To facilitate the safe deletion of objfiles, this patch also adds
> basic_safe_reverse_range and basic_safe_reverse_iterator.  This allows
> objfiles to be removed from the objfiles_list in a loop without iterator
> invalidation.
>
> If a forward safe iterator were to be used, the deletion of an
> objfile could invalidate the safe iterator's reference to the next
> objfile in the objfiles_list.  This can happen when the deletion
> of an objfile causes the deletion of a separate debug objfile that
> happens to the be next element in the objfiles_list.
>
> The standard reverse iterator is not suitable for safe objfile deletion.
> In order to safely delete the first objfile in the objfiles_list, the
> standard reverse iterator's underlying begin iterator would have to be
> decremented, resulting in undefined behavior.
>
> A small change was also made to a testcase in py-objfile.exp to
> account for the new placement of separate debug objfiles in
> objfiles_list.
> ---
>  gdb/jit.c                               |   7 +-
>  gdb/objfiles.c                          |  14 +--
>  gdb/objfiles.h                          |  17 +++-
>  gdb/progspace.c                         |  19 +++-
>  gdb/progspace.h                         |  31 ++++---
>  gdb/symfile-debug.c                     |  34 +++----
>  gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
>  gdbsupport/safe-iterator.h              | 115 ++++++++++++++++++++++++
>  8 files changed, 195 insertions(+), 44 deletions(-)
>
> diff --git a/gdb/jit.c b/gdb/jit.c
> index 85a10be3055..10ee5f9b749 100644
> --- a/gdb/jit.c
> +++ b/gdb/jit.c
> @@ -1240,11 +1240,10 @@ jit_breakpoint_re_set (void)
>  static void
>  jit_inferior_exit_hook (struct inferior *inf)
>  {
> -  for (objfile *objf : current_program_space->objfiles_safe ())
> +  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
>      {
> -      if (objf->jited_data != nullptr && objf->jited_data->addr != 0)
> -       objf->unlink ();
> -    }
> +      return (objf->jited_data != nullptr) && (objf->jited_data->addr != 0);
> +    });
>  }
>
>  void
> diff --git a/gdb/objfiles.c b/gdb/objfiles.c
> index 8f085b1bb7c..d53dfaca90b 100644
> --- a/gdb/objfiles.c
> +++ b/gdb/objfiles.c
> @@ -470,6 +470,12 @@ objfile::unlink ()
>    current_program_space->remove_objfile (this);
>  }
>
> +qf_safe_range
> +objfile::qf_safe ()
> +{
> +  return qf_safe_range (qf_range (qf.begin (), qf.end ()));
> +}
> +
>  /* Free all separate debug objfile of OBJFILE, but don't free OBJFILE
>     itself.  */
>
> @@ -793,14 +799,12 @@ have_full_symbols (void)
>  void
>  objfile_purge_solibs (void)
>  {
> -  for (objfile *objf : current_program_space->objfiles_safe ())
> +  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
>      {
>        /* We assume that the solib package has been purged already, or will
>          be soon.  */
> -
> -      if (!(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED))
> -       objf->unlink ();
> -    }
> +      return !(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED);
> +    });
>  }
>
>
> diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> index 621342d22b7..b917886988d 100644
> --- a/gdb/objfiles.h
> +++ b/gdb/objfiles.h
> @@ -370,6 +370,12 @@ class separate_debug_iterator
>
>  typedef iterator_range<separate_debug_iterator> separate_debug_range;
>
> +/* See objfile::qf_safe.  */
> +
> +using qf_list = std::forward_list<quick_symbol_functions_up>;
> +using qf_range = iterator_range<qf_list::iterator>;
> +using qf_safe_range = basic_safe_range<qf_range>;
> +
>  /* Sections in an objfile.  The section offsets are stored in the
>     OBJFILE.  */
>
> @@ -761,8 +767,15 @@ struct objfile
>    const struct sym_fns *sf = nullptr;
>
>    /* The "quick" (aka partial) symbol functions for this symbol
> -     reader.  */
> -  std::forward_list<quick_symbol_functions_up> qf;
> +     reader.  Many quick_symbol_functions methods may result
> +     in the deletion of a quick_symbol_functions from this
> +     qf_list.  It is recommended that qf_safe be used to iterate
> +     over the qf_list.  */
> +  qf_list qf;
> +
> +  /* Returns an iterable object that allows for safe deletion during
> +     iteration.  See gdbsupport/safe-iterator.h.  */
> +  qf_safe_range qf_safe ();
>
>    /* Per objfile data-pointers required by other GDB modules.  */
>
> diff --git a/gdb/progspace.c b/gdb/progspace.c
> index 0fc1fdd57ab..c4cc2e2e8af 100644
> --- a/gdb/progspace.c
> +++ b/gdb/progspace.c
> @@ -141,19 +141,19 @@ program_space::free_all_objfiles ()
>
>  void
>  program_space::add_objfile (std::unique_ptr<objfile> &&objfile,
> -                           struct objfile *before)
> +                           struct objfile *after)
>  {
> -  if (before == nullptr)
> +  if (after == nullptr)
>      objfiles_list.push_back (std::move (objfile));
>    else
>      {
>        auto iter = std::find_if (objfiles_list.begin (), objfiles_list.end (),
>                                 [=] (const std::unique_ptr<::objfile> &objf)
>                                 {
> -                                 return objf.get () == before;
> +                                 return objf.get () == after;
>                                 });
>        gdb_assert (iter != objfiles_list.end ());
> -      objfiles_list.insert (iter, std::move (objfile));
> +      objfiles_list.insert (++iter, std::move (objfile));
>      }
>  }
>
> @@ -182,6 +182,17 @@ program_space::remove_objfile (struct objfile *objfile)
>
>  /* See progspace.h.  */
>
> +void
> +program_space::unlink_objfiles_if
> +  (gdb::function_view<bool (const objfile *objfile)> predicate)
> +{
> +  for (auto &it : objfiles_safe ())
> +    if (predicate (it.get ()))
> +      it->unlink ();
> +}
> +
> +/* See progspace.h.  */
> +
>  struct objfile *
>  program_space::objfile_for_address (CORE_ADDR address)
>  {
> diff --git a/gdb/progspace.h b/gdb/progspace.h
> index 163cbf6a0f8..f23a57dc842 100644
> --- a/gdb/progspace.h
> +++ b/gdb/progspace.h
> @@ -250,28 +250,32 @@ struct program_space
>         unwrapping_objfile_iterator (objfiles_list.end ()));
>    }
>
> -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> +  using objfiles_it_range = iterator_range<objfile_list::iterator>;
> +  using objfiles_safe_reverse_range
> +    = basic_safe_reverse_range<objfiles_it_range>;
>
>    /* An iterable object that can be used to iterate over all objfiles.
>       The basic use is in a foreach, like:
>
>       for (objfile *objf : pspace->objfiles_safe ()) { ... }
>
> -     This variant uses a basic_safe_iterator so that objfiles can be
> -     deleted during iteration.  */
> -  objfiles_safe_range objfiles_safe ()
> +     This variant uses a basic_safe_reverse_iterator so that objfiles
> +     can be deleted during iteration.
> +
> +     The use of a reverse iterator helps ensure that separate debug
> +     objfiles are deleted before their parent objfile.  This prevents
> +     iterator invalidation due to the deletion of a parent objfile.  */
> + objfiles_safe_reverse_range objfiles_safe ()
>    {
> -    return objfiles_safe_range
> -      (objfiles_range
> -        (unwrapping_objfile_iterator (objfiles_list.begin ()),
> -         unwrapping_objfile_iterator (objfiles_list.end ())));
> +    return objfiles_safe_reverse_range
> +      (objfiles_it_range (objfiles_list.begin (), objfiles_list.end ()));
>    }
>
> -  /* Add OBJFILE to the list of objfiles, putting it just before
> -     BEFORE.  If BEFORE is nullptr, it will go at the end of the
> +  /* Add OBJFILE to the list of objfiles, putting it just after
> +     AFTER.  If AFTER is nullptr, it will go at the end of the
>       list.  */
>    void add_objfile (std::unique_ptr<objfile> &&objfile,
> -                   struct objfile *before);
> +                   struct objfile *after);
>
>    /* Remove OBJFILE from the list of objfiles.  */
>    void remove_objfile (struct objfile *objfile);
> @@ -286,6 +290,11 @@ struct program_space
>    /* Free all the objfiles associated with this program space.  */
>    void free_all_objfiles ();
>
> +  /* Unlink all objfiles associated with this program space for which
> +     PREDICATE evaluates to true.  */
> +  void unlink_objfiles_if
> +    (gdb::function_view<bool (const objfile *objfile)> predicate);
> +
>    /* Return the objfile containing ADDRESS, or nullptr if the address
>       is outside all objfiles in this progspace.  */
>    struct objfile *objfile_for_address (CORE_ADDR address);
> diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
> index 32c3ed9d349..c684f9cdd43 100644
> --- a/gdb/symfile-debug.c
> +++ b/gdb/symfile-debug.c
> @@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
>       them, then that is an indication that they are in fact available.  Without
>       this function the symbols may have been already read in but they also may
>       not be present in this objfile.  */
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        retval = iter->has_symbols (this);
>        if (retval)
> @@ -108,7 +108,7 @@ objfile::has_unexpanded_symtabs ()
>                 objfile_debug_name (this));
>
>    bool result = false;
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        if (iter->has_unexpanded_symtabs (this))
>         {
> @@ -133,7 +133,7 @@ objfile::find_last_source_symtab ()
>      gdb_printf (gdb_stdlog, "qf->find_last_source_symtab (%s)\n",
>                 objfile_debug_name (this));
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        retval = iter->find_last_source_symtab (this);
>        if (retval != nullptr)
> @@ -166,7 +166,7 @@ objfile::forget_cached_source_info ()
>         }
>      }
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->forget_cached_source_info (this);
>  }
>
> @@ -213,7 +213,7 @@ objfile::map_symtabs_matching_filename
>      return result;
>    };
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        if (!iter->expand_symtabs_matching (this,
>                                           match_one_filename,
> @@ -278,7 +278,7 @@ objfile::lookup_symbol (block_enum kind, const char *name, domain_enum domain)
>      return true;
>    };
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        if (!iter->expand_symtabs_matching (this,
>                                           nullptr,
> @@ -309,7 +309,7 @@ objfile::print_stats (bool print_bcache)
>      gdb_printf (gdb_stdlog, "qf->print_stats (%s, %d)\n",
>                 objfile_debug_name (this), print_bcache);
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->print_stats (this, print_bcache);
>  }
>
> @@ -320,7 +320,7 @@ objfile::dump ()
>      gdb_printf (gdb_stdlog, "qf->dump (%s)\n",
>                 objfile_debug_name (this));
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->dump (this);
>  }
>
> @@ -335,7 +335,7 @@ objfile::expand_symtabs_for_function (const char *func_name)
>    lookup_name_info base_lookup (func_name, symbol_name_match_type::FULL);
>    lookup_name_info lookup_name = base_lookup.make_ignore_params ();
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->expand_symtabs_matching (this,
>                                    nullptr,
>                                    &lookup_name,
> @@ -354,7 +354,7 @@ objfile::expand_all_symtabs ()
>      gdb_printf (gdb_stdlog, "qf->expand_all_symtabs (%s)\n",
>                 objfile_debug_name (this));
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->expand_all_symtabs (this);
>  }
>
> @@ -372,7 +372,7 @@ objfile::expand_symtabs_with_fullname (const char *fullname)
>      return filename_cmp (basenames ? basename : fullname, filename) == 0;
>    };
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->expand_symtabs_matching (this,
>                                    file_matcher,
>                                    nullptr,
> @@ -406,7 +406,7 @@ objfile::expand_symtabs_matching
>                 host_address_to_string (&expansion_notify),
>                 search_domain_name (kind));
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      if (!iter->expand_symtabs_matching (this, file_matcher, lookup_name,
>                                         symbol_matcher, expansion_notify,
>                                         search_flags, domain, kind))
> @@ -431,7 +431,7 @@ objfile::find_pc_sect_compunit_symtab (struct bound_minimal_symbol msymbol,
>                 host_address_to_string (section),
>                 warn_if_readin);
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        retval = iter->find_pc_sect_compunit_symtab (this, msymbol, pc, section,
>                                                    warn_if_readin);
> @@ -459,7 +459,7 @@ objfile::map_symbol_filenames (gdb::function_view<symbol_filename_ftype> fun,
>                 objfile_debug_name (this),
>                 need_fullname);
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->map_symbol_filenames (this, fun, need_fullname);
>  }
>
> @@ -471,7 +471,7 @@ objfile::compute_main_name ()
>                 "qf->compute_main_name (%s)\n",
>                 objfile_debug_name (this));
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      iter->compute_main_name (this);
>  }
>
> @@ -485,7 +485,7 @@ objfile::find_compunit_symtab_by_address (CORE_ADDR address)
>                 hex_string (address));
>
>    struct compunit_symtab *result = NULL;
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        result = iter->find_compunit_symtab_by_address (this, address);
>        if (result != nullptr)
> @@ -510,7 +510,7 @@ objfile::lookup_global_symbol_language (const char *name,
>    enum language result = language_unknown;
>    *symbol_found_p = false;
>
> -  for (const auto &iter : qf)
> +  for (const auto &iter : qf_safe ())
>      {
>        result = iter->lookup_global_symbol_language (this, name, domain,
>                                                     symbol_found_p);
> diff --git a/gdb/testsuite/gdb.python/py-objfile.exp b/gdb/testsuite/gdb.python/py-objfile.exp
> index 61b9942de79..0bf49976b73 100644
> --- a/gdb/testsuite/gdb.python/py-objfile.exp
> +++ b/gdb/testsuite/gdb.python/py-objfile.exp
> @@ -135,7 +135,7 @@ gdb_test "p main" "= {<text variable, no debug info>} $hex <main>" \
>  gdb_py_test_silent_cmd "python objfile.add_separate_debug_file(\"${binfile}\")" \
>      "Add separate debug file file" 1
>
> -gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[0\]" \
> +gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[1\]" \
>      "Get separate debug info objfile" 1
>
>  gdb_test "python print (sep_objfile.owner.filename)" "${testfile}2" \
> diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h
> index b4891328f1a..d1b9ce21ffc 100644
> --- a/gdbsupport/safe-iterator.h
> +++ b/gdbsupport/safe-iterator.h
> @@ -136,4 +136,119 @@ class basic_safe_range
>    Range m_range;
>  };
>
> +/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
> +
> +template<typename Iterator>
> +class basic_safe_reverse_iterator
> +{
> +public:
> +  typedef basic_safe_reverse_iterator self_type;
> +  typedef typename Iterator::value_type value_type;
> +  typedef typename Iterator::reference reference;
> +  typedef typename Iterator::pointer pointer;
> +  typedef typename Iterator::iterator_category iterator_category;
> +  typedef typename Iterator::difference_type difference_type;
> +
> +  /* Construct the iterator using ARG, and construct the end iterator
> +     using ARG2.  ARG and ARG2 should be forward iterators, typically
> +     from begin and end methods, respectively.
> +
> +     For example if ARG1 is created with container.begin and ARG2 is
> +     is created with container.end, then the basic_safe_reverse_iterator
> +     will traverse from the last element in the container to the first
> +     element in the container.  */
> +  template<typename Arg>
> +  explicit basic_safe_reverse_iterator (Arg &&arg, Arg &&arg2)
> +    : m_begin (std::forward<Arg> (arg)),
> +      m_end (std::forward<Arg> (arg2)),
> +      m_it (m_end),
> +      m_next (m_end)
> +  {
> +    /* M_IT and M_NEXT are initialized as one-past-end.  Set M_IT to point
> +       to the last element and set M_NEXT to point to the second last element,
> +       if such elements exist.  */
> +    if (m_it != m_begin)
> +      {
> +       --m_it;
> +
> +       if (m_it != m_begin)
> +         {
> +           --m_next;
> +           --m_next;
> +         }
> +      }
> +  }
> +
> +  typename std::invoke_result<decltype(&Iterator::operator*), Iterator>::type
> +    operator* () const
> +  { return *m_it; }
> +
> +  self_type &operator++ ()
> +  {
> +    m_it = m_next;
> +
> +    if (m_it != m_end)
> +      {
> +       /* Use M_BEGIN only if we sure that it is valid.  */
> +       if (m_it == m_begin)
> +         m_next = m_end;
> +       else
> +         --m_next;
> +      }
> +
> +    return *this;
> +  }
> +
> +  bool operator== (const self_type &other) const
> +  { return m_it == other.m_it; }
> +
> +  bool operator!= (const self_type &other) const
> +  { return m_it != other.m_it; }
> +
> +private:
> +  /* The first element.  */
> +  Iterator m_begin {};
> +
> +  /* A one-past-end iterator.  */
> +  Iterator m_end {};
> +
> +  /* The current element.  */
> +  Iterator m_it {};
> +
> +  /* The next element.  Always one element ahead of M_IT.  */
> +  Iterator m_next {};
> +};
> +
> +/* A range adapter that wraps a forward range, and then returns
> +   safe reverse iterators wrapping the original range's iterators.  */
> +
> +template<typename Range>
> +class basic_safe_reverse_range
> +{
> +public:
> +
> +  typedef basic_safe_reverse_iterator<typename Range::iterator> iterator;
> +
> +  /* RANGE must be a forward range.  basic_safe_reverse_iterators
> +     will be used to traverse the forward range from the last element
> +     to the first.  */
> +  explicit basic_safe_reverse_range (Range range)
> +    : m_range (range)
> +  {
> +  }
> +
> +  iterator begin ()
> +  {
> +    return iterator (m_range.begin (), m_range.end ());
> +  }
> +
> +  iterator end ()
> +  {
> +    return iterator (m_range.end (), m_range.end ());
> +  }
> +
> +private:
> +
> +  Range m_range;
> +};
>  #endif /* COMMON_SAFE_ITERATOR_H */
> --
> 2.43.0
>


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

* [PING][PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading
  2024-01-19  5:12     ` Aaron Merey
@ 2024-02-07 21:25       ` Aaron Merey
  2024-02-22 22:23         ` [PING*2][PATCH " Aaron Merey
  2024-03-27 10:58       ` [PATCH " Andrew Burgess
  1 sibling, 1 reply; 29+ messages in thread
From: Aaron Merey @ 2024-02-07 21:25 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Thiago Jung Bauermann

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
>


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

* [PING][PATCH 4/4 v6] gdb/debuginfod: Add .debug_line downloading
  2024-01-17 15:48 ` [PATCH 4/4 v6] gdb/debuginfod: Add .debug_line downloading Aaron Merey
@ 2024-02-07 21:25   ` Aaron Merey
  2024-02-22 22:23     ` [PING*2][PATCH " Aaron Merey
  0 siblings, 1 reply; 29+ messages in thread
From: Aaron Merey @ 2024-02-07 21:25 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess, Thiago Jung Bauermann

Ping,

Thanks,
Aaron

On Wed, Jan 17, 2024 at 10:49 AM Aaron Merey <amerey@redhat.com> wrote:
>
> v6 adds a testcase for downloads during autocompletion.  It also
> implements the suggestions from Thiago's review:
> https://sourceware.org/pipermail/gdb-patches/2023-December/205507.html
>
> ELF/DWARF section downloading allows gdb to download .gdb_index files in
> order to defer full debuginfo downloads.  However .gdb_index does not
> contain any information regarding source filenames.  When a gdb command
> includes a filename argument (ex. 'break main.c:50'), this results in
> the mass downloading of all deferred debuginfo so that gdb can search the
> debuginfo for matching source filenames.  This can result in unnecessary
> downloads.
>
> To improve this, have gdb instead download each debuginfo's .debug_line
> (and .debug_line_str if using DWARF5) when executing these commands.
> Download full debuginfo only when its .debug_line contains a matching
> filename.
>
> Since the combined size of .debug_line and .debug_line_str is only about
> 1% the size of the corresponding debuginfo, significant time can be saved
> by checking these sections before choosing to download an entire debuginfo.
>
> This patch also redirects stdout and stderr of the debuginfod server
> used by testsuite/gdb.debuginfod tests to a server_log standard output
> file.  While adding tests for this patch I ran into an issue where the
> test server would block when logging to stderr, presumably because the
> stderr buffer filled up and wasn't being read from.  Redirecting the
> log to a file fixes this and also makes the server log more accessible
> when debugging test failures.
> ---
>  gdb/cli-out.c                            |  11 +-
>  gdb/completer.c                          |  18 +-
>  gdb/dwarf2/line-header.c                 | 161 ++++++++++++------
>  gdb/dwarf2/line-header.h                 |  10 ++
>  gdb/dwarf2/read-gdb-index.c              |  60 +++++++
>  gdb/dwarf2/read.c                        | 208 +++++++++++++++++++++++
>  gdb/dwarf2/read.h                        |  37 ++++
>  gdb/mi/mi-out.c                          |   9 +-
>  gdb/testsuite/gdb.debuginfod/section.exp |  27 +++
>  gdb/ui-out.c                             |   3 +
>  gdb/ui-out.h                             |  20 +++
>  11 files changed, 511 insertions(+), 53 deletions(-)
>
> diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> index b4c7a657423..79f61eed62c 100644
> --- a/gdb/cli-out.c
> +++ b/gdb/cli-out.c
> @@ -307,16 +307,23 @@ cli_ui_out::do_progress_notify (const std::string &msg,
>
>    if (info.state == progress_update::START)
>      {
> +      std::string prefix;
> +      if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED)
> +       {
> +         prefix = "\n";
> +         cur_prefix_state = prefix_state_t::NEWLINE_PRINTED;
> +       }
> +
>        if (stream->isatty ()
>           && current_ui->input_interactive_p ()
>           && chars_per_line >= MIN_CHARS_PER_LINE)
>         {
> -         gdb_printf (stream, "%s\n", msg.c_str ());
> +         gdb_printf (stream, "%s\n", (prefix + msg).c_str ());
>           info.state = progress_update::BAR;
>         }
>        else
>         {
> -         gdb_printf (stream, "%s...\n", msg.c_str ());
> +         gdb_printf (stream, "%s...\n", (prefix + msg).c_str ());
>           info.state = progress_update::WORKING;
>         }
>      }
> diff --git a/gdb/completer.c b/gdb/completer.c
> index 8e2f031f13a..6d453d0d197 100644
> --- a/gdb/completer.c
> +++ b/gdb/completer.c
> @@ -1362,6 +1362,10 @@ complete_line_internal_1 (completion_tracker &tracker,
>      {
>        /* We've recognized a full command.  */
>
> +      /* Disable pagination since responding to the pagination prompt
> +        overwrites rl_line_buffer.  */
> +      scoped_restore pag_restore = make_scoped_restore (&pagination_enabled, false);
> +
>        if (p == tmp_command + point)
>         {
>           /* There is no non-whitespace in the line beyond the
> @@ -1461,7 +1465,8 @@ complete_line_internal_1 (completion_tracker &tracker,
>  }
>
>  /* Wrapper around complete_line_internal_1 to handle
> -   MAX_COMPLETIONS_REACHED_ERROR.  */
> +   MAX_COMPLETIONS_REACHED_ERROR and possible progress update
> +   interactions.  */
>
>  static void
>  complete_line_internal (completion_tracker &tracker,
> @@ -1469,6 +1474,11 @@ complete_line_internal (completion_tracker &tracker,
>                         const char *line_buffer, int point,
>                         complete_line_internal_reason reason)
>  {
> +  scoped_restore restore_prefix_state
> +    = make_scoped_restore
> +      (&cur_prefix_state,
> +       ui_out::progress_update::prefix_state::NEWLINE_NEEDED);
> +
>    try
>      {
>        complete_line_internal_1 (tracker, text, line_buffer, point, reason);
> @@ -1478,6 +1488,12 @@ complete_line_internal (completion_tracker &tracker,
>        if (except.error != MAX_COMPLETIONS_REACHED_ERROR)
>         throw;
>      }
> +
> +  /* If progress update messages printed, then the text being completed
> +     needs to be printed again.  */
> +  if (cur_prefix_state
> +      == ui_out::progress_update::prefix_state::NEWLINE_PRINTED)
> +    rl_forced_update_display ();
>  }
>
>  /* See completer.h.  */
> diff --git a/gdb/dwarf2/line-header.c b/gdb/dwarf2/line-header.c
> index ddaf7e4a527..aec02b3e1f7 100644
> --- a/gdb/dwarf2/line-header.c
> +++ b/gdb/dwarf2/line-header.c
> @@ -102,12 +102,15 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
>  {
>    LONGEST length = read_initial_length (abfd, buf, bytes_read);
>
> -  gdb_assert (cu_header->initial_length_size == 4
> -             || cu_header->initial_length_size == 8
> -             || cu_header->initial_length_size == 12);
> +  if (cu_header != nullptr)
> +    {
> +      gdb_assert (cu_header->initial_length_size == 4
> +                 || cu_header->initial_length_size == 8
> +                 || cu_header->initial_length_size == 12);
>
> -  if (cu_header->initial_length_size != *bytes_read)
> -    complaint (_("intermixed 32-bit and 64-bit DWARF sections"));
> +      if (cu_header->initial_length_size != *bytes_read)
> +       complaint (_("intermixed 32-bit and 64-bit DWARF sections"));
> +    }
>
>    *offset_size = (*bytes_read == 4) ? 4 : 8;
>    return length;
> @@ -116,21 +119,27 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
>  /* Read directory or file name entry format, starting with byte of
>     format count entries, ULEB128 pairs of entry formats, ULEB128 of
>     entries count and the entries themselves in the described entry
> -   format.  */
> +   format.
> +
> +   .debug_line and .debug_line_str sections are stored in LINE_BUFP
> +   and LINE_STR_DATA respectively.  */
>
>  static void
> -read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> -                       const gdb_byte **bufp, struct line_header *lh,
> -                       unsigned int offset_size,
> -                       void (*callback) (struct line_header *lh,
> -                                         const char *name,
> -                                         dir_index d_index,
> -                                         unsigned int mod_time,
> -                                         unsigned int length))
> +read_formatted_entries
> +  (bfd *abfd, const gdb_byte **line_bufp,
> +   const gdb::array_view<const gdb_byte> line_str_data,
> +   struct line_header *lh,
> +   unsigned int offset_size,
> +   void (*callback) (struct line_header *lh,
> +                    const char *name,
> +                    dir_index d_index,
> +                    unsigned int mod_time,
> +                    unsigned int length))
>  {
>    gdb_byte format_count, formati;
>    ULONGEST data_count, datai;
> -  const gdb_byte *buf = *bufp;
> +  const gdb_byte *buf = *line_bufp;
> +  const gdb_byte *str_buf = line_str_data.data ();
>    const gdb_byte *format_header_data;
>    unsigned int bytes_read;
>
> @@ -157,7 +166,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
>           ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
>           format += bytes_read;
>
> -         ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
> +         ULONGEST form = read_unsigned_leb128 (abfd, format, &bytes_read);
>           format += bytes_read;
>
>           std::optional<const char *> string;
> @@ -172,12 +181,24 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
>
>             case DW_FORM_line_strp:
>               {
> -               const char *str
> -                 = per_objfile->read_line_string (buf, offset_size);
> +               if (line_str_data.empty ())
> +                 warning (_("Dwarf Error: DW_FORM_line_strp used without " \
> +                            "required section"));
> +
> +               if (line_str_data.size () <= offset_size)
> +                 warning (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
> +                            "of section .debug_line_str"));
> +
> +               ULONGEST str_offset = read_offset (abfd, buf, offset_size);
> +               const char *str;
> +               if (str_buf[str_offset] == '\0')
> +                 str = nullptr;
> +               else
> +                 str = (const char *) (str_buf + str_offset);
>                 string.emplace (str);
>                 buf += offset_size;
> +               break;
>               }
> -             break;
>
>             case DW_FORM_data1:
>               uint.emplace (read_1_byte (abfd, buf));
> @@ -248,28 +269,30 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
>        callback (lh, fe.name, fe.d_index, fe.mod_time, fe.length);
>      }
>
> -  *bufp = buf;
> +  *line_bufp = buf;
>  }
>
>  /* See line-header.h.  */
>
>  line_header_up
> -dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> -                          dwarf2_per_objfile *per_objfile,
> -                          struct dwarf2_section_info *section,
> -                          const struct comp_unit_head *cu_header,
> -                          const char *comp_dir)
> +dwarf_decode_line_header (bfd *abfd,
> +                         gdb::array_view<const gdb_byte> line_data,
> +                         gdb::array_view<const gdb_byte> line_str_data,
> +                         const gdb_byte **debug_line_ptr,
> +                         bool is_dwz,
> +                         const struct comp_unit_head *cu_header,
> +                         const char *comp_dir)
>  {
> -  const gdb_byte *line_ptr;
> +  const gdb_byte *line_ptr, *buf;
>    unsigned int bytes_read, offset_size;
>    int i;
>    const char *cur_dir, *cur_file;
>
> -  bfd *abfd = section->get_bfd_owner ();
> +  buf = *debug_line_ptr;
>
>    /* Make sure that at least there's room for the total_length field.
>       That could be 12 bytes long, but we're just going to fudge that.  */
> -  if (to_underlying (sect_off) + 4 >= section->size)
> +  if (buf + 4 >= line_data.data () + line_data.size ())
>      {
>        dwarf2_statement_list_fits_in_line_number_section_complaint ();
>        return 0;
> @@ -277,24 +300,26 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
>
>    line_header_up lh (new line_header (comp_dir));
>
> -  lh->sect_off = sect_off;
> +  lh->sect_off = (sect_offset) (buf - line_data.data ());
>    lh->offset_in_dwz = is_dwz;
>
> -  line_ptr = section->buffer + to_underlying (sect_off);
> +  line_ptr = buf;
>
>    /* Read in the header.  */
>    LONGEST unit_length
> -    = read_checked_initial_length_and_offset (abfd, line_ptr, cu_header,
> +    = read_checked_initial_length_and_offset (abfd, buf, cu_header,
>                                               &bytes_read, &offset_size);
> -  line_ptr += bytes_read;
>
> -  const gdb_byte *start_here = line_ptr;
> +  line_ptr += bytes_read;
>
> -  if (line_ptr + unit_length > (section->buffer + section->size))
> +  if (line_ptr + unit_length > buf + line_data.size ())
>      {
>        dwarf2_statement_list_fits_in_line_number_section_complaint ();
>        return 0;
>      }
> +
> +  const gdb_byte *start_here = line_ptr;
> +
>    lh->statement_program_end = start_here + unit_length;
>    lh->version = read_2_bytes (abfd, line_ptr);
>    line_ptr += 2;
> @@ -303,7 +328,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
>        /* This is a version we don't understand.  The format could have
>          changed in ways we don't handle properly so just punt.  */
>        complaint (_("unsupported version in .debug_line section"));
> -      return NULL;
> +      return nullptr;
>      }
>    if (lh->version >= 5)
>      {
> @@ -320,13 +345,14 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
>           complaint (_("unsupported segment selector size %u "
>                        "in .debug_line section"),
>                      segment_selector_size);
> -         return NULL;
> +         return nullptr;
>         }
>      }
>
>    LONGEST header_length = read_offset (abfd, line_ptr, offset_size);
>    line_ptr += offset_size;
>    lh->statement_program_start = line_ptr + header_length;
> +
>    lh->minimum_instruction_length = read_1_byte (abfd, line_ptr);
>    line_ptr += 1;
>
> @@ -347,12 +373,16 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
>
>    lh->default_is_stmt = read_1_byte (abfd, line_ptr);
>    line_ptr += 1;
> +
>    lh->line_base = read_1_signed_byte (abfd, line_ptr);
>    line_ptr += 1;
> +
>    lh->line_range = read_1_byte (abfd, line_ptr);
>    line_ptr += 1;
> +
>    lh->opcode_base = read_1_byte (abfd, line_ptr);
>    line_ptr += 1;
> +
>    lh->standard_opcode_lengths.reset (new unsigned char[lh->opcode_base]);
>
>    lh->standard_opcode_lengths[0] = 1;  /* This should never be used anyway.  */
> @@ -365,21 +395,23 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
>    if (lh->version >= 5)
>      {
>        /* Read directory table.  */
> -      read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
> -                             offset_size,
> -                             [] (struct line_header *header, const char *name,
> -                                 dir_index d_index, unsigned int mod_time,
> -                                 unsigned int length)
> +      read_formatted_entries
> +       (abfd, &line_ptr, line_str_data,
> +        lh.get (), offset_size,
> +        [] (struct line_header *header, const char *name,
> +            dir_index d_index, unsigned int mod_time,
> +            unsigned int length)
>         {
>           header->add_include_dir (name);
>         });
>
>        /* Read file name table.  */
> -      read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
> -                             offset_size,
> -                             [] (struct line_header *header, const char *name,
> -                                 dir_index d_index, unsigned int mod_time,
> -                                 unsigned int length)
> +      read_formatted_entries
> +       (abfd, &line_ptr, line_str_data,
> +        lh.get (), offset_size,
> +        [] (struct line_header *header, const char *name,
> +            dir_index d_index, unsigned int mod_time,
> +            unsigned int length)
>         {
>           header->add_file_name (name, d_index, mod_time, length);
>         });
> @@ -387,7 +419,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
>    else
>      {
>        /* Read directory table.  */
> -      while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL)
> +      while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr)
>         {
>           line_ptr += bytes_read;
>           lh->add_include_dir (cur_dir);
> @@ -395,7 +427,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
>        line_ptr += bytes_read;
>
>        /* Read file name table.  */
> -      while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL)
> +      while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr)
>         {
>           unsigned int mod_time, length;
>           dir_index d_index;
> @@ -413,9 +445,40 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
>        line_ptr += bytes_read;
>      }
>
> -  if (line_ptr > (section->buffer + section->size))
> +  if (line_ptr > (buf + line_data.size ()))
>      complaint (_("line number info header doesn't "
>                  "fit in `.debug_line' section"));
>
> +  *debug_line_ptr += unit_length + offset_size;
>    return lh;
>  }
> +
> +line_header_up
> +dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> +                          dwarf2_per_objfile *per_objfile,
> +                          struct dwarf2_section_info *section,
> +                          const struct comp_unit_head *cu_header,
> +                          const char *comp_dir)
> +{
> +  struct objfile *objfile = per_objfile->objfile;
> +  struct dwarf2_per_bfd *per_bfd = per_objfile->per_bfd;
> +
> +  /* Read .debug_line.  */
> +  dwarf2_section_info *line_sec = &per_bfd->line;
> +  bfd_size_type line_size = line_sec->get_size (objfile);
> +
> +  gdb::array_view<const gdb_byte> line (line_sec->buffer, line_size);
> +
> +  /* Read .debug_line_str.  */
> +  dwarf2_section_info *line_str_sec = &per_bfd->line_str;
> +  bfd_size_type line_str_size = line_str_sec->get_size (objfile);
> +
> +  gdb::array_view<const gdb_byte> line_str (line_str_sec->buffer,
> +                                           line_str_size);
> +
> +  const gdb_byte *line_ptr = line.data () + to_underlying (sect_off);
> +
> +  return dwarf_decode_line_header
> +    (per_bfd->obfd, line, line_str, &line_ptr,
> +     is_dwz, cu_header, comp_dir);
> +}
> diff --git a/gdb/dwarf2/line-header.h b/gdb/dwarf2/line-header.h
> index 06d2eec573b..1dffb7c7e5e 100644
> --- a/gdb/dwarf2/line-header.h
> +++ b/gdb/dwarf2/line-header.h
> @@ -217,4 +217,14 @@ extern line_header_up dwarf_decode_line_header
>     struct dwarf2_section_info *section, const struct comp_unit_head *cu_header,
>     const char *comp_dir);
>
> +/* Like above but the .debug_line and .debug_line_str are stored in
> +   LINE_DATA and LINE_STR_DATA.  *DEBUG_LINE_PTR should point to a
> +   statement program header within LINE_DATA.  */
> +
> +extern line_header_up dwarf_decode_line_header
> +  (bfd *abfd, gdb::array_view<const gdb_byte> line_data,
> +   gdb::array_view<const gdb_byte> line_str_data,
> +   const gdb_byte **debug_line_ptr, bool is_dwz,
> +  const comp_unit_head *cu_header, const char *comp_dir);
> +
>  #endif /* DWARF2_LINE_HEADER_H */
> diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
> index 55b04391eb5..3d75678d7dc 100644
> --- a/gdb/dwarf2/read-gdb-index.c
> +++ b/gdb/dwarf2/read-gdb-index.c
> @@ -131,6 +131,9 @@ struct mapped_gdb_index final : public mapped_index_base
>    }
>  };
>
> +struct mapped_debug_line;
> +typedef std::unique_ptr<mapped_debug_line> mapped_debug_line_up;
> +
>  struct dwarf2_gdb_index : public dwarf2_base_index_functions
>  {
>    /* This dumps minimal information about the index.
> @@ -162,6 +165,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>       domain_enum domain,
>       enum search_domain kind);
>
> + /* If OBJFILE's debuginfo download has been deferred, use a mapped_debug_line
> +    to generate filenames.
> +
> +    Otherwise call dwarf2_base_index_functions::map_symbol_filenames.  */
> +
> +  void map_symbol_filenames (struct objfile *objfile,
> +                            gdb::function_view<symbol_filename_ftype> fun,
> +                            bool need_fullname) override;
> +
>    /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
>       debuginfo if necessary.  */
>    void expand_all_symtabs (struct objfile *objfile) override;
> @@ -169,6 +181,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>    /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
>       debuginfo if necessary.  */
>    struct symtab *find_last_source_symtab (struct objfile *objfile) override;
> +
> +  /* Filename information related to this .gdb_index.  */
> +  mapped_debug_line_up mdl;
> +
> +  /* Return true if any of the filenames in this .gdb_index's .debug_line
> +     mapping match FILE_MATCHER.  Initializes the mapping if necessary.  */
> +  bool filename_in_debug_line
> +    (objfile *objfile,
> +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher);
>  };
>
>  void
> @@ -204,6 +225,30 @@ dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
>      }
>  }
>
> +void
> +dwarf2_gdb_index::map_symbol_filenames
> +     (struct objfile *objfile,
> +      gdb::function_view<symbol_filename_ftype> fun,
> +      bool need_fullname)
> +{
> +  try
> +    {
> +      dwarf2_base_index_functions::map_symbol_filenames (objfile, fun,
> +                                                        need_fullname);
> +    }
> +  catch (const gdb_exception &e)
> +    {
> +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> +       exception_print (gdb_stderr, e);
> +      else
> +       {
> +         if (mdl == nullptr)
> +           mdl.reset (new mapped_debug_line (objfile));
> +         mdl->map_filenames (fun, need_fullname);
> +       }
> +    }
> +}
> +
>  /* 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
> @@ -371,6 +416,17 @@ dwarf2_gdb_index::do_expand_symtabs_matching
>    return result;
>  }
>
> +bool
> +dwarf2_gdb_index::filename_in_debug_line
> +  (objfile *objfile,
> +   gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> +{
> +  if (mdl == nullptr)
> +    mdl.reset (new mapped_debug_line (objfile));
> +
> +  return mdl->contains_matching_filename (file_matcher);
> +}
> +
>  bool
>  dwarf2_gdb_index::expand_symtabs_matching
>      (struct objfile *objfile,
> @@ -399,6 +455,10 @@ dwarf2_gdb_index::expand_symtabs_matching
>           return false;
>         }
>
> +      if (file_matcher != nullptr
> +         && !filename_in_debug_line (objfile, file_matcher))
> +       return true;
> +
>        read_full_dwarf_from_debuginfod (objfile, this);
>        return true;
>      }
> diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> index 8725a2fbcdb..4af83728c63 100644
> --- a/gdb/dwarf2/read.c
> +++ b/gdb/dwarf2/read.c
> @@ -82,6 +82,7 @@
>  #include <optional>
>  #include "gdbsupport/underlying.h"
>  #include "gdbsupport/hash_enum.h"
> +#include "gdbsupport/scoped_mmap.h"
>  #include "filename-seen-cache.h"
>  #include "producer.h"
>  #include <fcntl.h>
> @@ -1950,6 +1951,213 @@ dw2_get_file_names (dwarf2_per_cu_data *this_cu,
>    return this_cu->file_names;
>  }
>
> +#if !HAVE_SYS_MMAN_H
> +
> +bool
> +mapped_debug_line::contains_matching_filename
> +  (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> +{
> +  return false;
> +}
> +
> +gdb::array_view<const gdb_byte>
> +mapped_debug_line::read_debug_line_separate
> +  (const char *filename, std::unique_ptr<index_cache_resource> *resource)
> +{
> +  return {};
> +}
> +
> +bool
> +mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile)
> +{
> +  return false;
> +}
> +
> +void
> +mapped_debug_line::map_filenames
> +  (gdb::function_view<symbol_filename_ftype> fun,
> +   bool need_fullname)
> +{
> +  return;
> +}
> +
> +#else /* !HAVE_SYS_MMAN_H */
> +
> +struct line_resource_mmap final : public index_cache_resource
> +{
> +  /* Try to mmap FILENAME.  Throw an exception on failure, including if the
> +     file doesn't exist.  */
> +  line_resource_mmap (const char *filename)
> +    : mapping (mmap_file (filename))
> +  {}
> +
> +  scoped_mmap mapping;
> +};
> +
> +/* See read.h.  */
> +
> +bool
> +mapped_debug_line::contains_matching_filename
> +  (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> +{
> +  for (line_header_up &lh : line_headers)
> +    for (file_entry &fe : lh->file_names ())
> +      {
> +       const char *filename = fe.name;
> +
> +       if (file_matcher (fe.name, false))
> +         return true;
> +
> +       bool basename_match = file_matcher (lbasename (fe.name), true);
> +
> +       if (!basenames_may_differ && !basename_match)
> +         continue;
> +
> +       /* DW_AT_comp_dir is not explicitly mentioned in the .debug_line
> +          until DWARF5.  Since we don't have access to the CU at this
> +          point we just check for a partial match on the filename.
> +          If there is a match, the full debuginfo will be downloaded
> +          ane the match will be re-evalute with DW_AT_comp_dir.  */
> +       if (lh->version < 5 && fe.d_index == 0)
> +         return basename_match;
> +
> +       const char *dirname = fe.include_dir (&*lh);
> +       std::string fullname;
> +
> +       if (dirname == nullptr || IS_ABSOLUTE_PATH (filename))
> +         fullname = filename;
> +       else
> +         fullname = std::string (dirname) + SLASH_STRING + filename;
> +
> +       gdb::unique_xmalloc_ptr<char> rewritten
> +         = rewrite_source_path (fullname.c_str ());
> +       if (rewritten != nullptr)
> +         fullname = rewritten.release ();
> +
> +       if (file_matcher (fullname.c_str (), false))
> +         return true;
> +      }
> +
> +  return false;
> +}
> +
> +/* See read.h.  */
> +
> +void
> +mapped_debug_line::map_filenames
> +  (gdb::function_view<symbol_filename_ftype> fun,
> +   bool need_fullname)
> +{
> +  for (line_header_up &lh : line_headers)
> +    for (file_entry &fe : lh->file_names ())
> +      {
> +       const char *filename = fe.name;
> +
> +       if (!need_fullname)
> +         {
> +           fun (filename, nullptr);
> +           continue;
> +         }
> +
> +       const char *dirname = fe.include_dir (&*lh);
> +       std::string fullname;
> +
> +       if (dirname == nullptr || IS_ABSOLUTE_PATH (filename))
> +         fullname = filename;
> +       else
> +         fullname = std::string (dirname) + SLASH_STRING + filename;
> +
> +       gdb::unique_xmalloc_ptr<char> rewritten
> +         = rewrite_source_path (fullname.c_str ());
> +       if (rewritten != nullptr)
> +         fullname = rewritten.release ();
> +
> +       fun (filename, fullname.c_str ());
> +      }
> +}
> +
> +/* See read.h.  */
> +
> +gdb::array_view<const gdb_byte>
> +mapped_debug_line::read_debug_line_separate
> +  (const char *filename, std::unique_ptr<index_cache_resource> *resource)
> +{
> +  if (filename == nullptr)
> +    return {};
> +
> +  try
> +  {
> +    line_resource_mmap *mmap_resource
> +      = new line_resource_mmap (filename);
> +
> +    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 &except)
> +  {
> +    exception_print (gdb_stderr, except);
> +  }
> +
> +  return {};
> +}
> +
> +/* See read.h.  */
> +
> +bool
> +mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile)
> +{
> +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> +  if (build_id == nullptr)
> +    return false;
> +
> +  gdb::unique_xmalloc_ptr<char> line_path;
> +  scoped_fd line_fd = debuginfod_section_query (build_id->data,
> +                                               build_id->size,
> +                                               bfd_get_filename
> +                                                 (objfile->obfd.get ()),
> +                                               ".debug_line",
> +                                               &line_path);
> +
> +  if (line_fd.get () < 0)
> +    return false;
> +
> +  gdb::unique_xmalloc_ptr<char> line_str_path;
> +  scoped_fd line_str_fd = debuginfod_section_query (build_id->data,
> +                                                   build_id->size,
> +                                                   bfd_get_filename
> +                                                     (objfile->obfd.get ()),
> +                                                   ".debug_line_str",
> +                                                   &line_str_path);
> +
> +  line_data = read_debug_line_separate (line_path.get (), &line_resource);
> +  line_str_data = read_debug_line_separate (line_str_path.get (),
> +                                           &line_str_resource);
> +
> +  const gdb_byte *line_ptr = line_data.data ();
> +
> +  while (line_ptr < line_data.data () + line_data.size ())
> +    {
> +      line_header_up lh
> +       = dwarf_decode_line_header (objfile->obfd.get (),
> +                                   line_data, line_str_data,
> +                                   &line_ptr, false,
> +                                   nullptr, nullptr);
> +      line_headers.emplace_back (std::move (lh));
> +    }
> +
> +  return true;
> +}
> +#endif /* !HAVE_SYS_MMAN_H */
> +
> +mapped_debug_line::mapped_debug_line (objfile *objfile)
> +{
> +  if (!read_debug_line_from_debuginfod (objfile))
> +    line_headers.clear ();
> +}
> +
>  /* A helper for the "quick" functions which computes and caches the
>     real path for a given file name from the line table.  */
>
> diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> index 8b6f1864458..db16160b43e 100644
> --- a/gdb/dwarf2/read.h
> +++ b/gdb/dwarf2/read.h
> @@ -33,6 +33,7 @@
>  #include "gdbsupport/hash_enum.h"
>  #include "gdbsupport/function-view.h"
>  #include "gdbsupport/packed.h"
> +#include "dwarf2/line-header.h"
>
>  /* Hold 'maintenance (set|show) dwarf' commands.  */
>  extern struct cmd_list_element *set_dwarf_cmdlist;
> @@ -982,4 +983,40 @@ extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
>  extern void read_full_dwarf_from_debuginfod (struct objfile *,
>                                              dwarf2_base_index_functions *);
>
> +struct mapped_debug_line
> +{
> +  mapped_debug_line (objfile *objfile);
> +
> +  /* Return true if any of the mapped .debug_line's filenames match
> +     FILE_MATCHER.  */
> +
> +  bool contains_matching_filename
> +    (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher);
> +
> +  /* Call FUN with each filename in this mapped .debug_line.  Include
> +     each file's fullname if NEED_FULLNAME is true.  */
> +
> +  void map_filenames (gdb::function_view<symbol_filename_ftype> fun,
> +                     bool need_fullname);
> +
> +private:
> +  std::vector<line_header_up> line_headers;
> +
> +  gdb::array_view<const gdb_byte> line_data;
> +  gdb::array_view<const gdb_byte> line_str_data;
> +
> +  std::unique_ptr<index_cache_resource> line_resource;
> +  std::unique_ptr<index_cache_resource> line_str_resource;
> +
> +  /* Download the .debug_line and .debug_line_str associated with OBJFILE
> +     and populate line_headers.  */
> +
> +  bool read_debug_line_from_debuginfod (objfile *objfile);
> +
> +  /* Initialize line_data and line_str_data with the .debug_line and
> +    .debug_line_str downloaded read_debug_line_from_debuginfod.  */
> +
> +  gdb::array_view<const gdb_byte> read_debug_line_separate
> +    (const char *filename, std::unique_ptr<index_cache_resource> *resource);
> +};
>  #endif /* DWARF2READ_H */
> diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c
> index 0bc13f963bc..ab955a0fc05 100644
> --- a/gdb/mi/mi-out.c
> +++ b/gdb/mi/mi-out.c
> @@ -278,7 +278,14 @@ mi_ui_out::do_progress_notify (const std::string &msg, const char *unit,
>
>    if (info.state == progress_update::START)
>      {
> -      gdb_printf ("%s...\n", msg.c_str ());
> +      std::string prefix;
> +      if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED)
> +       {
> +         prefix = "\n";
> +         cur_prefix_state = prefix_state_t::NEWLINE_PRINTED;
> +       }
> +
> +      gdb_printf ("%s...\n", (prefix + msg).c_str ());
>        info.state = progress_update::WORKING;
>      }
>  }
> diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
> index c3872ecdfc9..3ac11152daa 100644
> --- a/gdb/testsuite/gdb.debuginfod/section.exp
> +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> @@ -18,6 +18,7 @@
>  standard_testfile
>
>  load_lib debuginfod-support.exp
> +load_lib completion-support.exp
>
>  require allow_debuginfod_tests
>
> @@ -159,6 +160,32 @@ proc_with_prefix local_url { } {
>      # Hit the breakpoint.
>      set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
>      gdb_test "c" $res "break continue"
> +
> +    clean_restart_with_prompt $sectexec "line 1"
> +
> +    # List source file using .debug_line download.
> +    set res ".*\.debug_line.*$lib_sl1.*21.*extern void libsection2_test.*"
> +    gdb_test "list $libsrc1:21" $res "line 1 list"
> +
> +    clean_restart_with_prompt $sectexec "line 2"
> +
> +    # Set breakpoint using .debug_line download.
> +    set res ".*section \.debug_line for $lib_sl1.*Breakpoint 2 at.*$libsrc1.*"
> +    gdb_test "br $libsrc1:41" $res "line 2 br"
> +
> +    # Continue to breakpoint.
> +    set res "Breakpoint 2, libsection1_test.*\"Cancelling thread\\\\n\".*"
> +    gdb_test "c" $res "line 2 continue"
> +
> +    # Check that download progress message is correctly formatted
> +    # when printing threads.
> +    set res ".*separate debug info for $lib_sl2\.\.\.\r\n.* 2    Thread.*"
> +    gdb_test "info thr" $res "line thread"
> +
> +    clean_restart_with_prompt $sectexec "autocomplete"
> +
> +    # Download debuginfo during autocompletion.
> +    test_gdb_complete_tab_unique "br lib" ".*Downloading separate debug.*" ""
>  }
>
>  # Create CACHE and DB directories ready for debuginfod to use.
> diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> index 75f9f1612cb..d9a620b70e8 100644
> --- a/gdb/ui-out.c
> +++ b/gdb/ui-out.c
> @@ -32,6 +32,9 @@
>  #include <memory>
>  #include <string>
>
> +/* Current state of newline prefixing for progress update messages.  */
> +prefix_state_t cur_prefix_state = prefix_state_t::NEWLINE_OFF;
> +
>  namespace {
>
>  /* A header of a ui_out_table.  */
> diff --git a/gdb/ui-out.h b/gdb/ui-out.h
> index 2934d3a6b34..2c21603f1a7 100644
> --- a/gdb/ui-out.h
> +++ b/gdb/ui-out.h
> @@ -296,6 +296,21 @@ class ui_out
>        BAR
>      };
>
> +    /* Used to communicate the status of a newline prefix for the next progress
> +       update message.  */
> +    enum prefix_state
> +    {
> +      /* Do not modify the next progress update message.  */
> +      NEWLINE_OFF,
> +
> +      /* The next progress update message should include a newline prefix.  */
> +      NEWLINE_NEEDED,
> +
> +      /* A newline prefix was included in a debuginfod progress update
> +        message.  */
> +      NEWLINE_PRINTED
> +    };
> +
>      /* SHOULD_PRINT indicates whether something should be printed for a tty.  */
>      progress_update ()
>      {
> @@ -393,6 +408,11 @@ class ui_out
>    ui_out_level *current_level () const;
>  };
>
> +typedef ui_out::progress_update::prefix_state prefix_state_t;
> +
> +/* Current state of the newline prefix.  */
> +extern prefix_state_t cur_prefix_state;
> +
>  /* Start a new tuple or list on construction, and end it on
>     destruction.  Normally this is used via the typedefs
>     ui_out_emit_tuple and ui_out_emit_list.  */
> --
> 2.43.0
>


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

* [PING*2][PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator
  2024-02-07 21:24       ` [PING][PATCH " Aaron Merey
@ 2024-02-22 22:22         ` Aaron Merey
  2024-03-01  0:09           ` [PING*3][PATCH " Aaron Merey
  2024-03-12 22:14           ` Aaron Merey
  0 siblings, 2 replies; 29+ messages in thread
From: Aaron Merey @ 2024-02-22 22:22 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Thiago Jung Bauermann

Ping. Ready to be pushed to master?

Aaron

On Wed, Feb 7, 2024 at 4:24 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Fri, Jan 19, 2024 at 12:09 AM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Hi Andrew,
> >
> > Thanks for the review.
> >
> > On Thu, Jan 18, 2024 at 7:03 AM Andrew Burgess <aburgess@redhat.com> wrote:
> > >
> > > Aaron Merey <amerey@redhat.com> writes:
> > > > +using qf_list = std::forward_list<quick_symbol_functions_up>;
> > > > +using qf_range = iterator_range<qf_list::iterator>;
> > > > +using qf_safe_range = basic_safe_range<qf_range>;
> > > > +
> > > >  /* Sections in an objfile.  The section offsets are stored in the
> > > >     OBJFILE.  */
> > > >
> > > > @@ -764,6 +770,10 @@ struct objfile
> > > >       reader.  */
> > > >    std::forward_list<quick_symbol_functions_up> qf;
> > >
> > > I think this should be updated to use qf_list.
> >
> > Done.
> >
> > > > --- a/gdb/symfile-debug.c
> > > > +++ b/gdb/symfile-debug.c
> > > > @@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
> > > >       them, then that is an indication that they are in fact available.  Without
> > > >       this function the symbols may have been already read in but they also may
> > > >       not be present in this objfile.  */
> > > > -  for (const auto &iter : qf)
> > > > +  for (const auto &iter : qf_safe ())
> > >
> > > I read the commit message a couple of times, but I'm still not 100% sure
> > > I understand what's happening here.  Is the change from qf -> qf_safe()
> > > because calling (in this case has_symbols()) might download the full
> > > debug info, and in doing do, might delete a previously downloaded
> > > .debug_info section?
> >
> > quick_functions_up made from a separately downloaded .gdb_index is at
> > first associated with the parent objfile (i.e. the objfile of the shared
> > library containing a .gnu_debuglink that refers to the separate debug info
> > which actually contains this .gdb_index section).
> >
> > When the separate debuginfo is downloaded (possibly during iteration
> > over the parent's qf_list), we need to remove the gdb_index qf from
> > the parent's qf_list.  This prevents gdb from later attempting to read
> > the parent objfile for debug info that is actually in the corresponding
> > separate debug objfile.
> >
> > > If this is the case then is it true that we will almost never want to
> > > iterate over `qf` directly as most quick_symbol_functions methods might
> > > result in this download/delete behaviour?
> > >
> > > If I'm right, then I think it might be useful to place a warning in the
> > > comment before `qf` explaining this.
> >
> > Done.
> >
> > > > +/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
> > > > +
> > > > +template<typename Iterator>
> > > > +class basic_safe_reverse_iterator
> > > > +{
> > > > +public:
> > > > +  typedef basic_safe_reverse_iterator self_type;
> > > > +  typedef typename Iterator::value_type value_type;
> > > > +  typedef typename Iterator::reference reference;
> > > > +  typedef typename Iterator::pointer pointer;
> > > > +  typedef typename Iterator::iterator_category iterator_category;
> > > > +  typedef typename Iterator::difference_type difference_type;
> > > > +
> > > > +  /* Construct the iterator using ARG, and construct the end iterator
> > > > +     using ARG2.  */
> > >
> > > Maybe this is a standard thing, and I'm just showing my lack of
> > > experience, but, given the existence of methods like rbegin() and
> > > rend(), I think the comments here should be doing more to explain what's
> > > expected of this input.
> > >
> > > Plus this comment seems confusing, though I can sort of see what it's
> > > trying to say.
> > >
> > > We're creating a *_iterator object, which itself contains other
> > > iterators.  We need to be super clear which particular "iterator" we're
> > > talking about in the comments.
> > >
> > > Also, I think we need to be explicit that for this reverse iterator, the
> > > first argument to the constructor is going to be the LAST element that
> > > is iterated over.  One might be tempted to think that a safe reverse iterator
> > > should be initialised with rbegin() and rend(), which isn't going to be
> > > correct (unless I'm not understand, please correct me if so).
> >
> > That's right, the arguments to basic_safe_reverse_iterator should be
> > forward iterators.  This will not work with reverse iterators.
> >
> > I added more information to this comment to hopefully clear up any
> > confusion.
> >
> > > > +
> > > > +/* A range adapter that wraps a forward range, and then returns
> > > > +   safe reverse iterators wrapping the original range's iterators.  */
> > >
> > > I think the fact that this takes a FORWARD range is really important,
> > > and we should do more to advertise this.  E.g. I think there should be a
> > > comment on the constructor saying that RANGE is a forward range over
> > > which we will iterate backwards.
> > >
> >
> > Done.  The updated patch is included below.
> >
> > Aaron
> >
> > Commit message:
> >
> > This patch changes progspace objfile_list insertion so that separate
> > debug objfiles are placed into the list after the parent objfile,
> > instead of before.  Additionally qf_require_partial_symbols now returns
> > a safe_range.
> >
> > These changes are intended to prepare gdb for on-demand debuginfo
> > downloading and the downloading of .gdb_index sections.
> >
> > With on-demand downloading enabled, gdb might need to delete a
> > .gdb_index quick_symbol_functions from a parent objfile while looping
> > the objfile's list of quick_symbol_functions because the separate
> > debug objfile has just been downloaded.  The use of a safe_range
> > prevents this removal from causing iterator invalidation.
> >
> > gdb might also download a debuginfo file during symtab expansion.
> > In this case an objfile will be added to the current progspace's
> > objfiles_list during iteration over the list (for example, in
> > iterate_over_symtabs).  We want these loops to also iterate over
> > newly downloaded objfiles.  So objfiles need to be inserted into
> > objfiles_list after their parent since it is during the search of
> > the parent objfile for some symbol or filename that the separate
> > debug objfile might be downloaded.
> >
> > To facilitate the safe deletion of objfiles, this patch also adds
> > basic_safe_reverse_range and basic_safe_reverse_iterator.  This allows
> > objfiles to be removed from the objfiles_list in a loop without iterator
> > invalidation.
> >
> > If a forward safe iterator were to be used, the deletion of an
> > objfile could invalidate the safe iterator's reference to the next
> > objfile in the objfiles_list.  This can happen when the deletion
> > of an objfile causes the deletion of a separate debug objfile that
> > happens to the be next element in the objfiles_list.
> >
> > The standard reverse iterator is not suitable for safe objfile deletion.
> > In order to safely delete the first objfile in the objfiles_list, the
> > standard reverse iterator's underlying begin iterator would have to be
> > decremented, resulting in undefined behavior.
> >
> > A small change was also made to a testcase in py-objfile.exp to
> > account for the new placement of separate debug objfiles in
> > objfiles_list.
> > ---
> >  gdb/jit.c                               |   7 +-
> >  gdb/objfiles.c                          |  14 +--
> >  gdb/objfiles.h                          |  17 +++-
> >  gdb/progspace.c                         |  19 +++-
> >  gdb/progspace.h                         |  31 ++++---
> >  gdb/symfile-debug.c                     |  34 +++----
> >  gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
> >  gdbsupport/safe-iterator.h              | 115 ++++++++++++++++++++++++
> >  8 files changed, 195 insertions(+), 44 deletions(-)
> >
> > diff --git a/gdb/jit.c b/gdb/jit.c
> > index 85a10be3055..10ee5f9b749 100644
> > --- a/gdb/jit.c
> > +++ b/gdb/jit.c
> > @@ -1240,11 +1240,10 @@ jit_breakpoint_re_set (void)
> >  static void
> >  jit_inferior_exit_hook (struct inferior *inf)
> >  {
> > -  for (objfile *objf : current_program_space->objfiles_safe ())
> > +  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
> >      {
> > -      if (objf->jited_data != nullptr && objf->jited_data->addr != 0)
> > -       objf->unlink ();
> > -    }
> > +      return (objf->jited_data != nullptr) && (objf->jited_data->addr != 0);
> > +    });
> >  }
> >
> >  void
> > diff --git a/gdb/objfiles.c b/gdb/objfiles.c
> > index 8f085b1bb7c..d53dfaca90b 100644
> > --- a/gdb/objfiles.c
> > +++ b/gdb/objfiles.c
> > @@ -470,6 +470,12 @@ objfile::unlink ()
> >    current_program_space->remove_objfile (this);
> >  }
> >
> > +qf_safe_range
> > +objfile::qf_safe ()
> > +{
> > +  return qf_safe_range (qf_range (qf.begin (), qf.end ()));
> > +}
> > +
> >  /* Free all separate debug objfile of OBJFILE, but don't free OBJFILE
> >     itself.  */
> >
> > @@ -793,14 +799,12 @@ have_full_symbols (void)
> >  void
> >  objfile_purge_solibs (void)
> >  {
> > -  for (objfile *objf : current_program_space->objfiles_safe ())
> > +  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
> >      {
> >        /* We assume that the solib package has been purged already, or will
> >          be soon.  */
> > -
> > -      if (!(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED))
> > -       objf->unlink ();
> > -    }
> > +      return !(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED);
> > +    });
> >  }
> >
> >
> > diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> > index 621342d22b7..b917886988d 100644
> > --- a/gdb/objfiles.h
> > +++ b/gdb/objfiles.h
> > @@ -370,6 +370,12 @@ class separate_debug_iterator
> >
> >  typedef iterator_range<separate_debug_iterator> separate_debug_range;
> >
> > +/* See objfile::qf_safe.  */
> > +
> > +using qf_list = std::forward_list<quick_symbol_functions_up>;
> > +using qf_range = iterator_range<qf_list::iterator>;
> > +using qf_safe_range = basic_safe_range<qf_range>;
> > +
> >  /* Sections in an objfile.  The section offsets are stored in the
> >     OBJFILE.  */
> >
> > @@ -761,8 +767,15 @@ struct objfile
> >    const struct sym_fns *sf = nullptr;
> >
> >    /* The "quick" (aka partial) symbol functions for this symbol
> > -     reader.  */
> > -  std::forward_list<quick_symbol_functions_up> qf;
> > +     reader.  Many quick_symbol_functions methods may result
> > +     in the deletion of a quick_symbol_functions from this
> > +     qf_list.  It is recommended that qf_safe be used to iterate
> > +     over the qf_list.  */
> > +  qf_list qf;
> > +
> > +  /* Returns an iterable object that allows for safe deletion during
> > +     iteration.  See gdbsupport/safe-iterator.h.  */
> > +  qf_safe_range qf_safe ();
> >
> >    /* Per objfile data-pointers required by other GDB modules.  */
> >
> > diff --git a/gdb/progspace.c b/gdb/progspace.c
> > index 0fc1fdd57ab..c4cc2e2e8af 100644
> > --- a/gdb/progspace.c
> > +++ b/gdb/progspace.c
> > @@ -141,19 +141,19 @@ program_space::free_all_objfiles ()
> >
> >  void
> >  program_space::add_objfile (std::unique_ptr<objfile> &&objfile,
> > -                           struct objfile *before)
> > +                           struct objfile *after)
> >  {
> > -  if (before == nullptr)
> > +  if (after == nullptr)
> >      objfiles_list.push_back (std::move (objfile));
> >    else
> >      {
> >        auto iter = std::find_if (objfiles_list.begin (), objfiles_list.end (),
> >                                 [=] (const std::unique_ptr<::objfile> &objf)
> >                                 {
> > -                                 return objf.get () == before;
> > +                                 return objf.get () == after;
> >                                 });
> >        gdb_assert (iter != objfiles_list.end ());
> > -      objfiles_list.insert (iter, std::move (objfile));
> > +      objfiles_list.insert (++iter, std::move (objfile));
> >      }
> >  }
> >
> > @@ -182,6 +182,17 @@ program_space::remove_objfile (struct objfile *objfile)
> >
> >  /* See progspace.h.  */
> >
> > +void
> > +program_space::unlink_objfiles_if
> > +  (gdb::function_view<bool (const objfile *objfile)> predicate)
> > +{
> > +  for (auto &it : objfiles_safe ())
> > +    if (predicate (it.get ()))
> > +      it->unlink ();
> > +}
> > +
> > +/* See progspace.h.  */
> > +
> >  struct objfile *
> >  program_space::objfile_for_address (CORE_ADDR address)
> >  {
> > diff --git a/gdb/progspace.h b/gdb/progspace.h
> > index 163cbf6a0f8..f23a57dc842 100644
> > --- a/gdb/progspace.h
> > +++ b/gdb/progspace.h
> > @@ -250,28 +250,32 @@ struct program_space
> >         unwrapping_objfile_iterator (objfiles_list.end ()));
> >    }
> >
> > -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> > +  using objfiles_it_range = iterator_range<objfile_list::iterator>;
> > +  using objfiles_safe_reverse_range
> > +    = basic_safe_reverse_range<objfiles_it_range>;
> >
> >    /* An iterable object that can be used to iterate over all objfiles.
> >       The basic use is in a foreach, like:
> >
> >       for (objfile *objf : pspace->objfiles_safe ()) { ... }
> >
> > -     This variant uses a basic_safe_iterator so that objfiles can be
> > -     deleted during iteration.  */
> > -  objfiles_safe_range objfiles_safe ()
> > +     This variant uses a basic_safe_reverse_iterator so that objfiles
> > +     can be deleted during iteration.
> > +
> > +     The use of a reverse iterator helps ensure that separate debug
> > +     objfiles are deleted before their parent objfile.  This prevents
> > +     iterator invalidation due to the deletion of a parent objfile.  */
> > + objfiles_safe_reverse_range objfiles_safe ()
> >    {
> > -    return objfiles_safe_range
> > -      (objfiles_range
> > -        (unwrapping_objfile_iterator (objfiles_list.begin ()),
> > -         unwrapping_objfile_iterator (objfiles_list.end ())));
> > +    return objfiles_safe_reverse_range
> > +      (objfiles_it_range (objfiles_list.begin (), objfiles_list.end ()));
> >    }
> >
> > -  /* Add OBJFILE to the list of objfiles, putting it just before
> > -     BEFORE.  If BEFORE is nullptr, it will go at the end of the
> > +  /* Add OBJFILE to the list of objfiles, putting it just after
> > +     AFTER.  If AFTER is nullptr, it will go at the end of the
> >       list.  */
> >    void add_objfile (std::unique_ptr<objfile> &&objfile,
> > -                   struct objfile *before);
> > +                   struct objfile *after);
> >
> >    /* Remove OBJFILE from the list of objfiles.  */
> >    void remove_objfile (struct objfile *objfile);
> > @@ -286,6 +290,11 @@ struct program_space
> >    /* Free all the objfiles associated with this program space.  */
> >    void free_all_objfiles ();
> >
> > +  /* Unlink all objfiles associated with this program space for which
> > +     PREDICATE evaluates to true.  */
> > +  void unlink_objfiles_if
> > +    (gdb::function_view<bool (const objfile *objfile)> predicate);
> > +
> >    /* Return the objfile containing ADDRESS, or nullptr if the address
> >       is outside all objfiles in this progspace.  */
> >    struct objfile *objfile_for_address (CORE_ADDR address);
> > diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
> > index 32c3ed9d349..c684f9cdd43 100644
> > --- a/gdb/symfile-debug.c
> > +++ b/gdb/symfile-debug.c
> > @@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
> >       them, then that is an indication that they are in fact available.  Without
> >       this function the symbols may have been already read in but they also may
> >       not be present in this objfile.  */
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      {
> >        retval = iter->has_symbols (this);
> >        if (retval)
> > @@ -108,7 +108,7 @@ objfile::has_unexpanded_symtabs ()
> >                 objfile_debug_name (this));
> >
> >    bool result = false;
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      {
> >        if (iter->has_unexpanded_symtabs (this))
> >         {
> > @@ -133,7 +133,7 @@ objfile::find_last_source_symtab ()
> >      gdb_printf (gdb_stdlog, "qf->find_last_source_symtab (%s)\n",
> >                 objfile_debug_name (this));
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      {
> >        retval = iter->find_last_source_symtab (this);
> >        if (retval != nullptr)
> > @@ -166,7 +166,7 @@ objfile::forget_cached_source_info ()
> >         }
> >      }
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      iter->forget_cached_source_info (this);
> >  }
> >
> > @@ -213,7 +213,7 @@ objfile::map_symtabs_matching_filename
> >      return result;
> >    };
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      {
> >        if (!iter->expand_symtabs_matching (this,
> >                                           match_one_filename,
> > @@ -278,7 +278,7 @@ objfile::lookup_symbol (block_enum kind, const char *name, domain_enum domain)
> >      return true;
> >    };
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      {
> >        if (!iter->expand_symtabs_matching (this,
> >                                           nullptr,
> > @@ -309,7 +309,7 @@ objfile::print_stats (bool print_bcache)
> >      gdb_printf (gdb_stdlog, "qf->print_stats (%s, %d)\n",
> >                 objfile_debug_name (this), print_bcache);
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      iter->print_stats (this, print_bcache);
> >  }
> >
> > @@ -320,7 +320,7 @@ objfile::dump ()
> >      gdb_printf (gdb_stdlog, "qf->dump (%s)\n",
> >                 objfile_debug_name (this));
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      iter->dump (this);
> >  }
> >
> > @@ -335,7 +335,7 @@ objfile::expand_symtabs_for_function (const char *func_name)
> >    lookup_name_info base_lookup (func_name, symbol_name_match_type::FULL);
> >    lookup_name_info lookup_name = base_lookup.make_ignore_params ();
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      iter->expand_symtabs_matching (this,
> >                                    nullptr,
> >                                    &lookup_name,
> > @@ -354,7 +354,7 @@ objfile::expand_all_symtabs ()
> >      gdb_printf (gdb_stdlog, "qf->expand_all_symtabs (%s)\n",
> >                 objfile_debug_name (this));
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      iter->expand_all_symtabs (this);
> >  }
> >
> > @@ -372,7 +372,7 @@ objfile::expand_symtabs_with_fullname (const char *fullname)
> >      return filename_cmp (basenames ? basename : fullname, filename) == 0;
> >    };
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      iter->expand_symtabs_matching (this,
> >                                    file_matcher,
> >                                    nullptr,
> > @@ -406,7 +406,7 @@ objfile::expand_symtabs_matching
> >                 host_address_to_string (&expansion_notify),
> >                 search_domain_name (kind));
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      if (!iter->expand_symtabs_matching (this, file_matcher, lookup_name,
> >                                         symbol_matcher, expansion_notify,
> >                                         search_flags, domain, kind))
> > @@ -431,7 +431,7 @@ objfile::find_pc_sect_compunit_symtab (struct bound_minimal_symbol msymbol,
> >                 host_address_to_string (section),
> >                 warn_if_readin);
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      {
> >        retval = iter->find_pc_sect_compunit_symtab (this, msymbol, pc, section,
> >                                                    warn_if_readin);
> > @@ -459,7 +459,7 @@ objfile::map_symbol_filenames (gdb::function_view<symbol_filename_ftype> fun,
> >                 objfile_debug_name (this),
> >                 need_fullname);
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      iter->map_symbol_filenames (this, fun, need_fullname);
> >  }
> >
> > @@ -471,7 +471,7 @@ objfile::compute_main_name ()
> >                 "qf->compute_main_name (%s)\n",
> >                 objfile_debug_name (this));
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      iter->compute_main_name (this);
> >  }
> >
> > @@ -485,7 +485,7 @@ objfile::find_compunit_symtab_by_address (CORE_ADDR address)
> >                 hex_string (address));
> >
> >    struct compunit_symtab *result = NULL;
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      {
> >        result = iter->find_compunit_symtab_by_address (this, address);
> >        if (result != nullptr)
> > @@ -510,7 +510,7 @@ objfile::lookup_global_symbol_language (const char *name,
> >    enum language result = language_unknown;
> >    *symbol_found_p = false;
> >
> > -  for (const auto &iter : qf)
> > +  for (const auto &iter : qf_safe ())
> >      {
> >        result = iter->lookup_global_symbol_language (this, name, domain,
> >                                                     symbol_found_p);
> > diff --git a/gdb/testsuite/gdb.python/py-objfile.exp b/gdb/testsuite/gdb.python/py-objfile.exp
> > index 61b9942de79..0bf49976b73 100644
> > --- a/gdb/testsuite/gdb.python/py-objfile.exp
> > +++ b/gdb/testsuite/gdb.python/py-objfile.exp
> > @@ -135,7 +135,7 @@ gdb_test "p main" "= {<text variable, no debug info>} $hex <main>" \
> >  gdb_py_test_silent_cmd "python objfile.add_separate_debug_file(\"${binfile}\")" \
> >      "Add separate debug file file" 1
> >
> > -gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[0\]" \
> > +gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[1\]" \
> >      "Get separate debug info objfile" 1
> >
> >  gdb_test "python print (sep_objfile.owner.filename)" "${testfile}2" \
> > diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h
> > index b4891328f1a..d1b9ce21ffc 100644
> > --- a/gdbsupport/safe-iterator.h
> > +++ b/gdbsupport/safe-iterator.h
> > @@ -136,4 +136,119 @@ class basic_safe_range
> >    Range m_range;
> >  };
> >
> > +/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
> > +
> > +template<typename Iterator>
> > +class basic_safe_reverse_iterator
> > +{
> > +public:
> > +  typedef basic_safe_reverse_iterator self_type;
> > +  typedef typename Iterator::value_type value_type;
> > +  typedef typename Iterator::reference reference;
> > +  typedef typename Iterator::pointer pointer;
> > +  typedef typename Iterator::iterator_category iterator_category;
> > +  typedef typename Iterator::difference_type difference_type;
> > +
> > +  /* Construct the iterator using ARG, and construct the end iterator
> > +     using ARG2.  ARG and ARG2 should be forward iterators, typically
> > +     from begin and end methods, respectively.
> > +
> > +     For example if ARG1 is created with container.begin and ARG2 is
> > +     is created with container.end, then the basic_safe_reverse_iterator
> > +     will traverse from the last element in the container to the first
> > +     element in the container.  */
> > +  template<typename Arg>
> > +  explicit basic_safe_reverse_iterator (Arg &&arg, Arg &&arg2)
> > +    : m_begin (std::forward<Arg> (arg)),
> > +      m_end (std::forward<Arg> (arg2)),
> > +      m_it (m_end),
> > +      m_next (m_end)
> > +  {
> > +    /* M_IT and M_NEXT are initialized as one-past-end.  Set M_IT to point
> > +       to the last element and set M_NEXT to point to the second last element,
> > +       if such elements exist.  */
> > +    if (m_it != m_begin)
> > +      {
> > +       --m_it;
> > +
> > +       if (m_it != m_begin)
> > +         {
> > +           --m_next;
> > +           --m_next;
> > +         }
> > +      }
> > +  }
> > +
> > +  typename std::invoke_result<decltype(&Iterator::operator*), Iterator>::type
> > +    operator* () const
> > +  { return *m_it; }
> > +
> > +  self_type &operator++ ()
> > +  {
> > +    m_it = m_next;
> > +
> > +    if (m_it != m_end)
> > +      {
> > +       /* Use M_BEGIN only if we sure that it is valid.  */
> > +       if (m_it == m_begin)
> > +         m_next = m_end;
> > +       else
> > +         --m_next;
> > +      }
> > +
> > +    return *this;
> > +  }
> > +
> > +  bool operator== (const self_type &other) const
> > +  { return m_it == other.m_it; }
> > +
> > +  bool operator!= (const self_type &other) const
> > +  { return m_it != other.m_it; }
> > +
> > +private:
> > +  /* The first element.  */
> > +  Iterator m_begin {};
> > +
> > +  /* A one-past-end iterator.  */
> > +  Iterator m_end {};
> > +
> > +  /* The current element.  */
> > +  Iterator m_it {};
> > +
> > +  /* The next element.  Always one element ahead of M_IT.  */
> > +  Iterator m_next {};
> > +};
> > +
> > +/* A range adapter that wraps a forward range, and then returns
> > +   safe reverse iterators wrapping the original range's iterators.  */
> > +
> > +template<typename Range>
> > +class basic_safe_reverse_range
> > +{
> > +public:
> > +
> > +  typedef basic_safe_reverse_iterator<typename Range::iterator> iterator;
> > +
> > +  /* RANGE must be a forward range.  basic_safe_reverse_iterators
> > +     will be used to traverse the forward range from the last element
> > +     to the first.  */
> > +  explicit basic_safe_reverse_range (Range range)
> > +    : m_range (range)
> > +  {
> > +  }
> > +
> > +  iterator begin ()
> > +  {
> > +    return iterator (m_range.begin (), m_range.end ());
> > +  }
> > +
> > +  iterator end ()
> > +  {
> > +    return iterator (m_range.end (), m_range.end ());
> > +  }
> > +
> > +private:
> > +
> > +  Range m_range;
> > +};
> >  #endif /* COMMON_SAFE_ITERATOR_H */
> > --
> > 2.43.0
> >


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

* [PING*2][PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading
  2024-02-07 21:25       ` [PING][PATCH " Aaron Merey
@ 2024-02-22 22:23         ` Aaron Merey
  2024-03-01  0:09           ` [PING*3][PATCH " Aaron Merey
  2024-03-12 22:15           ` Aaron Merey
  0 siblings, 2 replies; 29+ messages in thread
From: Aaron Merey @ 2024-02-22 22:23 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Thiago Jung Bauermann

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


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

* [PING*2][PATCH 4/4 v6] gdb/debuginfod: Add .debug_line downloading
  2024-02-07 21:25   ` [PING][PATCH " Aaron Merey
@ 2024-02-22 22:23     ` Aaron Merey
  2024-03-01  0:10       ` [PING*3][PATCH " Aaron Merey
  2024-03-12 22:15       ` Aaron Merey
  0 siblings, 2 replies; 29+ messages in thread
From: Aaron Merey @ 2024-02-22 22:23 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess, Thiago Jung Bauermann

Ping

Thanks,
Aaron

On Wed, Feb 7, 2024 at 4:25 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping,
>
> Thanks,
> Aaron
>
> On Wed, Jan 17, 2024 at 10:49 AM Aaron Merey <amerey@redhat.com> wrote:
> >
> > v6 adds a testcase for downloads during autocompletion.  It also
> > implements the suggestions from Thiago's review:
> > https://sourceware.org/pipermail/gdb-patches/2023-December/205507.html
> >
> > ELF/DWARF section downloading allows gdb to download .gdb_index files in
> > order to defer full debuginfo downloads.  However .gdb_index does not
> > contain any information regarding source filenames.  When a gdb command
> > includes a filename argument (ex. 'break main.c:50'), this results in
> > the mass downloading of all deferred debuginfo so that gdb can search the
> > debuginfo for matching source filenames.  This can result in unnecessary
> > downloads.
> >
> > To improve this, have gdb instead download each debuginfo's .debug_line
> > (and .debug_line_str if using DWARF5) when executing these commands.
> > Download full debuginfo only when its .debug_line contains a matching
> > filename.
> >
> > Since the combined size of .debug_line and .debug_line_str is only about
> > 1% the size of the corresponding debuginfo, significant time can be saved
> > by checking these sections before choosing to download an entire debuginfo.
> >
> > This patch also redirects stdout and stderr of the debuginfod server
> > used by testsuite/gdb.debuginfod tests to a server_log standard output
> > file.  While adding tests for this patch I ran into an issue where the
> > test server would block when logging to stderr, presumably because the
> > stderr buffer filled up and wasn't being read from.  Redirecting the
> > log to a file fixes this and also makes the server log more accessible
> > when debugging test failures.
> > ---
> >  gdb/cli-out.c                            |  11 +-
> >  gdb/completer.c                          |  18 +-
> >  gdb/dwarf2/line-header.c                 | 161 ++++++++++++------
> >  gdb/dwarf2/line-header.h                 |  10 ++
> >  gdb/dwarf2/read-gdb-index.c              |  60 +++++++
> >  gdb/dwarf2/read.c                        | 208 +++++++++++++++++++++++
> >  gdb/dwarf2/read.h                        |  37 ++++
> >  gdb/mi/mi-out.c                          |   9 +-
> >  gdb/testsuite/gdb.debuginfod/section.exp |  27 +++
> >  gdb/ui-out.c                             |   3 +
> >  gdb/ui-out.h                             |  20 +++
> >  11 files changed, 511 insertions(+), 53 deletions(-)
> >
> > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > index b4c7a657423..79f61eed62c 100644
> > --- a/gdb/cli-out.c
> > +++ b/gdb/cli-out.c
> > @@ -307,16 +307,23 @@ cli_ui_out::do_progress_notify (const std::string &msg,
> >
> >    if (info.state == progress_update::START)
> >      {
> > +      std::string prefix;
> > +      if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED)
> > +       {
> > +         prefix = "\n";
> > +         cur_prefix_state = prefix_state_t::NEWLINE_PRINTED;
> > +       }
> > +
> >        if (stream->isatty ()
> >           && current_ui->input_interactive_p ()
> >           && chars_per_line >= MIN_CHARS_PER_LINE)
> >         {
> > -         gdb_printf (stream, "%s\n", msg.c_str ());
> > +         gdb_printf (stream, "%s\n", (prefix + msg).c_str ());
> >           info.state = progress_update::BAR;
> >         }
> >        else
> >         {
> > -         gdb_printf (stream, "%s...\n", msg.c_str ());
> > +         gdb_printf (stream, "%s...\n", (prefix + msg).c_str ());
> >           info.state = progress_update::WORKING;
> >         }
> >      }
> > diff --git a/gdb/completer.c b/gdb/completer.c
> > index 8e2f031f13a..6d453d0d197 100644
> > --- a/gdb/completer.c
> > +++ b/gdb/completer.c
> > @@ -1362,6 +1362,10 @@ complete_line_internal_1 (completion_tracker &tracker,
> >      {
> >        /* We've recognized a full command.  */
> >
> > +      /* Disable pagination since responding to the pagination prompt
> > +        overwrites rl_line_buffer.  */
> > +      scoped_restore pag_restore = make_scoped_restore (&pagination_enabled, false);
> > +
> >        if (p == tmp_command + point)
> >         {
> >           /* There is no non-whitespace in the line beyond the
> > @@ -1461,7 +1465,8 @@ complete_line_internal_1 (completion_tracker &tracker,
> >  }
> >
> >  /* Wrapper around complete_line_internal_1 to handle
> > -   MAX_COMPLETIONS_REACHED_ERROR.  */
> > +   MAX_COMPLETIONS_REACHED_ERROR and possible progress update
> > +   interactions.  */
> >
> >  static void
> >  complete_line_internal (completion_tracker &tracker,
> > @@ -1469,6 +1474,11 @@ complete_line_internal (completion_tracker &tracker,
> >                         const char *line_buffer, int point,
> >                         complete_line_internal_reason reason)
> >  {
> > +  scoped_restore restore_prefix_state
> > +    = make_scoped_restore
> > +      (&cur_prefix_state,
> > +       ui_out::progress_update::prefix_state::NEWLINE_NEEDED);
> > +
> >    try
> >      {
> >        complete_line_internal_1 (tracker, text, line_buffer, point, reason);
> > @@ -1478,6 +1488,12 @@ complete_line_internal (completion_tracker &tracker,
> >        if (except.error != MAX_COMPLETIONS_REACHED_ERROR)
> >         throw;
> >      }
> > +
> > +  /* If progress update messages printed, then the text being completed
> > +     needs to be printed again.  */
> > +  if (cur_prefix_state
> > +      == ui_out::progress_update::prefix_state::NEWLINE_PRINTED)
> > +    rl_forced_update_display ();
> >  }
> >
> >  /* See completer.h.  */
> > diff --git a/gdb/dwarf2/line-header.c b/gdb/dwarf2/line-header.c
> > index ddaf7e4a527..aec02b3e1f7 100644
> > --- a/gdb/dwarf2/line-header.c
> > +++ b/gdb/dwarf2/line-header.c
> > @@ -102,12 +102,15 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
> >  {
> >    LONGEST length = read_initial_length (abfd, buf, bytes_read);
> >
> > -  gdb_assert (cu_header->initial_length_size == 4
> > -             || cu_header->initial_length_size == 8
> > -             || cu_header->initial_length_size == 12);
> > +  if (cu_header != nullptr)
> > +    {
> > +      gdb_assert (cu_header->initial_length_size == 4
> > +                 || cu_header->initial_length_size == 8
> > +                 || cu_header->initial_length_size == 12);
> >
> > -  if (cu_header->initial_length_size != *bytes_read)
> > -    complaint (_("intermixed 32-bit and 64-bit DWARF sections"));
> > +      if (cu_header->initial_length_size != *bytes_read)
> > +       complaint (_("intermixed 32-bit and 64-bit DWARF sections"));
> > +    }
> >
> >    *offset_size = (*bytes_read == 4) ? 4 : 8;
> >    return length;
> > @@ -116,21 +119,27 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
> >  /* Read directory or file name entry format, starting with byte of
> >     format count entries, ULEB128 pairs of entry formats, ULEB128 of
> >     entries count and the entries themselves in the described entry
> > -   format.  */
> > +   format.
> > +
> > +   .debug_line and .debug_line_str sections are stored in LINE_BUFP
> > +   and LINE_STR_DATA respectively.  */
> >
> >  static void
> > -read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > -                       const gdb_byte **bufp, struct line_header *lh,
> > -                       unsigned int offset_size,
> > -                       void (*callback) (struct line_header *lh,
> > -                                         const char *name,
> > -                                         dir_index d_index,
> > -                                         unsigned int mod_time,
> > -                                         unsigned int length))
> > +read_formatted_entries
> > +  (bfd *abfd, const gdb_byte **line_bufp,
> > +   const gdb::array_view<const gdb_byte> line_str_data,
> > +   struct line_header *lh,
> > +   unsigned int offset_size,
> > +   void (*callback) (struct line_header *lh,
> > +                    const char *name,
> > +                    dir_index d_index,
> > +                    unsigned int mod_time,
> > +                    unsigned int length))
> >  {
> >    gdb_byte format_count, formati;
> >    ULONGEST data_count, datai;
> > -  const gdb_byte *buf = *bufp;
> > +  const gdb_byte *buf = *line_bufp;
> > +  const gdb_byte *str_buf = line_str_data.data ();
> >    const gdb_byte *format_header_data;
> >    unsigned int bytes_read;
> >
> > @@ -157,7 +166,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> >           ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
> >           format += bytes_read;
> >
> > -         ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
> > +         ULONGEST form = read_unsigned_leb128 (abfd, format, &bytes_read);
> >           format += bytes_read;
> >
> >           std::optional<const char *> string;
> > @@ -172,12 +181,24 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> >
> >             case DW_FORM_line_strp:
> >               {
> > -               const char *str
> > -                 = per_objfile->read_line_string (buf, offset_size);
> > +               if (line_str_data.empty ())
> > +                 warning (_("Dwarf Error: DW_FORM_line_strp used without " \
> > +                            "required section"));
> > +
> > +               if (line_str_data.size () <= offset_size)
> > +                 warning (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
> > +                            "of section .debug_line_str"));
> > +
> > +               ULONGEST str_offset = read_offset (abfd, buf, offset_size);
> > +               const char *str;
> > +               if (str_buf[str_offset] == '\0')
> > +                 str = nullptr;
> > +               else
> > +                 str = (const char *) (str_buf + str_offset);
> >                 string.emplace (str);
> >                 buf += offset_size;
> > +               break;
> >               }
> > -             break;
> >
> >             case DW_FORM_data1:
> >               uint.emplace (read_1_byte (abfd, buf));
> > @@ -248,28 +269,30 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> >        callback (lh, fe.name, fe.d_index, fe.mod_time, fe.length);
> >      }
> >
> > -  *bufp = buf;
> > +  *line_bufp = buf;
> >  }
> >
> >  /* See line-header.h.  */
> >
> >  line_header_up
> > -dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > -                          dwarf2_per_objfile *per_objfile,
> > -                          struct dwarf2_section_info *section,
> > -                          const struct comp_unit_head *cu_header,
> > -                          const char *comp_dir)
> > +dwarf_decode_line_header (bfd *abfd,
> > +                         gdb::array_view<const gdb_byte> line_data,
> > +                         gdb::array_view<const gdb_byte> line_str_data,
> > +                         const gdb_byte **debug_line_ptr,
> > +                         bool is_dwz,
> > +                         const struct comp_unit_head *cu_header,
> > +                         const char *comp_dir)
> >  {
> > -  const gdb_byte *line_ptr;
> > +  const gdb_byte *line_ptr, *buf;
> >    unsigned int bytes_read, offset_size;
> >    int i;
> >    const char *cur_dir, *cur_file;
> >
> > -  bfd *abfd = section->get_bfd_owner ();
> > +  buf = *debug_line_ptr;
> >
> >    /* Make sure that at least there's room for the total_length field.
> >       That could be 12 bytes long, but we're just going to fudge that.  */
> > -  if (to_underlying (sect_off) + 4 >= section->size)
> > +  if (buf + 4 >= line_data.data () + line_data.size ())
> >      {
> >        dwarf2_statement_list_fits_in_line_number_section_complaint ();
> >        return 0;
> > @@ -277,24 +300,26 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> >
> >    line_header_up lh (new line_header (comp_dir));
> >
> > -  lh->sect_off = sect_off;
> > +  lh->sect_off = (sect_offset) (buf - line_data.data ());
> >    lh->offset_in_dwz = is_dwz;
> >
> > -  line_ptr = section->buffer + to_underlying (sect_off);
> > +  line_ptr = buf;
> >
> >    /* Read in the header.  */
> >    LONGEST unit_length
> > -    = read_checked_initial_length_and_offset (abfd, line_ptr, cu_header,
> > +    = read_checked_initial_length_and_offset (abfd, buf, cu_header,
> >                                               &bytes_read, &offset_size);
> > -  line_ptr += bytes_read;
> >
> > -  const gdb_byte *start_here = line_ptr;
> > +  line_ptr += bytes_read;
> >
> > -  if (line_ptr + unit_length > (section->buffer + section->size))
> > +  if (line_ptr + unit_length > buf + line_data.size ())
> >      {
> >        dwarf2_statement_list_fits_in_line_number_section_complaint ();
> >        return 0;
> >      }
> > +
> > +  const gdb_byte *start_here = line_ptr;
> > +
> >    lh->statement_program_end = start_here + unit_length;
> >    lh->version = read_2_bytes (abfd, line_ptr);
> >    line_ptr += 2;
> > @@ -303,7 +328,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> >        /* This is a version we don't understand.  The format could have
> >          changed in ways we don't handle properly so just punt.  */
> >        complaint (_("unsupported version in .debug_line section"));
> > -      return NULL;
> > +      return nullptr;
> >      }
> >    if (lh->version >= 5)
> >      {
> > @@ -320,13 +345,14 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> >           complaint (_("unsupported segment selector size %u "
> >                        "in .debug_line section"),
> >                      segment_selector_size);
> > -         return NULL;
> > +         return nullptr;
> >         }
> >      }
> >
> >    LONGEST header_length = read_offset (abfd, line_ptr, offset_size);
> >    line_ptr += offset_size;
> >    lh->statement_program_start = line_ptr + header_length;
> > +
> >    lh->minimum_instruction_length = read_1_byte (abfd, line_ptr);
> >    line_ptr += 1;
> >
> > @@ -347,12 +373,16 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> >
> >    lh->default_is_stmt = read_1_byte (abfd, line_ptr);
> >    line_ptr += 1;
> > +
> >    lh->line_base = read_1_signed_byte (abfd, line_ptr);
> >    line_ptr += 1;
> > +
> >    lh->line_range = read_1_byte (abfd, line_ptr);
> >    line_ptr += 1;
> > +
> >    lh->opcode_base = read_1_byte (abfd, line_ptr);
> >    line_ptr += 1;
> > +
> >    lh->standard_opcode_lengths.reset (new unsigned char[lh->opcode_base]);
> >
> >    lh->standard_opcode_lengths[0] = 1;  /* This should never be used anyway.  */
> > @@ -365,21 +395,23 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> >    if (lh->version >= 5)
> >      {
> >        /* Read directory table.  */
> > -      read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
> > -                             offset_size,
> > -                             [] (struct line_header *header, const char *name,
> > -                                 dir_index d_index, unsigned int mod_time,
> > -                                 unsigned int length)
> > +      read_formatted_entries
> > +       (abfd, &line_ptr, line_str_data,
> > +        lh.get (), offset_size,
> > +        [] (struct line_header *header, const char *name,
> > +            dir_index d_index, unsigned int mod_time,
> > +            unsigned int length)
> >         {
> >           header->add_include_dir (name);
> >         });
> >
> >        /* Read file name table.  */
> > -      read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
> > -                             offset_size,
> > -                             [] (struct line_header *header, const char *name,
> > -                                 dir_index d_index, unsigned int mod_time,
> > -                                 unsigned int length)
> > +      read_formatted_entries
> > +       (abfd, &line_ptr, line_str_data,
> > +        lh.get (), offset_size,
> > +        [] (struct line_header *header, const char *name,
> > +            dir_index d_index, unsigned int mod_time,
> > +            unsigned int length)
> >         {
> >           header->add_file_name (name, d_index, mod_time, length);
> >         });
> > @@ -387,7 +419,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> >    else
> >      {
> >        /* Read directory table.  */
> > -      while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL)
> > +      while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr)
> >         {
> >           line_ptr += bytes_read;
> >           lh->add_include_dir (cur_dir);
> > @@ -395,7 +427,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> >        line_ptr += bytes_read;
> >
> >        /* Read file name table.  */
> > -      while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL)
> > +      while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr)
> >         {
> >           unsigned int mod_time, length;
> >           dir_index d_index;
> > @@ -413,9 +445,40 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> >        line_ptr += bytes_read;
> >      }
> >
> > -  if (line_ptr > (section->buffer + section->size))
> > +  if (line_ptr > (buf + line_data.size ()))
> >      complaint (_("line number info header doesn't "
> >                  "fit in `.debug_line' section"));
> >
> > +  *debug_line_ptr += unit_length + offset_size;
> >    return lh;
> >  }
> > +
> > +line_header_up
> > +dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > +                          dwarf2_per_objfile *per_objfile,
> > +                          struct dwarf2_section_info *section,
> > +                          const struct comp_unit_head *cu_header,
> > +                          const char *comp_dir)
> > +{
> > +  struct objfile *objfile = per_objfile->objfile;
> > +  struct dwarf2_per_bfd *per_bfd = per_objfile->per_bfd;
> > +
> > +  /* Read .debug_line.  */
> > +  dwarf2_section_info *line_sec = &per_bfd->line;
> > +  bfd_size_type line_size = line_sec->get_size (objfile);
> > +
> > +  gdb::array_view<const gdb_byte> line (line_sec->buffer, line_size);
> > +
> > +  /* Read .debug_line_str.  */
> > +  dwarf2_section_info *line_str_sec = &per_bfd->line_str;
> > +  bfd_size_type line_str_size = line_str_sec->get_size (objfile);
> > +
> > +  gdb::array_view<const gdb_byte> line_str (line_str_sec->buffer,
> > +                                           line_str_size);
> > +
> > +  const gdb_byte *line_ptr = line.data () + to_underlying (sect_off);
> > +
> > +  return dwarf_decode_line_header
> > +    (per_bfd->obfd, line, line_str, &line_ptr,
> > +     is_dwz, cu_header, comp_dir);
> > +}
> > diff --git a/gdb/dwarf2/line-header.h b/gdb/dwarf2/line-header.h
> > index 06d2eec573b..1dffb7c7e5e 100644
> > --- a/gdb/dwarf2/line-header.h
> > +++ b/gdb/dwarf2/line-header.h
> > @@ -217,4 +217,14 @@ extern line_header_up dwarf_decode_line_header
> >     struct dwarf2_section_info *section, const struct comp_unit_head *cu_header,
> >     const char *comp_dir);
> >
> > +/* Like above but the .debug_line and .debug_line_str are stored in
> > +   LINE_DATA and LINE_STR_DATA.  *DEBUG_LINE_PTR should point to a
> > +   statement program header within LINE_DATA.  */
> > +
> > +extern line_header_up dwarf_decode_line_header
> > +  (bfd *abfd, gdb::array_view<const gdb_byte> line_data,
> > +   gdb::array_view<const gdb_byte> line_str_data,
> > +   const gdb_byte **debug_line_ptr, bool is_dwz,
> > +  const comp_unit_head *cu_header, const char *comp_dir);
> > +
> >  #endif /* DWARF2_LINE_HEADER_H */
> > diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
> > index 55b04391eb5..3d75678d7dc 100644
> > --- a/gdb/dwarf2/read-gdb-index.c
> > +++ b/gdb/dwarf2/read-gdb-index.c
> > @@ -131,6 +131,9 @@ struct mapped_gdb_index final : public mapped_index_base
> >    }
> >  };
> >
> > +struct mapped_debug_line;
> > +typedef std::unique_ptr<mapped_debug_line> mapped_debug_line_up;
> > +
> >  struct dwarf2_gdb_index : public dwarf2_base_index_functions
> >  {
> >    /* This dumps minimal information about the index.
> > @@ -162,6 +165,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> >       domain_enum domain,
> >       enum search_domain kind);
> >
> > + /* If OBJFILE's debuginfo download has been deferred, use a mapped_debug_line
> > +    to generate filenames.
> > +
> > +    Otherwise call dwarf2_base_index_functions::map_symbol_filenames.  */
> > +
> > +  void map_symbol_filenames (struct objfile *objfile,
> > +                            gdb::function_view<symbol_filename_ftype> fun,
> > +                            bool need_fullname) override;
> > +
> >    /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
> >       debuginfo if necessary.  */
> >    void expand_all_symtabs (struct objfile *objfile) override;
> > @@ -169,6 +181,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> >    /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
> >       debuginfo if necessary.  */
> >    struct symtab *find_last_source_symtab (struct objfile *objfile) override;
> > +
> > +  /* Filename information related to this .gdb_index.  */
> > +  mapped_debug_line_up mdl;
> > +
> > +  /* Return true if any of the filenames in this .gdb_index's .debug_line
> > +     mapping match FILE_MATCHER.  Initializes the mapping if necessary.  */
> > +  bool filename_in_debug_line
> > +    (objfile *objfile,
> > +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher);
> >  };
> >
> >  void
> > @@ -204,6 +225,30 @@ dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
> >      }
> >  }
> >
> > +void
> > +dwarf2_gdb_index::map_symbol_filenames
> > +     (struct objfile *objfile,
> > +      gdb::function_view<symbol_filename_ftype> fun,
> > +      bool need_fullname)
> > +{
> > +  try
> > +    {
> > +      dwarf2_base_index_functions::map_symbol_filenames (objfile, fun,
> > +                                                        need_fullname);
> > +    }
> > +  catch (const gdb_exception &e)
> > +    {
> > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > +       exception_print (gdb_stderr, e);
> > +      else
> > +       {
> > +         if (mdl == nullptr)
> > +           mdl.reset (new mapped_debug_line (objfile));
> > +         mdl->map_filenames (fun, need_fullname);
> > +       }
> > +    }
> > +}
> > +
> >  /* 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
> > @@ -371,6 +416,17 @@ dwarf2_gdb_index::do_expand_symtabs_matching
> >    return result;
> >  }
> >
> > +bool
> > +dwarf2_gdb_index::filename_in_debug_line
> > +  (objfile *objfile,
> > +   gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> > +{
> > +  if (mdl == nullptr)
> > +    mdl.reset (new mapped_debug_line (objfile));
> > +
> > +  return mdl->contains_matching_filename (file_matcher);
> > +}
> > +
> >  bool
> >  dwarf2_gdb_index::expand_symtabs_matching
> >      (struct objfile *objfile,
> > @@ -399,6 +455,10 @@ dwarf2_gdb_index::expand_symtabs_matching
> >           return false;
> >         }
> >
> > +      if (file_matcher != nullptr
> > +         && !filename_in_debug_line (objfile, file_matcher))
> > +       return true;
> > +
> >        read_full_dwarf_from_debuginfod (objfile, this);
> >        return true;
> >      }
> > diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> > index 8725a2fbcdb..4af83728c63 100644
> > --- a/gdb/dwarf2/read.c
> > +++ b/gdb/dwarf2/read.c
> > @@ -82,6 +82,7 @@
> >  #include <optional>
> >  #include "gdbsupport/underlying.h"
> >  #include "gdbsupport/hash_enum.h"
> > +#include "gdbsupport/scoped_mmap.h"
> >  #include "filename-seen-cache.h"
> >  #include "producer.h"
> >  #include <fcntl.h>
> > @@ -1950,6 +1951,213 @@ dw2_get_file_names (dwarf2_per_cu_data *this_cu,
> >    return this_cu->file_names;
> >  }
> >
> > +#if !HAVE_SYS_MMAN_H
> > +
> > +bool
> > +mapped_debug_line::contains_matching_filename
> > +  (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> > +{
> > +  return false;
> > +}
> > +
> > +gdb::array_view<const gdb_byte>
> > +mapped_debug_line::read_debug_line_separate
> > +  (const char *filename, std::unique_ptr<index_cache_resource> *resource)
> > +{
> > +  return {};
> > +}
> > +
> > +bool
> > +mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile)
> > +{
> > +  return false;
> > +}
> > +
> > +void
> > +mapped_debug_line::map_filenames
> > +  (gdb::function_view<symbol_filename_ftype> fun,
> > +   bool need_fullname)
> > +{
> > +  return;
> > +}
> > +
> > +#else /* !HAVE_SYS_MMAN_H */
> > +
> > +struct line_resource_mmap final : public index_cache_resource
> > +{
> > +  /* Try to mmap FILENAME.  Throw an exception on failure, including if the
> > +     file doesn't exist.  */
> > +  line_resource_mmap (const char *filename)
> > +    : mapping (mmap_file (filename))
> > +  {}
> > +
> > +  scoped_mmap mapping;
> > +};
> > +
> > +/* See read.h.  */
> > +
> > +bool
> > +mapped_debug_line::contains_matching_filename
> > +  (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> > +{
> > +  for (line_header_up &lh : line_headers)
> > +    for (file_entry &fe : lh->file_names ())
> > +      {
> > +       const char *filename = fe.name;
> > +
> > +       if (file_matcher (fe.name, false))
> > +         return true;
> > +
> > +       bool basename_match = file_matcher (lbasename (fe.name), true);
> > +
> > +       if (!basenames_may_differ && !basename_match)
> > +         continue;
> > +
> > +       /* DW_AT_comp_dir is not explicitly mentioned in the .debug_line
> > +          until DWARF5.  Since we don't have access to the CU at this
> > +          point we just check for a partial match on the filename.
> > +          If there is a match, the full debuginfo will be downloaded
> > +          ane the match will be re-evalute with DW_AT_comp_dir.  */
> > +       if (lh->version < 5 && fe.d_index == 0)
> > +         return basename_match;
> > +
> > +       const char *dirname = fe.include_dir (&*lh);
> > +       std::string fullname;
> > +
> > +       if (dirname == nullptr || IS_ABSOLUTE_PATH (filename))
> > +         fullname = filename;
> > +       else
> > +         fullname = std::string (dirname) + SLASH_STRING + filename;
> > +
> > +       gdb::unique_xmalloc_ptr<char> rewritten
> > +         = rewrite_source_path (fullname.c_str ());
> > +       if (rewritten != nullptr)
> > +         fullname = rewritten.release ();
> > +
> > +       if (file_matcher (fullname.c_str (), false))
> > +         return true;
> > +      }
> > +
> > +  return false;
> > +}
> > +
> > +/* See read.h.  */
> > +
> > +void
> > +mapped_debug_line::map_filenames
> > +  (gdb::function_view<symbol_filename_ftype> fun,
> > +   bool need_fullname)
> > +{
> > +  for (line_header_up &lh : line_headers)
> > +    for (file_entry &fe : lh->file_names ())
> > +      {
> > +       const char *filename = fe.name;
> > +
> > +       if (!need_fullname)
> > +         {
> > +           fun (filename, nullptr);
> > +           continue;
> > +         }
> > +
> > +       const char *dirname = fe.include_dir (&*lh);
> > +       std::string fullname;
> > +
> > +       if (dirname == nullptr || IS_ABSOLUTE_PATH (filename))
> > +         fullname = filename;
> > +       else
> > +         fullname = std::string (dirname) + SLASH_STRING + filename;
> > +
> > +       gdb::unique_xmalloc_ptr<char> rewritten
> > +         = rewrite_source_path (fullname.c_str ());
> > +       if (rewritten != nullptr)
> > +         fullname = rewritten.release ();
> > +
> > +       fun (filename, fullname.c_str ());
> > +      }
> > +}
> > +
> > +/* See read.h.  */
> > +
> > +gdb::array_view<const gdb_byte>
> > +mapped_debug_line::read_debug_line_separate
> > +  (const char *filename, std::unique_ptr<index_cache_resource> *resource)
> > +{
> > +  if (filename == nullptr)
> > +    return {};
> > +
> > +  try
> > +  {
> > +    line_resource_mmap *mmap_resource
> > +      = new line_resource_mmap (filename);
> > +
> > +    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 &except)
> > +  {
> > +    exception_print (gdb_stderr, except);
> > +  }
> > +
> > +  return {};
> > +}
> > +
> > +/* See read.h.  */
> > +
> > +bool
> > +mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile)
> > +{
> > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > +  if (build_id == nullptr)
> > +    return false;
> > +
> > +  gdb::unique_xmalloc_ptr<char> line_path;
> > +  scoped_fd line_fd = debuginfod_section_query (build_id->data,
> > +                                               build_id->size,
> > +                                               bfd_get_filename
> > +                                                 (objfile->obfd.get ()),
> > +                                               ".debug_line",
> > +                                               &line_path);
> > +
> > +  if (line_fd.get () < 0)
> > +    return false;
> > +
> > +  gdb::unique_xmalloc_ptr<char> line_str_path;
> > +  scoped_fd line_str_fd = debuginfod_section_query (build_id->data,
> > +                                                   build_id->size,
> > +                                                   bfd_get_filename
> > +                                                     (objfile->obfd.get ()),
> > +                                                   ".debug_line_str",
> > +                                                   &line_str_path);
> > +
> > +  line_data = read_debug_line_separate (line_path.get (), &line_resource);
> > +  line_str_data = read_debug_line_separate (line_str_path.get (),
> > +                                           &line_str_resource);
> > +
> > +  const gdb_byte *line_ptr = line_data.data ();
> > +
> > +  while (line_ptr < line_data.data () + line_data.size ())
> > +    {
> > +      line_header_up lh
> > +       = dwarf_decode_line_header (objfile->obfd.get (),
> > +                                   line_data, line_str_data,
> > +                                   &line_ptr, false,
> > +                                   nullptr, nullptr);
> > +      line_headers.emplace_back (std::move (lh));
> > +    }
> > +
> > +  return true;
> > +}
> > +#endif /* !HAVE_SYS_MMAN_H */
> > +
> > +mapped_debug_line::mapped_debug_line (objfile *objfile)
> > +{
> > +  if (!read_debug_line_from_debuginfod (objfile))
> > +    line_headers.clear ();
> > +}
> > +
> >  /* A helper for the "quick" functions which computes and caches the
> >     real path for a given file name from the line table.  */
> >
> > diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> > index 8b6f1864458..db16160b43e 100644
> > --- a/gdb/dwarf2/read.h
> > +++ b/gdb/dwarf2/read.h
> > @@ -33,6 +33,7 @@
> >  #include "gdbsupport/hash_enum.h"
> >  #include "gdbsupport/function-view.h"
> >  #include "gdbsupport/packed.h"
> > +#include "dwarf2/line-header.h"
> >
> >  /* Hold 'maintenance (set|show) dwarf' commands.  */
> >  extern struct cmd_list_element *set_dwarf_cmdlist;
> > @@ -982,4 +983,40 @@ extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
> >  extern void read_full_dwarf_from_debuginfod (struct objfile *,
> >                                              dwarf2_base_index_functions *);
> >
> > +struct mapped_debug_line
> > +{
> > +  mapped_debug_line (objfile *objfile);
> > +
> > +  /* Return true if any of the mapped .debug_line's filenames match
> > +     FILE_MATCHER.  */
> > +
> > +  bool contains_matching_filename
> > +    (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher);
> > +
> > +  /* Call FUN with each filename in this mapped .debug_line.  Include
> > +     each file's fullname if NEED_FULLNAME is true.  */
> > +
> > +  void map_filenames (gdb::function_view<symbol_filename_ftype> fun,
> > +                     bool need_fullname);
> > +
> > +private:
> > +  std::vector<line_header_up> line_headers;
> > +
> > +  gdb::array_view<const gdb_byte> line_data;
> > +  gdb::array_view<const gdb_byte> line_str_data;
> > +
> > +  std::unique_ptr<index_cache_resource> line_resource;
> > +  std::unique_ptr<index_cache_resource> line_str_resource;
> > +
> > +  /* Download the .debug_line and .debug_line_str associated with OBJFILE
> > +     and populate line_headers.  */
> > +
> > +  bool read_debug_line_from_debuginfod (objfile *objfile);
> > +
> > +  /* Initialize line_data and line_str_data with the .debug_line and
> > +    .debug_line_str downloaded read_debug_line_from_debuginfod.  */
> > +
> > +  gdb::array_view<const gdb_byte> read_debug_line_separate
> > +    (const char *filename, std::unique_ptr<index_cache_resource> *resource);
> > +};
> >  #endif /* DWARF2READ_H */
> > diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c
> > index 0bc13f963bc..ab955a0fc05 100644
> > --- a/gdb/mi/mi-out.c
> > +++ b/gdb/mi/mi-out.c
> > @@ -278,7 +278,14 @@ mi_ui_out::do_progress_notify (const std::string &msg, const char *unit,
> >
> >    if (info.state == progress_update::START)
> >      {
> > -      gdb_printf ("%s...\n", msg.c_str ());
> > +      std::string prefix;
> > +      if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED)
> > +       {
> > +         prefix = "\n";
> > +         cur_prefix_state = prefix_state_t::NEWLINE_PRINTED;
> > +       }
> > +
> > +      gdb_printf ("%s...\n", (prefix + msg).c_str ());
> >        info.state = progress_update::WORKING;
> >      }
> >  }
> > diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
> > index c3872ecdfc9..3ac11152daa 100644
> > --- a/gdb/testsuite/gdb.debuginfod/section.exp
> > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > @@ -18,6 +18,7 @@
> >  standard_testfile
> >
> >  load_lib debuginfod-support.exp
> > +load_lib completion-support.exp
> >
> >  require allow_debuginfod_tests
> >
> > @@ -159,6 +160,32 @@ proc_with_prefix local_url { } {
> >      # Hit the breakpoint.
> >      set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
> >      gdb_test "c" $res "break continue"
> > +
> > +    clean_restart_with_prompt $sectexec "line 1"
> > +
> > +    # List source file using .debug_line download.
> > +    set res ".*\.debug_line.*$lib_sl1.*21.*extern void libsection2_test.*"
> > +    gdb_test "list $libsrc1:21" $res "line 1 list"
> > +
> > +    clean_restart_with_prompt $sectexec "line 2"
> > +
> > +    # Set breakpoint using .debug_line download.
> > +    set res ".*section \.debug_line for $lib_sl1.*Breakpoint 2 at.*$libsrc1.*"
> > +    gdb_test "br $libsrc1:41" $res "line 2 br"
> > +
> > +    # Continue to breakpoint.
> > +    set res "Breakpoint 2, libsection1_test.*\"Cancelling thread\\\\n\".*"
> > +    gdb_test "c" $res "line 2 continue"
> > +
> > +    # Check that download progress message is correctly formatted
> > +    # when printing threads.
> > +    set res ".*separate debug info for $lib_sl2\.\.\.\r\n.* 2    Thread.*"
> > +    gdb_test "info thr" $res "line thread"
> > +
> > +    clean_restart_with_prompt $sectexec "autocomplete"
> > +
> > +    # Download debuginfo during autocompletion.
> > +    test_gdb_complete_tab_unique "br lib" ".*Downloading separate debug.*" ""
> >  }
> >
> >  # Create CACHE and DB directories ready for debuginfod to use.
> > diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> > index 75f9f1612cb..d9a620b70e8 100644
> > --- a/gdb/ui-out.c
> > +++ b/gdb/ui-out.c
> > @@ -32,6 +32,9 @@
> >  #include <memory>
> >  #include <string>
> >
> > +/* Current state of newline prefixing for progress update messages.  */
> > +prefix_state_t cur_prefix_state = prefix_state_t::NEWLINE_OFF;
> > +
> >  namespace {
> >
> >  /* A header of a ui_out_table.  */
> > diff --git a/gdb/ui-out.h b/gdb/ui-out.h
> > index 2934d3a6b34..2c21603f1a7 100644
> > --- a/gdb/ui-out.h
> > +++ b/gdb/ui-out.h
> > @@ -296,6 +296,21 @@ class ui_out
> >        BAR
> >      };
> >
> > +    /* Used to communicate the status of a newline prefix for the next progress
> > +       update message.  */
> > +    enum prefix_state
> > +    {
> > +      /* Do not modify the next progress update message.  */
> > +      NEWLINE_OFF,
> > +
> > +      /* The next progress update message should include a newline prefix.  */
> > +      NEWLINE_NEEDED,
> > +
> > +      /* A newline prefix was included in a debuginfod progress update
> > +        message.  */
> > +      NEWLINE_PRINTED
> > +    };
> > +
> >      /* SHOULD_PRINT indicates whether something should be printed for a tty.  */
> >      progress_update ()
> >      {
> > @@ -393,6 +408,11 @@ class ui_out
> >    ui_out_level *current_level () const;
> >  };
> >
> > +typedef ui_out::progress_update::prefix_state prefix_state_t;
> > +
> > +/* Current state of the newline prefix.  */
> > +extern prefix_state_t cur_prefix_state;
> > +
> >  /* Start a new tuple or list on construction, and end it on
> >     destruction.  Normally this is used via the typedefs
> >     ui_out_emit_tuple and ui_out_emit_list.  */
> > --
> > 2.43.0
> >


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

* [PING*3][PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator
  2024-02-22 22:22         ` [PING*2][PATCH " Aaron Merey
@ 2024-03-01  0:09           ` Aaron Merey
  2024-03-12 22:14           ` Aaron Merey
  1 sibling, 0 replies; 29+ messages in thread
From: Aaron Merey @ 2024-03-01  0:09 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Thiago Jung Bauermann

Ping. Ready to be pushed to master?

Thanks,
Aaron

On Thu, Feb 22, 2024 at 5:22 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping. Ready to be pushed to master?
>
> Aaron
>
> On Wed, Feb 7, 2024 at 4:24 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Fri, Jan 19, 2024 at 12:09 AM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Hi Andrew,
> > >
> > > Thanks for the review.
> > >
> > > On Thu, Jan 18, 2024 at 7:03 AM Andrew Burgess <aburgess@redhat.com> wrote:
> > > >
> > > > Aaron Merey <amerey@redhat.com> writes:
> > > > > +using qf_list = std::forward_list<quick_symbol_functions_up>;
> > > > > +using qf_range = iterator_range<qf_list::iterator>;
> > > > > +using qf_safe_range = basic_safe_range<qf_range>;
> > > > > +
> > > > >  /* Sections in an objfile.  The section offsets are stored in the
> > > > >     OBJFILE.  */
> > > > >
> > > > > @@ -764,6 +770,10 @@ struct objfile
> > > > >       reader.  */
> > > > >    std::forward_list<quick_symbol_functions_up> qf;
> > > >
> > > > I think this should be updated to use qf_list.
> > >
> > > Done.
> > >
> > > > > --- a/gdb/symfile-debug.c
> > > > > +++ b/gdb/symfile-debug.c
> > > > > @@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
> > > > >       them, then that is an indication that they are in fact available.  Without
> > > > >       this function the symbols may have been already read in but they also may
> > > > >       not be present in this objfile.  */
> > > > > -  for (const auto &iter : qf)
> > > > > +  for (const auto &iter : qf_safe ())
> > > >
> > > > I read the commit message a couple of times, but I'm still not 100% sure
> > > > I understand what's happening here.  Is the change from qf -> qf_safe()
> > > > because calling (in this case has_symbols()) might download the full
> > > > debug info, and in doing do, might delete a previously downloaded
> > > > .debug_info section?
> > >
> > > quick_functions_up made from a separately downloaded .gdb_index is at
> > > first associated with the parent objfile (i.e. the objfile of the shared
> > > library containing a .gnu_debuglink that refers to the separate debug info
> > > which actually contains this .gdb_index section).
> > >
> > > When the separate debuginfo is downloaded (possibly during iteration
> > > over the parent's qf_list), we need to remove the gdb_index qf from
> > > the parent's qf_list.  This prevents gdb from later attempting to read
> > > the parent objfile for debug info that is actually in the corresponding
> > > separate debug objfile.
> > >
> > > > If this is the case then is it true that we will almost never want to
> > > > iterate over `qf` directly as most quick_symbol_functions methods might
> > > > result in this download/delete behaviour?
> > > >
> > > > If I'm right, then I think it might be useful to place a warning in the
> > > > comment before `qf` explaining this.
> > >
> > > Done.
> > >
> > > > > +/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
> > > > > +
> > > > > +template<typename Iterator>
> > > > > +class basic_safe_reverse_iterator
> > > > > +{
> > > > > +public:
> > > > > +  typedef basic_safe_reverse_iterator self_type;
> > > > > +  typedef typename Iterator::value_type value_type;
> > > > > +  typedef typename Iterator::reference reference;
> > > > > +  typedef typename Iterator::pointer pointer;
> > > > > +  typedef typename Iterator::iterator_category iterator_category;
> > > > > +  typedef typename Iterator::difference_type difference_type;
> > > > > +
> > > > > +  /* Construct the iterator using ARG, and construct the end iterator
> > > > > +     using ARG2.  */
> > > >
> > > > Maybe this is a standard thing, and I'm just showing my lack of
> > > > experience, but, given the existence of methods like rbegin() and
> > > > rend(), I think the comments here should be doing more to explain what's
> > > > expected of this input.
> > > >
> > > > Plus this comment seems confusing, though I can sort of see what it's
> > > > trying to say.
> > > >
> > > > We're creating a *_iterator object, which itself contains other
> > > > iterators.  We need to be super clear which particular "iterator" we're
> > > > talking about in the comments.
> > > >
> > > > Also, I think we need to be explicit that for this reverse iterator, the
> > > > first argument to the constructor is going to be the LAST element that
> > > > is iterated over.  One might be tempted to think that a safe reverse iterator
> > > > should be initialised with rbegin() and rend(), which isn't going to be
> > > > correct (unless I'm not understand, please correct me if so).
> > >
> > > That's right, the arguments to basic_safe_reverse_iterator should be
> > > forward iterators.  This will not work with reverse iterators.
> > >
> > > I added more information to this comment to hopefully clear up any
> > > confusion.
> > >
> > > > > +
> > > > > +/* A range adapter that wraps a forward range, and then returns
> > > > > +   safe reverse iterators wrapping the original range's iterators.  */
> > > >
> > > > I think the fact that this takes a FORWARD range is really important,
> > > > and we should do more to advertise this.  E.g. I think there should be a
> > > > comment on the constructor saying that RANGE is a forward range over
> > > > which we will iterate backwards.
> > > >
> > >
> > > Done.  The updated patch is included below.
> > >
> > > Aaron
> > >
> > > Commit message:
> > >
> > > This patch changes progspace objfile_list insertion so that separate
> > > debug objfiles are placed into the list after the parent objfile,
> > > instead of before.  Additionally qf_require_partial_symbols now returns
> > > a safe_range.
> > >
> > > These changes are intended to prepare gdb for on-demand debuginfo
> > > downloading and the downloading of .gdb_index sections.
> > >
> > > With on-demand downloading enabled, gdb might need to delete a
> > > .gdb_index quick_symbol_functions from a parent objfile while looping
> > > the objfile's list of quick_symbol_functions because the separate
> > > debug objfile has just been downloaded.  The use of a safe_range
> > > prevents this removal from causing iterator invalidation.
> > >
> > > gdb might also download a debuginfo file during symtab expansion.
> > > In this case an objfile will be added to the current progspace's
> > > objfiles_list during iteration over the list (for example, in
> > > iterate_over_symtabs).  We want these loops to also iterate over
> > > newly downloaded objfiles.  So objfiles need to be inserted into
> > > objfiles_list after their parent since it is during the search of
> > > the parent objfile for some symbol or filename that the separate
> > > debug objfile might be downloaded.
> > >
> > > To facilitate the safe deletion of objfiles, this patch also adds
> > > basic_safe_reverse_range and basic_safe_reverse_iterator.  This allows
> > > objfiles to be removed from the objfiles_list in a loop without iterator
> > > invalidation.
> > >
> > > If a forward safe iterator were to be used, the deletion of an
> > > objfile could invalidate the safe iterator's reference to the next
> > > objfile in the objfiles_list.  This can happen when the deletion
> > > of an objfile causes the deletion of a separate debug objfile that
> > > happens to the be next element in the objfiles_list.
> > >
> > > The standard reverse iterator is not suitable for safe objfile deletion.
> > > In order to safely delete the first objfile in the objfiles_list, the
> > > standard reverse iterator's underlying begin iterator would have to be
> > > decremented, resulting in undefined behavior.
> > >
> > > A small change was also made to a testcase in py-objfile.exp to
> > > account for the new placement of separate debug objfiles in
> > > objfiles_list.
> > > ---
> > >  gdb/jit.c                               |   7 +-
> > >  gdb/objfiles.c                          |  14 +--
> > >  gdb/objfiles.h                          |  17 +++-
> > >  gdb/progspace.c                         |  19 +++-
> > >  gdb/progspace.h                         |  31 ++++---
> > >  gdb/symfile-debug.c                     |  34 +++----
> > >  gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
> > >  gdbsupport/safe-iterator.h              | 115 ++++++++++++++++++++++++
> > >  8 files changed, 195 insertions(+), 44 deletions(-)
> > >
> > > diff --git a/gdb/jit.c b/gdb/jit.c
> > > index 85a10be3055..10ee5f9b749 100644
> > > --- a/gdb/jit.c
> > > +++ b/gdb/jit.c
> > > @@ -1240,11 +1240,10 @@ jit_breakpoint_re_set (void)
> > >  static void
> > >  jit_inferior_exit_hook (struct inferior *inf)
> > >  {
> > > -  for (objfile *objf : current_program_space->objfiles_safe ())
> > > +  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
> > >      {
> > > -      if (objf->jited_data != nullptr && objf->jited_data->addr != 0)
> > > -       objf->unlink ();
> > > -    }
> > > +      return (objf->jited_data != nullptr) && (objf->jited_data->addr != 0);
> > > +    });
> > >  }
> > >
> > >  void
> > > diff --git a/gdb/objfiles.c b/gdb/objfiles.c
> > > index 8f085b1bb7c..d53dfaca90b 100644
> > > --- a/gdb/objfiles.c
> > > +++ b/gdb/objfiles.c
> > > @@ -470,6 +470,12 @@ objfile::unlink ()
> > >    current_program_space->remove_objfile (this);
> > >  }
> > >
> > > +qf_safe_range
> > > +objfile::qf_safe ()
> > > +{
> > > +  return qf_safe_range (qf_range (qf.begin (), qf.end ()));
> > > +}
> > > +
> > >  /* Free all separate debug objfile of OBJFILE, but don't free OBJFILE
> > >     itself.  */
> > >
> > > @@ -793,14 +799,12 @@ have_full_symbols (void)
> > >  void
> > >  objfile_purge_solibs (void)
> > >  {
> > > -  for (objfile *objf : current_program_space->objfiles_safe ())
> > > +  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
> > >      {
> > >        /* We assume that the solib package has been purged already, or will
> > >          be soon.  */
> > > -
> > > -      if (!(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED))
> > > -       objf->unlink ();
> > > -    }
> > > +      return !(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED);
> > > +    });
> > >  }
> > >
> > >
> > > diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> > > index 621342d22b7..b917886988d 100644
> > > --- a/gdb/objfiles.h
> > > +++ b/gdb/objfiles.h
> > > @@ -370,6 +370,12 @@ class separate_debug_iterator
> > >
> > >  typedef iterator_range<separate_debug_iterator> separate_debug_range;
> > >
> > > +/* See objfile::qf_safe.  */
> > > +
> > > +using qf_list = std::forward_list<quick_symbol_functions_up>;
> > > +using qf_range = iterator_range<qf_list::iterator>;
> > > +using qf_safe_range = basic_safe_range<qf_range>;
> > > +
> > >  /* Sections in an objfile.  The section offsets are stored in the
> > >     OBJFILE.  */
> > >
> > > @@ -761,8 +767,15 @@ struct objfile
> > >    const struct sym_fns *sf = nullptr;
> > >
> > >    /* The "quick" (aka partial) symbol functions for this symbol
> > > -     reader.  */
> > > -  std::forward_list<quick_symbol_functions_up> qf;
> > > +     reader.  Many quick_symbol_functions methods may result
> > > +     in the deletion of a quick_symbol_functions from this
> > > +     qf_list.  It is recommended that qf_safe be used to iterate
> > > +     over the qf_list.  */
> > > +  qf_list qf;
> > > +
> > > +  /* Returns an iterable object that allows for safe deletion during
> > > +     iteration.  See gdbsupport/safe-iterator.h.  */
> > > +  qf_safe_range qf_safe ();
> > >
> > >    /* Per objfile data-pointers required by other GDB modules.  */
> > >
> > > diff --git a/gdb/progspace.c b/gdb/progspace.c
> > > index 0fc1fdd57ab..c4cc2e2e8af 100644
> > > --- a/gdb/progspace.c
> > > +++ b/gdb/progspace.c
> > > @@ -141,19 +141,19 @@ program_space::free_all_objfiles ()
> > >
> > >  void
> > >  program_space::add_objfile (std::unique_ptr<objfile> &&objfile,
> > > -                           struct objfile *before)
> > > +                           struct objfile *after)
> > >  {
> > > -  if (before == nullptr)
> > > +  if (after == nullptr)
> > >      objfiles_list.push_back (std::move (objfile));
> > >    else
> > >      {
> > >        auto iter = std::find_if (objfiles_list.begin (), objfiles_list.end (),
> > >                                 [=] (const std::unique_ptr<::objfile> &objf)
> > >                                 {
> > > -                                 return objf.get () == before;
> > > +                                 return objf.get () == after;
> > >                                 });
> > >        gdb_assert (iter != objfiles_list.end ());
> > > -      objfiles_list.insert (iter, std::move (objfile));
> > > +      objfiles_list.insert (++iter, std::move (objfile));
> > >      }
> > >  }
> > >
> > > @@ -182,6 +182,17 @@ program_space::remove_objfile (struct objfile *objfile)
> > >
> > >  /* See progspace.h.  */
> > >
> > > +void
> > > +program_space::unlink_objfiles_if
> > > +  (gdb::function_view<bool (const objfile *objfile)> predicate)
> > > +{
> > > +  for (auto &it : objfiles_safe ())
> > > +    if (predicate (it.get ()))
> > > +      it->unlink ();
> > > +}
> > > +
> > > +/* See progspace.h.  */
> > > +
> > >  struct objfile *
> > >  program_space::objfile_for_address (CORE_ADDR address)
> > >  {
> > > diff --git a/gdb/progspace.h b/gdb/progspace.h
> > > index 163cbf6a0f8..f23a57dc842 100644
> > > --- a/gdb/progspace.h
> > > +++ b/gdb/progspace.h
> > > @@ -250,28 +250,32 @@ struct program_space
> > >         unwrapping_objfile_iterator (objfiles_list.end ()));
> > >    }
> > >
> > > -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> > > +  using objfiles_it_range = iterator_range<objfile_list::iterator>;
> > > +  using objfiles_safe_reverse_range
> > > +    = basic_safe_reverse_range<objfiles_it_range>;
> > >
> > >    /* An iterable object that can be used to iterate over all objfiles.
> > >       The basic use is in a foreach, like:
> > >
> > >       for (objfile *objf : pspace->objfiles_safe ()) { ... }
> > >
> > > -     This variant uses a basic_safe_iterator so that objfiles can be
> > > -     deleted during iteration.  */
> > > -  objfiles_safe_range objfiles_safe ()
> > > +     This variant uses a basic_safe_reverse_iterator so that objfiles
> > > +     can be deleted during iteration.
> > > +
> > > +     The use of a reverse iterator helps ensure that separate debug
> > > +     objfiles are deleted before their parent objfile.  This prevents
> > > +     iterator invalidation due to the deletion of a parent objfile.  */
> > > + objfiles_safe_reverse_range objfiles_safe ()
> > >    {
> > > -    return objfiles_safe_range
> > > -      (objfiles_range
> > > -        (unwrapping_objfile_iterator (objfiles_list.begin ()),
> > > -         unwrapping_objfile_iterator (objfiles_list.end ())));
> > > +    return objfiles_safe_reverse_range
> > > +      (objfiles_it_range (objfiles_list.begin (), objfiles_list.end ()));
> > >    }
> > >
> > > -  /* Add OBJFILE to the list of objfiles, putting it just before
> > > -     BEFORE.  If BEFORE is nullptr, it will go at the end of the
> > > +  /* Add OBJFILE to the list of objfiles, putting it just after
> > > +     AFTER.  If AFTER is nullptr, it will go at the end of the
> > >       list.  */
> > >    void add_objfile (std::unique_ptr<objfile> &&objfile,
> > > -                   struct objfile *before);
> > > +                   struct objfile *after);
> > >
> > >    /* Remove OBJFILE from the list of objfiles.  */
> > >    void remove_objfile (struct objfile *objfile);
> > > @@ -286,6 +290,11 @@ struct program_space
> > >    /* Free all the objfiles associated with this program space.  */
> > >    void free_all_objfiles ();
> > >
> > > +  /* Unlink all objfiles associated with this program space for which
> > > +     PREDICATE evaluates to true.  */
> > > +  void unlink_objfiles_if
> > > +    (gdb::function_view<bool (const objfile *objfile)> predicate);
> > > +
> > >    /* Return the objfile containing ADDRESS, or nullptr if the address
> > >       is outside all objfiles in this progspace.  */
> > >    struct objfile *objfile_for_address (CORE_ADDR address);
> > > diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
> > > index 32c3ed9d349..c684f9cdd43 100644
> > > --- a/gdb/symfile-debug.c
> > > +++ b/gdb/symfile-debug.c
> > > @@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
> > >       them, then that is an indication that they are in fact available.  Without
> > >       this function the symbols may have been already read in but they also may
> > >       not be present in this objfile.  */
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        retval = iter->has_symbols (this);
> > >        if (retval)
> > > @@ -108,7 +108,7 @@ objfile::has_unexpanded_symtabs ()
> > >                 objfile_debug_name (this));
> > >
> > >    bool result = false;
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        if (iter->has_unexpanded_symtabs (this))
> > >         {
> > > @@ -133,7 +133,7 @@ objfile::find_last_source_symtab ()
> > >      gdb_printf (gdb_stdlog, "qf->find_last_source_symtab (%s)\n",
> > >                 objfile_debug_name (this));
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        retval = iter->find_last_source_symtab (this);
> > >        if (retval != nullptr)
> > > @@ -166,7 +166,7 @@ objfile::forget_cached_source_info ()
> > >         }
> > >      }
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->forget_cached_source_info (this);
> > >  }
> > >
> > > @@ -213,7 +213,7 @@ objfile::map_symtabs_matching_filename
> > >      return result;
> > >    };
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        if (!iter->expand_symtabs_matching (this,
> > >                                           match_one_filename,
> > > @@ -278,7 +278,7 @@ objfile::lookup_symbol (block_enum kind, const char *name, domain_enum domain)
> > >      return true;
> > >    };
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        if (!iter->expand_symtabs_matching (this,
> > >                                           nullptr,
> > > @@ -309,7 +309,7 @@ objfile::print_stats (bool print_bcache)
> > >      gdb_printf (gdb_stdlog, "qf->print_stats (%s, %d)\n",
> > >                 objfile_debug_name (this), print_bcache);
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->print_stats (this, print_bcache);
> > >  }
> > >
> > > @@ -320,7 +320,7 @@ objfile::dump ()
> > >      gdb_printf (gdb_stdlog, "qf->dump (%s)\n",
> > >                 objfile_debug_name (this));
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->dump (this);
> > >  }
> > >
> > > @@ -335,7 +335,7 @@ objfile::expand_symtabs_for_function (const char *func_name)
> > >    lookup_name_info base_lookup (func_name, symbol_name_match_type::FULL);
> > >    lookup_name_info lookup_name = base_lookup.make_ignore_params ();
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->expand_symtabs_matching (this,
> > >                                    nullptr,
> > >                                    &lookup_name,
> > > @@ -354,7 +354,7 @@ objfile::expand_all_symtabs ()
> > >      gdb_printf (gdb_stdlog, "qf->expand_all_symtabs (%s)\n",
> > >                 objfile_debug_name (this));
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->expand_all_symtabs (this);
> > >  }
> > >
> > > @@ -372,7 +372,7 @@ objfile::expand_symtabs_with_fullname (const char *fullname)
> > >      return filename_cmp (basenames ? basename : fullname, filename) == 0;
> > >    };
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->expand_symtabs_matching (this,
> > >                                    file_matcher,
> > >                                    nullptr,
> > > @@ -406,7 +406,7 @@ objfile::expand_symtabs_matching
> > >                 host_address_to_string (&expansion_notify),
> > >                 search_domain_name (kind));
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      if (!iter->expand_symtabs_matching (this, file_matcher, lookup_name,
> > >                                         symbol_matcher, expansion_notify,
> > >                                         search_flags, domain, kind))
> > > @@ -431,7 +431,7 @@ objfile::find_pc_sect_compunit_symtab (struct bound_minimal_symbol msymbol,
> > >                 host_address_to_string (section),
> > >                 warn_if_readin);
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        retval = iter->find_pc_sect_compunit_symtab (this, msymbol, pc, section,
> > >                                                    warn_if_readin);
> > > @@ -459,7 +459,7 @@ objfile::map_symbol_filenames (gdb::function_view<symbol_filename_ftype> fun,
> > >                 objfile_debug_name (this),
> > >                 need_fullname);
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->map_symbol_filenames (this, fun, need_fullname);
> > >  }
> > >
> > > @@ -471,7 +471,7 @@ objfile::compute_main_name ()
> > >                 "qf->compute_main_name (%s)\n",
> > >                 objfile_debug_name (this));
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->compute_main_name (this);
> > >  }
> > >
> > > @@ -485,7 +485,7 @@ objfile::find_compunit_symtab_by_address (CORE_ADDR address)
> > >                 hex_string (address));
> > >
> > >    struct compunit_symtab *result = NULL;
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        result = iter->find_compunit_symtab_by_address (this, address);
> > >        if (result != nullptr)
> > > @@ -510,7 +510,7 @@ objfile::lookup_global_symbol_language (const char *name,
> > >    enum language result = language_unknown;
> > >    *symbol_found_p = false;
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        result = iter->lookup_global_symbol_language (this, name, domain,
> > >                                                     symbol_found_p);
> > > diff --git a/gdb/testsuite/gdb.python/py-objfile.exp b/gdb/testsuite/gdb.python/py-objfile.exp
> > > index 61b9942de79..0bf49976b73 100644
> > > --- a/gdb/testsuite/gdb.python/py-objfile.exp
> > > +++ b/gdb/testsuite/gdb.python/py-objfile.exp
> > > @@ -135,7 +135,7 @@ gdb_test "p main" "= {<text variable, no debug info>} $hex <main>" \
> > >  gdb_py_test_silent_cmd "python objfile.add_separate_debug_file(\"${binfile}\")" \
> > >      "Add separate debug file file" 1
> > >
> > > -gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[0\]" \
> > > +gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[1\]" \
> > >      "Get separate debug info objfile" 1
> > >
> > >  gdb_test "python print (sep_objfile.owner.filename)" "${testfile}2" \
> > > diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h
> > > index b4891328f1a..d1b9ce21ffc 100644
> > > --- a/gdbsupport/safe-iterator.h
> > > +++ b/gdbsupport/safe-iterator.h
> > > @@ -136,4 +136,119 @@ class basic_safe_range
> > >    Range m_range;
> > >  };
> > >
> > > +/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
> > > +
> > > +template<typename Iterator>
> > > +class basic_safe_reverse_iterator
> > > +{
> > > +public:
> > > +  typedef basic_safe_reverse_iterator self_type;
> > > +  typedef typename Iterator::value_type value_type;
> > > +  typedef typename Iterator::reference reference;
> > > +  typedef typename Iterator::pointer pointer;
> > > +  typedef typename Iterator::iterator_category iterator_category;
> > > +  typedef typename Iterator::difference_type difference_type;
> > > +
> > > +  /* Construct the iterator using ARG, and construct the end iterator
> > > +     using ARG2.  ARG and ARG2 should be forward iterators, typically
> > > +     from begin and end methods, respectively.
> > > +
> > > +     For example if ARG1 is created with container.begin and ARG2 is
> > > +     is created with container.end, then the basic_safe_reverse_iterator
> > > +     will traverse from the last element in the container to the first
> > > +     element in the container.  */
> > > +  template<typename Arg>
> > > +  explicit basic_safe_reverse_iterator (Arg &&arg, Arg &&arg2)
> > > +    : m_begin (std::forward<Arg> (arg)),
> > > +      m_end (std::forward<Arg> (arg2)),
> > > +      m_it (m_end),
> > > +      m_next (m_end)
> > > +  {
> > > +    /* M_IT and M_NEXT are initialized as one-past-end.  Set M_IT to point
> > > +       to the last element and set M_NEXT to point to the second last element,
> > > +       if such elements exist.  */
> > > +    if (m_it != m_begin)
> > > +      {
> > > +       --m_it;
> > > +
> > > +       if (m_it != m_begin)
> > > +         {
> > > +           --m_next;
> > > +           --m_next;
> > > +         }
> > > +      }
> > > +  }
> > > +
> > > +  typename std::invoke_result<decltype(&Iterator::operator*), Iterator>::type
> > > +    operator* () const
> > > +  { return *m_it; }
> > > +
> > > +  self_type &operator++ ()
> > > +  {
> > > +    m_it = m_next;
> > > +
> > > +    if (m_it != m_end)
> > > +      {
> > > +       /* Use M_BEGIN only if we sure that it is valid.  */
> > > +       if (m_it == m_begin)
> > > +         m_next = m_end;
> > > +       else
> > > +         --m_next;
> > > +      }
> > > +
> > > +    return *this;
> > > +  }
> > > +
> > > +  bool operator== (const self_type &other) const
> > > +  { return m_it == other.m_it; }
> > > +
> > > +  bool operator!= (const self_type &other) const
> > > +  { return m_it != other.m_it; }
> > > +
> > > +private:
> > > +  /* The first element.  */
> > > +  Iterator m_begin {};
> > > +
> > > +  /* A one-past-end iterator.  */
> > > +  Iterator m_end {};
> > > +
> > > +  /* The current element.  */
> > > +  Iterator m_it {};
> > > +
> > > +  /* The next element.  Always one element ahead of M_IT.  */
> > > +  Iterator m_next {};
> > > +};
> > > +
> > > +/* A range adapter that wraps a forward range, and then returns
> > > +   safe reverse iterators wrapping the original range's iterators.  */
> > > +
> > > +template<typename Range>
> > > +class basic_safe_reverse_range
> > > +{
> > > +public:
> > > +
> > > +  typedef basic_safe_reverse_iterator<typename Range::iterator> iterator;
> > > +
> > > +  /* RANGE must be a forward range.  basic_safe_reverse_iterators
> > > +     will be used to traverse the forward range from the last element
> > > +     to the first.  */
> > > +  explicit basic_safe_reverse_range (Range range)
> > > +    : m_range (range)
> > > +  {
> > > +  }
> > > +
> > > +  iterator begin ()
> > > +  {
> > > +    return iterator (m_range.begin (), m_range.end ());
> > > +  }
> > > +
> > > +  iterator end ()
> > > +  {
> > > +    return iterator (m_range.end (), m_range.end ());
> > > +  }
> > > +
> > > +private:
> > > +
> > > +  Range m_range;
> > > +};
> > >  #endif /* COMMON_SAFE_ITERATOR_H */
> > > --
> > > 2.43.0
> > >


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

* [PING*3][PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading
  2024-02-22 22:23         ` [PING*2][PATCH " Aaron Merey
@ 2024-03-01  0:09           ` Aaron Merey
  2024-03-12 22:15           ` Aaron Merey
  1 sibling, 0 replies; 29+ messages in thread
From: Aaron Merey @ 2024-03-01  0:09 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Thiago Jung Bauermann

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


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

* [PING*3][PATCH 4/4 v6] gdb/debuginfod: Add .debug_line downloading
  2024-02-22 22:23     ` [PING*2][PATCH " Aaron Merey
@ 2024-03-01  0:10       ` Aaron Merey
  2024-03-12 22:15       ` Aaron Merey
  1 sibling, 0 replies; 29+ messages in thread
From: Aaron Merey @ 2024-03-01  0:10 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess, Thiago Jung Bauermann

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 Wed, Jan 17, 2024 at 10:49 AM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > v6 adds a testcase for downloads during autocompletion.  It also
> > > implements the suggestions from Thiago's review:
> > > https://sourceware.org/pipermail/gdb-patches/2023-December/205507.html
> > >
> > > ELF/DWARF section downloading allows gdb to download .gdb_index files in
> > > order to defer full debuginfo downloads.  However .gdb_index does not
> > > contain any information regarding source filenames.  When a gdb command
> > > includes a filename argument (ex. 'break main.c:50'), this results in
> > > the mass downloading of all deferred debuginfo so that gdb can search the
> > > debuginfo for matching source filenames.  This can result in unnecessary
> > > downloads.
> > >
> > > To improve this, have gdb instead download each debuginfo's .debug_line
> > > (and .debug_line_str if using DWARF5) when executing these commands.
> > > Download full debuginfo only when its .debug_line contains a matching
> > > filename.
> > >
> > > Since the combined size of .debug_line and .debug_line_str is only about
> > > 1% the size of the corresponding debuginfo, significant time can be saved
> > > by checking these sections before choosing to download an entire debuginfo.
> > >
> > > This patch also redirects stdout and stderr of the debuginfod server
> > > used by testsuite/gdb.debuginfod tests to a server_log standard output
> > > file.  While adding tests for this patch I ran into an issue where the
> > > test server would block when logging to stderr, presumably because the
> > > stderr buffer filled up and wasn't being read from.  Redirecting the
> > > log to a file fixes this and also makes the server log more accessible
> > > when debugging test failures.
> > > ---
> > >  gdb/cli-out.c                            |  11 +-
> > >  gdb/completer.c                          |  18 +-
> > >  gdb/dwarf2/line-header.c                 | 161 ++++++++++++------
> > >  gdb/dwarf2/line-header.h                 |  10 ++
> > >  gdb/dwarf2/read-gdb-index.c              |  60 +++++++
> > >  gdb/dwarf2/read.c                        | 208 +++++++++++++++++++++++
> > >  gdb/dwarf2/read.h                        |  37 ++++
> > >  gdb/mi/mi-out.c                          |   9 +-
> > >  gdb/testsuite/gdb.debuginfod/section.exp |  27 +++
> > >  gdb/ui-out.c                             |   3 +
> > >  gdb/ui-out.h                             |  20 +++
> > >  11 files changed, 511 insertions(+), 53 deletions(-)
> > >
> > > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > > index b4c7a657423..79f61eed62c 100644
> > > --- a/gdb/cli-out.c
> > > +++ b/gdb/cli-out.c
> > > @@ -307,16 +307,23 @@ cli_ui_out::do_progress_notify (const std::string &msg,
> > >
> > >    if (info.state == progress_update::START)
> > >      {
> > > +      std::string prefix;
> > > +      if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED)
> > > +       {
> > > +         prefix = "\n";
> > > +         cur_prefix_state = prefix_state_t::NEWLINE_PRINTED;
> > > +       }
> > > +
> > >        if (stream->isatty ()
> > >           && current_ui->input_interactive_p ()
> > >           && chars_per_line >= MIN_CHARS_PER_LINE)
> > >         {
> > > -         gdb_printf (stream, "%s\n", msg.c_str ());
> > > +         gdb_printf (stream, "%s\n", (prefix + msg).c_str ());
> > >           info.state = progress_update::BAR;
> > >         }
> > >        else
> > >         {
> > > -         gdb_printf (stream, "%s...\n", msg.c_str ());
> > > +         gdb_printf (stream, "%s...\n", (prefix + msg).c_str ());
> > >           info.state = progress_update::WORKING;
> > >         }
> > >      }
> > > diff --git a/gdb/completer.c b/gdb/completer.c
> > > index 8e2f031f13a..6d453d0d197 100644
> > > --- a/gdb/completer.c
> > > +++ b/gdb/completer.c
> > > @@ -1362,6 +1362,10 @@ complete_line_internal_1 (completion_tracker &tracker,
> > >      {
> > >        /* We've recognized a full command.  */
> > >
> > > +      /* Disable pagination since responding to the pagination prompt
> > > +        overwrites rl_line_buffer.  */
> > > +      scoped_restore pag_restore = make_scoped_restore (&pagination_enabled, false);
> > > +
> > >        if (p == tmp_command + point)
> > >         {
> > >           /* There is no non-whitespace in the line beyond the
> > > @@ -1461,7 +1465,8 @@ complete_line_internal_1 (completion_tracker &tracker,
> > >  }
> > >
> > >  /* Wrapper around complete_line_internal_1 to handle
> > > -   MAX_COMPLETIONS_REACHED_ERROR.  */
> > > +   MAX_COMPLETIONS_REACHED_ERROR and possible progress update
> > > +   interactions.  */
> > >
> > >  static void
> > >  complete_line_internal (completion_tracker &tracker,
> > > @@ -1469,6 +1474,11 @@ complete_line_internal (completion_tracker &tracker,
> > >                         const char *line_buffer, int point,
> > >                         complete_line_internal_reason reason)
> > >  {
> > > +  scoped_restore restore_prefix_state
> > > +    = make_scoped_restore
> > > +      (&cur_prefix_state,
> > > +       ui_out::progress_update::prefix_state::NEWLINE_NEEDED);
> > > +
> > >    try
> > >      {
> > >        complete_line_internal_1 (tracker, text, line_buffer, point, reason);
> > > @@ -1478,6 +1488,12 @@ complete_line_internal (completion_tracker &tracker,
> > >        if (except.error != MAX_COMPLETIONS_REACHED_ERROR)
> > >         throw;
> > >      }
> > > +
> > > +  /* If progress update messages printed, then the text being completed
> > > +     needs to be printed again.  */
> > > +  if (cur_prefix_state
> > > +      == ui_out::progress_update::prefix_state::NEWLINE_PRINTED)
> > > +    rl_forced_update_display ();
> > >  }
> > >
> > >  /* See completer.h.  */
> > > diff --git a/gdb/dwarf2/line-header.c b/gdb/dwarf2/line-header.c
> > > index ddaf7e4a527..aec02b3e1f7 100644
> > > --- a/gdb/dwarf2/line-header.c
> > > +++ b/gdb/dwarf2/line-header.c
> > > @@ -102,12 +102,15 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
> > >  {
> > >    LONGEST length = read_initial_length (abfd, buf, bytes_read);
> > >
> > > -  gdb_assert (cu_header->initial_length_size == 4
> > > -             || cu_header->initial_length_size == 8
> > > -             || cu_header->initial_length_size == 12);
> > > +  if (cu_header != nullptr)
> > > +    {
> > > +      gdb_assert (cu_header->initial_length_size == 4
> > > +                 || cu_header->initial_length_size == 8
> > > +                 || cu_header->initial_length_size == 12);
> > >
> > > -  if (cu_header->initial_length_size != *bytes_read)
> > > -    complaint (_("intermixed 32-bit and 64-bit DWARF sections"));
> > > +      if (cu_header->initial_length_size != *bytes_read)
> > > +       complaint (_("intermixed 32-bit and 64-bit DWARF sections"));
> > > +    }
> > >
> > >    *offset_size = (*bytes_read == 4) ? 4 : 8;
> > >    return length;
> > > @@ -116,21 +119,27 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
> > >  /* Read directory or file name entry format, starting with byte of
> > >     format count entries, ULEB128 pairs of entry formats, ULEB128 of
> > >     entries count and the entries themselves in the described entry
> > > -   format.  */
> > > +   format.
> > > +
> > > +   .debug_line and .debug_line_str sections are stored in LINE_BUFP
> > > +   and LINE_STR_DATA respectively.  */
> > >
> > >  static void
> > > -read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > > -                       const gdb_byte **bufp, struct line_header *lh,
> > > -                       unsigned int offset_size,
> > > -                       void (*callback) (struct line_header *lh,
> > > -                                         const char *name,
> > > -                                         dir_index d_index,
> > > -                                         unsigned int mod_time,
> > > -                                         unsigned int length))
> > > +read_formatted_entries
> > > +  (bfd *abfd, const gdb_byte **line_bufp,
> > > +   const gdb::array_view<const gdb_byte> line_str_data,
> > > +   struct line_header *lh,
> > > +   unsigned int offset_size,
> > > +   void (*callback) (struct line_header *lh,
> > > +                    const char *name,
> > > +                    dir_index d_index,
> > > +                    unsigned int mod_time,
> > > +                    unsigned int length))
> > >  {
> > >    gdb_byte format_count, formati;
> > >    ULONGEST data_count, datai;
> > > -  const gdb_byte *buf = *bufp;
> > > +  const gdb_byte *buf = *line_bufp;
> > > +  const gdb_byte *str_buf = line_str_data.data ();
> > >    const gdb_byte *format_header_data;
> > >    unsigned int bytes_read;
> > >
> > > @@ -157,7 +166,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > >           ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
> > >           format += bytes_read;
> > >
> > > -         ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
> > > +         ULONGEST form = read_unsigned_leb128 (abfd, format, &bytes_read);
> > >           format += bytes_read;
> > >
> > >           std::optional<const char *> string;
> > > @@ -172,12 +181,24 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > >
> > >             case DW_FORM_line_strp:
> > >               {
> > > -               const char *str
> > > -                 = per_objfile->read_line_string (buf, offset_size);
> > > +               if (line_str_data.empty ())
> > > +                 warning (_("Dwarf Error: DW_FORM_line_strp used without " \
> > > +                            "required section"));
> > > +
> > > +               if (line_str_data.size () <= offset_size)
> > > +                 warning (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
> > > +                            "of section .debug_line_str"));
> > > +
> > > +               ULONGEST str_offset = read_offset (abfd, buf, offset_size);
> > > +               const char *str;
> > > +               if (str_buf[str_offset] == '\0')
> > > +                 str = nullptr;
> > > +               else
> > > +                 str = (const char *) (str_buf + str_offset);
> > >                 string.emplace (str);
> > >                 buf += offset_size;
> > > +               break;
> > >               }
> > > -             break;
> > >
> > >             case DW_FORM_data1:
> > >               uint.emplace (read_1_byte (abfd, buf));
> > > @@ -248,28 +269,30 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > >        callback (lh, fe.name, fe.d_index, fe.mod_time, fe.length);
> > >      }
> > >
> > > -  *bufp = buf;
> > > +  *line_bufp = buf;
> > >  }
> > >
> > >  /* See line-header.h.  */
> > >
> > >  line_header_up
> > > -dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > > -                          dwarf2_per_objfile *per_objfile,
> > > -                          struct dwarf2_section_info *section,
> > > -                          const struct comp_unit_head *cu_header,
> > > -                          const char *comp_dir)
> > > +dwarf_decode_line_header (bfd *abfd,
> > > +                         gdb::array_view<const gdb_byte> line_data,
> > > +                         gdb::array_view<const gdb_byte> line_str_data,
> > > +                         const gdb_byte **debug_line_ptr,
> > > +                         bool is_dwz,
> > > +                         const struct comp_unit_head *cu_header,
> > > +                         const char *comp_dir)
> > >  {
> > > -  const gdb_byte *line_ptr;
> > > +  const gdb_byte *line_ptr, *buf;
> > >    unsigned int bytes_read, offset_size;
> > >    int i;
> > >    const char *cur_dir, *cur_file;
> > >
> > > -  bfd *abfd = section->get_bfd_owner ();
> > > +  buf = *debug_line_ptr;
> > >
> > >    /* Make sure that at least there's room for the total_length field.
> > >       That could be 12 bytes long, but we're just going to fudge that.  */
> > > -  if (to_underlying (sect_off) + 4 >= section->size)
> > > +  if (buf + 4 >= line_data.data () + line_data.size ())
> > >      {
> > >        dwarf2_statement_list_fits_in_line_number_section_complaint ();
> > >        return 0;
> > > @@ -277,24 +300,26 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >
> > >    line_header_up lh (new line_header (comp_dir));
> > >
> > > -  lh->sect_off = sect_off;
> > > +  lh->sect_off = (sect_offset) (buf - line_data.data ());
> > >    lh->offset_in_dwz = is_dwz;
> > >
> > > -  line_ptr = section->buffer + to_underlying (sect_off);
> > > +  line_ptr = buf;
> > >
> > >    /* Read in the header.  */
> > >    LONGEST unit_length
> > > -    = read_checked_initial_length_and_offset (abfd, line_ptr, cu_header,
> > > +    = read_checked_initial_length_and_offset (abfd, buf, cu_header,
> > >                                               &bytes_read, &offset_size);
> > > -  line_ptr += bytes_read;
> > >
> > > -  const gdb_byte *start_here = line_ptr;
> > > +  line_ptr += bytes_read;
> > >
> > > -  if (line_ptr + unit_length > (section->buffer + section->size))
> > > +  if (line_ptr + unit_length > buf + line_data.size ())
> > >      {
> > >        dwarf2_statement_list_fits_in_line_number_section_complaint ();
> > >        return 0;
> > >      }
> > > +
> > > +  const gdb_byte *start_here = line_ptr;
> > > +
> > >    lh->statement_program_end = start_here + unit_length;
> > >    lh->version = read_2_bytes (abfd, line_ptr);
> > >    line_ptr += 2;
> > > @@ -303,7 +328,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >        /* This is a version we don't understand.  The format could have
> > >          changed in ways we don't handle properly so just punt.  */
> > >        complaint (_("unsupported version in .debug_line section"));
> > > -      return NULL;
> > > +      return nullptr;
> > >      }
> > >    if (lh->version >= 5)
> > >      {
> > > @@ -320,13 +345,14 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >           complaint (_("unsupported segment selector size %u "
> > >                        "in .debug_line section"),
> > >                      segment_selector_size);
> > > -         return NULL;
> > > +         return nullptr;
> > >         }
> > >      }
> > >
> > >    LONGEST header_length = read_offset (abfd, line_ptr, offset_size);
> > >    line_ptr += offset_size;
> > >    lh->statement_program_start = line_ptr + header_length;
> > > +
> > >    lh->minimum_instruction_length = read_1_byte (abfd, line_ptr);
> > >    line_ptr += 1;
> > >
> > > @@ -347,12 +373,16 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >
> > >    lh->default_is_stmt = read_1_byte (abfd, line_ptr);
> > >    line_ptr += 1;
> > > +
> > >    lh->line_base = read_1_signed_byte (abfd, line_ptr);
> > >    line_ptr += 1;
> > > +
> > >    lh->line_range = read_1_byte (abfd, line_ptr);
> > >    line_ptr += 1;
> > > +
> > >    lh->opcode_base = read_1_byte (abfd, line_ptr);
> > >    line_ptr += 1;
> > > +
> > >    lh->standard_opcode_lengths.reset (new unsigned char[lh->opcode_base]);
> > >
> > >    lh->standard_opcode_lengths[0] = 1;  /* This should never be used anyway.  */
> > > @@ -365,21 +395,23 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >    if (lh->version >= 5)
> > >      {
> > >        /* Read directory table.  */
> > > -      read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
> > > -                             offset_size,
> > > -                             [] (struct line_header *header, const char *name,
> > > -                                 dir_index d_index, unsigned int mod_time,
> > > -                                 unsigned int length)
> > > +      read_formatted_entries
> > > +       (abfd, &line_ptr, line_str_data,
> > > +        lh.get (), offset_size,
> > > +        [] (struct line_header *header, const char *name,
> > > +            dir_index d_index, unsigned int mod_time,
> > > +            unsigned int length)
> > >         {
> > >           header->add_include_dir (name);
> > >         });
> > >
> > >        /* Read file name table.  */
> > > -      read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
> > > -                             offset_size,
> > > -                             [] (struct line_header *header, const char *name,
> > > -                                 dir_index d_index, unsigned int mod_time,
> > > -                                 unsigned int length)
> > > +      read_formatted_entries
> > > +       (abfd, &line_ptr, line_str_data,
> > > +        lh.get (), offset_size,
> > > +        [] (struct line_header *header, const char *name,
> > > +            dir_index d_index, unsigned int mod_time,
> > > +            unsigned int length)
> > >         {
> > >           header->add_file_name (name, d_index, mod_time, length);
> > >         });
> > > @@ -387,7 +419,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >    else
> > >      {
> > >        /* Read directory table.  */
> > > -      while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL)
> > > +      while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr)
> > >         {
> > >           line_ptr += bytes_read;
> > >           lh->add_include_dir (cur_dir);
> > > @@ -395,7 +427,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >        line_ptr += bytes_read;
> > >
> > >        /* Read file name table.  */
> > > -      while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL)
> > > +      while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr)
> > >         {
> > >           unsigned int mod_time, length;
> > >           dir_index d_index;
> > > @@ -413,9 +445,40 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >        line_ptr += bytes_read;
> > >      }
> > >
> > > -  if (line_ptr > (section->buffer + section->size))
> > > +  if (line_ptr > (buf + line_data.size ()))
> > >      complaint (_("line number info header doesn't "
> > >                  "fit in `.debug_line' section"));
> > >
> > > +  *debug_line_ptr += unit_length + offset_size;
> > >    return lh;
> > >  }
> > > +
> > > +line_header_up
> > > +dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > > +                          dwarf2_per_objfile *per_objfile,
> > > +                          struct dwarf2_section_info *section,
> > > +                          const struct comp_unit_head *cu_header,
> > > +                          const char *comp_dir)
> > > +{
> > > +  struct objfile *objfile = per_objfile->objfile;
> > > +  struct dwarf2_per_bfd *per_bfd = per_objfile->per_bfd;
> > > +
> > > +  /* Read .debug_line.  */
> > > +  dwarf2_section_info *line_sec = &per_bfd->line;
> > > +  bfd_size_type line_size = line_sec->get_size (objfile);
> > > +
> > > +  gdb::array_view<const gdb_byte> line (line_sec->buffer, line_size);
> > > +
> > > +  /* Read .debug_line_str.  */
> > > +  dwarf2_section_info *line_str_sec = &per_bfd->line_str;
> > > +  bfd_size_type line_str_size = line_str_sec->get_size (objfile);
> > > +
> > > +  gdb::array_view<const gdb_byte> line_str (line_str_sec->buffer,
> > > +                                           line_str_size);
> > > +
> > > +  const gdb_byte *line_ptr = line.data () + to_underlying (sect_off);
> > > +
> > > +  return dwarf_decode_line_header
> > > +    (per_bfd->obfd, line, line_str, &line_ptr,
> > > +     is_dwz, cu_header, comp_dir);
> > > +}
> > > diff --git a/gdb/dwarf2/line-header.h b/gdb/dwarf2/line-header.h
> > > index 06d2eec573b..1dffb7c7e5e 100644
> > > --- a/gdb/dwarf2/line-header.h
> > > +++ b/gdb/dwarf2/line-header.h
> > > @@ -217,4 +217,14 @@ extern line_header_up dwarf_decode_line_header
> > >     struct dwarf2_section_info *section, const struct comp_unit_head *cu_header,
> > >     const char *comp_dir);
> > >
> > > +/* Like above but the .debug_line and .debug_line_str are stored in
> > > +   LINE_DATA and LINE_STR_DATA.  *DEBUG_LINE_PTR should point to a
> > > +   statement program header within LINE_DATA.  */
> > > +
> > > +extern line_header_up dwarf_decode_line_header
> > > +  (bfd *abfd, gdb::array_view<const gdb_byte> line_data,
> > > +   gdb::array_view<const gdb_byte> line_str_data,
> > > +   const gdb_byte **debug_line_ptr, bool is_dwz,
> > > +  const comp_unit_head *cu_header, const char *comp_dir);
> > > +
> > >  #endif /* DWARF2_LINE_HEADER_H */
> > > diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
> > > index 55b04391eb5..3d75678d7dc 100644
> > > --- a/gdb/dwarf2/read-gdb-index.c
> > > +++ b/gdb/dwarf2/read-gdb-index.c
> > > @@ -131,6 +131,9 @@ struct mapped_gdb_index final : public mapped_index_base
> > >    }
> > >  };
> > >
> > > +struct mapped_debug_line;
> > > +typedef std::unique_ptr<mapped_debug_line> mapped_debug_line_up;
> > > +
> > >  struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >  {
> > >    /* This dumps minimal information about the index.
> > > @@ -162,6 +165,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >       domain_enum domain,
> > >       enum search_domain kind);
> > >
> > > + /* If OBJFILE's debuginfo download has been deferred, use a mapped_debug_line
> > > +    to generate filenames.
> > > +
> > > +    Otherwise call dwarf2_base_index_functions::map_symbol_filenames.  */
> > > +
> > > +  void map_symbol_filenames (struct objfile *objfile,
> > > +                            gdb::function_view<symbol_filename_ftype> fun,
> > > +                            bool need_fullname) override;
> > > +
> > >    /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
> > >       debuginfo if necessary.  */
> > >    void expand_all_symtabs (struct objfile *objfile) override;
> > > @@ -169,6 +181,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >    /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
> > >       debuginfo if necessary.  */
> > >    struct symtab *find_last_source_symtab (struct objfile *objfile) override;
> > > +
> > > +  /* Filename information related to this .gdb_index.  */
> > > +  mapped_debug_line_up mdl;
> > > +
> > > +  /* Return true if any of the filenames in this .gdb_index's .debug_line
> > > +     mapping match FILE_MATCHER.  Initializes the mapping if necessary.  */
> > > +  bool filename_in_debug_line
> > > +    (objfile *objfile,
> > > +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher);
> > >  };
> > >
> > >  void
> > > @@ -204,6 +225,30 @@ dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
> > >      }
> > >  }
> > >
> > > +void
> > > +dwarf2_gdb_index::map_symbol_filenames
> > > +     (struct objfile *objfile,
> > > +      gdb::function_view<symbol_filename_ftype> fun,
> > > +      bool need_fullname)
> > > +{
> > > +  try
> > > +    {
> > > +      dwarf2_base_index_functions::map_symbol_filenames (objfile, fun,
> > > +                                                        need_fullname);
> > > +    }
> > > +  catch (const gdb_exception &e)
> > > +    {
> > > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       exception_print (gdb_stderr, e);
> > > +      else
> > > +       {
> > > +         if (mdl == nullptr)
> > > +           mdl.reset (new mapped_debug_line (objfile));
> > > +         mdl->map_filenames (fun, need_fullname);
> > > +       }
> > > +    }
> > > +}
> > > +
> > >  /* 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
> > > @@ -371,6 +416,17 @@ dwarf2_gdb_index::do_expand_symtabs_matching
> > >    return result;
> > >  }
> > >
> > > +bool
> > > +dwarf2_gdb_index::filename_in_debug_line
> > > +  (objfile *objfile,
> > > +   gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> > > +{
> > > +  if (mdl == nullptr)
> > > +    mdl.reset (new mapped_debug_line (objfile));
> > > +
> > > +  return mdl->contains_matching_filename (file_matcher);
> > > +}
> > > +
> > >  bool
> > >  dwarf2_gdb_index::expand_symtabs_matching
> > >      (struct objfile *objfile,
> > > @@ -399,6 +455,10 @@ dwarf2_gdb_index::expand_symtabs_matching
> > >           return false;
> > >         }
> > >
> > > +      if (file_matcher != nullptr
> > > +         && !filename_in_debug_line (objfile, file_matcher))
> > > +       return true;
> > > +
> > >        read_full_dwarf_from_debuginfod (objfile, this);
> > >        return true;
> > >      }
> > > diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> > > index 8725a2fbcdb..4af83728c63 100644
> > > --- a/gdb/dwarf2/read.c
> > > +++ b/gdb/dwarf2/read.c
> > > @@ -82,6 +82,7 @@
> > >  #include <optional>
> > >  #include "gdbsupport/underlying.h"
> > >  #include "gdbsupport/hash_enum.h"
> > > +#include "gdbsupport/scoped_mmap.h"
> > >  #include "filename-seen-cache.h"
> > >  #include "producer.h"
> > >  #include <fcntl.h>
> > > @@ -1950,6 +1951,213 @@ dw2_get_file_names (dwarf2_per_cu_data *this_cu,
> > >    return this_cu->file_names;
> > >  }
> > >
> > > +#if !HAVE_SYS_MMAN_H
> > > +
> > > +bool
> > > +mapped_debug_line::contains_matching_filename
> > > +  (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> > > +{
> > > +  return false;
> > > +}
> > > +
> > > +gdb::array_view<const gdb_byte>
> > > +mapped_debug_line::read_debug_line_separate
> > > +  (const char *filename, std::unique_ptr<index_cache_resource> *resource)
> > > +{
> > > +  return {};
> > > +}
> > > +
> > > +bool
> > > +mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile)
> > > +{
> > > +  return false;
> > > +}
> > > +
> > > +void
> > > +mapped_debug_line::map_filenames
> > > +  (gdb::function_view<symbol_filename_ftype> fun,
> > > +   bool need_fullname)
> > > +{
> > > +  return;
> > > +}
> > > +
> > > +#else /* !HAVE_SYS_MMAN_H */
> > > +
> > > +struct line_resource_mmap final : public index_cache_resource
> > > +{
> > > +  /* Try to mmap FILENAME.  Throw an exception on failure, including if the
> > > +     file doesn't exist.  */
> > > +  line_resource_mmap (const char *filename)
> > > +    : mapping (mmap_file (filename))
> > > +  {}
> > > +
> > > +  scoped_mmap mapping;
> > > +};
> > > +
> > > +/* See read.h.  */
> > > +
> > > +bool
> > > +mapped_debug_line::contains_matching_filename
> > > +  (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> > > +{
> > > +  for (line_header_up &lh : line_headers)
> > > +    for (file_entry &fe : lh->file_names ())
> > > +      {
> > > +       const char *filename = fe.name;
> > > +
> > > +       if (file_matcher (fe.name, false))
> > > +         return true;
> > > +
> > > +       bool basename_match = file_matcher (lbasename (fe.name), true);
> > > +
> > > +       if (!basenames_may_differ && !basename_match)
> > > +         continue;
> > > +
> > > +       /* DW_AT_comp_dir is not explicitly mentioned in the .debug_line
> > > +          until DWARF5.  Since we don't have access to the CU at this
> > > +          point we just check for a partial match on the filename.
> > > +          If there is a match, the full debuginfo will be downloaded
> > > +          ane the match will be re-evalute with DW_AT_comp_dir.  */
> > > +       if (lh->version < 5 && fe.d_index == 0)
> > > +         return basename_match;
> > > +
> > > +       const char *dirname = fe.include_dir (&*lh);
> > > +       std::string fullname;
> > > +
> > > +       if (dirname == nullptr || IS_ABSOLUTE_PATH (filename))
> > > +         fullname = filename;
> > > +       else
> > > +         fullname = std::string (dirname) + SLASH_STRING + filename;
> > > +
> > > +       gdb::unique_xmalloc_ptr<char> rewritten
> > > +         = rewrite_source_path (fullname.c_str ());
> > > +       if (rewritten != nullptr)
> > > +         fullname = rewritten.release ();
> > > +
> > > +       if (file_matcher (fullname.c_str (), false))
> > > +         return true;
> > > +      }
> > > +
> > > +  return false;
> > > +}
> > > +
> > > +/* See read.h.  */
> > > +
> > > +void
> > > +mapped_debug_line::map_filenames
> > > +  (gdb::function_view<symbol_filename_ftype> fun,
> > > +   bool need_fullname)
> > > +{
> > > +  for (line_header_up &lh : line_headers)
> > > +    for (file_entry &fe : lh->file_names ())
> > > +      {
> > > +       const char *filename = fe.name;
> > > +
> > > +       if (!need_fullname)
> > > +         {
> > > +           fun (filename, nullptr);
> > > +           continue;
> > > +         }
> > > +
> > > +       const char *dirname = fe.include_dir (&*lh);
> > > +       std::string fullname;
> > > +
> > > +       if (dirname == nullptr || IS_ABSOLUTE_PATH (filename))
> > > +         fullname = filename;
> > > +       else
> > > +         fullname = std::string (dirname) + SLASH_STRING + filename;
> > > +
> > > +       gdb::unique_xmalloc_ptr<char> rewritten
> > > +         = rewrite_source_path (fullname.c_str ());
> > > +       if (rewritten != nullptr)
> > > +         fullname = rewritten.release ();
> > > +
> > > +       fun (filename, fullname.c_str ());
> > > +      }
> > > +}
> > > +
> > > +/* See read.h.  */
> > > +
> > > +gdb::array_view<const gdb_byte>
> > > +mapped_debug_line::read_debug_line_separate
> > > +  (const char *filename, std::unique_ptr<index_cache_resource> *resource)
> > > +{
> > > +  if (filename == nullptr)
> > > +    return {};
> > > +
> > > +  try
> > > +  {
> > > +    line_resource_mmap *mmap_resource
> > > +      = new line_resource_mmap (filename);
> > > +
> > > +    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 &except)
> > > +  {
> > > +    exception_print (gdb_stderr, except);
> > > +  }
> > > +
> > > +  return {};
> > > +}
> > > +
> > > +/* See read.h.  */
> > > +
> > > +bool
> > > +mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile)
> > > +{
> > > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > +  if (build_id == nullptr)
> > > +    return false;
> > > +
> > > +  gdb::unique_xmalloc_ptr<char> line_path;
> > > +  scoped_fd line_fd = debuginfod_section_query (build_id->data,
> > > +                                               build_id->size,
> > > +                                               bfd_get_filename
> > > +                                                 (objfile->obfd.get ()),
> > > +                                               ".debug_line",
> > > +                                               &line_path);
> > > +
> > > +  if (line_fd.get () < 0)
> > > +    return false;
> > > +
> > > +  gdb::unique_xmalloc_ptr<char> line_str_path;
> > > +  scoped_fd line_str_fd = debuginfod_section_query (build_id->data,
> > > +                                                   build_id->size,
> > > +                                                   bfd_get_filename
> > > +                                                     (objfile->obfd.get ()),
> > > +                                                   ".debug_line_str",
> > > +                                                   &line_str_path);
> > > +
> > > +  line_data = read_debug_line_separate (line_path.get (), &line_resource);
> > > +  line_str_data = read_debug_line_separate (line_str_path.get (),
> > > +                                           &line_str_resource);
> > > +
> > > +  const gdb_byte *line_ptr = line_data.data ();
> > > +
> > > +  while (line_ptr < line_data.data () + line_data.size ())
> > > +    {
> > > +      line_header_up lh
> > > +       = dwarf_decode_line_header (objfile->obfd.get (),
> > > +                                   line_data, line_str_data,
> > > +                                   &line_ptr, false,
> > > +                                   nullptr, nullptr);
> > > +      line_headers.emplace_back (std::move (lh));
> > > +    }
> > > +
> > > +  return true;
> > > +}
> > > +#endif /* !HAVE_SYS_MMAN_H */
> > > +
> > > +mapped_debug_line::mapped_debug_line (objfile *objfile)
> > > +{
> > > +  if (!read_debug_line_from_debuginfod (objfile))
> > > +    line_headers.clear ();
> > > +}
> > > +
> > >  /* A helper for the "quick" functions which computes and caches the
> > >     real path for a given file name from the line table.  */
> > >
> > > diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> > > index 8b6f1864458..db16160b43e 100644
> > > --- a/gdb/dwarf2/read.h
> > > +++ b/gdb/dwarf2/read.h
> > > @@ -33,6 +33,7 @@
> > >  #include "gdbsupport/hash_enum.h"
> > >  #include "gdbsupport/function-view.h"
> > >  #include "gdbsupport/packed.h"
> > > +#include "dwarf2/line-header.h"
> > >
> > >  /* Hold 'maintenance (set|show) dwarf' commands.  */
> > >  extern struct cmd_list_element *set_dwarf_cmdlist;
> > > @@ -982,4 +983,40 @@ extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
> > >  extern void read_full_dwarf_from_debuginfod (struct objfile *,
> > >                                              dwarf2_base_index_functions *);
> > >
> > > +struct mapped_debug_line
> > > +{
> > > +  mapped_debug_line (objfile *objfile);
> > > +
> > > +  /* Return true if any of the mapped .debug_line's filenames match
> > > +     FILE_MATCHER.  */
> > > +
> > > +  bool contains_matching_filename
> > > +    (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher);
> > > +
> > > +  /* Call FUN with each filename in this mapped .debug_line.  Include
> > > +     each file's fullname if NEED_FULLNAME is true.  */
> > > +
> > > +  void map_filenames (gdb::function_view<symbol_filename_ftype> fun,
> > > +                     bool need_fullname);
> > > +
> > > +private:
> > > +  std::vector<line_header_up> line_headers;
> > > +
> > > +  gdb::array_view<const gdb_byte> line_data;
> > > +  gdb::array_view<const gdb_byte> line_str_data;
> > > +
> > > +  std::unique_ptr<index_cache_resource> line_resource;
> > > +  std::unique_ptr<index_cache_resource> line_str_resource;
> > > +
> > > +  /* Download the .debug_line and .debug_line_str associated with OBJFILE
> > > +     and populate line_headers.  */
> > > +
> > > +  bool read_debug_line_from_debuginfod (objfile *objfile);
> > > +
> > > +  /* Initialize line_data and line_str_data with the .debug_line and
> > > +    .debug_line_str downloaded read_debug_line_from_debuginfod.  */
> > > +
> > > +  gdb::array_view<const gdb_byte> read_debug_line_separate
> > > +    (const char *filename, std::unique_ptr<index_cache_resource> *resource);
> > > +};
> > >  #endif /* DWARF2READ_H */
> > > diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c
> > > index 0bc13f963bc..ab955a0fc05 100644
> > > --- a/gdb/mi/mi-out.c
> > > +++ b/gdb/mi/mi-out.c
> > > @@ -278,7 +278,14 @@ mi_ui_out::do_progress_notify (const std::string &msg, const char *unit,
> > >
> > >    if (info.state == progress_update::START)
> > >      {
> > > -      gdb_printf ("%s...\n", msg.c_str ());
> > > +      std::string prefix;
> > > +      if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED)
> > > +       {
> > > +         prefix = "\n";
> > > +         cur_prefix_state = prefix_state_t::NEWLINE_PRINTED;
> > > +       }
> > > +
> > > +      gdb_printf ("%s...\n", (prefix + msg).c_str ());
> > >        info.state = progress_update::WORKING;
> > >      }
> > >  }
> > > diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
> > > index c3872ecdfc9..3ac11152daa 100644
> > > --- a/gdb/testsuite/gdb.debuginfod/section.exp
> > > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > > @@ -18,6 +18,7 @@
> > >  standard_testfile
> > >
> > >  load_lib debuginfod-support.exp
> > > +load_lib completion-support.exp
> > >
> > >  require allow_debuginfod_tests
> > >
> > > @@ -159,6 +160,32 @@ proc_with_prefix local_url { } {
> > >      # Hit the breakpoint.
> > >      set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
> > >      gdb_test "c" $res "break continue"
> > > +
> > > +    clean_restart_with_prompt $sectexec "line 1"
> > > +
> > > +    # List source file using .debug_line download.
> > > +    set res ".*\.debug_line.*$lib_sl1.*21.*extern void libsection2_test.*"
> > > +    gdb_test "list $libsrc1:21" $res "line 1 list"
> > > +
> > > +    clean_restart_with_prompt $sectexec "line 2"
> > > +
> > > +    # Set breakpoint using .debug_line download.
> > > +    set res ".*section \.debug_line for $lib_sl1.*Breakpoint 2 at.*$libsrc1.*"
> > > +    gdb_test "br $libsrc1:41" $res "line 2 br"
> > > +
> > > +    # Continue to breakpoint.
> > > +    set res "Breakpoint 2, libsection1_test.*\"Cancelling thread\\\\n\".*"
> > > +    gdb_test "c" $res "line 2 continue"
> > > +
> > > +    # Check that download progress message is correctly formatted
> > > +    # when printing threads.
> > > +    set res ".*separate debug info for $lib_sl2\.\.\.\r\n.* 2    Thread.*"
> > > +    gdb_test "info thr" $res "line thread"
> > > +
> > > +    clean_restart_with_prompt $sectexec "autocomplete"
> > > +
> > > +    # Download debuginfo during autocompletion.
> > > +    test_gdb_complete_tab_unique "br lib" ".*Downloading separate debug.*" ""
> > >  }
> > >
> > >  # Create CACHE and DB directories ready for debuginfod to use.
> > > diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> > > index 75f9f1612cb..d9a620b70e8 100644
> > > --- a/gdb/ui-out.c
> > > +++ b/gdb/ui-out.c
> > > @@ -32,6 +32,9 @@
> > >  #include <memory>
> > >  #include <string>
> > >
> > > +/* Current state of newline prefixing for progress update messages.  */
> > > +prefix_state_t cur_prefix_state = prefix_state_t::NEWLINE_OFF;
> > > +
> > >  namespace {
> > >
> > >  /* A header of a ui_out_table.  */
> > > diff --git a/gdb/ui-out.h b/gdb/ui-out.h
> > > index 2934d3a6b34..2c21603f1a7 100644
> > > --- a/gdb/ui-out.h
> > > +++ b/gdb/ui-out.h
> > > @@ -296,6 +296,21 @@ class ui_out
> > >        BAR
> > >      };
> > >
> > > +    /* Used to communicate the status of a newline prefix for the next progress
> > > +       update message.  */
> > > +    enum prefix_state
> > > +    {
> > > +      /* Do not modify the next progress update message.  */
> > > +      NEWLINE_OFF,
> > > +
> > > +      /* The next progress update message should include a newline prefix.  */
> > > +      NEWLINE_NEEDED,
> > > +
> > > +      /* A newline prefix was included in a debuginfod progress update
> > > +        message.  */
> > > +      NEWLINE_PRINTED
> > > +    };
> > > +
> > >      /* SHOULD_PRINT indicates whether something should be printed for a tty.  */
> > >      progress_update ()
> > >      {
> > > @@ -393,6 +408,11 @@ class ui_out
> > >    ui_out_level *current_level () const;
> > >  };
> > >
> > > +typedef ui_out::progress_update::prefix_state prefix_state_t;
> > > +
> > > +/* Current state of the newline prefix.  */
> > > +extern prefix_state_t cur_prefix_state;
> > > +
> > >  /* Start a new tuple or list on construction, and end it on
> > >     destruction.  Normally this is used via the typedefs
> > >     ui_out_emit_tuple and ui_out_emit_list.  */
> > > --
> > > 2.43.0
> > >


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

* [PING*3][PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator
  2024-02-22 22:22         ` [PING*2][PATCH " Aaron Merey
  2024-03-01  0:09           ` [PING*3][PATCH " Aaron Merey
@ 2024-03-12 22:14           ` Aaron Merey
  1 sibling, 0 replies; 29+ messages in thread
From: Aaron Merey @ 2024-03-12 22:14 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Thiago Jung Bauermann

Ping. Ready to be pushed to master?

Aaron

On Thu, Feb 22, 2024 at 5:22 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping. Ready to be pushed to master?
>
> Aaron
>
> On Wed, Feb 7, 2024 at 4:24 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Fri, Jan 19, 2024 at 12:09 AM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Hi Andrew,
> > >
> > > Thanks for the review.
> > >
> > > On Thu, Jan 18, 2024 at 7:03 AM Andrew Burgess <aburgess@redhat.com> wrote:
> > > >
> > > > Aaron Merey <amerey@redhat.com> writes:
> > > > > +using qf_list = std::forward_list<quick_symbol_functions_up>;
> > > > > +using qf_range = iterator_range<qf_list::iterator>;
> > > > > +using qf_safe_range = basic_safe_range<qf_range>;
> > > > > +
> > > > >  /* Sections in an objfile.  The section offsets are stored in the
> > > > >     OBJFILE.  */
> > > > >
> > > > > @@ -764,6 +770,10 @@ struct objfile
> > > > >       reader.  */
> > > > >    std::forward_list<quick_symbol_functions_up> qf;
> > > >
> > > > I think this should be updated to use qf_list.
> > >
> > > Done.
> > >
> > > > > --- a/gdb/symfile-debug.c
> > > > > +++ b/gdb/symfile-debug.c
> > > > > @@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
> > > > >       them, then that is an indication that they are in fact available.  Without
> > > > >       this function the symbols may have been already read in but they also may
> > > > >       not be present in this objfile.  */
> > > > > -  for (const auto &iter : qf)
> > > > > +  for (const auto &iter : qf_safe ())
> > > >
> > > > I read the commit message a couple of times, but I'm still not 100% sure
> > > > I understand what's happening here.  Is the change from qf -> qf_safe()
> > > > because calling (in this case has_symbols()) might download the full
> > > > debug info, and in doing do, might delete a previously downloaded
> > > > .debug_info section?
> > >
> > > quick_functions_up made from a separately downloaded .gdb_index is at
> > > first associated with the parent objfile (i.e. the objfile of the shared
> > > library containing a .gnu_debuglink that refers to the separate debug info
> > > which actually contains this .gdb_index section).
> > >
> > > When the separate debuginfo is downloaded (possibly during iteration
> > > over the parent's qf_list), we need to remove the gdb_index qf from
> > > the parent's qf_list.  This prevents gdb from later attempting to read
> > > the parent objfile for debug info that is actually in the corresponding
> > > separate debug objfile.
> > >
> > > > If this is the case then is it true that we will almost never want to
> > > > iterate over `qf` directly as most quick_symbol_functions methods might
> > > > result in this download/delete behaviour?
> > > >
> > > > If I'm right, then I think it might be useful to place a warning in the
> > > > comment before `qf` explaining this.
> > >
> > > Done.
> > >
> > > > > +/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
> > > > > +
> > > > > +template<typename Iterator>
> > > > > +class basic_safe_reverse_iterator
> > > > > +{
> > > > > +public:
> > > > > +  typedef basic_safe_reverse_iterator self_type;
> > > > > +  typedef typename Iterator::value_type value_type;
> > > > > +  typedef typename Iterator::reference reference;
> > > > > +  typedef typename Iterator::pointer pointer;
> > > > > +  typedef typename Iterator::iterator_category iterator_category;
> > > > > +  typedef typename Iterator::difference_type difference_type;
> > > > > +
> > > > > +  /* Construct the iterator using ARG, and construct the end iterator
> > > > > +     using ARG2.  */
> > > >
> > > > Maybe this is a standard thing, and I'm just showing my lack of
> > > > experience, but, given the existence of methods like rbegin() and
> > > > rend(), I think the comments here should be doing more to explain what's
> > > > expected of this input.
> > > >
> > > > Plus this comment seems confusing, though I can sort of see what it's
> > > > trying to say.
> > > >
> > > > We're creating a *_iterator object, which itself contains other
> > > > iterators.  We need to be super clear which particular "iterator" we're
> > > > talking about in the comments.
> > > >
> > > > Also, I think we need to be explicit that for this reverse iterator, the
> > > > first argument to the constructor is going to be the LAST element that
> > > > is iterated over.  One might be tempted to think that a safe reverse iterator
> > > > should be initialised with rbegin() and rend(), which isn't going to be
> > > > correct (unless I'm not understand, please correct me if so).
> > >
> > > That's right, the arguments to basic_safe_reverse_iterator should be
> > > forward iterators.  This will not work with reverse iterators.
> > >
> > > I added more information to this comment to hopefully clear up any
> > > confusion.
> > >
> > > > > +
> > > > > +/* A range adapter that wraps a forward range, and then returns
> > > > > +   safe reverse iterators wrapping the original range's iterators.  */
> > > >
> > > > I think the fact that this takes a FORWARD range is really important,
> > > > and we should do more to advertise this.  E.g. I think there should be a
> > > > comment on the constructor saying that RANGE is a forward range over
> > > > which we will iterate backwards.
> > > >
> > >
> > > Done.  The updated patch is included below.
> > >
> > > Aaron
> > >
> > > Commit message:
> > >
> > > This patch changes progspace objfile_list insertion so that separate
> > > debug objfiles are placed into the list after the parent objfile,
> > > instead of before.  Additionally qf_require_partial_symbols now returns
> > > a safe_range.
> > >
> > > These changes are intended to prepare gdb for on-demand debuginfo
> > > downloading and the downloading of .gdb_index sections.
> > >
> > > With on-demand downloading enabled, gdb might need to delete a
> > > .gdb_index quick_symbol_functions from a parent objfile while looping
> > > the objfile's list of quick_symbol_functions because the separate
> > > debug objfile has just been downloaded.  The use of a safe_range
> > > prevents this removal from causing iterator invalidation.
> > >
> > > gdb might also download a debuginfo file during symtab expansion.
> > > In this case an objfile will be added to the current progspace's
> > > objfiles_list during iteration over the list (for example, in
> > > iterate_over_symtabs).  We want these loops to also iterate over
> > > newly downloaded objfiles.  So objfiles need to be inserted into
> > > objfiles_list after their parent since it is during the search of
> > > the parent objfile for some symbol or filename that the separate
> > > debug objfile might be downloaded.
> > >
> > > To facilitate the safe deletion of objfiles, this patch also adds
> > > basic_safe_reverse_range and basic_safe_reverse_iterator.  This allows
> > > objfiles to be removed from the objfiles_list in a loop without iterator
> > > invalidation.
> > >
> > > If a forward safe iterator were to be used, the deletion of an
> > > objfile could invalidate the safe iterator's reference to the next
> > > objfile in the objfiles_list.  This can happen when the deletion
> > > of an objfile causes the deletion of a separate debug objfile that
> > > happens to the be next element in the objfiles_list.
> > >
> > > The standard reverse iterator is not suitable for safe objfile deletion.
> > > In order to safely delete the first objfile in the objfiles_list, the
> > > standard reverse iterator's underlying begin iterator would have to be
> > > decremented, resulting in undefined behavior.
> > >
> > > A small change was also made to a testcase in py-objfile.exp to
> > > account for the new placement of separate debug objfiles in
> > > objfiles_list.
> > > ---
> > >  gdb/jit.c                               |   7 +-
> > >  gdb/objfiles.c                          |  14 +--
> > >  gdb/objfiles.h                          |  17 +++-
> > >  gdb/progspace.c                         |  19 +++-
> > >  gdb/progspace.h                         |  31 ++++---
> > >  gdb/symfile-debug.c                     |  34 +++----
> > >  gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
> > >  gdbsupport/safe-iterator.h              | 115 ++++++++++++++++++++++++
> > >  8 files changed, 195 insertions(+), 44 deletions(-)
> > >
> > > diff --git a/gdb/jit.c b/gdb/jit.c
> > > index 85a10be3055..10ee5f9b749 100644
> > > --- a/gdb/jit.c
> > > +++ b/gdb/jit.c
> > > @@ -1240,11 +1240,10 @@ jit_breakpoint_re_set (void)
> > >  static void
> > >  jit_inferior_exit_hook (struct inferior *inf)
> > >  {
> > > -  for (objfile *objf : current_program_space->objfiles_safe ())
> > > +  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
> > >      {
> > > -      if (objf->jited_data != nullptr && objf->jited_data->addr != 0)
> > > -       objf->unlink ();
> > > -    }
> > > +      return (objf->jited_data != nullptr) && (objf->jited_data->addr != 0);
> > > +    });
> > >  }
> > >
> > >  void
> > > diff --git a/gdb/objfiles.c b/gdb/objfiles.c
> > > index 8f085b1bb7c..d53dfaca90b 100644
> > > --- a/gdb/objfiles.c
> > > +++ b/gdb/objfiles.c
> > > @@ -470,6 +470,12 @@ objfile::unlink ()
> > >    current_program_space->remove_objfile (this);
> > >  }
> > >
> > > +qf_safe_range
> > > +objfile::qf_safe ()
> > > +{
> > > +  return qf_safe_range (qf_range (qf.begin (), qf.end ()));
> > > +}
> > > +
> > >  /* Free all separate debug objfile of OBJFILE, but don't free OBJFILE
> > >     itself.  */
> > >
> > > @@ -793,14 +799,12 @@ have_full_symbols (void)
> > >  void
> > >  objfile_purge_solibs (void)
> > >  {
> > > -  for (objfile *objf : current_program_space->objfiles_safe ())
> > > +  current_program_space->unlink_objfiles_if ([&] (const objfile *objf)
> > >      {
> > >        /* We assume that the solib package has been purged already, or will
> > >          be soon.  */
> > > -
> > > -      if (!(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED))
> > > -       objf->unlink ();
> > > -    }
> > > +      return !(objf->flags & OBJF_USERLOADED) && (objf->flags & OBJF_SHARED);
> > > +    });
> > >  }
> > >
> > >
> > > diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> > > index 621342d22b7..b917886988d 100644
> > > --- a/gdb/objfiles.h
> > > +++ b/gdb/objfiles.h
> > > @@ -370,6 +370,12 @@ class separate_debug_iterator
> > >
> > >  typedef iterator_range<separate_debug_iterator> separate_debug_range;
> > >
> > > +/* See objfile::qf_safe.  */
> > > +
> > > +using qf_list = std::forward_list<quick_symbol_functions_up>;
> > > +using qf_range = iterator_range<qf_list::iterator>;
> > > +using qf_safe_range = basic_safe_range<qf_range>;
> > > +
> > >  /* Sections in an objfile.  The section offsets are stored in the
> > >     OBJFILE.  */
> > >
> > > @@ -761,8 +767,15 @@ struct objfile
> > >    const struct sym_fns *sf = nullptr;
> > >
> > >    /* The "quick" (aka partial) symbol functions for this symbol
> > > -     reader.  */
> > > -  std::forward_list<quick_symbol_functions_up> qf;
> > > +     reader.  Many quick_symbol_functions methods may result
> > > +     in the deletion of a quick_symbol_functions from this
> > > +     qf_list.  It is recommended that qf_safe be used to iterate
> > > +     over the qf_list.  */
> > > +  qf_list qf;
> > > +
> > > +  /* Returns an iterable object that allows for safe deletion during
> > > +     iteration.  See gdbsupport/safe-iterator.h.  */
> > > +  qf_safe_range qf_safe ();
> > >
> > >    /* Per objfile data-pointers required by other GDB modules.  */
> > >
> > > diff --git a/gdb/progspace.c b/gdb/progspace.c
> > > index 0fc1fdd57ab..c4cc2e2e8af 100644
> > > --- a/gdb/progspace.c
> > > +++ b/gdb/progspace.c
> > > @@ -141,19 +141,19 @@ program_space::free_all_objfiles ()
> > >
> > >  void
> > >  program_space::add_objfile (std::unique_ptr<objfile> &&objfile,
> > > -                           struct objfile *before)
> > > +                           struct objfile *after)
> > >  {
> > > -  if (before == nullptr)
> > > +  if (after == nullptr)
> > >      objfiles_list.push_back (std::move (objfile));
> > >    else
> > >      {
> > >        auto iter = std::find_if (objfiles_list.begin (), objfiles_list.end (),
> > >                                 [=] (const std::unique_ptr<::objfile> &objf)
> > >                                 {
> > > -                                 return objf.get () == before;
> > > +                                 return objf.get () == after;
> > >                                 });
> > >        gdb_assert (iter != objfiles_list.end ());
> > > -      objfiles_list.insert (iter, std::move (objfile));
> > > +      objfiles_list.insert (++iter, std::move (objfile));
> > >      }
> > >  }
> > >
> > > @@ -182,6 +182,17 @@ program_space::remove_objfile (struct objfile *objfile)
> > >
> > >  /* See progspace.h.  */
> > >
> > > +void
> > > +program_space::unlink_objfiles_if
> > > +  (gdb::function_view<bool (const objfile *objfile)> predicate)
> > > +{
> > > +  for (auto &it : objfiles_safe ())
> > > +    if (predicate (it.get ()))
> > > +      it->unlink ();
> > > +}
> > > +
> > > +/* See progspace.h.  */
> > > +
> > >  struct objfile *
> > >  program_space::objfile_for_address (CORE_ADDR address)
> > >  {
> > > diff --git a/gdb/progspace.h b/gdb/progspace.h
> > > index 163cbf6a0f8..f23a57dc842 100644
> > > --- a/gdb/progspace.h
> > > +++ b/gdb/progspace.h
> > > @@ -250,28 +250,32 @@ struct program_space
> > >         unwrapping_objfile_iterator (objfiles_list.end ()));
> > >    }
> > >
> > > -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> > > +  using objfiles_it_range = iterator_range<objfile_list::iterator>;
> > > +  using objfiles_safe_reverse_range
> > > +    = basic_safe_reverse_range<objfiles_it_range>;
> > >
> > >    /* An iterable object that can be used to iterate over all objfiles.
> > >       The basic use is in a foreach, like:
> > >
> > >       for (objfile *objf : pspace->objfiles_safe ()) { ... }
> > >
> > > -     This variant uses a basic_safe_iterator so that objfiles can be
> > > -     deleted during iteration.  */
> > > -  objfiles_safe_range objfiles_safe ()
> > > +     This variant uses a basic_safe_reverse_iterator so that objfiles
> > > +     can be deleted during iteration.
> > > +
> > > +     The use of a reverse iterator helps ensure that separate debug
> > > +     objfiles are deleted before their parent objfile.  This prevents
> > > +     iterator invalidation due to the deletion of a parent objfile.  */
> > > + objfiles_safe_reverse_range objfiles_safe ()
> > >    {
> > > -    return objfiles_safe_range
> > > -      (objfiles_range
> > > -        (unwrapping_objfile_iterator (objfiles_list.begin ()),
> > > -         unwrapping_objfile_iterator (objfiles_list.end ())));
> > > +    return objfiles_safe_reverse_range
> > > +      (objfiles_it_range (objfiles_list.begin (), objfiles_list.end ()));
> > >    }
> > >
> > > -  /* Add OBJFILE to the list of objfiles, putting it just before
> > > -     BEFORE.  If BEFORE is nullptr, it will go at the end of the
> > > +  /* Add OBJFILE to the list of objfiles, putting it just after
> > > +     AFTER.  If AFTER is nullptr, it will go at the end of the
> > >       list.  */
> > >    void add_objfile (std::unique_ptr<objfile> &&objfile,
> > > -                   struct objfile *before);
> > > +                   struct objfile *after);
> > >
> > >    /* Remove OBJFILE from the list of objfiles.  */
> > >    void remove_objfile (struct objfile *objfile);
> > > @@ -286,6 +290,11 @@ struct program_space
> > >    /* Free all the objfiles associated with this program space.  */
> > >    void free_all_objfiles ();
> > >
> > > +  /* Unlink all objfiles associated with this program space for which
> > > +     PREDICATE evaluates to true.  */
> > > +  void unlink_objfiles_if
> > > +    (gdb::function_view<bool (const objfile *objfile)> predicate);
> > > +
> > >    /* Return the objfile containing ADDRESS, or nullptr if the address
> > >       is outside all objfiles in this progspace.  */
> > >    struct objfile *objfile_for_address (CORE_ADDR address);
> > > diff --git a/gdb/symfile-debug.c b/gdb/symfile-debug.c
> > > index 32c3ed9d349..c684f9cdd43 100644
> > > --- a/gdb/symfile-debug.c
> > > +++ b/gdb/symfile-debug.c
> > > @@ -85,7 +85,7 @@ objfile::has_partial_symbols ()
> > >       them, then that is an indication that they are in fact available.  Without
> > >       this function the symbols may have been already read in but they also may
> > >       not be present in this objfile.  */
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        retval = iter->has_symbols (this);
> > >        if (retval)
> > > @@ -108,7 +108,7 @@ objfile::has_unexpanded_symtabs ()
> > >                 objfile_debug_name (this));
> > >
> > >    bool result = false;
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        if (iter->has_unexpanded_symtabs (this))
> > >         {
> > > @@ -133,7 +133,7 @@ objfile::find_last_source_symtab ()
> > >      gdb_printf (gdb_stdlog, "qf->find_last_source_symtab (%s)\n",
> > >                 objfile_debug_name (this));
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        retval = iter->find_last_source_symtab (this);
> > >        if (retval != nullptr)
> > > @@ -166,7 +166,7 @@ objfile::forget_cached_source_info ()
> > >         }
> > >      }
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->forget_cached_source_info (this);
> > >  }
> > >
> > > @@ -213,7 +213,7 @@ objfile::map_symtabs_matching_filename
> > >      return result;
> > >    };
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        if (!iter->expand_symtabs_matching (this,
> > >                                           match_one_filename,
> > > @@ -278,7 +278,7 @@ objfile::lookup_symbol (block_enum kind, const char *name, domain_enum domain)
> > >      return true;
> > >    };
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        if (!iter->expand_symtabs_matching (this,
> > >                                           nullptr,
> > > @@ -309,7 +309,7 @@ objfile::print_stats (bool print_bcache)
> > >      gdb_printf (gdb_stdlog, "qf->print_stats (%s, %d)\n",
> > >                 objfile_debug_name (this), print_bcache);
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->print_stats (this, print_bcache);
> > >  }
> > >
> > > @@ -320,7 +320,7 @@ objfile::dump ()
> > >      gdb_printf (gdb_stdlog, "qf->dump (%s)\n",
> > >                 objfile_debug_name (this));
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->dump (this);
> > >  }
> > >
> > > @@ -335,7 +335,7 @@ objfile::expand_symtabs_for_function (const char *func_name)
> > >    lookup_name_info base_lookup (func_name, symbol_name_match_type::FULL);
> > >    lookup_name_info lookup_name = base_lookup.make_ignore_params ();
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->expand_symtabs_matching (this,
> > >                                    nullptr,
> > >                                    &lookup_name,
> > > @@ -354,7 +354,7 @@ objfile::expand_all_symtabs ()
> > >      gdb_printf (gdb_stdlog, "qf->expand_all_symtabs (%s)\n",
> > >                 objfile_debug_name (this));
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->expand_all_symtabs (this);
> > >  }
> > >
> > > @@ -372,7 +372,7 @@ objfile::expand_symtabs_with_fullname (const char *fullname)
> > >      return filename_cmp (basenames ? basename : fullname, filename) == 0;
> > >    };
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->expand_symtabs_matching (this,
> > >                                    file_matcher,
> > >                                    nullptr,
> > > @@ -406,7 +406,7 @@ objfile::expand_symtabs_matching
> > >                 host_address_to_string (&expansion_notify),
> > >                 search_domain_name (kind));
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      if (!iter->expand_symtabs_matching (this, file_matcher, lookup_name,
> > >                                         symbol_matcher, expansion_notify,
> > >                                         search_flags, domain, kind))
> > > @@ -431,7 +431,7 @@ objfile::find_pc_sect_compunit_symtab (struct bound_minimal_symbol msymbol,
> > >                 host_address_to_string (section),
> > >                 warn_if_readin);
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        retval = iter->find_pc_sect_compunit_symtab (this, msymbol, pc, section,
> > >                                                    warn_if_readin);
> > > @@ -459,7 +459,7 @@ objfile::map_symbol_filenames (gdb::function_view<symbol_filename_ftype> fun,
> > >                 objfile_debug_name (this),
> > >                 need_fullname);
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->map_symbol_filenames (this, fun, need_fullname);
> > >  }
> > >
> > > @@ -471,7 +471,7 @@ objfile::compute_main_name ()
> > >                 "qf->compute_main_name (%s)\n",
> > >                 objfile_debug_name (this));
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      iter->compute_main_name (this);
> > >  }
> > >
> > > @@ -485,7 +485,7 @@ objfile::find_compunit_symtab_by_address (CORE_ADDR address)
> > >                 hex_string (address));
> > >
> > >    struct compunit_symtab *result = NULL;
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        result = iter->find_compunit_symtab_by_address (this, address);
> > >        if (result != nullptr)
> > > @@ -510,7 +510,7 @@ objfile::lookup_global_symbol_language (const char *name,
> > >    enum language result = language_unknown;
> > >    *symbol_found_p = false;
> > >
> > > -  for (const auto &iter : qf)
> > > +  for (const auto &iter : qf_safe ())
> > >      {
> > >        result = iter->lookup_global_symbol_language (this, name, domain,
> > >                                                     symbol_found_p);
> > > diff --git a/gdb/testsuite/gdb.python/py-objfile.exp b/gdb/testsuite/gdb.python/py-objfile.exp
> > > index 61b9942de79..0bf49976b73 100644
> > > --- a/gdb/testsuite/gdb.python/py-objfile.exp
> > > +++ b/gdb/testsuite/gdb.python/py-objfile.exp
> > > @@ -135,7 +135,7 @@ gdb_test "p main" "= {<text variable, no debug info>} $hex <main>" \
> > >  gdb_py_test_silent_cmd "python objfile.add_separate_debug_file(\"${binfile}\")" \
> > >      "Add separate debug file file" 1
> > >
> > > -gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[0\]" \
> > > +gdb_py_test_silent_cmd "python sep_objfile = gdb.objfiles()\[1\]" \
> > >      "Get separate debug info objfile" 1
> > >
> > >  gdb_test "python print (sep_objfile.owner.filename)" "${testfile}2" \
> > > diff --git a/gdbsupport/safe-iterator.h b/gdbsupport/safe-iterator.h
> > > index b4891328f1a..d1b9ce21ffc 100644
> > > --- a/gdbsupport/safe-iterator.h
> > > +++ b/gdbsupport/safe-iterator.h
> > > @@ -136,4 +136,119 @@ class basic_safe_range
> > >    Range m_range;
> > >  };
> > >
> > > +/* A reverse basic_safe_iterator.  See basic_safe_iterator for intended use.  */
> > > +
> > > +template<typename Iterator>
> > > +class basic_safe_reverse_iterator
> > > +{
> > > +public:
> > > +  typedef basic_safe_reverse_iterator self_type;
> > > +  typedef typename Iterator::value_type value_type;
> > > +  typedef typename Iterator::reference reference;
> > > +  typedef typename Iterator::pointer pointer;
> > > +  typedef typename Iterator::iterator_category iterator_category;
> > > +  typedef typename Iterator::difference_type difference_type;
> > > +
> > > +  /* Construct the iterator using ARG, and construct the end iterator
> > > +     using ARG2.  ARG and ARG2 should be forward iterators, typically
> > > +     from begin and end methods, respectively.
> > > +
> > > +     For example if ARG1 is created with container.begin and ARG2 is
> > > +     is created with container.end, then the basic_safe_reverse_iterator
> > > +     will traverse from the last element in the container to the first
> > > +     element in the container.  */
> > > +  template<typename Arg>
> > > +  explicit basic_safe_reverse_iterator (Arg &&arg, Arg &&arg2)
> > > +    : m_begin (std::forward<Arg> (arg)),
> > > +      m_end (std::forward<Arg> (arg2)),
> > > +      m_it (m_end),
> > > +      m_next (m_end)
> > > +  {
> > > +    /* M_IT and M_NEXT are initialized as one-past-end.  Set M_IT to point
> > > +       to the last element and set M_NEXT to point to the second last element,
> > > +       if such elements exist.  */
> > > +    if (m_it != m_begin)
> > > +      {
> > > +       --m_it;
> > > +
> > > +       if (m_it != m_begin)
> > > +         {
> > > +           --m_next;
> > > +           --m_next;
> > > +         }
> > > +      }
> > > +  }
> > > +
> > > +  typename std::invoke_result<decltype(&Iterator::operator*), Iterator>::type
> > > +    operator* () const
> > > +  { return *m_it; }
> > > +
> > > +  self_type &operator++ ()
> > > +  {
> > > +    m_it = m_next;
> > > +
> > > +    if (m_it != m_end)
> > > +      {
> > > +       /* Use M_BEGIN only if we sure that it is valid.  */
> > > +       if (m_it == m_begin)
> > > +         m_next = m_end;
> > > +       else
> > > +         --m_next;
> > > +      }
> > > +
> > > +    return *this;
> > > +  }
> > > +
> > > +  bool operator== (const self_type &other) const
> > > +  { return m_it == other.m_it; }
> > > +
> > > +  bool operator!= (const self_type &other) const
> > > +  { return m_it != other.m_it; }
> > > +
> > > +private:
> > > +  /* The first element.  */
> > > +  Iterator m_begin {};
> > > +
> > > +  /* A one-past-end iterator.  */
> > > +  Iterator m_end {};
> > > +
> > > +  /* The current element.  */
> > > +  Iterator m_it {};
> > > +
> > > +  /* The next element.  Always one element ahead of M_IT.  */
> > > +  Iterator m_next {};
> > > +};
> > > +
> > > +/* A range adapter that wraps a forward range, and then returns
> > > +   safe reverse iterators wrapping the original range's iterators.  */
> > > +
> > > +template<typename Range>
> > > +class basic_safe_reverse_range
> > > +{
> > > +public:
> > > +
> > > +  typedef basic_safe_reverse_iterator<typename Range::iterator> iterator;
> > > +
> > > +  /* RANGE must be a forward range.  basic_safe_reverse_iterators
> > > +     will be used to traverse the forward range from the last element
> > > +     to the first.  */
> > > +  explicit basic_safe_reverse_range (Range range)
> > > +    : m_range (range)
> > > +  {
> > > +  }
> > > +
> > > +  iterator begin ()
> > > +  {
> > > +    return iterator (m_range.begin (), m_range.end ());
> > > +  }
> > > +
> > > +  iterator end ()
> > > +  {
> > > +    return iterator (m_range.end (), m_range.end ());
> > > +  }
> > > +
> > > +private:
> > > +
> > > +  Range m_range;
> > > +};
> > >  #endif /* COMMON_SAFE_ITERATOR_H */
> > > --
> > > 2.43.0
> > >


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

* [PING*3][PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading
  2024-02-22 22:23         ` [PING*2][PATCH " Aaron Merey
  2024-03-01  0:09           ` [PING*3][PATCH " Aaron Merey
@ 2024-03-12 22:15           ` Aaron Merey
  1 sibling, 0 replies; 29+ messages in thread
From: Aaron Merey @ 2024-03-12 22:15 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Thiago Jung Bauermann

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


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

* [PING*3][PATCH 4/4 v6] gdb/debuginfod: Add .debug_line downloading
  2024-02-22 22:23     ` [PING*2][PATCH " Aaron Merey
  2024-03-01  0:10       ` [PING*3][PATCH " Aaron Merey
@ 2024-03-12 22:15       ` Aaron Merey
  1 sibling, 0 replies; 29+ messages in thread
From: Aaron Merey @ 2024-03-12 22:15 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess, Thiago Jung Bauermann

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 Wed, Jan 17, 2024 at 10:49 AM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > v6 adds a testcase for downloads during autocompletion.  It also
> > > implements the suggestions from Thiago's review:
> > > https://sourceware.org/pipermail/gdb-patches/2023-December/205507.html
> > >
> > > ELF/DWARF section downloading allows gdb to download .gdb_index files in
> > > order to defer full debuginfo downloads.  However .gdb_index does not
> > > contain any information regarding source filenames.  When a gdb command
> > > includes a filename argument (ex. 'break main.c:50'), this results in
> > > the mass downloading of all deferred debuginfo so that gdb can search the
> > > debuginfo for matching source filenames.  This can result in unnecessary
> > > downloads.
> > >
> > > To improve this, have gdb instead download each debuginfo's .debug_line
> > > (and .debug_line_str if using DWARF5) when executing these commands.
> > > Download full debuginfo only when its .debug_line contains a matching
> > > filename.
> > >
> > > Since the combined size of .debug_line and .debug_line_str is only about
> > > 1% the size of the corresponding debuginfo, significant time can be saved
> > > by checking these sections before choosing to download an entire debuginfo.
> > >
> > > This patch also redirects stdout and stderr of the debuginfod server
> > > used by testsuite/gdb.debuginfod tests to a server_log standard output
> > > file.  While adding tests for this patch I ran into an issue where the
> > > test server would block when logging to stderr, presumably because the
> > > stderr buffer filled up and wasn't being read from.  Redirecting the
> > > log to a file fixes this and also makes the server log more accessible
> > > when debugging test failures.
> > > ---
> > >  gdb/cli-out.c                            |  11 +-
> > >  gdb/completer.c                          |  18 +-
> > >  gdb/dwarf2/line-header.c                 | 161 ++++++++++++------
> > >  gdb/dwarf2/line-header.h                 |  10 ++
> > >  gdb/dwarf2/read-gdb-index.c              |  60 +++++++
> > >  gdb/dwarf2/read.c                        | 208 +++++++++++++++++++++++
> > >  gdb/dwarf2/read.h                        |  37 ++++
> > >  gdb/mi/mi-out.c                          |   9 +-
> > >  gdb/testsuite/gdb.debuginfod/section.exp |  27 +++
> > >  gdb/ui-out.c                             |   3 +
> > >  gdb/ui-out.h                             |  20 +++
> > >  11 files changed, 511 insertions(+), 53 deletions(-)
> > >
> > > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > > index b4c7a657423..79f61eed62c 100644
> > > --- a/gdb/cli-out.c
> > > +++ b/gdb/cli-out.c
> > > @@ -307,16 +307,23 @@ cli_ui_out::do_progress_notify (const std::string &msg,
> > >
> > >    if (info.state == progress_update::START)
> > >      {
> > > +      std::string prefix;
> > > +      if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED)
> > > +       {
> > > +         prefix = "\n";
> > > +         cur_prefix_state = prefix_state_t::NEWLINE_PRINTED;
> > > +       }
> > > +
> > >        if (stream->isatty ()
> > >           && current_ui->input_interactive_p ()
> > >           && chars_per_line >= MIN_CHARS_PER_LINE)
> > >         {
> > > -         gdb_printf (stream, "%s\n", msg.c_str ());
> > > +         gdb_printf (stream, "%s\n", (prefix + msg).c_str ());
> > >           info.state = progress_update::BAR;
> > >         }
> > >        else
> > >         {
> > > -         gdb_printf (stream, "%s...\n", msg.c_str ());
> > > +         gdb_printf (stream, "%s...\n", (prefix + msg).c_str ());
> > >           info.state = progress_update::WORKING;
> > >         }
> > >      }
> > > diff --git a/gdb/completer.c b/gdb/completer.c
> > > index 8e2f031f13a..6d453d0d197 100644
> > > --- a/gdb/completer.c
> > > +++ b/gdb/completer.c
> > > @@ -1362,6 +1362,10 @@ complete_line_internal_1 (completion_tracker &tracker,
> > >      {
> > >        /* We've recognized a full command.  */
> > >
> > > +      /* Disable pagination since responding to the pagination prompt
> > > +        overwrites rl_line_buffer.  */
> > > +      scoped_restore pag_restore = make_scoped_restore (&pagination_enabled, false);
> > > +
> > >        if (p == tmp_command + point)
> > >         {
> > >           /* There is no non-whitespace in the line beyond the
> > > @@ -1461,7 +1465,8 @@ complete_line_internal_1 (completion_tracker &tracker,
> > >  }
> > >
> > >  /* Wrapper around complete_line_internal_1 to handle
> > > -   MAX_COMPLETIONS_REACHED_ERROR.  */
> > > +   MAX_COMPLETIONS_REACHED_ERROR and possible progress update
> > > +   interactions.  */
> > >
> > >  static void
> > >  complete_line_internal (completion_tracker &tracker,
> > > @@ -1469,6 +1474,11 @@ complete_line_internal (completion_tracker &tracker,
> > >                         const char *line_buffer, int point,
> > >                         complete_line_internal_reason reason)
> > >  {
> > > +  scoped_restore restore_prefix_state
> > > +    = make_scoped_restore
> > > +      (&cur_prefix_state,
> > > +       ui_out::progress_update::prefix_state::NEWLINE_NEEDED);
> > > +
> > >    try
> > >      {
> > >        complete_line_internal_1 (tracker, text, line_buffer, point, reason);
> > > @@ -1478,6 +1488,12 @@ complete_line_internal (completion_tracker &tracker,
> > >        if (except.error != MAX_COMPLETIONS_REACHED_ERROR)
> > >         throw;
> > >      }
> > > +
> > > +  /* If progress update messages printed, then the text being completed
> > > +     needs to be printed again.  */
> > > +  if (cur_prefix_state
> > > +      == ui_out::progress_update::prefix_state::NEWLINE_PRINTED)
> > > +    rl_forced_update_display ();
> > >  }
> > >
> > >  /* See completer.h.  */
> > > diff --git a/gdb/dwarf2/line-header.c b/gdb/dwarf2/line-header.c
> > > index ddaf7e4a527..aec02b3e1f7 100644
> > > --- a/gdb/dwarf2/line-header.c
> > > +++ b/gdb/dwarf2/line-header.c
> > > @@ -102,12 +102,15 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
> > >  {
> > >    LONGEST length = read_initial_length (abfd, buf, bytes_read);
> > >
> > > -  gdb_assert (cu_header->initial_length_size == 4
> > > -             || cu_header->initial_length_size == 8
> > > -             || cu_header->initial_length_size == 12);
> > > +  if (cu_header != nullptr)
> > > +    {
> > > +      gdb_assert (cu_header->initial_length_size == 4
> > > +                 || cu_header->initial_length_size == 8
> > > +                 || cu_header->initial_length_size == 12);
> > >
> > > -  if (cu_header->initial_length_size != *bytes_read)
> > > -    complaint (_("intermixed 32-bit and 64-bit DWARF sections"));
> > > +      if (cu_header->initial_length_size != *bytes_read)
> > > +       complaint (_("intermixed 32-bit and 64-bit DWARF sections"));
> > > +    }
> > >
> > >    *offset_size = (*bytes_read == 4) ? 4 : 8;
> > >    return length;
> > > @@ -116,21 +119,27 @@ read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
> > >  /* Read directory or file name entry format, starting with byte of
> > >     format count entries, ULEB128 pairs of entry formats, ULEB128 of
> > >     entries count and the entries themselves in the described entry
> > > -   format.  */
> > > +   format.
> > > +
> > > +   .debug_line and .debug_line_str sections are stored in LINE_BUFP
> > > +   and LINE_STR_DATA respectively.  */
> > >
> > >  static void
> > > -read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > > -                       const gdb_byte **bufp, struct line_header *lh,
> > > -                       unsigned int offset_size,
> > > -                       void (*callback) (struct line_header *lh,
> > > -                                         const char *name,
> > > -                                         dir_index d_index,
> > > -                                         unsigned int mod_time,
> > > -                                         unsigned int length))
> > > +read_formatted_entries
> > > +  (bfd *abfd, const gdb_byte **line_bufp,
> > > +   const gdb::array_view<const gdb_byte> line_str_data,
> > > +   struct line_header *lh,
> > > +   unsigned int offset_size,
> > > +   void (*callback) (struct line_header *lh,
> > > +                    const char *name,
> > > +                    dir_index d_index,
> > > +                    unsigned int mod_time,
> > > +                    unsigned int length))
> > >  {
> > >    gdb_byte format_count, formati;
> > >    ULONGEST data_count, datai;
> > > -  const gdb_byte *buf = *bufp;
> > > +  const gdb_byte *buf = *line_bufp;
> > > +  const gdb_byte *str_buf = line_str_data.data ();
> > >    const gdb_byte *format_header_data;
> > >    unsigned int bytes_read;
> > >
> > > @@ -157,7 +166,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > >           ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
> > >           format += bytes_read;
> > >
> > > -         ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
> > > +         ULONGEST form = read_unsigned_leb128 (abfd, format, &bytes_read);
> > >           format += bytes_read;
> > >
> > >           std::optional<const char *> string;
> > > @@ -172,12 +181,24 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > >
> > >             case DW_FORM_line_strp:
> > >               {
> > > -               const char *str
> > > -                 = per_objfile->read_line_string (buf, offset_size);
> > > +               if (line_str_data.empty ())
> > > +                 warning (_("Dwarf Error: DW_FORM_line_strp used without " \
> > > +                            "required section"));
> > > +
> > > +               if (line_str_data.size () <= offset_size)
> > > +                 warning (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
> > > +                            "of section .debug_line_str"));
> > > +
> > > +               ULONGEST str_offset = read_offset (abfd, buf, offset_size);
> > > +               const char *str;
> > > +               if (str_buf[str_offset] == '\0')
> > > +                 str = nullptr;
> > > +               else
> > > +                 str = (const char *) (str_buf + str_offset);
> > >                 string.emplace (str);
> > >                 buf += offset_size;
> > > +               break;
> > >               }
> > > -             break;
> > >
> > >             case DW_FORM_data1:
> > >               uint.emplace (read_1_byte (abfd, buf));
> > > @@ -248,28 +269,30 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > >        callback (lh, fe.name, fe.d_index, fe.mod_time, fe.length);
> > >      }
> > >
> > > -  *bufp = buf;
> > > +  *line_bufp = buf;
> > >  }
> > >
> > >  /* See line-header.h.  */
> > >
> > >  line_header_up
> > > -dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > > -                          dwarf2_per_objfile *per_objfile,
> > > -                          struct dwarf2_section_info *section,
> > > -                          const struct comp_unit_head *cu_header,
> > > -                          const char *comp_dir)
> > > +dwarf_decode_line_header (bfd *abfd,
> > > +                         gdb::array_view<const gdb_byte> line_data,
> > > +                         gdb::array_view<const gdb_byte> line_str_data,
> > > +                         const gdb_byte **debug_line_ptr,
> > > +                         bool is_dwz,
> > > +                         const struct comp_unit_head *cu_header,
> > > +                         const char *comp_dir)
> > >  {
> > > -  const gdb_byte *line_ptr;
> > > +  const gdb_byte *line_ptr, *buf;
> > >    unsigned int bytes_read, offset_size;
> > >    int i;
> > >    const char *cur_dir, *cur_file;
> > >
> > > -  bfd *abfd = section->get_bfd_owner ();
> > > +  buf = *debug_line_ptr;
> > >
> > >    /* Make sure that at least there's room for the total_length field.
> > >       That could be 12 bytes long, but we're just going to fudge that.  */
> > > -  if (to_underlying (sect_off) + 4 >= section->size)
> > > +  if (buf + 4 >= line_data.data () + line_data.size ())
> > >      {
> > >        dwarf2_statement_list_fits_in_line_number_section_complaint ();
> > >        return 0;
> > > @@ -277,24 +300,26 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >
> > >    line_header_up lh (new line_header (comp_dir));
> > >
> > > -  lh->sect_off = sect_off;
> > > +  lh->sect_off = (sect_offset) (buf - line_data.data ());
> > >    lh->offset_in_dwz = is_dwz;
> > >
> > > -  line_ptr = section->buffer + to_underlying (sect_off);
> > > +  line_ptr = buf;
> > >
> > >    /* Read in the header.  */
> > >    LONGEST unit_length
> > > -    = read_checked_initial_length_and_offset (abfd, line_ptr, cu_header,
> > > +    = read_checked_initial_length_and_offset (abfd, buf, cu_header,
> > >                                               &bytes_read, &offset_size);
> > > -  line_ptr += bytes_read;
> > >
> > > -  const gdb_byte *start_here = line_ptr;
> > > +  line_ptr += bytes_read;
> > >
> > > -  if (line_ptr + unit_length > (section->buffer + section->size))
> > > +  if (line_ptr + unit_length > buf + line_data.size ())
> > >      {
> > >        dwarf2_statement_list_fits_in_line_number_section_complaint ();
> > >        return 0;
> > >      }
> > > +
> > > +  const gdb_byte *start_here = line_ptr;
> > > +
> > >    lh->statement_program_end = start_here + unit_length;
> > >    lh->version = read_2_bytes (abfd, line_ptr);
> > >    line_ptr += 2;
> > > @@ -303,7 +328,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >        /* This is a version we don't understand.  The format could have
> > >          changed in ways we don't handle properly so just punt.  */
> > >        complaint (_("unsupported version in .debug_line section"));
> > > -      return NULL;
> > > +      return nullptr;
> > >      }
> > >    if (lh->version >= 5)
> > >      {
> > > @@ -320,13 +345,14 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >           complaint (_("unsupported segment selector size %u "
> > >                        "in .debug_line section"),
> > >                      segment_selector_size);
> > > -         return NULL;
> > > +         return nullptr;
> > >         }
> > >      }
> > >
> > >    LONGEST header_length = read_offset (abfd, line_ptr, offset_size);
> > >    line_ptr += offset_size;
> > >    lh->statement_program_start = line_ptr + header_length;
> > > +
> > >    lh->minimum_instruction_length = read_1_byte (abfd, line_ptr);
> > >    line_ptr += 1;
> > >
> > > @@ -347,12 +373,16 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >
> > >    lh->default_is_stmt = read_1_byte (abfd, line_ptr);
> > >    line_ptr += 1;
> > > +
> > >    lh->line_base = read_1_signed_byte (abfd, line_ptr);
> > >    line_ptr += 1;
> > > +
> > >    lh->line_range = read_1_byte (abfd, line_ptr);
> > >    line_ptr += 1;
> > > +
> > >    lh->opcode_base = read_1_byte (abfd, line_ptr);
> > >    line_ptr += 1;
> > > +
> > >    lh->standard_opcode_lengths.reset (new unsigned char[lh->opcode_base]);
> > >
> > >    lh->standard_opcode_lengths[0] = 1;  /* This should never be used anyway.  */
> > > @@ -365,21 +395,23 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >    if (lh->version >= 5)
> > >      {
> > >        /* Read directory table.  */
> > > -      read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
> > > -                             offset_size,
> > > -                             [] (struct line_header *header, const char *name,
> > > -                                 dir_index d_index, unsigned int mod_time,
> > > -                                 unsigned int length)
> > > +      read_formatted_entries
> > > +       (abfd, &line_ptr, line_str_data,
> > > +        lh.get (), offset_size,
> > > +        [] (struct line_header *header, const char *name,
> > > +            dir_index d_index, unsigned int mod_time,
> > > +            unsigned int length)
> > >         {
> > >           header->add_include_dir (name);
> > >         });
> > >
> > >        /* Read file name table.  */
> > > -      read_formatted_entries (per_objfile, abfd, &line_ptr, lh.get (),
> > > -                             offset_size,
> > > -                             [] (struct line_header *header, const char *name,
> > > -                                 dir_index d_index, unsigned int mod_time,
> > > -                                 unsigned int length)
> > > +      read_formatted_entries
> > > +       (abfd, &line_ptr, line_str_data,
> > > +        lh.get (), offset_size,
> > > +        [] (struct line_header *header, const char *name,
> > > +            dir_index d_index, unsigned int mod_time,
> > > +            unsigned int length)
> > >         {
> > >           header->add_file_name (name, d_index, mod_time, length);
> > >         });
> > > @@ -387,7 +419,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >    else
> > >      {
> > >        /* Read directory table.  */
> > > -      while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL)
> > > +      while ((cur_dir = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr)
> > >         {
> > >           line_ptr += bytes_read;
> > >           lh->add_include_dir (cur_dir);
> > > @@ -395,7 +427,7 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >        line_ptr += bytes_read;
> > >
> > >        /* Read file name table.  */
> > > -      while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != NULL)
> > > +      while ((cur_file = read_direct_string (abfd, line_ptr, &bytes_read)) != nullptr)
> > >         {
> > >           unsigned int mod_time, length;
> > >           dir_index d_index;
> > > @@ -413,9 +445,40 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >        line_ptr += bytes_read;
> > >      }
> > >
> > > -  if (line_ptr > (section->buffer + section->size))
> > > +  if (line_ptr > (buf + line_data.size ()))
> > >      complaint (_("line number info header doesn't "
> > >                  "fit in `.debug_line' section"));
> > >
> > > +  *debug_line_ptr += unit_length + offset_size;
> > >    return lh;
> > >  }
> > > +
> > > +line_header_up
> > > +dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > > +                          dwarf2_per_objfile *per_objfile,
> > > +                          struct dwarf2_section_info *section,
> > > +                          const struct comp_unit_head *cu_header,
> > > +                          const char *comp_dir)
> > > +{
> > > +  struct objfile *objfile = per_objfile->objfile;
> > > +  struct dwarf2_per_bfd *per_bfd = per_objfile->per_bfd;
> > > +
> > > +  /* Read .debug_line.  */
> > > +  dwarf2_section_info *line_sec = &per_bfd->line;
> > > +  bfd_size_type line_size = line_sec->get_size (objfile);
> > > +
> > > +  gdb::array_view<const gdb_byte> line (line_sec->buffer, line_size);
> > > +
> > > +  /* Read .debug_line_str.  */
> > > +  dwarf2_section_info *line_str_sec = &per_bfd->line_str;
> > > +  bfd_size_type line_str_size = line_str_sec->get_size (objfile);
> > > +
> > > +  gdb::array_view<const gdb_byte> line_str (line_str_sec->buffer,
> > > +                                           line_str_size);
> > > +
> > > +  const gdb_byte *line_ptr = line.data () + to_underlying (sect_off);
> > > +
> > > +  return dwarf_decode_line_header
> > > +    (per_bfd->obfd, line, line_str, &line_ptr,
> > > +     is_dwz, cu_header, comp_dir);
> > > +}
> > > diff --git a/gdb/dwarf2/line-header.h b/gdb/dwarf2/line-header.h
> > > index 06d2eec573b..1dffb7c7e5e 100644
> > > --- a/gdb/dwarf2/line-header.h
> > > +++ b/gdb/dwarf2/line-header.h
> > > @@ -217,4 +217,14 @@ extern line_header_up dwarf_decode_line_header
> > >     struct dwarf2_section_info *section, const struct comp_unit_head *cu_header,
> > >     const char *comp_dir);
> > >
> > > +/* Like above but the .debug_line and .debug_line_str are stored in
> > > +   LINE_DATA and LINE_STR_DATA.  *DEBUG_LINE_PTR should point to a
> > > +   statement program header within LINE_DATA.  */
> > > +
> > > +extern line_header_up dwarf_decode_line_header
> > > +  (bfd *abfd, gdb::array_view<const gdb_byte> line_data,
> > > +   gdb::array_view<const gdb_byte> line_str_data,
> > > +   const gdb_byte **debug_line_ptr, bool is_dwz,
> > > +  const comp_unit_head *cu_header, const char *comp_dir);
> > > +
> > >  #endif /* DWARF2_LINE_HEADER_H */
> > > diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
> > > index 55b04391eb5..3d75678d7dc 100644
> > > --- a/gdb/dwarf2/read-gdb-index.c
> > > +++ b/gdb/dwarf2/read-gdb-index.c
> > > @@ -131,6 +131,9 @@ struct mapped_gdb_index final : public mapped_index_base
> > >    }
> > >  };
> > >
> > > +struct mapped_debug_line;
> > > +typedef std::unique_ptr<mapped_debug_line> mapped_debug_line_up;
> > > +
> > >  struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >  {
> > >    /* This dumps minimal information about the index.
> > > @@ -162,6 +165,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >       domain_enum domain,
> > >       enum search_domain kind);
> > >
> > > + /* If OBJFILE's debuginfo download has been deferred, use a mapped_debug_line
> > > +    to generate filenames.
> > > +
> > > +    Otherwise call dwarf2_base_index_functions::map_symbol_filenames.  */
> > > +
> > > +  void map_symbol_filenames (struct objfile *objfile,
> > > +                            gdb::function_view<symbol_filename_ftype> fun,
> > > +                            bool need_fullname) override;
> > > +
> > >    /* Calls dwarf2_base_index_functions::expand_all_symtabs and downloads
> > >       debuginfo if necessary.  */
> > >    void expand_all_symtabs (struct objfile *objfile) override;
> > > @@ -169,6 +181,15 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >    /* Calls dwarf2_base_index_functions::find_last_source_symtab and downloads
> > >       debuginfo if necessary.  */
> > >    struct symtab *find_last_source_symtab (struct objfile *objfile) override;
> > > +
> > > +  /* Filename information related to this .gdb_index.  */
> > > +  mapped_debug_line_up mdl;
> > > +
> > > +  /* Return true if any of the filenames in this .gdb_index's .debug_line
> > > +     mapping match FILE_MATCHER.  Initializes the mapping if necessary.  */
> > > +  bool filename_in_debug_line
> > > +    (objfile *objfile,
> > > +     gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher);
> > >  };
> > >
> > >  void
> > > @@ -204,6 +225,30 @@ dwarf2_gdb_index::find_last_source_symtab (struct objfile *objfile)
> > >      }
> > >  }
> > >
> > > +void
> > > +dwarf2_gdb_index::map_symbol_filenames
> > > +     (struct objfile *objfile,
> > > +      gdb::function_view<symbol_filename_ftype> fun,
> > > +      bool need_fullname)
> > > +{
> > > +  try
> > > +    {
> > > +      dwarf2_base_index_functions::map_symbol_filenames (objfile, fun,
> > > +                                                        need_fullname);
> > > +    }
> > > +  catch (const gdb_exception &e)
> > > +    {
> > > +      if ((objfile->flags & OBJF_DOWNLOAD_DEFERRED) == 0)
> > > +       exception_print (gdb_stderr, e);
> > > +      else
> > > +       {
> > > +         if (mdl == nullptr)
> > > +           mdl.reset (new mapped_debug_line (objfile));
> > > +         mdl->map_filenames (fun, need_fullname);
> > > +       }
> > > +    }
> > > +}
> > > +
> > >  /* 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
> > > @@ -371,6 +416,17 @@ dwarf2_gdb_index::do_expand_symtabs_matching
> > >    return result;
> > >  }
> > >
> > > +bool
> > > +dwarf2_gdb_index::filename_in_debug_line
> > > +  (objfile *objfile,
> > > +   gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> > > +{
> > > +  if (mdl == nullptr)
> > > +    mdl.reset (new mapped_debug_line (objfile));
> > > +
> > > +  return mdl->contains_matching_filename (file_matcher);
> > > +}
> > > +
> > >  bool
> > >  dwarf2_gdb_index::expand_symtabs_matching
> > >      (struct objfile *objfile,
> > > @@ -399,6 +455,10 @@ dwarf2_gdb_index::expand_symtabs_matching
> > >           return false;
> > >         }
> > >
> > > +      if (file_matcher != nullptr
> > > +         && !filename_in_debug_line (objfile, file_matcher))
> > > +       return true;
> > > +
> > >        read_full_dwarf_from_debuginfod (objfile, this);
> > >        return true;
> > >      }
> > > diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
> > > index 8725a2fbcdb..4af83728c63 100644
> > > --- a/gdb/dwarf2/read.c
> > > +++ b/gdb/dwarf2/read.c
> > > @@ -82,6 +82,7 @@
> > >  #include <optional>
> > >  #include "gdbsupport/underlying.h"
> > >  #include "gdbsupport/hash_enum.h"
> > > +#include "gdbsupport/scoped_mmap.h"
> > >  #include "filename-seen-cache.h"
> > >  #include "producer.h"
> > >  #include <fcntl.h>
> > > @@ -1950,6 +1951,213 @@ dw2_get_file_names (dwarf2_per_cu_data *this_cu,
> > >    return this_cu->file_names;
> > >  }
> > >
> > > +#if !HAVE_SYS_MMAN_H
> > > +
> > > +bool
> > > +mapped_debug_line::contains_matching_filename
> > > +  (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> > > +{
> > > +  return false;
> > > +}
> > > +
> > > +gdb::array_view<const gdb_byte>
> > > +mapped_debug_line::read_debug_line_separate
> > > +  (const char *filename, std::unique_ptr<index_cache_resource> *resource)
> > > +{
> > > +  return {};
> > > +}
> > > +
> > > +bool
> > > +mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile)
> > > +{
> > > +  return false;
> > > +}
> > > +
> > > +void
> > > +mapped_debug_line::map_filenames
> > > +  (gdb::function_view<symbol_filename_ftype> fun,
> > > +   bool need_fullname)
> > > +{
> > > +  return;
> > > +}
> > > +
> > > +#else /* !HAVE_SYS_MMAN_H */
> > > +
> > > +struct line_resource_mmap final : public index_cache_resource
> > > +{
> > > +  /* Try to mmap FILENAME.  Throw an exception on failure, including if the
> > > +     file doesn't exist.  */
> > > +  line_resource_mmap (const char *filename)
> > > +    : mapping (mmap_file (filename))
> > > +  {}
> > > +
> > > +  scoped_mmap mapping;
> > > +};
> > > +
> > > +/* See read.h.  */
> > > +
> > > +bool
> > > +mapped_debug_line::contains_matching_filename
> > > +  (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher)
> > > +{
> > > +  for (line_header_up &lh : line_headers)
> > > +    for (file_entry &fe : lh->file_names ())
> > > +      {
> > > +       const char *filename = fe.name;
> > > +
> > > +       if (file_matcher (fe.name, false))
> > > +         return true;
> > > +
> > > +       bool basename_match = file_matcher (lbasename (fe.name), true);
> > > +
> > > +       if (!basenames_may_differ && !basename_match)
> > > +         continue;
> > > +
> > > +       /* DW_AT_comp_dir is not explicitly mentioned in the .debug_line
> > > +          until DWARF5.  Since we don't have access to the CU at this
> > > +          point we just check for a partial match on the filename.
> > > +          If there is a match, the full debuginfo will be downloaded
> > > +          ane the match will be re-evalute with DW_AT_comp_dir.  */
> > > +       if (lh->version < 5 && fe.d_index == 0)
> > > +         return basename_match;
> > > +
> > > +       const char *dirname = fe.include_dir (&*lh);
> > > +       std::string fullname;
> > > +
> > > +       if (dirname == nullptr || IS_ABSOLUTE_PATH (filename))
> > > +         fullname = filename;
> > > +       else
> > > +         fullname = std::string (dirname) + SLASH_STRING + filename;
> > > +
> > > +       gdb::unique_xmalloc_ptr<char> rewritten
> > > +         = rewrite_source_path (fullname.c_str ());
> > > +       if (rewritten != nullptr)
> > > +         fullname = rewritten.release ();
> > > +
> > > +       if (file_matcher (fullname.c_str (), false))
> > > +         return true;
> > > +      }
> > > +
> > > +  return false;
> > > +}
> > > +
> > > +/* See read.h.  */
> > > +
> > > +void
> > > +mapped_debug_line::map_filenames
> > > +  (gdb::function_view<symbol_filename_ftype> fun,
> > > +   bool need_fullname)
> > > +{
> > > +  for (line_header_up &lh : line_headers)
> > > +    for (file_entry &fe : lh->file_names ())
> > > +      {
> > > +       const char *filename = fe.name;
> > > +
> > > +       if (!need_fullname)
> > > +         {
> > > +           fun (filename, nullptr);
> > > +           continue;
> > > +         }
> > > +
> > > +       const char *dirname = fe.include_dir (&*lh);
> > > +       std::string fullname;
> > > +
> > > +       if (dirname == nullptr || IS_ABSOLUTE_PATH (filename))
> > > +         fullname = filename;
> > > +       else
> > > +         fullname = std::string (dirname) + SLASH_STRING + filename;
> > > +
> > > +       gdb::unique_xmalloc_ptr<char> rewritten
> > > +         = rewrite_source_path (fullname.c_str ());
> > > +       if (rewritten != nullptr)
> > > +         fullname = rewritten.release ();
> > > +
> > > +       fun (filename, fullname.c_str ());
> > > +      }
> > > +}
> > > +
> > > +/* See read.h.  */
> > > +
> > > +gdb::array_view<const gdb_byte>
> > > +mapped_debug_line::read_debug_line_separate
> > > +  (const char *filename, std::unique_ptr<index_cache_resource> *resource)
> > > +{
> > > +  if (filename == nullptr)
> > > +    return {};
> > > +
> > > +  try
> > > +  {
> > > +    line_resource_mmap *mmap_resource
> > > +      = new line_resource_mmap (filename);
> > > +
> > > +    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 &except)
> > > +  {
> > > +    exception_print (gdb_stderr, except);
> > > +  }
> > > +
> > > +  return {};
> > > +}
> > > +
> > > +/* See read.h.  */
> > > +
> > > +bool
> > > +mapped_debug_line::read_debug_line_from_debuginfod (objfile *objfile)
> > > +{
> > > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > +  if (build_id == nullptr)
> > > +    return false;
> > > +
> > > +  gdb::unique_xmalloc_ptr<char> line_path;
> > > +  scoped_fd line_fd = debuginfod_section_query (build_id->data,
> > > +                                               build_id->size,
> > > +                                               bfd_get_filename
> > > +                                                 (objfile->obfd.get ()),
> > > +                                               ".debug_line",
> > > +                                               &line_path);
> > > +
> > > +  if (line_fd.get () < 0)
> > > +    return false;
> > > +
> > > +  gdb::unique_xmalloc_ptr<char> line_str_path;
> > > +  scoped_fd line_str_fd = debuginfod_section_query (build_id->data,
> > > +                                                   build_id->size,
> > > +                                                   bfd_get_filename
> > > +                                                     (objfile->obfd.get ()),
> > > +                                                   ".debug_line_str",
> > > +                                                   &line_str_path);
> > > +
> > > +  line_data = read_debug_line_separate (line_path.get (), &line_resource);
> > > +  line_str_data = read_debug_line_separate (line_str_path.get (),
> > > +                                           &line_str_resource);
> > > +
> > > +  const gdb_byte *line_ptr = line_data.data ();
> > > +
> > > +  while (line_ptr < line_data.data () + line_data.size ())
> > > +    {
> > > +      line_header_up lh
> > > +       = dwarf_decode_line_header (objfile->obfd.get (),
> > > +                                   line_data, line_str_data,
> > > +                                   &line_ptr, false,
> > > +                                   nullptr, nullptr);
> > > +      line_headers.emplace_back (std::move (lh));
> > > +    }
> > > +
> > > +  return true;
> > > +}
> > > +#endif /* !HAVE_SYS_MMAN_H */
> > > +
> > > +mapped_debug_line::mapped_debug_line (objfile *objfile)
> > > +{
> > > +  if (!read_debug_line_from_debuginfod (objfile))
> > > +    line_headers.clear ();
> > > +}
> > > +
> > >  /* A helper for the "quick" functions which computes and caches the
> > >     real path for a given file name from the line table.  */
> > >
> > > diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> > > index 8b6f1864458..db16160b43e 100644
> > > --- a/gdb/dwarf2/read.h
> > > +++ b/gdb/dwarf2/read.h
> > > @@ -33,6 +33,7 @@
> > >  #include "gdbsupport/hash_enum.h"
> > >  #include "gdbsupport/function-view.h"
> > >  #include "gdbsupport/packed.h"
> > > +#include "dwarf2/line-header.h"
> > >
> > >  /* Hold 'maintenance (set|show) dwarf' commands.  */
> > >  extern struct cmd_list_element *set_dwarf_cmdlist;
> > > @@ -982,4 +983,40 @@ extern htab_up create_quick_file_names_table (unsigned int nr_initial_entries);
> > >  extern void read_full_dwarf_from_debuginfod (struct objfile *,
> > >                                              dwarf2_base_index_functions *);
> > >
> > > +struct mapped_debug_line
> > > +{
> > > +  mapped_debug_line (objfile *objfile);
> > > +
> > > +  /* Return true if any of the mapped .debug_line's filenames match
> > > +     FILE_MATCHER.  */
> > > +
> > > +  bool contains_matching_filename
> > > +    (gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher);
> > > +
> > > +  /* Call FUN with each filename in this mapped .debug_line.  Include
> > > +     each file's fullname if NEED_FULLNAME is true.  */
> > > +
> > > +  void map_filenames (gdb::function_view<symbol_filename_ftype> fun,
> > > +                     bool need_fullname);
> > > +
> > > +private:
> > > +  std::vector<line_header_up> line_headers;
> > > +
> > > +  gdb::array_view<const gdb_byte> line_data;
> > > +  gdb::array_view<const gdb_byte> line_str_data;
> > > +
> > > +  std::unique_ptr<index_cache_resource> line_resource;
> > > +  std::unique_ptr<index_cache_resource> line_str_resource;
> > > +
> > > +  /* Download the .debug_line and .debug_line_str associated with OBJFILE
> > > +     and populate line_headers.  */
> > > +
> > > +  bool read_debug_line_from_debuginfod (objfile *objfile);
> > > +
> > > +  /* Initialize line_data and line_str_data with the .debug_line and
> > > +    .debug_line_str downloaded read_debug_line_from_debuginfod.  */
> > > +
> > > +  gdb::array_view<const gdb_byte> read_debug_line_separate
> > > +    (const char *filename, std::unique_ptr<index_cache_resource> *resource);
> > > +};
> > >  #endif /* DWARF2READ_H */
> > > diff --git a/gdb/mi/mi-out.c b/gdb/mi/mi-out.c
> > > index 0bc13f963bc..ab955a0fc05 100644
> > > --- a/gdb/mi/mi-out.c
> > > +++ b/gdb/mi/mi-out.c
> > > @@ -278,7 +278,14 @@ mi_ui_out::do_progress_notify (const std::string &msg, const char *unit,
> > >
> > >    if (info.state == progress_update::START)
> > >      {
> > > -      gdb_printf ("%s...\n", msg.c_str ());
> > > +      std::string prefix;
> > > +      if (cur_prefix_state == prefix_state_t::NEWLINE_NEEDED)
> > > +       {
> > > +         prefix = "\n";
> > > +         cur_prefix_state = prefix_state_t::NEWLINE_PRINTED;
> > > +       }
> > > +
> > > +      gdb_printf ("%s...\n", (prefix + msg).c_str ());
> > >        info.state = progress_update::WORKING;
> > >      }
> > >  }
> > > diff --git a/gdb/testsuite/gdb.debuginfod/section.exp b/gdb/testsuite/gdb.debuginfod/section.exp
> > > index c3872ecdfc9..3ac11152daa 100644
> > > --- a/gdb/testsuite/gdb.debuginfod/section.exp
> > > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > > @@ -18,6 +18,7 @@
> > >  standard_testfile
> > >
> > >  load_lib debuginfod-support.exp
> > > +load_lib completion-support.exp
> > >
> > >  require allow_debuginfod_tests
> > >
> > > @@ -159,6 +160,32 @@ proc_with_prefix local_url { } {
> > >      # Hit the breakpoint.
> > >      set res ".*Breakpoint 2, libsection2_test.*\"In ${libfile2}\\\\n\".*"
> > >      gdb_test "c" $res "break continue"
> > > +
> > > +    clean_restart_with_prompt $sectexec "line 1"
> > > +
> > > +    # List source file using .debug_line download.
> > > +    set res ".*\.debug_line.*$lib_sl1.*21.*extern void libsection2_test.*"
> > > +    gdb_test "list $libsrc1:21" $res "line 1 list"
> > > +
> > > +    clean_restart_with_prompt $sectexec "line 2"
> > > +
> > > +    # Set breakpoint using .debug_line download.
> > > +    set res ".*section \.debug_line for $lib_sl1.*Breakpoint 2 at.*$libsrc1.*"
> > > +    gdb_test "br $libsrc1:41" $res "line 2 br"
> > > +
> > > +    # Continue to breakpoint.
> > > +    set res "Breakpoint 2, libsection1_test.*\"Cancelling thread\\\\n\".*"
> > > +    gdb_test "c" $res "line 2 continue"
> > > +
> > > +    # Check that download progress message is correctly formatted
> > > +    # when printing threads.
> > > +    set res ".*separate debug info for $lib_sl2\.\.\.\r\n.* 2    Thread.*"
> > > +    gdb_test "info thr" $res "line thread"
> > > +
> > > +    clean_restart_with_prompt $sectexec "autocomplete"
> > > +
> > > +    # Download debuginfo during autocompletion.
> > > +    test_gdb_complete_tab_unique "br lib" ".*Downloading separate debug.*" ""
> > >  }
> > >
> > >  # Create CACHE and DB directories ready for debuginfod to use.
> > > diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> > > index 75f9f1612cb..d9a620b70e8 100644
> > > --- a/gdb/ui-out.c
> > > +++ b/gdb/ui-out.c
> > > @@ -32,6 +32,9 @@
> > >  #include <memory>
> > >  #include <string>
> > >
> > > +/* Current state of newline prefixing for progress update messages.  */
> > > +prefix_state_t cur_prefix_state = prefix_state_t::NEWLINE_OFF;
> > > +
> > >  namespace {
> > >
> > >  /* A header of a ui_out_table.  */
> > > diff --git a/gdb/ui-out.h b/gdb/ui-out.h
> > > index 2934d3a6b34..2c21603f1a7 100644
> > > --- a/gdb/ui-out.h
> > > +++ b/gdb/ui-out.h
> > > @@ -296,6 +296,21 @@ class ui_out
> > >        BAR
> > >      };
> > >
> > > +    /* Used to communicate the status of a newline prefix for the next progress
> > > +       update message.  */
> > > +    enum prefix_state
> > > +    {
> > > +      /* Do not modify the next progress update message.  */
> > > +      NEWLINE_OFF,
> > > +
> > > +      /* The next progress update message should include a newline prefix.  */
> > > +      NEWLINE_NEEDED,
> > > +
> > > +      /* A newline prefix was included in a debuginfod progress update
> > > +        message.  */
> > > +      NEWLINE_PRINTED
> > > +    };
> > > +
> > >      /* SHOULD_PRINT indicates whether something should be printed for a tty.  */
> > >      progress_update ()
> > >      {
> > > @@ -393,6 +408,11 @@ class ui_out
> > >    ui_out_level *current_level () const;
> > >  };
> > >
> > > +typedef ui_out::progress_update::prefix_state prefix_state_t;
> > > +
> > > +/* Current state of the newline prefix.  */
> > > +extern prefix_state_t cur_prefix_state;
> > > +
> > >  /* Start a new tuple or list on construction, and end it on
> > >     destruction.  Normally this is used via the typedefs
> > >     ui_out_emit_tuple and ui_out_emit_list.  */
> > > --
> > > 2.43.0
> > >


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

* Re: [PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading
  2024-01-19  5:12     ` Aaron Merey
  2024-02-07 21:25       ` [PING][PATCH " Aaron Merey
@ 2024-03-27 10:58       ` Andrew Burgess
  2024-03-27 23:56         ` Aaron Merey
  1 sibling, 1 reply; 29+ messages in thread
From: Andrew Burgess @ 2024-03-27 10:58 UTC (permalink / raw)
  To: Aaron Merey; +Cc: gdb-patches, thiago.bauermann, Aaron Merey

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


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

* Re: [PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading
  2024-03-27 10:58       ` [PATCH " Andrew Burgess
@ 2024-03-27 23:56         ` Aaron Merey
  0 siblings, 0 replies; 29+ messages in thread
From: Aaron Merey @ 2024-03-27 23:56 UTC (permalink / raw)
  To: Andrew Burgess; +Cc: gdb-patches, Thiago Jung Bauermann

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


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

end of thread, other threads:[~2024-03-27 23:57 UTC | newest]

Thread overview: 29+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2024-01-17 15:48 [PATCH 0/4] On-demand debuginfo downloading Aaron Merey
2024-01-17 15:48 ` [PATCH 1/4 v8] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
2024-01-18 11:22   ` Andrew Burgess
2024-01-19  5:28     ` Aaron Merey
2024-01-19 14:35       ` Luis Machado
2024-01-19 17:13         ` Aaron Merey
2024-01-19 19:34           ` Tom de Vries
2024-01-19 19:52             ` Aaron Merey
2024-01-17 15:48 ` [PATCH 2/4 v3] gdb/progspace: Add reverse safe iterator Aaron Merey
2024-01-18 12:02   ` Andrew Burgess
2024-01-19  5:09     ` Aaron Merey
2024-02-07 21:24       ` [PING][PATCH " Aaron Merey
2024-02-22 22:22         ` [PING*2][PATCH " Aaron Merey
2024-03-01  0:09           ` [PING*3][PATCH " Aaron Merey
2024-03-12 22:14           ` Aaron Merey
2024-01-17 15:48 ` [PATCH 3/4 v5] gdb/debuginfod: Support on-demand debuginfo downloading Aaron Merey
2024-01-18 15:34   ` Andrew Burgess
2024-01-19  5:12     ` Aaron Merey
2024-02-07 21:25       ` [PING][PATCH " Aaron Merey
2024-02-22 22:23         ` [PING*2][PATCH " Aaron Merey
2024-03-01  0:09           ` [PING*3][PATCH " Aaron Merey
2024-03-12 22:15           ` Aaron Merey
2024-03-27 10:58       ` [PATCH " Andrew Burgess
2024-03-27 23:56         ` Aaron Merey
2024-01-17 15:48 ` [PATCH 4/4 v6] gdb/debuginfod: Add .debug_line downloading Aaron Merey
2024-02-07 21:25   ` [PING][PATCH " Aaron Merey
2024-02-22 22:23     ` [PING*2][PATCH " Aaron Merey
2024-03-01  0:10       ` [PING*3][PATCH " Aaron Merey
2024-03-12 22:15       ` Aaron Merey

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox