public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 0/4] On-demand debuginfo downloading
@ 2023-10-28  0:20 Aaron Merey
  2023-10-28  0:20 ` [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
                   ` (3 more replies)
  0 siblings, 4 replies; 31+ messages in thread
From: Aaron Merey @ 2023-10-28  0:20 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, 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 versions can be found here:
https://sourceware.org/pipermail/gdb-patches/2023-August/201644.html

Patch 2/4 and 3/4 have not changed since posted at the above link and
are included here for convenience.

Aaron Merey (4):
  gdb: Buffer output streams during events that might download debuginfo
  gdb/progspace: Add reverse safe iterator and template for unwrapping
    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                   | 215 ++++++++-----
 gdb/dwarf2/line-header.h                   |  10 +
 gdb/dwarf2/public.h                        |   7 +
 gdb/dwarf2/read-gdb-index.c                | 216 +++++++++++--
 gdb/dwarf2/read.c                          | 354 ++++++++++++++++++++-
 gdb/dwarf2/read.h                          |  47 +++
 gdb/dwarf2/section.c                       |   3 +-
 gdb/elfread.c                              |   2 +-
 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                             |   8 +-
 gdb/objfiles.h                             |  28 +-
 gdb/progspace.c                            |  19 +-
 gdb/progspace.h                            |  31 +-
 gdb/python/py-mi.c                         |   3 +
 gdb/quick-symbol.h                         |   4 +
 gdb/stack.c                                |  35 +-
 gdb/symfile.c                              |  13 +-
 gdb/symtab.c                               |  18 +-
 gdb/testsuite/gdb.debuginfod/libsection1.c |  40 +++
 gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++
 gdb/testsuite/gdb.debuginfod/section.c     |  29 ++
 gdb/testsuite/gdb.debuginfod/section.exp   | 205 ++++++++++++
 gdb/testsuite/gdb.python/py-objfile.exp    |   2 +-
 gdb/testsuite/lib/debuginfod-support.exp   |  27 +-
 gdb/thread.c                               | 171 +++++-----
 gdb/ui-file.h                              |   2 +-
 gdb/ui-out.c                               | 147 +++++++++
 gdb/ui-out.h                               | 206 ++++++++++++
 gdbsupport/safe-iterator.h                 | 106 ++++++
 42 files changed, 1924 insertions(+), 227 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.41.0


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

* [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo
  2023-10-28  0:20 [PATCH 0/4] On-demand debuginfo downloading Aaron Merey
@ 2023-10-28  0:20 ` Aaron Merey
  2023-11-12 20:20   ` Aaron Merey
                     ` (2 more replies)
  2023-10-28  0:20 ` [PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator Aaron Merey
                   ` (2 subsequent siblings)
  3 siblings, 3 replies; 31+ messages in thread
From: Aaron Merey @ 2023-10-28  0:20 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, Aaron Merey

v6: https://sourceware.org/pipermail/gdb-patches/2023-October/203147.html

v7 adds support for buffering output stream flush().  Newline prefix
states have been removed from this patch and instead added to patch 4/4
in this series.

Commit message:

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-mi.c       |   3 +
 gdb/stack.c              |  35 +++++---
 gdb/thread.c             | 171 ++++++++++++++++++++---------------
 gdb/ui-file.h            |   2 +-
 gdb/ui-out.c             | 144 ++++++++++++++++++++++++++++++
 gdb/ui-out.h             | 186 +++++++++++++++++++++++++++++++++++++++
 11 files changed, 493 insertions(+), 95 deletions(-)

diff --git a/gdb/cli-out.c b/gdb/cli-out.c
index 20d3d93f1ad..c919622d418 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 902af405cc6..b36fb8c35de 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;
     }
@@ -296,10 +297,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 4fde96800fb..7c1a7cca74f 100644
--- a/gdb/infrun.c
+++ b/gdb/infrun.c
@@ -8788,10 +8788,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;
@@ -8820,6 +8820,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-mi.c b/gdb/python/py-mi.c
index a7b4f4fa3cf..ba913bf1fee 100644
--- a/gdb/python/py-mi.c
+++ b/gdb/python/py-mi.c
@@ -61,6 +61,9 @@ class py_ui_out : public ui_out
     return current ().obj.release ();
   }
 
+  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 0b35d62f82f..0560261144c 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 (gdb::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 c8145da59bc..f6cf2eb9cf4 100644
--- a/gdb/thread.c
+++ b/gdb/thread.c
@@ -1064,6 +1064,103 @@ 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;
+
+  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.  */
@@ -1147,82 +1244,12 @@ 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;
 
-	  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 defa8f9dfa4..9f643b1ce95 100644
--- a/gdb/ui-out.c
+++ b/gdb/ui-out.c
@@ -871,3 +871,147 @@ 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.  */ }
+
+buffer_group::~buffer_group ()
+{ /* 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..70a7145741f 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,187 @@ 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);
+
+  ~buffer_group ();
+
+  /* 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.  */
+  gdb::optional<buffering_file> m_buffered_current_uiout;
+
+  /* Additional ui_out being buffered.  */
+  ui_out *m_uiout;
+
+  /* Buffer for m_uiout's output stream.  */
+  gdb::optional<buffering_file> m_buffered_uiout;
+};
+
 #endif /* UI_OUT_H */
-- 
2.41.0


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

* [PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator
  2023-10-28  0:20 [PATCH 0/4] On-demand debuginfo downloading Aaron Merey
  2023-10-28  0:20 ` [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
@ 2023-10-28  0:20 ` Aaron Merey
  2023-11-12 20:20   ` Aaron Merey
  2023-12-26 17:09   ` [PATCH " Thiago Jung Bauermann
  2023-10-28  0:20 ` [PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading Aaron Merey
  2023-10-28  0:20 ` [PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading Aaron Merey
  3 siblings, 2 replies; 31+ messages in thread
From: Aaron Merey @ 2023-10-28  0:20 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, Aaron Merey

v1: https://sourceware.org/pipermail/gdb-patches/2023-June/199984.html

v2 removes unwrapping_reverse_objfile_iterator and adds
basic_safe_reverse_range and basic_safe_reverse_iterator.

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 becasue 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                          |   8 +-
 gdb/objfiles.h                          |   8 +-
 gdb/progspace.c                         |  19 ++++-
 gdb/progspace.h                         |  31 ++++---
 gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
 gdbsupport/safe-iterator.h              | 106 ++++++++++++++++++++++++
 7 files changed, 154 insertions(+), 27 deletions(-)

diff --git a/gdb/jit.c b/gdb/jit.c
index 9e8325ab803..a39fdc5a96d 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..9822c179962 100644
--- a/gdb/objfiles.c
+++ b/gdb/objfiles.c
@@ -793,14 +793,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 4b8aa9bfcec..c20b63ceadf 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -698,13 +698,17 @@ struct objfile
 
 private:
 
+  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>;
+
   /* Ensure that partial symbols have been read and return the "quick" (aka
      partial) symbol functions for this symbol reader.  */
-  const std::forward_list<quick_symbol_functions_up> &
+  qf_safe_range
   qf_require_partial_symbols ()
   {
     this->require_partial_symbols (true);
-    return qf;
+    return qf_safe_range (qf_range (qf.begin (), qf.end ()));
   }
 
 public:
diff --git a/gdb/progspace.c b/gdb/progspace.c
index 839707e9d71..c0fca1dace7 100644
--- a/gdb/progspace.c
+++ b/gdb/progspace.c
@@ -143,19 +143,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));
     }
 }
 
@@ -184,6 +184,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 a22e427400e..17bb1710ccf 100644
--- a/gdb/progspace.h
+++ b/gdb/progspace.h
@@ -214,28 +214,32 @@ struct program_space
        unwrapping_objfile_iterator (objfiles_list.end ()));
   }
 
-  using objfiles_safe_range = basic_safe_range<objfiles_range>;
+  using objfiles_safe_range = iterator_range<objfile_list::iterator>;
+  using objfiles_safe_reverse_range
+    = basic_safe_reverse_range<objfiles_safe_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_safe_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);
@@ -250,6 +254,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/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 ccd772ca2a5..9f57c1543cf 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 gdb::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.41.0


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

* [PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading
  2023-10-28  0:20 [PATCH 0/4] On-demand debuginfo downloading Aaron Merey
  2023-10-28  0:20 ` [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
  2023-10-28  0:20 ` [PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator Aaron Merey
@ 2023-10-28  0:20 ` Aaron Merey
  2023-11-12 20:20   ` Aaron Merey
  2023-12-26 18:35   ` [PATCH " Thiago Jung Bauermann
  2023-10-28  0:20 ` [PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading Aaron Merey
  3 siblings, 2 replies; 31+ messages in thread
From: Aaron Merey @ 2023-10-28  0:20 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, Aaron Merey

v3: https://sourceware.org/pipermail/gdb-patches/2023-June/199987.html

v4 improves testcases when running with --target_board=native-gdbserver.

v4 also fixes a bug where objfile observers could clear selected_frame
if debuginfo was downloaded during get_selected_frame.

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                | 156 +++++++++++++++--
 gdb/dwarf2/read.c                          | 146 +++++++++++++++-
 gdb/dwarf2/read.h                          |  10 ++
 gdb/dwarf2/section.c                       |   3 +-
 gdb/elfread.c                              |   2 +-
 gdb/frame.c                                |   7 +
 gdb/objfile-flags.h                        |   4 +
 gdb/objfiles.h                             |  20 +++
 gdb/quick-symbol.h                         |   4 +
 gdb/symfile.c                              |  13 +-
 gdb/symtab.c                               |  18 +-
 gdb/testsuite/gdb.debuginfod/libsection1.c |  40 +++++
 gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
 gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
 gdb/testsuite/gdb.debuginfod/section.exp   | 184 +++++++++++++++++++++
 gdb/testsuite/lib/debuginfod-support.exp   |  27 ++-
 21 files changed, 746 insertions(+), 24 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 abc8d613482..257f0316731 100644
--- a/gdb/dwarf2/frame.c
+++ b/gdb/dwarf2/frame.c
@@ -1617,6 +1617,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 cfa45435fbd..9d18717fe56 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 0e74857eb1a..4a44cdbc223 100644
--- a/gdb/dwarf2/public.h
+++ b/gdb/dwarf2/public.h
@@ -40,4 +40,11 @@ extern void dwarf2_initialize_objfile (struct objfile *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 e789e9c2654..da88a8b405c 100644
--- a/gdb/dwarf2/read-gdb-index.c
+++ b/gdb/dwarf2/read-gdb-index.c
@@ -139,6 +139,7 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
      gdb.dwarf2/gdb-index.exp testcase.  */
   void dump (struct objfile *objfile) override;
 
+  /* Calls do_expand_matching_symbols and downloads debuginfo if necessary.  */
   void expand_matching_symbols
     (struct objfile *,
      const lookup_name_info &lookup_name,
@@ -146,6 +147,14 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
      int global,
      symbol_compare_ftype *ordered_compare) override;
 
+  void do_expand_matching_symbols
+    (struct objfile *,
+     const lookup_name_info &lookup_name,
+     domain_enum domain,
+     int global,
+     symbol_compare_ftype *ordered_compare);
+
+  /* Calls do_expand_symtabs_matching and downloads debuginfo if necessary.  */
   bool expand_symtabs_matching
     (struct objfile *objfile,
      gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
@@ -155,8 +164,59 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
      block_search_flags search_flags,
      domain_enum domain,
      enum search_domain kind) override;
+
+  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
@@ -318,7 +378,7 @@ dw2_symtab_iter_next (struct dw2_symtab_iterator *iter,
 }
 
 void
-dwarf2_gdb_index::expand_matching_symbols
+dwarf2_gdb_index::do_expand_matching_symbols
   (struct objfile *objfile,
    const lookup_name_info &name, domain_enum domain,
    int global,
@@ -356,6 +416,29 @@ dwarf2_gdb_index::expand_matching_symbols
     }, per_objfile);
 }
 
+void
+dwarf2_gdb_index::expand_matching_symbols
+  (struct objfile *objfile,
+   const lookup_name_info &lookup_name,
+   domain_enum domain,
+   int global,
+   symbol_compare_ftype *ordered_compare)
+{
+  try
+    {
+      do_expand_matching_symbols (objfile, lookup_name, domain,
+				  global, ordered_compare);
+    }
+  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;
+    }
+}
+
 /* Helper for dw2_expand_matching symtabs.  Called on each symbol
    matched, to expand corresponding CUs that were marked.  IDX is the
    index of the symbol name that matched.  */
@@ -458,7 +541,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,
@@ -507,6 +590,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
 {
@@ -842,28 +958,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 ea0b2328a3e..0c5689c63ef 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -34,6 +34,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"
@@ -95,6 +96,8 @@
 #include "split-name.h"
 #include "gdbsupport/parallel-for.h"
 #include "gdbsupport/thread-pool.h"
+#include "inferior.h"
+#include "debuginfod-support.h"
 
 /* When == 1, print basic high level tracing messages.
    When > 1, be more verbose.
@@ -3188,7 +3191,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,
@@ -3219,6 +3222,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,
@@ -3375,6 +3404,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 ();
 
 /* See dwarf2/public.h.  */
@@ -3440,10 +3492,102 @@ dwarf2_initialize_objfile (struct objfile *objfile)
       return;
     }
 
+  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;
+      return;
+    }
+
   global_index_cache.miss ();
   objfile->qf.push_front (make_cooked_index_funcs ());
 }
 
+/* See read.h.  */
+
+void
+read_full_dwarf_from_debuginfod (struct objfile *objfile,
+				 dwarf2_base_index_functions *fncs)
+{
+  gdb_assert (objfile->flags & OBJF_DOWNLOAD_DEFERRED);
+
+  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)
+    goto unset;
+
+  filename = bfd_get_filename (objfile->obfd.get ());
+  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
+				   filename, &symfile_path);
+  if (fd.get () < 0)
+    goto unset;
+
+  /* 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);
+      goto unset;
+    }
+
+  /* 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);
+
+unset:
+  objfile->remove_deferred_status ();
+}
+
+/* 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;
+
+  gdb::unique_xmalloc_ptr<char> index_path;
+  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
+
+  if (build_id == nullptr)
+    return false;
+
+  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
 
 /* Build a partial symbol table.  */
diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
index dc7abf23ba4..6ed0be7203b 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -883,6 +883,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
      CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
        override final;
 
+  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
   {
@@ -959,4 +963,10 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
 				       dwarf2_section_info *section,
 				       addrmap *mutable_map);
 
+/* 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 7900dfbc388..5c89598eee7 100644
--- a/gdb/elfread.c
+++ b/gdb/elfread.c
@@ -1235,7 +1235,7 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
 	    symbol_file_add_separate (debug_bfd, debugfile.c_str (),
 				      symfile_flags, objfile);
 	}
-      else
+      else if (!dwarf2_has_separate_index (objfile))
 	{
 	  has_dwarf2 = false;
 	  const struct bfd_build_id *build_id
diff --git a/gdb/frame.c b/gdb/frame.c
index 7077016ccba..00dbffed1ef 100644
--- a/gdb/frame.c
+++ b/gdb/frame.c
@@ -1892,6 +1892,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 9dee2ee51a0..fb3f741c899 100644
--- a/gdb/objfile-flags.h
+++ b/gdb/objfile-flags.h
@@ -60,6 +60,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 c20b63ceadf..ea9bd2157dc 100644
--- a/gdb/objfiles.h
+++ b/gdb/objfiles.h
@@ -612,6 +612,26 @@ struct objfile
   /* See quick_symbol_functions.  */
   void require_partial_symbols (bool verbose);
 
+  /* Indicate that the aquisition of this objfile's separate debug objfile
+     is no longer deferred.  Used when the debug objfile has been aquired
+     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 a7fea2ccb49..e7163503e39 100644
--- a/gdb/quick-symbol.h
+++ b/gdb/quick-symbol.h
@@ -225,6 +225,10 @@ struct quick_symbol_functions
   virtual void read_partial_symbols (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.c b/gdb/symfile.c
index eebc5ea44b9..0491a33e8f5 100644
--- a/gdb/symfile.c
+++ b/gdb/symfile.c
@@ -991,6 +991,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.  */
@@ -1001,7 +1005,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 ();
     }
@@ -1122,6 +1127,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);
 
   bfd_cache_close_all ();
diff --git a/gdb/symtab.c b/gdb/symtab.c
index 5ec56f4f2af..bd01a75189d 100644
--- a/gdb/symtab.c
+++ b/gdb/symtab.c
@@ -2925,14 +2925,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..60824b415c6
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
@@ -0,0 +1,40 @@
+/* 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 *);
+
+void
+libsection1_test ()
+{
+  pthread_t thr;
+
+  printf ("In libsection1\n");
+  libsection2_test ();
+
+  pthread_create (&thr, NULL, libsection2_thread_test, NULL);
+
+  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
+  sleep (3);
+  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..629a67f94a5
--- /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>
+
+void
+libsection2_test ()
+{
+  printf ("In libsection2\n");
+}
+
+void *
+libsection2_thread_test (void *arg)
+{
+  (void) arg;
+
+  printf ("In thread test\n");
+
+  while (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..d391a8f898e
--- /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..ff57c6e32b7
--- /dev/null
+++ b/gdb/testsuite/gdb.debuginfod/section.exp
@@ -0,0 +1,184 @@
+# 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 { [have_index $lib_sl1] != "gdb_index"
+     && [add_gdb_index $lib_sl1] == 0 } {
+    untested "failed to add .gdb_index to $libfile1"
+    return -1
+}
+
+if { [have_index $lib_sl2] != "gdb_index"
+     && [add_gdb_index $lib_sl2] == 0 } {
+    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 res 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
+
+    if {![runto_main]} {
+       return
+    }
+}
+
+# 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
+
+    # Download .gdb_index for solibs.
+    set res ".*section \.gdb_index for $lib_sl1.*\
+	section \.gdb_index for $lib_sl2.*"
+    clean_restart_with_prompt $sectexec $res "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"
+
+    # Check that download progress message is correctly formatted
+    # during backtrace.
+    set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
+    gdb_test "bt" $res "break backtrace"
+}
+
+# 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
+	}
+    }
+}
-- 
2.41.0


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

* [PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading
  2023-10-28  0:20 [PATCH 0/4] On-demand debuginfo downloading Aaron Merey
                   ` (2 preceding siblings ...)
  2023-10-28  0:20 ` [PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading Aaron Merey
@ 2023-10-28  0:20 ` Aaron Merey
  2023-11-12 20:21   ` Aaron Merey
  2023-12-27  0:30   ` [PATCH " Thiago Jung Bauermann
  3 siblings, 2 replies; 31+ messages in thread
From: Aaron Merey @ 2023-10-28  0:20 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess, Aaron Merey

v4: https://sourceware.org/pipermail/gdb-patches/2023-August/201651.html

v5 adds prefix_state to progress_update objects to track when
a newline prefix needs to be added to a download progress message.
This is used to correctly format progress messages that occur during
command autocompletion.

Commit message:

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                 | 215 +++++++++++++++--------
 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 |  21 +++
 gdb/ui-out.c                             |   3 +
 gdb/ui-out.h                             |  20 +++
 11 files changed, 531 insertions(+), 81 deletions(-)

diff --git a/gdb/cli-out.c b/gdb/cli-out.c
index c919622d418..a570a2d939d 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 2abf3998345..9c299f3eb65 100644
--- a/gdb/completer.c
+++ b/gdb/completer.c
@@ -1345,6 +1345,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
@@ -1444,7 +1448,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,
@@ -1452,6 +1457,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);
@@ -1461,6 +1471,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 d072a91bac9..b9210d84f6b 100644
--- a/gdb/dwarf2/line-header.c
+++ b/gdb/dwarf2/line-header.c
@@ -102,50 +102,57 @@ 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;
 }
 
-/* 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.  */
+
+/* Like read_formatted_entries but the .debug_line and .debug_line_str
+   are stored in LINE_BUFP and LINE_STR_DATA.  This is used for cases
+   where these sections are read from separate files without necessarily
+   having access to the entire debuginfo file they originate from.  */
 
 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 *parent_bfd, 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;
 
-  format_count = read_1_byte (abfd, buf);
+  format_count = read_1_byte (parent_bfd, buf);
   buf += 1;
   format_header_data = buf;
   for (formati = 0; formati < format_count; formati++)
     {
-      read_unsigned_leb128 (abfd, buf, &bytes_read);
+      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
       buf += bytes_read;
-      read_unsigned_leb128 (abfd, buf, &bytes_read);
+      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
       buf += bytes_read;
     }
 
-  data_count = read_unsigned_leb128 (abfd, buf, &bytes_read);
+  data_count = read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
   buf += bytes_read;
   for (datai = 0; datai < data_count; datai++)
     {
@@ -154,10 +161,10 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
 
       for (formati = 0; formati < format_count; formati++)
 	{
-	  ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
+	  ULONGEST content_type = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
 	  format += bytes_read;
 
-	  ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
+	  ULONGEST form  = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
 	  format += bytes_read;
 
 	  gdb::optional<const char *> string;
@@ -166,36 +173,48 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
 	  switch (form)
 	    {
 	    case DW_FORM_string:
-	      string.emplace (read_direct_string (abfd, buf, &bytes_read));
+	      string.emplace (read_direct_string (parent_bfd, buf, &bytes_read));
 	      buf += bytes_read;
 	      break;
 
 	    case DW_FORM_line_strp:
 	      {
-		const char *str
-		  = per_objfile->read_line_string (buf, offset_size);
+		if (line_str_data.empty ())
+		  error (_("Dwarf Error: DW_FORM_line_strp used without " \
+			   "required section"));
+		if (line_str_data.size () <= offset_size)
+		  error (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
+			   "of section .debug_line"));
+
+		ULONGEST str_offset = read_offset (parent_bfd, 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));
+	      uint.emplace (read_1_byte (parent_bfd, buf));
 	      buf += 1;
 	      break;
 
 	    case DW_FORM_data2:
-	      uint.emplace (read_2_bytes (abfd, buf));
+	      uint.emplace (read_2_bytes (parent_bfd, buf));
 	      buf += 2;
 	      break;
 
 	    case DW_FORM_data4:
-	      uint.emplace (read_4_bytes (abfd, buf));
+	      uint.emplace (read_4_bytes (parent_bfd, buf));
 	      buf += 4;
 	      break;
 
 	    case DW_FORM_data8:
-	      uint.emplace (read_8_bytes (abfd, buf));
+	      uint.emplace (read_8_bytes (parent_bfd, buf));
 	      buf += 8;
 	      break;
 
@@ -205,7 +224,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
 	      break;
 
 	    case DW_FORM_udata:
-	      uint.emplace (read_unsigned_leb128 (abfd, buf, &bytes_read));
+	      uint.emplace (read_unsigned_leb128 (parent_bfd, buf, &bytes_read));
 	      buf += bytes_read;
 	      break;
 
@@ -248,28 +267,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 *parent_bfd,
+			  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,62 +298,65 @@ 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 (parent_bfd, 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);
+  lh->version = read_2_bytes (parent_bfd, line_ptr);
   line_ptr += 2;
   if (lh->version > 5)
     {
       /* 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)
     {
       gdb_byte segment_selector_size;
 
       /* Skip address size.  */
-      read_1_byte (abfd, line_ptr);
+      read_1_byte (parent_bfd, line_ptr);
       line_ptr += 1;
 
-      segment_selector_size = read_1_byte (abfd, line_ptr);
+      segment_selector_size = read_1_byte (parent_bfd, line_ptr);
       line_ptr += 1;
       if (segment_selector_size != 0)
 	{
 	  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);
+  LONGEST header_length = read_offset (parent_bfd, 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);
+
+  lh->minimum_instruction_length = read_1_byte (parent_bfd, line_ptr);
   line_ptr += 1;
 
   if (lh->version >= 4)
     {
-      lh->maximum_ops_per_instruction = read_1_byte (abfd, line_ptr);
+      lh->maximum_ops_per_instruction = read_1_byte (parent_bfd, line_ptr);
       line_ptr += 1;
     }
   else
@@ -345,41 +369,47 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
 		   "in `.debug_line' section"));
     }
 
-  lh->default_is_stmt = read_1_byte (abfd, line_ptr);
+  lh->default_is_stmt = read_1_byte (parent_bfd, line_ptr);
   line_ptr += 1;
-  lh->line_base = read_1_signed_byte (abfd, line_ptr);
+
+  lh->line_base = read_1_signed_byte (parent_bfd, line_ptr);
   line_ptr += 1;
-  lh->line_range = read_1_byte (abfd, line_ptr);
+
+  lh->line_range = read_1_byte (parent_bfd, line_ptr);
   line_ptr += 1;
-  lh->opcode_base = read_1_byte (abfd, line_ptr);
+
+  lh->opcode_base = read_1_byte (parent_bfd, 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.  */
   for (i = 1; i < lh->opcode_base; ++i)
     {
-      lh->standard_opcode_lengths[i] = read_1_byte (abfd, line_ptr);
+      lh->standard_opcode_lengths[i] = read_1_byte (parent_bfd, line_ptr);
       line_ptr += 1;
     }
 
   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
+	(parent_bfd, &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
+	(parent_bfd, &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 +417,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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
 	{
 	  line_ptr += bytes_read;
 	  lh->add_include_dir (cur_dir);
@@ -395,17 +425,17 @@ 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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
 	{
 	  unsigned int mod_time, length;
 	  dir_index d_index;
 
 	  line_ptr += bytes_read;
-	  d_index = (dir_index) read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
+	  d_index = (dir_index) read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
 	  line_ptr += bytes_read;
-	  mod_time = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
+	  mod_time = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
 	  line_ptr += bytes_read;
-	  length = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
+	  length = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
 	  line_ptr += bytes_read;
 
 	  lh->add_file_name (cur_file, d_index, mod_time, length);
@@ -413,9 +443,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..22db9f9aa78 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 *parent_bfd, 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 da88a8b405c..c0e51357ce2 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.
@@ -175,6 +178,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;
@@ -182,6 +194,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
@@ -217,6 +238,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
@@ -590,6 +635,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,
@@ -618,6 +674,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 0c5689c63ef..876e3aedcf1 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -81,6 +81,7 @@
 #include "gdbsupport/gdb_optional.h"
 #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>
@@ -2135,6 +2136,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
+  (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
+  (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 (lh.release ());
+    }
+
+  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 6ed0be7203b..49fea22c092 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;
@@ -969,4 +970,40 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
 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
+    (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 bbd21287b28..110864adac3 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 ff57c6e32b7..b5c6929fcf7 100644
--- a/gdb/testsuite/gdb.debuginfod/section.exp
+++ b/gdb/testsuite/gdb.debuginfod/section.exp
@@ -171,6 +171,27 @@ proc_with_prefix local_url { } {
     # during backtrace.
     set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
     gdb_test "bt" $res "break backtrace"
+
+    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:37" $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"
 }
 
 # Create CACHE and DB directories ready for debuginfod to use.
diff --git a/gdb/ui-out.c b/gdb/ui-out.c
index 9f643b1ce95..fde46bfbe94 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 70a7145741f..7de8796aee0 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.41.0


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

* Re: [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo
  2023-10-28  0:20 ` [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
@ 2023-11-12 20:20   ` Aaron Merey
  2023-11-20 18:38     ` [PING*2][PATCH " Aaron Merey
  2023-12-26 16:28   ` [PATCH " Thiago Jung Bauermann
  2024-01-17 18:05   ` Andrew Burgess
  2 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-12 20:20 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess

Ping

Thanks,
Aaron

On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
>
> v6: https://sourceware.org/pipermail/gdb-patches/2023-October/203147.html
>
> v7 adds support for buffering output stream flush().  Newline prefix
> states have been removed from this patch and instead added to patch 4/4
> in this series.
>
> Commit message:
>
> 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-mi.c       |   3 +
>  gdb/stack.c              |  35 +++++---
>  gdb/thread.c             | 171 ++++++++++++++++++++---------------
>  gdb/ui-file.h            |   2 +-
>  gdb/ui-out.c             | 144 ++++++++++++++++++++++++++++++
>  gdb/ui-out.h             | 186 +++++++++++++++++++++++++++++++++++++++
>  11 files changed, 493 insertions(+), 95 deletions(-)
>
> diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> index 20d3d93f1ad..c919622d418 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 902af405cc6..b36fb8c35de 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;
>      }
> @@ -296,10 +297,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 4fde96800fb..7c1a7cca74f 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -8788,10 +8788,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;
> @@ -8820,6 +8820,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-mi.c b/gdb/python/py-mi.c
> index a7b4f4fa3cf..ba913bf1fee 100644
> --- a/gdb/python/py-mi.c
> +++ b/gdb/python/py-mi.c
> @@ -61,6 +61,9 @@ class py_ui_out : public ui_out
>      return current ().obj.release ();
>    }
>
> +  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 0b35d62f82f..0560261144c 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 (gdb::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 c8145da59bc..f6cf2eb9cf4 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -1064,6 +1064,103 @@ 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;
> +
> +  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.  */
> @@ -1147,82 +1244,12 @@ 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;
>
> -         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 defa8f9dfa4..9f643b1ce95 100644
> --- a/gdb/ui-out.c
> +++ b/gdb/ui-out.c
> @@ -871,3 +871,147 @@ 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.  */ }
> +
> +buffer_group::~buffer_group ()
> +{ /* 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..70a7145741f 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,187 @@ 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);
> +
> +  ~buffer_group ();
> +
> +  /* 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.  */
> +  gdb::optional<buffering_file> m_buffered_current_uiout;
> +
> +  /* Additional ui_out being buffered.  */
> +  ui_out *m_uiout;
> +
> +  /* Buffer for m_uiout's output stream.  */
> +  gdb::optional<buffering_file> m_buffered_uiout;
> +};
> +
>  #endif /* UI_OUT_H */
> --
> 2.41.0
>


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

* Re: [PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator
  2023-10-28  0:20 ` [PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator Aaron Merey
@ 2023-11-12 20:20   ` Aaron Merey
  2023-11-20 18:39     ` [PING*2][PATCH " Aaron Merey
  2023-12-26 17:09   ` [PATCH " Thiago Jung Bauermann
  1 sibling, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-12 20:20 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess

Ping

Thanks,
Aaron

On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
>
> v1: https://sourceware.org/pipermail/gdb-patches/2023-June/199984.html
>
> v2 removes unwrapping_reverse_objfile_iterator and adds
> basic_safe_reverse_range and basic_safe_reverse_iterator.
>
> 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 becasue 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                          |   8 +-
>  gdb/objfiles.h                          |   8 +-
>  gdb/progspace.c                         |  19 ++++-
>  gdb/progspace.h                         |  31 ++++---
>  gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
>  gdbsupport/safe-iterator.h              | 106 ++++++++++++++++++++++++
>  7 files changed, 154 insertions(+), 27 deletions(-)
>
> diff --git a/gdb/jit.c b/gdb/jit.c
> index 9e8325ab803..a39fdc5a96d 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..9822c179962 100644
> --- a/gdb/objfiles.c
> +++ b/gdb/objfiles.c
> @@ -793,14 +793,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 4b8aa9bfcec..c20b63ceadf 100644
> --- a/gdb/objfiles.h
> +++ b/gdb/objfiles.h
> @@ -698,13 +698,17 @@ struct objfile
>
>  private:
>
> +  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>;
> +
>    /* Ensure that partial symbols have been read and return the "quick" (aka
>       partial) symbol functions for this symbol reader.  */
> -  const std::forward_list<quick_symbol_functions_up> &
> +  qf_safe_range
>    qf_require_partial_symbols ()
>    {
>      this->require_partial_symbols (true);
> -    return qf;
> +    return qf_safe_range (qf_range (qf.begin (), qf.end ()));
>    }
>
>  public:
> diff --git a/gdb/progspace.c b/gdb/progspace.c
> index 839707e9d71..c0fca1dace7 100644
> --- a/gdb/progspace.c
> +++ b/gdb/progspace.c
> @@ -143,19 +143,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));
>      }
>  }
>
> @@ -184,6 +184,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 a22e427400e..17bb1710ccf 100644
> --- a/gdb/progspace.h
> +++ b/gdb/progspace.h
> @@ -214,28 +214,32 @@ struct program_space
>         unwrapping_objfile_iterator (objfiles_list.end ()));
>    }
>
> -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> +  using objfiles_safe_range = iterator_range<objfile_list::iterator>;
> +  using objfiles_safe_reverse_range
> +    = basic_safe_reverse_range<objfiles_safe_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_safe_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);
> @@ -250,6 +254,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/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 ccd772ca2a5..9f57c1543cf 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 gdb::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.41.0
>


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

* Re: [PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading
  2023-10-28  0:20 ` [PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading Aaron Merey
@ 2023-11-12 20:20   ` Aaron Merey
  2023-11-20 18:39     ` [PING*2][PATCH " Aaron Merey
  2023-12-26 18:35   ` [PATCH " Thiago Jung Bauermann
  1 sibling, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-12 20:20 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess

Ping

Thanks,
Aaron

On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
>
> v3: https://sourceware.org/pipermail/gdb-patches/2023-June/199987.html
>
> v4 improves testcases when running with --target_board=native-gdbserver.
>
> v4 also fixes a bug where objfile observers could clear selected_frame
> if debuginfo was downloaded during get_selected_frame.
>
> 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                | 156 +++++++++++++++--
>  gdb/dwarf2/read.c                          | 146 +++++++++++++++-
>  gdb/dwarf2/read.h                          |  10 ++
>  gdb/dwarf2/section.c                       |   3 +-
>  gdb/elfread.c                              |   2 +-
>  gdb/frame.c                                |   7 +
>  gdb/objfile-flags.h                        |   4 +
>  gdb/objfiles.h                             |  20 +++
>  gdb/quick-symbol.h                         |   4 +
>  gdb/symfile.c                              |  13 +-
>  gdb/symtab.c                               |  18 +-
>  gdb/testsuite/gdb.debuginfod/libsection1.c |  40 +++++
>  gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
>  gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
>  gdb/testsuite/gdb.debuginfod/section.exp   | 184 +++++++++++++++++++++
>  gdb/testsuite/lib/debuginfod-support.exp   |  27 ++-
>  21 files changed, 746 insertions(+), 24 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 abc8d613482..257f0316731 100644
> --- a/gdb/dwarf2/frame.c
> +++ b/gdb/dwarf2/frame.c
> @@ -1617,6 +1617,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 cfa45435fbd..9d18717fe56 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 0e74857eb1a..4a44cdbc223 100644
> --- a/gdb/dwarf2/public.h
> +++ b/gdb/dwarf2/public.h
> @@ -40,4 +40,11 @@ extern void dwarf2_initialize_objfile (struct objfile *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 e789e9c2654..da88a8b405c 100644
> --- a/gdb/dwarf2/read-gdb-index.c
> +++ b/gdb/dwarf2/read-gdb-index.c
> @@ -139,6 +139,7 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>       gdb.dwarf2/gdb-index.exp testcase.  */
>    void dump (struct objfile *objfile) override;
>
> +  /* Calls do_expand_matching_symbols and downloads debuginfo if necessary.  */
>    void expand_matching_symbols
>      (struct objfile *,
>       const lookup_name_info &lookup_name,
> @@ -146,6 +147,14 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>       int global,
>       symbol_compare_ftype *ordered_compare) override;
>
> +  void do_expand_matching_symbols
> +    (struct objfile *,
> +     const lookup_name_info &lookup_name,
> +     domain_enum domain,
> +     int global,
> +     symbol_compare_ftype *ordered_compare);
> +
> +  /* Calls do_expand_symtabs_matching and downloads debuginfo if necessary.  */
>    bool expand_symtabs_matching
>      (struct objfile *objfile,
>       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> @@ -155,8 +164,59 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
>       block_search_flags search_flags,
>       domain_enum domain,
>       enum search_domain kind) override;
> +
> +  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
> @@ -318,7 +378,7 @@ dw2_symtab_iter_next (struct dw2_symtab_iterator *iter,
>  }
>
>  void
> -dwarf2_gdb_index::expand_matching_symbols
> +dwarf2_gdb_index::do_expand_matching_symbols
>    (struct objfile *objfile,
>     const lookup_name_info &name, domain_enum domain,
>     int global,
> @@ -356,6 +416,29 @@ dwarf2_gdb_index::expand_matching_symbols
>      }, per_objfile);
>  }
>
> +void
> +dwarf2_gdb_index::expand_matching_symbols
> +  (struct objfile *objfile,
> +   const lookup_name_info &lookup_name,
> +   domain_enum domain,
> +   int global,
> +   symbol_compare_ftype *ordered_compare)
> +{
> +  try
> +    {
> +      do_expand_matching_symbols (objfile, lookup_name, domain,
> +                                 global, ordered_compare);
> +    }
> +  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;
> +    }
> +}
> +
>  /* Helper for dw2_expand_matching symtabs.  Called on each symbol
>     matched, to expand corresponding CUs that were marked.  IDX is the
>     index of the symbol name that matched.  */
> @@ -458,7 +541,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,
> @@ -507,6 +590,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
>  {
> @@ -842,28 +958,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 ea0b2328a3e..0c5689c63ef 100644
> --- a/gdb/dwarf2/read.c
> +++ b/gdb/dwarf2/read.c
> @@ -34,6 +34,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"
> @@ -95,6 +96,8 @@
>  #include "split-name.h"
>  #include "gdbsupport/parallel-for.h"
>  #include "gdbsupport/thread-pool.h"
> +#include "inferior.h"
> +#include "debuginfod-support.h"
>
>  /* When == 1, print basic high level tracing messages.
>     When > 1, be more verbose.
> @@ -3188,7 +3191,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,
> @@ -3219,6 +3222,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,
> @@ -3375,6 +3404,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 ();
>
>  /* See dwarf2/public.h.  */
> @@ -3440,10 +3492,102 @@ dwarf2_initialize_objfile (struct objfile *objfile)
>        return;
>      }
>
> +  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;
> +      return;
> +    }
> +
>    global_index_cache.miss ();
>    objfile->qf.push_front (make_cooked_index_funcs ());
>  }
>
> +/* See read.h.  */
> +
> +void
> +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> +                                dwarf2_base_index_functions *fncs)
> +{
> +  gdb_assert (objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> +
> +  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)
> +    goto unset;
> +
> +  filename = bfd_get_filename (objfile->obfd.get ());
> +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> +                                  filename, &symfile_path);
> +  if (fd.get () < 0)
> +    goto unset;
> +
> +  /* 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);
> +      goto unset;
> +    }
> +
> +  /* 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);
> +
> +unset:
> +  objfile->remove_deferred_status ();
> +}
> +
> +/* 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;
> +
> +  gdb::unique_xmalloc_ptr<char> index_path;
> +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> +
> +  if (build_id == nullptr)
> +    return false;
> +
> +  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;
> +}
> +
>
>
>  /* Build a partial symbol table.  */
> diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> index dc7abf23ba4..6ed0be7203b 100644
> --- a/gdb/dwarf2/read.h
> +++ b/gdb/dwarf2/read.h
> @@ -883,6 +883,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
>       CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
>         override final;
>
> +  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
>    {
> @@ -959,4 +963,10 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
>                                        dwarf2_section_info *section,
>                                        addrmap *mutable_map);
>
> +/* 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 7900dfbc388..5c89598eee7 100644
> --- a/gdb/elfread.c
> +++ b/gdb/elfread.c
> @@ -1235,7 +1235,7 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
>             symbol_file_add_separate (debug_bfd, debugfile.c_str (),
>                                       symfile_flags, objfile);
>         }
> -      else
> +      else if (!dwarf2_has_separate_index (objfile))
>         {
>           has_dwarf2 = false;
>           const struct bfd_build_id *build_id
> diff --git a/gdb/frame.c b/gdb/frame.c
> index 7077016ccba..00dbffed1ef 100644
> --- a/gdb/frame.c
> +++ b/gdb/frame.c
> @@ -1892,6 +1892,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 9dee2ee51a0..fb3f741c899 100644
> --- a/gdb/objfile-flags.h
> +++ b/gdb/objfile-flags.h
> @@ -60,6 +60,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 c20b63ceadf..ea9bd2157dc 100644
> --- a/gdb/objfiles.h
> +++ b/gdb/objfiles.h
> @@ -612,6 +612,26 @@ struct objfile
>    /* See quick_symbol_functions.  */
>    void require_partial_symbols (bool verbose);
>
> +  /* Indicate that the aquisition of this objfile's separate debug objfile
> +     is no longer deferred.  Used when the debug objfile has been aquired
> +     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 a7fea2ccb49..e7163503e39 100644
> --- a/gdb/quick-symbol.h
> +++ b/gdb/quick-symbol.h
> @@ -225,6 +225,10 @@ struct quick_symbol_functions
>    virtual void read_partial_symbols (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.c b/gdb/symfile.c
> index eebc5ea44b9..0491a33e8f5 100644
> --- a/gdb/symfile.c
> +++ b/gdb/symfile.c
> @@ -991,6 +991,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.  */
> @@ -1001,7 +1005,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 ();
>      }
> @@ -1122,6 +1127,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);
>
>    bfd_cache_close_all ();
> diff --git a/gdb/symtab.c b/gdb/symtab.c
> index 5ec56f4f2af..bd01a75189d 100644
> --- a/gdb/symtab.c
> +++ b/gdb/symtab.c
> @@ -2925,14 +2925,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..60824b415c6
> --- /dev/null
> +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> @@ -0,0 +1,40 @@
> +/* 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 *);
> +
> +void
> +libsection1_test ()
> +{
> +  pthread_t thr;
> +
> +  printf ("In libsection1\n");
> +  libsection2_test ();
> +
> +  pthread_create (&thr, NULL, libsection2_thread_test, NULL);
> +
> +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> +  sleep (3);
> +  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..629a67f94a5
> --- /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>
> +
> +void
> +libsection2_test ()
> +{
> +  printf ("In libsection2\n");
> +}
> +
> +void *
> +libsection2_thread_test (void *arg)
> +{
> +  (void) arg;
> +
> +  printf ("In thread test\n");
> +
> +  while (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..d391a8f898e
> --- /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..ff57c6e32b7
> --- /dev/null
> +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> @@ -0,0 +1,184 @@
> +# 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 { [have_index $lib_sl1] != "gdb_index"
> +     && [add_gdb_index $lib_sl1] == 0 } {
> +    untested "failed to add .gdb_index to $libfile1"
> +    return -1
> +}
> +
> +if { [have_index $lib_sl2] != "gdb_index"
> +     && [add_gdb_index $lib_sl2] == 0 } {
> +    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 res 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
> +
> +    if {![runto_main]} {
> +       return
> +    }
> +}
> +
> +# 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
> +
> +    # Download .gdb_index for solibs.
> +    set res ".*section \.gdb_index for $lib_sl1.*\
> +       section \.gdb_index for $lib_sl2.*"
> +    clean_restart_with_prompt $sectexec $res "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"
> +
> +    # Check that download progress message is correctly formatted
> +    # during backtrace.
> +    set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
> +    gdb_test "bt" $res "break backtrace"
> +}
> +
> +# 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
> +       }
> +    }
> +}
> --
> 2.41.0
>


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

* Re: [PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading
  2023-10-28  0:20 ` [PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading Aaron Merey
@ 2023-11-12 20:21   ` Aaron Merey
  2023-11-20 18:40     ` [PING*2][PATCH " Aaron Merey
  2023-12-27  0:30   ` [PATCH " Thiago Jung Bauermann
  1 sibling, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-12 20:21 UTC (permalink / raw)
  To: gdb-patches; +Cc: aburgess

Ping

Thanks,
Aaron

On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
>
> v4: https://sourceware.org/pipermail/gdb-patches/2023-August/201651.html
>
> v5 adds prefix_state to progress_update objects to track when
> a newline prefix needs to be added to a download progress message.
> This is used to correctly format progress messages that occur during
> command autocompletion.
>
> Commit message:
>
> 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                 | 215 +++++++++++++++--------
>  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 |  21 +++
>  gdb/ui-out.c                             |   3 +
>  gdb/ui-out.h                             |  20 +++
>  11 files changed, 531 insertions(+), 81 deletions(-)
>
> diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> index c919622d418..a570a2d939d 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 2abf3998345..9c299f3eb65 100644
> --- a/gdb/completer.c
> +++ b/gdb/completer.c
> @@ -1345,6 +1345,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
> @@ -1444,7 +1448,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,
> @@ -1452,6 +1457,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);
> @@ -1461,6 +1471,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 d072a91bac9..b9210d84f6b 100644
> --- a/gdb/dwarf2/line-header.c
> +++ b/gdb/dwarf2/line-header.c
> @@ -102,50 +102,57 @@ 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;
>  }
>
> -/* 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.  */
> +
> +/* Like read_formatted_entries but the .debug_line and .debug_line_str
> +   are stored in LINE_BUFP and LINE_STR_DATA.  This is used for cases
> +   where these sections are read from separate files without necessarily
> +   having access to the entire debuginfo file they originate from.  */
>
>  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 *parent_bfd, 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;
>
> -  format_count = read_1_byte (abfd, buf);
> +  format_count = read_1_byte (parent_bfd, buf);
>    buf += 1;
>    format_header_data = buf;
>    for (formati = 0; formati < format_count; formati++)
>      {
> -      read_unsigned_leb128 (abfd, buf, &bytes_read);
> +      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
>        buf += bytes_read;
> -      read_unsigned_leb128 (abfd, buf, &bytes_read);
> +      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
>        buf += bytes_read;
>      }
>
> -  data_count = read_unsigned_leb128 (abfd, buf, &bytes_read);
> +  data_count = read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
>    buf += bytes_read;
>    for (datai = 0; datai < data_count; datai++)
>      {
> @@ -154,10 +161,10 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
>
>        for (formati = 0; formati < format_count; formati++)
>         {
> -         ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
> +         ULONGEST content_type = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
>           format += bytes_read;
>
> -         ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
> +         ULONGEST form  = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
>           format += bytes_read;
>
>           gdb::optional<const char *> string;
> @@ -166,36 +173,48 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
>           switch (form)
>             {
>             case DW_FORM_string:
> -             string.emplace (read_direct_string (abfd, buf, &bytes_read));
> +             string.emplace (read_direct_string (parent_bfd, buf, &bytes_read));
>               buf += bytes_read;
>               break;
>
>             case DW_FORM_line_strp:
>               {
> -               const char *str
> -                 = per_objfile->read_line_string (buf, offset_size);
> +               if (line_str_data.empty ())
> +                 error (_("Dwarf Error: DW_FORM_line_strp used without " \
> +                          "required section"));
> +               if (line_str_data.size () <= offset_size)
> +                 error (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
> +                          "of section .debug_line"));
> +
> +               ULONGEST str_offset = read_offset (parent_bfd, 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));
> +             uint.emplace (read_1_byte (parent_bfd, buf));
>               buf += 1;
>               break;
>
>             case DW_FORM_data2:
> -             uint.emplace (read_2_bytes (abfd, buf));
> +             uint.emplace (read_2_bytes (parent_bfd, buf));
>               buf += 2;
>               break;
>
>             case DW_FORM_data4:
> -             uint.emplace (read_4_bytes (abfd, buf));
> +             uint.emplace (read_4_bytes (parent_bfd, buf));
>               buf += 4;
>               break;
>
>             case DW_FORM_data8:
> -             uint.emplace (read_8_bytes (abfd, buf));
> +             uint.emplace (read_8_bytes (parent_bfd, buf));
>               buf += 8;
>               break;
>
> @@ -205,7 +224,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
>               break;
>
>             case DW_FORM_udata:
> -             uint.emplace (read_unsigned_leb128 (abfd, buf, &bytes_read));
> +             uint.emplace (read_unsigned_leb128 (parent_bfd, buf, &bytes_read));
>               buf += bytes_read;
>               break;
>
> @@ -248,28 +267,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 *parent_bfd,
> +                         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,62 +298,65 @@ 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 (parent_bfd, 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);
> +  lh->version = read_2_bytes (parent_bfd, line_ptr);
>    line_ptr += 2;
>    if (lh->version > 5)
>      {
>        /* 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)
>      {
>        gdb_byte segment_selector_size;
>
>        /* Skip address size.  */
> -      read_1_byte (abfd, line_ptr);
> +      read_1_byte (parent_bfd, line_ptr);
>        line_ptr += 1;
>
> -      segment_selector_size = read_1_byte (abfd, line_ptr);
> +      segment_selector_size = read_1_byte (parent_bfd, line_ptr);
>        line_ptr += 1;
>        if (segment_selector_size != 0)
>         {
>           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);
> +  LONGEST header_length = read_offset (parent_bfd, 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);
> +
> +  lh->minimum_instruction_length = read_1_byte (parent_bfd, line_ptr);
>    line_ptr += 1;
>
>    if (lh->version >= 4)
>      {
> -      lh->maximum_ops_per_instruction = read_1_byte (abfd, line_ptr);
> +      lh->maximum_ops_per_instruction = read_1_byte (parent_bfd, line_ptr);
>        line_ptr += 1;
>      }
>    else
> @@ -345,41 +369,47 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
>                    "in `.debug_line' section"));
>      }
>
> -  lh->default_is_stmt = read_1_byte (abfd, line_ptr);
> +  lh->default_is_stmt = read_1_byte (parent_bfd, line_ptr);
>    line_ptr += 1;
> -  lh->line_base = read_1_signed_byte (abfd, line_ptr);
> +
> +  lh->line_base = read_1_signed_byte (parent_bfd, line_ptr);
>    line_ptr += 1;
> -  lh->line_range = read_1_byte (abfd, line_ptr);
> +
> +  lh->line_range = read_1_byte (parent_bfd, line_ptr);
>    line_ptr += 1;
> -  lh->opcode_base = read_1_byte (abfd, line_ptr);
> +
> +  lh->opcode_base = read_1_byte (parent_bfd, 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.  */
>    for (i = 1; i < lh->opcode_base; ++i)
>      {
> -      lh->standard_opcode_lengths[i] = read_1_byte (abfd, line_ptr);
> +      lh->standard_opcode_lengths[i] = read_1_byte (parent_bfd, line_ptr);
>        line_ptr += 1;
>      }
>
>    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
> +       (parent_bfd, &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
> +       (parent_bfd, &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 +417,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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
>         {
>           line_ptr += bytes_read;
>           lh->add_include_dir (cur_dir);
> @@ -395,17 +425,17 @@ 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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
>         {
>           unsigned int mod_time, length;
>           dir_index d_index;
>
>           line_ptr += bytes_read;
> -         d_index = (dir_index) read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> +         d_index = (dir_index) read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
>           line_ptr += bytes_read;
> -         mod_time = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> +         mod_time = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
>           line_ptr += bytes_read;
> -         length = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> +         length = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
>           line_ptr += bytes_read;
>
>           lh->add_file_name (cur_file, d_index, mod_time, length);
> @@ -413,9 +443,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..22db9f9aa78 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 *parent_bfd, 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 da88a8b405c..c0e51357ce2 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.
> @@ -175,6 +178,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;
> @@ -182,6 +194,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
> @@ -217,6 +238,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
> @@ -590,6 +635,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,
> @@ -618,6 +674,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 0c5689c63ef..876e3aedcf1 100644
> --- a/gdb/dwarf2/read.c
> +++ b/gdb/dwarf2/read.c
> @@ -81,6 +81,7 @@
>  #include "gdbsupport/gdb_optional.h"
>  #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>
> @@ -2135,6 +2136,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
> +  (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
> +  (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 (lh.release ());
> +    }
> +
> +  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 6ed0be7203b..49fea22c092 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;
> @@ -969,4 +970,40 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
>  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
> +    (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 bbd21287b28..110864adac3 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 ff57c6e32b7..b5c6929fcf7 100644
> --- a/gdb/testsuite/gdb.debuginfod/section.exp
> +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> @@ -171,6 +171,27 @@ proc_with_prefix local_url { } {
>      # during backtrace.
>      set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
>      gdb_test "bt" $res "break backtrace"
> +
> +    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:37" $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"
>  }
>
>  # Create CACHE and DB directories ready for debuginfod to use.
> diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> index 9f643b1ce95..fde46bfbe94 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 70a7145741f..7de8796aee0 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.41.0
>


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

* [PING*2][PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo
  2023-11-12 20:20   ` Aaron Merey
@ 2023-11-20 18:38     ` Aaron Merey
  2023-11-30 16:29       ` [PING*3][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-20 18:38 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > v6: https://sourceware.org/pipermail/gdb-patches/2023-October/203147.html
> >
> > v7 adds support for buffering output stream flush().  Newline prefix
> > states have been removed from this patch and instead added to patch 4/4
> > in this series.
> >
> > Commit message:
> >
> > 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-mi.c       |   3 +
> >  gdb/stack.c              |  35 +++++---
> >  gdb/thread.c             | 171 ++++++++++++++++++++---------------
> >  gdb/ui-file.h            |   2 +-
> >  gdb/ui-out.c             | 144 ++++++++++++++++++++++++++++++
> >  gdb/ui-out.h             | 186 +++++++++++++++++++++++++++++++++++++++
> >  11 files changed, 493 insertions(+), 95 deletions(-)
> >
> > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > index 20d3d93f1ad..c919622d418 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 902af405cc6..b36fb8c35de 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;
> >      }
> > @@ -296,10 +297,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 4fde96800fb..7c1a7cca74f 100644
> > --- a/gdb/infrun.c
> > +++ b/gdb/infrun.c
> > @@ -8788,10 +8788,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;
> > @@ -8820,6 +8820,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-mi.c b/gdb/python/py-mi.c
> > index a7b4f4fa3cf..ba913bf1fee 100644
> > --- a/gdb/python/py-mi.c
> > +++ b/gdb/python/py-mi.c
> > @@ -61,6 +61,9 @@ class py_ui_out : public ui_out
> >      return current ().obj.release ();
> >    }
> >
> > +  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 0b35d62f82f..0560261144c 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 (gdb::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 c8145da59bc..f6cf2eb9cf4 100644
> > --- a/gdb/thread.c
> > +++ b/gdb/thread.c
> > @@ -1064,6 +1064,103 @@ 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;
> > +
> > +  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.  */
> > @@ -1147,82 +1244,12 @@ 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;
> >
> > -         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 defa8f9dfa4..9f643b1ce95 100644
> > --- a/gdb/ui-out.c
> > +++ b/gdb/ui-out.c
> > @@ -871,3 +871,147 @@ 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.  */ }
> > +
> > +buffer_group::~buffer_group ()
> > +{ /* 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..70a7145741f 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,187 @@ 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);
> > +
> > +  ~buffer_group ();
> > +
> > +  /* 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.  */
> > +  gdb::optional<buffering_file> m_buffered_current_uiout;
> > +
> > +  /* Additional ui_out being buffered.  */
> > +  ui_out *m_uiout;
> > +
> > +  /* Buffer for m_uiout's output stream.  */
> > +  gdb::optional<buffering_file> m_buffered_uiout;
> > +};
> > +
> >  #endif /* UI_OUT_H */
> > --
> > 2.41.0
> >


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

* [PING*2][PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator
  2023-11-12 20:20   ` Aaron Merey
@ 2023-11-20 18:39     ` Aaron Merey
  2023-11-30 16:30       ` [PING*3][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-20 18:39 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > v1: https://sourceware.org/pipermail/gdb-patches/2023-June/199984.html
> >
> > v2 removes unwrapping_reverse_objfile_iterator and adds
> > basic_safe_reverse_range and basic_safe_reverse_iterator.
> >
> > 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 becasue 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                          |   8 +-
> >  gdb/objfiles.h                          |   8 +-
> >  gdb/progspace.c                         |  19 ++++-
> >  gdb/progspace.h                         |  31 ++++---
> >  gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
> >  gdbsupport/safe-iterator.h              | 106 ++++++++++++++++++++++++
> >  7 files changed, 154 insertions(+), 27 deletions(-)
> >
> > diff --git a/gdb/jit.c b/gdb/jit.c
> > index 9e8325ab803..a39fdc5a96d 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..9822c179962 100644
> > --- a/gdb/objfiles.c
> > +++ b/gdb/objfiles.c
> > @@ -793,14 +793,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 4b8aa9bfcec..c20b63ceadf 100644
> > --- a/gdb/objfiles.h
> > +++ b/gdb/objfiles.h
> > @@ -698,13 +698,17 @@ struct objfile
> >
> >  private:
> >
> > +  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>;
> > +
> >    /* Ensure that partial symbols have been read and return the "quick" (aka
> >       partial) symbol functions for this symbol reader.  */
> > -  const std::forward_list<quick_symbol_functions_up> &
> > +  qf_safe_range
> >    qf_require_partial_symbols ()
> >    {
> >      this->require_partial_symbols (true);
> > -    return qf;
> > +    return qf_safe_range (qf_range (qf.begin (), qf.end ()));
> >    }
> >
> >  public:
> > diff --git a/gdb/progspace.c b/gdb/progspace.c
> > index 839707e9d71..c0fca1dace7 100644
> > --- a/gdb/progspace.c
> > +++ b/gdb/progspace.c
> > @@ -143,19 +143,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));
> >      }
> >  }
> >
> > @@ -184,6 +184,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 a22e427400e..17bb1710ccf 100644
> > --- a/gdb/progspace.h
> > +++ b/gdb/progspace.h
> > @@ -214,28 +214,32 @@ struct program_space
> >         unwrapping_objfile_iterator (objfiles_list.end ()));
> >    }
> >
> > -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> > +  using objfiles_safe_range = iterator_range<objfile_list::iterator>;
> > +  using objfiles_safe_reverse_range
> > +    = basic_safe_reverse_range<objfiles_safe_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_safe_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);
> > @@ -250,6 +254,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/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 ccd772ca2a5..9f57c1543cf 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 gdb::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.41.0
> >


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

* [PING*2][PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading
  2023-11-12 20:20   ` Aaron Merey
@ 2023-11-20 18:39     ` Aaron Merey
  2023-11-30 16:30       ` [PING*3][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-20 18:39 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > v3: https://sourceware.org/pipermail/gdb-patches/2023-June/199987.html
> >
> > v4 improves testcases when running with --target_board=native-gdbserver.
> >
> > v4 also fixes a bug where objfile observers could clear selected_frame
> > if debuginfo was downloaded during get_selected_frame.
> >
> > 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                | 156 +++++++++++++++--
> >  gdb/dwarf2/read.c                          | 146 +++++++++++++++-
> >  gdb/dwarf2/read.h                          |  10 ++
> >  gdb/dwarf2/section.c                       |   3 +-
> >  gdb/elfread.c                              |   2 +-
> >  gdb/frame.c                                |   7 +
> >  gdb/objfile-flags.h                        |   4 +
> >  gdb/objfiles.h                             |  20 +++
> >  gdb/quick-symbol.h                         |   4 +
> >  gdb/symfile.c                              |  13 +-
> >  gdb/symtab.c                               |  18 +-
> >  gdb/testsuite/gdb.debuginfod/libsection1.c |  40 +++++
> >  gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
> >  gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
> >  gdb/testsuite/gdb.debuginfod/section.exp   | 184 +++++++++++++++++++++
> >  gdb/testsuite/lib/debuginfod-support.exp   |  27 ++-
> >  21 files changed, 746 insertions(+), 24 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 abc8d613482..257f0316731 100644
> > --- a/gdb/dwarf2/frame.c
> > +++ b/gdb/dwarf2/frame.c
> > @@ -1617,6 +1617,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 cfa45435fbd..9d18717fe56 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 0e74857eb1a..4a44cdbc223 100644
> > --- a/gdb/dwarf2/public.h
> > +++ b/gdb/dwarf2/public.h
> > @@ -40,4 +40,11 @@ extern void dwarf2_initialize_objfile (struct objfile *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 e789e9c2654..da88a8b405c 100644
> > --- a/gdb/dwarf2/read-gdb-index.c
> > +++ b/gdb/dwarf2/read-gdb-index.c
> > @@ -139,6 +139,7 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> >       gdb.dwarf2/gdb-index.exp testcase.  */
> >    void dump (struct objfile *objfile) override;
> >
> > +  /* Calls do_expand_matching_symbols and downloads debuginfo if necessary.  */
> >    void expand_matching_symbols
> >      (struct objfile *,
> >       const lookup_name_info &lookup_name,
> > @@ -146,6 +147,14 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> >       int global,
> >       symbol_compare_ftype *ordered_compare) override;
> >
> > +  void do_expand_matching_symbols
> > +    (struct objfile *,
> > +     const lookup_name_info &lookup_name,
> > +     domain_enum domain,
> > +     int global,
> > +     symbol_compare_ftype *ordered_compare);
> > +
> > +  /* Calls do_expand_symtabs_matching and downloads debuginfo if necessary.  */
> >    bool expand_symtabs_matching
> >      (struct objfile *objfile,
> >       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > @@ -155,8 +164,59 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> >       block_search_flags search_flags,
> >       domain_enum domain,
> >       enum search_domain kind) override;
> > +
> > +  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
> > @@ -318,7 +378,7 @@ dw2_symtab_iter_next (struct dw2_symtab_iterator *iter,
> >  }
> >
> >  void
> > -dwarf2_gdb_index::expand_matching_symbols
> > +dwarf2_gdb_index::do_expand_matching_symbols
> >    (struct objfile *objfile,
> >     const lookup_name_info &name, domain_enum domain,
> >     int global,
> > @@ -356,6 +416,29 @@ dwarf2_gdb_index::expand_matching_symbols
> >      }, per_objfile);
> >  }
> >
> > +void
> > +dwarf2_gdb_index::expand_matching_symbols
> > +  (struct objfile *objfile,
> > +   const lookup_name_info &lookup_name,
> > +   domain_enum domain,
> > +   int global,
> > +   symbol_compare_ftype *ordered_compare)
> > +{
> > +  try
> > +    {
> > +      do_expand_matching_symbols (objfile, lookup_name, domain,
> > +                                 global, ordered_compare);
> > +    }
> > +  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;
> > +    }
> > +}
> > +
> >  /* Helper for dw2_expand_matching symtabs.  Called on each symbol
> >     matched, to expand corresponding CUs that were marked.  IDX is the
> >     index of the symbol name that matched.  */
> > @@ -458,7 +541,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,
> > @@ -507,6 +590,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
> >  {
> > @@ -842,28 +958,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 ea0b2328a3e..0c5689c63ef 100644
> > --- a/gdb/dwarf2/read.c
> > +++ b/gdb/dwarf2/read.c
> > @@ -34,6 +34,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"
> > @@ -95,6 +96,8 @@
> >  #include "split-name.h"
> >  #include "gdbsupport/parallel-for.h"
> >  #include "gdbsupport/thread-pool.h"
> > +#include "inferior.h"
> > +#include "debuginfod-support.h"
> >
> >  /* When == 1, print basic high level tracing messages.
> >     When > 1, be more verbose.
> > @@ -3188,7 +3191,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,
> > @@ -3219,6 +3222,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,
> > @@ -3375,6 +3404,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 ();
> >
> >  /* See dwarf2/public.h.  */
> > @@ -3440,10 +3492,102 @@ dwarf2_initialize_objfile (struct objfile *objfile)
> >        return;
> >      }
> >
> > +  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;
> > +      return;
> > +    }
> > +
> >    global_index_cache.miss ();
> >    objfile->qf.push_front (make_cooked_index_funcs ());
> >  }
> >
> > +/* See read.h.  */
> > +
> > +void
> > +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> > +                                dwarf2_base_index_functions *fncs)
> > +{
> > +  gdb_assert (objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> > +
> > +  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)
> > +    goto unset;
> > +
> > +  filename = bfd_get_filename (objfile->obfd.get ());
> > +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> > +                                  filename, &symfile_path);
> > +  if (fd.get () < 0)
> > +    goto unset;
> > +
> > +  /* 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);
> > +      goto unset;
> > +    }
> > +
> > +  /* 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);
> > +
> > +unset:
> > +  objfile->remove_deferred_status ();
> > +}
> > +
> > +/* 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;
> > +
> > +  gdb::unique_xmalloc_ptr<char> index_path;
> > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > +
> > +  if (build_id == nullptr)
> > +    return false;
> > +
> > +  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;
> > +}
> > +
> >
> >
> >  /* Build a partial symbol table.  */
> > diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> > index dc7abf23ba4..6ed0be7203b 100644
> > --- a/gdb/dwarf2/read.h
> > +++ b/gdb/dwarf2/read.h
> > @@ -883,6 +883,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
> >       CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
> >         override final;
> >
> > +  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
> >    {
> > @@ -959,4 +963,10 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
> >                                        dwarf2_section_info *section,
> >                                        addrmap *mutable_map);
> >
> > +/* 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 7900dfbc388..5c89598eee7 100644
> > --- a/gdb/elfread.c
> > +++ b/gdb/elfread.c
> > @@ -1235,7 +1235,7 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> >             symbol_file_add_separate (debug_bfd, debugfile.c_str (),
> >                                       symfile_flags, objfile);
> >         }
> > -      else
> > +      else if (!dwarf2_has_separate_index (objfile))
> >         {
> >           has_dwarf2 = false;
> >           const struct bfd_build_id *build_id
> > diff --git a/gdb/frame.c b/gdb/frame.c
> > index 7077016ccba..00dbffed1ef 100644
> > --- a/gdb/frame.c
> > +++ b/gdb/frame.c
> > @@ -1892,6 +1892,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 9dee2ee51a0..fb3f741c899 100644
> > --- a/gdb/objfile-flags.h
> > +++ b/gdb/objfile-flags.h
> > @@ -60,6 +60,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 c20b63ceadf..ea9bd2157dc 100644
> > --- a/gdb/objfiles.h
> > +++ b/gdb/objfiles.h
> > @@ -612,6 +612,26 @@ struct objfile
> >    /* See quick_symbol_functions.  */
> >    void require_partial_symbols (bool verbose);
> >
> > +  /* Indicate that the aquisition of this objfile's separate debug objfile
> > +     is no longer deferred.  Used when the debug objfile has been aquired
> > +     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 a7fea2ccb49..e7163503e39 100644
> > --- a/gdb/quick-symbol.h
> > +++ b/gdb/quick-symbol.h
> > @@ -225,6 +225,10 @@ struct quick_symbol_functions
> >    virtual void read_partial_symbols (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.c b/gdb/symfile.c
> > index eebc5ea44b9..0491a33e8f5 100644
> > --- a/gdb/symfile.c
> > +++ b/gdb/symfile.c
> > @@ -991,6 +991,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.  */
> > @@ -1001,7 +1005,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 ();
> >      }
> > @@ -1122,6 +1127,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);
> >
> >    bfd_cache_close_all ();
> > diff --git a/gdb/symtab.c b/gdb/symtab.c
> > index 5ec56f4f2af..bd01a75189d 100644
> > --- a/gdb/symtab.c
> > +++ b/gdb/symtab.c
> > @@ -2925,14 +2925,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..60824b415c6
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > @@ -0,0 +1,40 @@
> > +/* 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 *);
> > +
> > +void
> > +libsection1_test ()
> > +{
> > +  pthread_t thr;
> > +
> > +  printf ("In libsection1\n");
> > +  libsection2_test ();
> > +
> > +  pthread_create (&thr, NULL, libsection2_thread_test, NULL);
> > +
> > +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> > +  sleep (3);
> > +  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..629a67f94a5
> > --- /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>
> > +
> > +void
> > +libsection2_test ()
> > +{
> > +  printf ("In libsection2\n");
> > +}
> > +
> > +void *
> > +libsection2_thread_test (void *arg)
> > +{
> > +  (void) arg;
> > +
> > +  printf ("In thread test\n");
> > +
> > +  while (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..d391a8f898e
> > --- /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..ff57c6e32b7
> > --- /dev/null
> > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > @@ -0,0 +1,184 @@
> > +# 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 { [have_index $lib_sl1] != "gdb_index"
> > +     && [add_gdb_index $lib_sl1] == 0 } {
> > +    untested "failed to add .gdb_index to $libfile1"
> > +    return -1
> > +}
> > +
> > +if { [have_index $lib_sl2] != "gdb_index"
> > +     && [add_gdb_index $lib_sl2] == 0 } {
> > +    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 res 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
> > +
> > +    if {![runto_main]} {
> > +       return
> > +    }
> > +}
> > +
> > +# 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
> > +
> > +    # Download .gdb_index for solibs.
> > +    set res ".*section \.gdb_index for $lib_sl1.*\
> > +       section \.gdb_index for $lib_sl2.*"
> > +    clean_restart_with_prompt $sectexec $res "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"
> > +
> > +    # Check that download progress message is correctly formatted
> > +    # during backtrace.
> > +    set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
> > +    gdb_test "bt" $res "break backtrace"
> > +}
> > +
> > +# 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
> > +       }
> > +    }
> > +}
> > --
> > 2.41.0
> >


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

* [PING*2][PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading
  2023-11-12 20:21   ` Aaron Merey
@ 2023-11-20 18:40     ` Aaron Merey
  2023-11-30 16:30       ` [PING*3][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-20 18:40 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Sun, Nov 12, 2023 at 3:21 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > v4: https://sourceware.org/pipermail/gdb-patches/2023-August/201651.html
> >
> > v5 adds prefix_state to progress_update objects to track when
> > a newline prefix needs to be added to a download progress message.
> > This is used to correctly format progress messages that occur during
> > command autocompletion.
> >
> > Commit message:
> >
> > 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                 | 215 +++++++++++++++--------
> >  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 |  21 +++
> >  gdb/ui-out.c                             |   3 +
> >  gdb/ui-out.h                             |  20 +++
> >  11 files changed, 531 insertions(+), 81 deletions(-)
> >
> > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > index c919622d418..a570a2d939d 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 2abf3998345..9c299f3eb65 100644
> > --- a/gdb/completer.c
> > +++ b/gdb/completer.c
> > @@ -1345,6 +1345,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
> > @@ -1444,7 +1448,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,
> > @@ -1452,6 +1457,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);
> > @@ -1461,6 +1471,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 d072a91bac9..b9210d84f6b 100644
> > --- a/gdb/dwarf2/line-header.c
> > +++ b/gdb/dwarf2/line-header.c
> > @@ -102,50 +102,57 @@ 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;
> >  }
> >
> > -/* 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.  */
> > +
> > +/* Like read_formatted_entries but the .debug_line and .debug_line_str
> > +   are stored in LINE_BUFP and LINE_STR_DATA.  This is used for cases
> > +   where these sections are read from separate files without necessarily
> > +   having access to the entire debuginfo file they originate from.  */
> >
> >  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 *parent_bfd, 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;
> >
> > -  format_count = read_1_byte (abfd, buf);
> > +  format_count = read_1_byte (parent_bfd, buf);
> >    buf += 1;
> >    format_header_data = buf;
> >    for (formati = 0; formati < format_count; formati++)
> >      {
> > -      read_unsigned_leb128 (abfd, buf, &bytes_read);
> > +      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> >        buf += bytes_read;
> > -      read_unsigned_leb128 (abfd, buf, &bytes_read);
> > +      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> >        buf += bytes_read;
> >      }
> >
> > -  data_count = read_unsigned_leb128 (abfd, buf, &bytes_read);
> > +  data_count = read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> >    buf += bytes_read;
> >    for (datai = 0; datai < data_count; datai++)
> >      {
> > @@ -154,10 +161,10 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> >
> >        for (formati = 0; formati < format_count; formati++)
> >         {
> > -         ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
> > +         ULONGEST content_type = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
> >           format += bytes_read;
> >
> > -         ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
> > +         ULONGEST form  = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
> >           format += bytes_read;
> >
> >           gdb::optional<const char *> string;
> > @@ -166,36 +173,48 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> >           switch (form)
> >             {
> >             case DW_FORM_string:
> > -             string.emplace (read_direct_string (abfd, buf, &bytes_read));
> > +             string.emplace (read_direct_string (parent_bfd, buf, &bytes_read));
> >               buf += bytes_read;
> >               break;
> >
> >             case DW_FORM_line_strp:
> >               {
> > -               const char *str
> > -                 = per_objfile->read_line_string (buf, offset_size);
> > +               if (line_str_data.empty ())
> > +                 error (_("Dwarf Error: DW_FORM_line_strp used without " \
> > +                          "required section"));
> > +               if (line_str_data.size () <= offset_size)
> > +                 error (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
> > +                          "of section .debug_line"));
> > +
> > +               ULONGEST str_offset = read_offset (parent_bfd, 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));
> > +             uint.emplace (read_1_byte (parent_bfd, buf));
> >               buf += 1;
> >               break;
> >
> >             case DW_FORM_data2:
> > -             uint.emplace (read_2_bytes (abfd, buf));
> > +             uint.emplace (read_2_bytes (parent_bfd, buf));
> >               buf += 2;
> >               break;
> >
> >             case DW_FORM_data4:
> > -             uint.emplace (read_4_bytes (abfd, buf));
> > +             uint.emplace (read_4_bytes (parent_bfd, buf));
> >               buf += 4;
> >               break;
> >
> >             case DW_FORM_data8:
> > -             uint.emplace (read_8_bytes (abfd, buf));
> > +             uint.emplace (read_8_bytes (parent_bfd, buf));
> >               buf += 8;
> >               break;
> >
> > @@ -205,7 +224,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> >               break;
> >
> >             case DW_FORM_udata:
> > -             uint.emplace (read_unsigned_leb128 (abfd, buf, &bytes_read));
> > +             uint.emplace (read_unsigned_leb128 (parent_bfd, buf, &bytes_read));
> >               buf += bytes_read;
> >               break;
> >
> > @@ -248,28 +267,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 *parent_bfd,
> > +                         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,62 +298,65 @@ 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 (parent_bfd, 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);
> > +  lh->version = read_2_bytes (parent_bfd, line_ptr);
> >    line_ptr += 2;
> >    if (lh->version > 5)
> >      {
> >        /* 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)
> >      {
> >        gdb_byte segment_selector_size;
> >
> >        /* Skip address size.  */
> > -      read_1_byte (abfd, line_ptr);
> > +      read_1_byte (parent_bfd, line_ptr);
> >        line_ptr += 1;
> >
> > -      segment_selector_size = read_1_byte (abfd, line_ptr);
> > +      segment_selector_size = read_1_byte (parent_bfd, line_ptr);
> >        line_ptr += 1;
> >        if (segment_selector_size != 0)
> >         {
> >           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);
> > +  LONGEST header_length = read_offset (parent_bfd, 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);
> > +
> > +  lh->minimum_instruction_length = read_1_byte (parent_bfd, line_ptr);
> >    line_ptr += 1;
> >
> >    if (lh->version >= 4)
> >      {
> > -      lh->maximum_ops_per_instruction = read_1_byte (abfd, line_ptr);
> > +      lh->maximum_ops_per_instruction = read_1_byte (parent_bfd, line_ptr);
> >        line_ptr += 1;
> >      }
> >    else
> > @@ -345,41 +369,47 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> >                    "in `.debug_line' section"));
> >      }
> >
> > -  lh->default_is_stmt = read_1_byte (abfd, line_ptr);
> > +  lh->default_is_stmt = read_1_byte (parent_bfd, line_ptr);
> >    line_ptr += 1;
> > -  lh->line_base = read_1_signed_byte (abfd, line_ptr);
> > +
> > +  lh->line_base = read_1_signed_byte (parent_bfd, line_ptr);
> >    line_ptr += 1;
> > -  lh->line_range = read_1_byte (abfd, line_ptr);
> > +
> > +  lh->line_range = read_1_byte (parent_bfd, line_ptr);
> >    line_ptr += 1;
> > -  lh->opcode_base = read_1_byte (abfd, line_ptr);
> > +
> > +  lh->opcode_base = read_1_byte (parent_bfd, 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.  */
> >    for (i = 1; i < lh->opcode_base; ++i)
> >      {
> > -      lh->standard_opcode_lengths[i] = read_1_byte (abfd, line_ptr);
> > +      lh->standard_opcode_lengths[i] = read_1_byte (parent_bfd, line_ptr);
> >        line_ptr += 1;
> >      }
> >
> >    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
> > +       (parent_bfd, &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
> > +       (parent_bfd, &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 +417,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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
> >         {
> >           line_ptr += bytes_read;
> >           lh->add_include_dir (cur_dir);
> > @@ -395,17 +425,17 @@ 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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
> >         {
> >           unsigned int mod_time, length;
> >           dir_index d_index;
> >
> >           line_ptr += bytes_read;
> > -         d_index = (dir_index) read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > +         d_index = (dir_index) read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> >           line_ptr += bytes_read;
> > -         mod_time = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > +         mod_time = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> >           line_ptr += bytes_read;
> > -         length = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > +         length = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> >           line_ptr += bytes_read;
> >
> >           lh->add_file_name (cur_file, d_index, mod_time, length);
> > @@ -413,9 +443,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..22db9f9aa78 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 *parent_bfd, 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 da88a8b405c..c0e51357ce2 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.
> > @@ -175,6 +178,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;
> > @@ -182,6 +194,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
> > @@ -217,6 +238,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
> > @@ -590,6 +635,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,
> > @@ -618,6 +674,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 0c5689c63ef..876e3aedcf1 100644
> > --- a/gdb/dwarf2/read.c
> > +++ b/gdb/dwarf2/read.c
> > @@ -81,6 +81,7 @@
> >  #include "gdbsupport/gdb_optional.h"
> >  #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>
> > @@ -2135,6 +2136,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
> > +  (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
> > +  (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 (lh.release ());
> > +    }
> > +
> > +  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 6ed0be7203b..49fea22c092 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;
> > @@ -969,4 +970,40 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
> >  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
> > +    (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 bbd21287b28..110864adac3 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 ff57c6e32b7..b5c6929fcf7 100644
> > --- a/gdb/testsuite/gdb.debuginfod/section.exp
> > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > @@ -171,6 +171,27 @@ proc_with_prefix local_url { } {
> >      # during backtrace.
> >      set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
> >      gdb_test "bt" $res "break backtrace"
> > +
> > +    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:37" $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"
> >  }
> >
> >  # Create CACHE and DB directories ready for debuginfod to use.
> > diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> > index 9f643b1ce95..fde46bfbe94 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 70a7145741f..7de8796aee0 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.41.0
> >


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

* [PING*3][PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo
  2023-11-20 18:38     ` [PING*2][PATCH " Aaron Merey
@ 2023-11-30 16:29       ` Aaron Merey
  2023-12-12 15:00         ` [PING*4][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-30 16:29 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Mon, Nov 20, 2023 at 1:38 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > v6: https://sourceware.org/pipermail/gdb-patches/2023-October/203147.html
> > >
> > > v7 adds support for buffering output stream flush().  Newline prefix
> > > states have been removed from this patch and instead added to patch 4/4
> > > in this series.
> > >
> > > Commit message:
> > >
> > > 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-mi.c       |   3 +
> > >  gdb/stack.c              |  35 +++++---
> > >  gdb/thread.c             | 171 ++++++++++++++++++++---------------
> > >  gdb/ui-file.h            |   2 +-
> > >  gdb/ui-out.c             | 144 ++++++++++++++++++++++++++++++
> > >  gdb/ui-out.h             | 186 +++++++++++++++++++++++++++++++++++++++
> > >  11 files changed, 493 insertions(+), 95 deletions(-)
> > >
> > > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > > index 20d3d93f1ad..c919622d418 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 902af405cc6..b36fb8c35de 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;
> > >      }
> > > @@ -296,10 +297,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 4fde96800fb..7c1a7cca74f 100644
> > > --- a/gdb/infrun.c
> > > +++ b/gdb/infrun.c
> > > @@ -8788,10 +8788,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;
> > > @@ -8820,6 +8820,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-mi.c b/gdb/python/py-mi.c
> > > index a7b4f4fa3cf..ba913bf1fee 100644
> > > --- a/gdb/python/py-mi.c
> > > +++ b/gdb/python/py-mi.c
> > > @@ -61,6 +61,9 @@ class py_ui_out : public ui_out
> > >      return current ().obj.release ();
> > >    }
> > >
> > > +  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 0b35d62f82f..0560261144c 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 (gdb::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 c8145da59bc..f6cf2eb9cf4 100644
> > > --- a/gdb/thread.c
> > > +++ b/gdb/thread.c
> > > @@ -1064,6 +1064,103 @@ 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;
> > > +
> > > +  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.  */
> > > @@ -1147,82 +1244,12 @@ 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;
> > >
> > > -         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 defa8f9dfa4..9f643b1ce95 100644
> > > --- a/gdb/ui-out.c
> > > +++ b/gdb/ui-out.c
> > > @@ -871,3 +871,147 @@ 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.  */ }
> > > +
> > > +buffer_group::~buffer_group ()
> > > +{ /* 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..70a7145741f 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,187 @@ 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);
> > > +
> > > +  ~buffer_group ();
> > > +
> > > +  /* 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.  */
> > > +  gdb::optional<buffering_file> m_buffered_current_uiout;
> > > +
> > > +  /* Additional ui_out being buffered.  */
> > > +  ui_out *m_uiout;
> > > +
> > > +  /* Buffer for m_uiout's output stream.  */
> > > +  gdb::optional<buffering_file> m_buffered_uiout;
> > > +};
> > > +
> > >  #endif /* UI_OUT_H */
> > > --
> > > 2.41.0
> > >


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

* [PING*3][PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator
  2023-11-20 18:39     ` [PING*2][PATCH " Aaron Merey
@ 2023-11-30 16:30       ` Aaron Merey
  2023-12-12 15:01         ` [PING*4][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-30 16:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Mon, Nov 20, 2023 at 1:39 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > v1: https://sourceware.org/pipermail/gdb-patches/2023-June/199984.html
> > >
> > > v2 removes unwrapping_reverse_objfile_iterator and adds
> > > basic_safe_reverse_range and basic_safe_reverse_iterator.
> > >
> > > 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 becasue 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                          |   8 +-
> > >  gdb/objfiles.h                          |   8 +-
> > >  gdb/progspace.c                         |  19 ++++-
> > >  gdb/progspace.h                         |  31 ++++---
> > >  gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
> > >  gdbsupport/safe-iterator.h              | 106 ++++++++++++++++++++++++
> > >  7 files changed, 154 insertions(+), 27 deletions(-)
> > >
> > > diff --git a/gdb/jit.c b/gdb/jit.c
> > > index 9e8325ab803..a39fdc5a96d 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..9822c179962 100644
> > > --- a/gdb/objfiles.c
> > > +++ b/gdb/objfiles.c
> > > @@ -793,14 +793,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 4b8aa9bfcec..c20b63ceadf 100644
> > > --- a/gdb/objfiles.h
> > > +++ b/gdb/objfiles.h
> > > @@ -698,13 +698,17 @@ struct objfile
> > >
> > >  private:
> > >
> > > +  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>;
> > > +
> > >    /* Ensure that partial symbols have been read and return the "quick" (aka
> > >       partial) symbol functions for this symbol reader.  */
> > > -  const std::forward_list<quick_symbol_functions_up> &
> > > +  qf_safe_range
> > >    qf_require_partial_symbols ()
> > >    {
> > >      this->require_partial_symbols (true);
> > > -    return qf;
> > > +    return qf_safe_range (qf_range (qf.begin (), qf.end ()));
> > >    }
> > >
> > >  public:
> > > diff --git a/gdb/progspace.c b/gdb/progspace.c
> > > index 839707e9d71..c0fca1dace7 100644
> > > --- a/gdb/progspace.c
> > > +++ b/gdb/progspace.c
> > > @@ -143,19 +143,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));
> > >      }
> > >  }
> > >
> > > @@ -184,6 +184,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 a22e427400e..17bb1710ccf 100644
> > > --- a/gdb/progspace.h
> > > +++ b/gdb/progspace.h
> > > @@ -214,28 +214,32 @@ struct program_space
> > >         unwrapping_objfile_iterator (objfiles_list.end ()));
> > >    }
> > >
> > > -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> > > +  using objfiles_safe_range = iterator_range<objfile_list::iterator>;
> > > +  using objfiles_safe_reverse_range
> > > +    = basic_safe_reverse_range<objfiles_safe_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_safe_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);
> > > @@ -250,6 +254,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/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 ccd772ca2a5..9f57c1543cf 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 gdb::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.41.0
> > >


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

* [PING*3][PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading
  2023-11-20 18:39     ` [PING*2][PATCH " Aaron Merey
@ 2023-11-30 16:30       ` Aaron Merey
  2023-12-12 15:01         ` [PING*4][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-30 16:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Mon, Nov 20, 2023 at 1:39 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > v3: https://sourceware.org/pipermail/gdb-patches/2023-June/199987.html
> > >
> > > v4 improves testcases when running with --target_board=native-gdbserver.
> > >
> > > v4 also fixes a bug where objfile observers could clear selected_frame
> > > if debuginfo was downloaded during get_selected_frame.
> > >
> > > 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                | 156 +++++++++++++++--
> > >  gdb/dwarf2/read.c                          | 146 +++++++++++++++-
> > >  gdb/dwarf2/read.h                          |  10 ++
> > >  gdb/dwarf2/section.c                       |   3 +-
> > >  gdb/elfread.c                              |   2 +-
> > >  gdb/frame.c                                |   7 +
> > >  gdb/objfile-flags.h                        |   4 +
> > >  gdb/objfiles.h                             |  20 +++
> > >  gdb/quick-symbol.h                         |   4 +
> > >  gdb/symfile.c                              |  13 +-
> > >  gdb/symtab.c                               |  18 +-
> > >  gdb/testsuite/gdb.debuginfod/libsection1.c |  40 +++++
> > >  gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
> > >  gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
> > >  gdb/testsuite/gdb.debuginfod/section.exp   | 184 +++++++++++++++++++++
> > >  gdb/testsuite/lib/debuginfod-support.exp   |  27 ++-
> > >  21 files changed, 746 insertions(+), 24 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 abc8d613482..257f0316731 100644
> > > --- a/gdb/dwarf2/frame.c
> > > +++ b/gdb/dwarf2/frame.c
> > > @@ -1617,6 +1617,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 cfa45435fbd..9d18717fe56 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 0e74857eb1a..4a44cdbc223 100644
> > > --- a/gdb/dwarf2/public.h
> > > +++ b/gdb/dwarf2/public.h
> > > @@ -40,4 +40,11 @@ extern void dwarf2_initialize_objfile (struct objfile *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 e789e9c2654..da88a8b405c 100644
> > > --- a/gdb/dwarf2/read-gdb-index.c
> > > +++ b/gdb/dwarf2/read-gdb-index.c
> > > @@ -139,6 +139,7 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >       gdb.dwarf2/gdb-index.exp testcase.  */
> > >    void dump (struct objfile *objfile) override;
> > >
> > > +  /* Calls do_expand_matching_symbols and downloads debuginfo if necessary.  */
> > >    void expand_matching_symbols
> > >      (struct objfile *,
> > >       const lookup_name_info &lookup_name,
> > > @@ -146,6 +147,14 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >       int global,
> > >       symbol_compare_ftype *ordered_compare) override;
> > >
> > > +  void do_expand_matching_symbols
> > > +    (struct objfile *,
> > > +     const lookup_name_info &lookup_name,
> > > +     domain_enum domain,
> > > +     int global,
> > > +     symbol_compare_ftype *ordered_compare);
> > > +
> > > +  /* Calls do_expand_symtabs_matching and downloads debuginfo if necessary.  */
> > >    bool expand_symtabs_matching
> > >      (struct objfile *objfile,
> > >       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > > @@ -155,8 +164,59 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > >       block_search_flags search_flags,
> > >       domain_enum domain,
> > >       enum search_domain kind) override;
> > > +
> > > +  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
> > > @@ -318,7 +378,7 @@ dw2_symtab_iter_next (struct dw2_symtab_iterator *iter,
> > >  }
> > >
> > >  void
> > > -dwarf2_gdb_index::expand_matching_symbols
> > > +dwarf2_gdb_index::do_expand_matching_symbols
> > >    (struct objfile *objfile,
> > >     const lookup_name_info &name, domain_enum domain,
> > >     int global,
> > > @@ -356,6 +416,29 @@ dwarf2_gdb_index::expand_matching_symbols
> > >      }, per_objfile);
> > >  }
> > >
> > > +void
> > > +dwarf2_gdb_index::expand_matching_symbols
> > > +  (struct objfile *objfile,
> > > +   const lookup_name_info &lookup_name,
> > > +   domain_enum domain,
> > > +   int global,
> > > +   symbol_compare_ftype *ordered_compare)
> > > +{
> > > +  try
> > > +    {
> > > +      do_expand_matching_symbols (objfile, lookup_name, domain,
> > > +                                 global, ordered_compare);
> > > +    }
> > > +  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;
> > > +    }
> > > +}
> > > +
> > >  /* Helper for dw2_expand_matching symtabs.  Called on each symbol
> > >     matched, to expand corresponding CUs that were marked.  IDX is the
> > >     index of the symbol name that matched.  */
> > > @@ -458,7 +541,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,
> > > @@ -507,6 +590,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
> > >  {
> > > @@ -842,28 +958,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 ea0b2328a3e..0c5689c63ef 100644
> > > --- a/gdb/dwarf2/read.c
> > > +++ b/gdb/dwarf2/read.c
> > > @@ -34,6 +34,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"
> > > @@ -95,6 +96,8 @@
> > >  #include "split-name.h"
> > >  #include "gdbsupport/parallel-for.h"
> > >  #include "gdbsupport/thread-pool.h"
> > > +#include "inferior.h"
> > > +#include "debuginfod-support.h"
> > >
> > >  /* When == 1, print basic high level tracing messages.
> > >     When > 1, be more verbose.
> > > @@ -3188,7 +3191,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,
> > > @@ -3219,6 +3222,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,
> > > @@ -3375,6 +3404,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 ();
> > >
> > >  /* See dwarf2/public.h.  */
> > > @@ -3440,10 +3492,102 @@ dwarf2_initialize_objfile (struct objfile *objfile)
> > >        return;
> > >      }
> > >
> > > +  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;
> > > +      return;
> > > +    }
> > > +
> > >    global_index_cache.miss ();
> > >    objfile->qf.push_front (make_cooked_index_funcs ());
> > >  }
> > >
> > > +/* See read.h.  */
> > > +
> > > +void
> > > +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> > > +                                dwarf2_base_index_functions *fncs)
> > > +{
> > > +  gdb_assert (objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> > > +
> > > +  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)
> > > +    goto unset;
> > > +
> > > +  filename = bfd_get_filename (objfile->obfd.get ());
> > > +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> > > +                                  filename, &symfile_path);
> > > +  if (fd.get () < 0)
> > > +    goto unset;
> > > +
> > > +  /* 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);
> > > +      goto unset;
> > > +    }
> > > +
> > > +  /* 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);
> > > +
> > > +unset:
> > > +  objfile->remove_deferred_status ();
> > > +}
> > > +
> > > +/* 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;
> > > +
> > > +  gdb::unique_xmalloc_ptr<char> index_path;
> > > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > +
> > > +  if (build_id == nullptr)
> > > +    return false;
> > > +
> > > +  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;
> > > +}
> > > +
> > >
> > >
> > >  /* Build a partial symbol table.  */
> > > diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> > > index dc7abf23ba4..6ed0be7203b 100644
> > > --- a/gdb/dwarf2/read.h
> > > +++ b/gdb/dwarf2/read.h
> > > @@ -883,6 +883,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
> > >       CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
> > >         override final;
> > >
> > > +  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
> > >    {
> > > @@ -959,4 +963,10 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
> > >                                        dwarf2_section_info *section,
> > >                                        addrmap *mutable_map);
> > >
> > > +/* 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 7900dfbc388..5c89598eee7 100644
> > > --- a/gdb/elfread.c
> > > +++ b/gdb/elfread.c
> > > @@ -1235,7 +1235,7 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> > >             symbol_file_add_separate (debug_bfd, debugfile.c_str (),
> > >                                       symfile_flags, objfile);
> > >         }
> > > -      else
> > > +      else if (!dwarf2_has_separate_index (objfile))
> > >         {
> > >           has_dwarf2 = false;
> > >           const struct bfd_build_id *build_id
> > > diff --git a/gdb/frame.c b/gdb/frame.c
> > > index 7077016ccba..00dbffed1ef 100644
> > > --- a/gdb/frame.c
> > > +++ b/gdb/frame.c
> > > @@ -1892,6 +1892,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 9dee2ee51a0..fb3f741c899 100644
> > > --- a/gdb/objfile-flags.h
> > > +++ b/gdb/objfile-flags.h
> > > @@ -60,6 +60,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 c20b63ceadf..ea9bd2157dc 100644
> > > --- a/gdb/objfiles.h
> > > +++ b/gdb/objfiles.h
> > > @@ -612,6 +612,26 @@ struct objfile
> > >    /* See quick_symbol_functions.  */
> > >    void require_partial_symbols (bool verbose);
> > >
> > > +  /* Indicate that the aquisition of this objfile's separate debug objfile
> > > +     is no longer deferred.  Used when the debug objfile has been aquired
> > > +     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 a7fea2ccb49..e7163503e39 100644
> > > --- a/gdb/quick-symbol.h
> > > +++ b/gdb/quick-symbol.h
> > > @@ -225,6 +225,10 @@ struct quick_symbol_functions
> > >    virtual void read_partial_symbols (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.c b/gdb/symfile.c
> > > index eebc5ea44b9..0491a33e8f5 100644
> > > --- a/gdb/symfile.c
> > > +++ b/gdb/symfile.c
> > > @@ -991,6 +991,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.  */
> > > @@ -1001,7 +1005,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 ();
> > >      }
> > > @@ -1122,6 +1127,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);
> > >
> > >    bfd_cache_close_all ();
> > > diff --git a/gdb/symtab.c b/gdb/symtab.c
> > > index 5ec56f4f2af..bd01a75189d 100644
> > > --- a/gdb/symtab.c
> > > +++ b/gdb/symtab.c
> > > @@ -2925,14 +2925,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..60824b415c6
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > @@ -0,0 +1,40 @@
> > > +/* 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 *);
> > > +
> > > +void
> > > +libsection1_test ()
> > > +{
> > > +  pthread_t thr;
> > > +
> > > +  printf ("In libsection1\n");
> > > +  libsection2_test ();
> > > +
> > > +  pthread_create (&thr, NULL, libsection2_thread_test, NULL);
> > > +
> > > +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> > > +  sleep (3);
> > > +  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..629a67f94a5
> > > --- /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>
> > > +
> > > +void
> > > +libsection2_test ()
> > > +{
> > > +  printf ("In libsection2\n");
> > > +}
> > > +
> > > +void *
> > > +libsection2_thread_test (void *arg)
> > > +{
> > > +  (void) arg;
> > > +
> > > +  printf ("In thread test\n");
> > > +
> > > +  while (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..d391a8f898e
> > > --- /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..ff57c6e32b7
> > > --- /dev/null
> > > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > > @@ -0,0 +1,184 @@
> > > +# 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 { [have_index $lib_sl1] != "gdb_index"
> > > +     && [add_gdb_index $lib_sl1] == 0 } {
> > > +    untested "failed to add .gdb_index to $libfile1"
> > > +    return -1
> > > +}
> > > +
> > > +if { [have_index $lib_sl2] != "gdb_index"
> > > +     && [add_gdb_index $lib_sl2] == 0 } {
> > > +    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 res 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
> > > +
> > > +    if {![runto_main]} {
> > > +       return
> > > +    }
> > > +}
> > > +
> > > +# 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
> > > +
> > > +    # Download .gdb_index for solibs.
> > > +    set res ".*section \.gdb_index for $lib_sl1.*\
> > > +       section \.gdb_index for $lib_sl2.*"
> > > +    clean_restart_with_prompt $sectexec $res "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"
> > > +
> > > +    # Check that download progress message is correctly formatted
> > > +    # during backtrace.
> > > +    set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
> > > +    gdb_test "bt" $res "break backtrace"
> > > +}
> > > +
> > > +# 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
> > > +       }
> > > +    }
> > > +}
> > > --
> > > 2.41.0
> > >


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

* [PING*3][PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading
  2023-11-20 18:40     ` [PING*2][PATCH " Aaron Merey
@ 2023-11-30 16:30       ` Aaron Merey
  2023-12-12 15:08         ` [PING*4][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-11-30 16:30 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Mon, Nov 20, 2023 at 1:40 PM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Sun, Nov 12, 2023 at 3:21 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > v4: https://sourceware.org/pipermail/gdb-patches/2023-August/201651.html
> > >
> > > v5 adds prefix_state to progress_update objects to track when
> > > a newline prefix needs to be added to a download progress message.
> > > This is used to correctly format progress messages that occur during
> > > command autocompletion.
> > >
> > > Commit message:
> > >
> > > 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                 | 215 +++++++++++++++--------
> > >  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 |  21 +++
> > >  gdb/ui-out.c                             |   3 +
> > >  gdb/ui-out.h                             |  20 +++
> > >  11 files changed, 531 insertions(+), 81 deletions(-)
> > >
> > > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > > index c919622d418..a570a2d939d 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 2abf3998345..9c299f3eb65 100644
> > > --- a/gdb/completer.c
> > > +++ b/gdb/completer.c
> > > @@ -1345,6 +1345,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
> > > @@ -1444,7 +1448,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,
> > > @@ -1452,6 +1457,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);
> > > @@ -1461,6 +1471,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 d072a91bac9..b9210d84f6b 100644
> > > --- a/gdb/dwarf2/line-header.c
> > > +++ b/gdb/dwarf2/line-header.c
> > > @@ -102,50 +102,57 @@ 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;
> > >  }
> > >
> > > -/* 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.  */
> > > +
> > > +/* Like read_formatted_entries but the .debug_line and .debug_line_str
> > > +   are stored in LINE_BUFP and LINE_STR_DATA.  This is used for cases
> > > +   where these sections are read from separate files without necessarily
> > > +   having access to the entire debuginfo file they originate from.  */
> > >
> > >  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 *parent_bfd, 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;
> > >
> > > -  format_count = read_1_byte (abfd, buf);
> > > +  format_count = read_1_byte (parent_bfd, buf);
> > >    buf += 1;
> > >    format_header_data = buf;
> > >    for (formati = 0; formati < format_count; formati++)
> > >      {
> > > -      read_unsigned_leb128 (abfd, buf, &bytes_read);
> > > +      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> > >        buf += bytes_read;
> > > -      read_unsigned_leb128 (abfd, buf, &bytes_read);
> > > +      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> > >        buf += bytes_read;
> > >      }
> > >
> > > -  data_count = read_unsigned_leb128 (abfd, buf, &bytes_read);
> > > +  data_count = read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> > >    buf += bytes_read;
> > >    for (datai = 0; datai < data_count; datai++)
> > >      {
> > > @@ -154,10 +161,10 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > >
> > >        for (formati = 0; formati < format_count; formati++)
> > >         {
> > > -         ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
> > > +         ULONGEST content_type = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
> > >           format += bytes_read;
> > >
> > > -         ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
> > > +         ULONGEST form  = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
> > >           format += bytes_read;
> > >
> > >           gdb::optional<const char *> string;
> > > @@ -166,36 +173,48 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > >           switch (form)
> > >             {
> > >             case DW_FORM_string:
> > > -             string.emplace (read_direct_string (abfd, buf, &bytes_read));
> > > +             string.emplace (read_direct_string (parent_bfd, buf, &bytes_read));
> > >               buf += bytes_read;
> > >               break;
> > >
> > >             case DW_FORM_line_strp:
> > >               {
> > > -               const char *str
> > > -                 = per_objfile->read_line_string (buf, offset_size);
> > > +               if (line_str_data.empty ())
> > > +                 error (_("Dwarf Error: DW_FORM_line_strp used without " \
> > > +                          "required section"));
> > > +               if (line_str_data.size () <= offset_size)
> > > +                 error (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
> > > +                          "of section .debug_line"));
> > > +
> > > +               ULONGEST str_offset = read_offset (parent_bfd, 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));
> > > +             uint.emplace (read_1_byte (parent_bfd, buf));
> > >               buf += 1;
> > >               break;
> > >
> > >             case DW_FORM_data2:
> > > -             uint.emplace (read_2_bytes (abfd, buf));
> > > +             uint.emplace (read_2_bytes (parent_bfd, buf));
> > >               buf += 2;
> > >               break;
> > >
> > >             case DW_FORM_data4:
> > > -             uint.emplace (read_4_bytes (abfd, buf));
> > > +             uint.emplace (read_4_bytes (parent_bfd, buf));
> > >               buf += 4;
> > >               break;
> > >
> > >             case DW_FORM_data8:
> > > -             uint.emplace (read_8_bytes (abfd, buf));
> > > +             uint.emplace (read_8_bytes (parent_bfd, buf));
> > >               buf += 8;
> > >               break;
> > >
> > > @@ -205,7 +224,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > >               break;
> > >
> > >             case DW_FORM_udata:
> > > -             uint.emplace (read_unsigned_leb128 (abfd, buf, &bytes_read));
> > > +             uint.emplace (read_unsigned_leb128 (parent_bfd, buf, &bytes_read));
> > >               buf += bytes_read;
> > >               break;
> > >
> > > @@ -248,28 +267,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 *parent_bfd,
> > > +                         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,62 +298,65 @@ 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 (parent_bfd, 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);
> > > +  lh->version = read_2_bytes (parent_bfd, line_ptr);
> > >    line_ptr += 2;
> > >    if (lh->version > 5)
> > >      {
> > >        /* 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)
> > >      {
> > >        gdb_byte segment_selector_size;
> > >
> > >        /* Skip address size.  */
> > > -      read_1_byte (abfd, line_ptr);
> > > +      read_1_byte (parent_bfd, line_ptr);
> > >        line_ptr += 1;
> > >
> > > -      segment_selector_size = read_1_byte (abfd, line_ptr);
> > > +      segment_selector_size = read_1_byte (parent_bfd, line_ptr);
> > >        line_ptr += 1;
> > >        if (segment_selector_size != 0)
> > >         {
> > >           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);
> > > +  LONGEST header_length = read_offset (parent_bfd, 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);
> > > +
> > > +  lh->minimum_instruction_length = read_1_byte (parent_bfd, line_ptr);
> > >    line_ptr += 1;
> > >
> > >    if (lh->version >= 4)
> > >      {
> > > -      lh->maximum_ops_per_instruction = read_1_byte (abfd, line_ptr);
> > > +      lh->maximum_ops_per_instruction = read_1_byte (parent_bfd, line_ptr);
> > >        line_ptr += 1;
> > >      }
> > >    else
> > > @@ -345,41 +369,47 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > >                    "in `.debug_line' section"));
> > >      }
> > >
> > > -  lh->default_is_stmt = read_1_byte (abfd, line_ptr);
> > > +  lh->default_is_stmt = read_1_byte (parent_bfd, line_ptr);
> > >    line_ptr += 1;
> > > -  lh->line_base = read_1_signed_byte (abfd, line_ptr);
> > > +
> > > +  lh->line_base = read_1_signed_byte (parent_bfd, line_ptr);
> > >    line_ptr += 1;
> > > -  lh->line_range = read_1_byte (abfd, line_ptr);
> > > +
> > > +  lh->line_range = read_1_byte (parent_bfd, line_ptr);
> > >    line_ptr += 1;
> > > -  lh->opcode_base = read_1_byte (abfd, line_ptr);
> > > +
> > > +  lh->opcode_base = read_1_byte (parent_bfd, 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.  */
> > >    for (i = 1; i < lh->opcode_base; ++i)
> > >      {
> > > -      lh->standard_opcode_lengths[i] = read_1_byte (abfd, line_ptr);
> > > +      lh->standard_opcode_lengths[i] = read_1_byte (parent_bfd, line_ptr);
> > >        line_ptr += 1;
> > >      }
> > >
> > >    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
> > > +       (parent_bfd, &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
> > > +       (parent_bfd, &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 +417,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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
> > >         {
> > >           line_ptr += bytes_read;
> > >           lh->add_include_dir (cur_dir);
> > > @@ -395,17 +425,17 @@ 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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
> > >         {
> > >           unsigned int mod_time, length;
> > >           dir_index d_index;
> > >
> > >           line_ptr += bytes_read;
> > > -         d_index = (dir_index) read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > > +         d_index = (dir_index) read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> > >           line_ptr += bytes_read;
> > > -         mod_time = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > > +         mod_time = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> > >           line_ptr += bytes_read;
> > > -         length = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > > +         length = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> > >           line_ptr += bytes_read;
> > >
> > >           lh->add_file_name (cur_file, d_index, mod_time, length);
> > > @@ -413,9 +443,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..22db9f9aa78 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 *parent_bfd, 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 da88a8b405c..c0e51357ce2 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.
> > > @@ -175,6 +178,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;
> > > @@ -182,6 +194,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
> > > @@ -217,6 +238,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
> > > @@ -590,6 +635,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,
> > > @@ -618,6 +674,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 0c5689c63ef..876e3aedcf1 100644
> > > --- a/gdb/dwarf2/read.c
> > > +++ b/gdb/dwarf2/read.c
> > > @@ -81,6 +81,7 @@
> > >  #include "gdbsupport/gdb_optional.h"
> > >  #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>
> > > @@ -2135,6 +2136,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
> > > +  (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
> > > +  (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 (lh.release ());
> > > +    }
> > > +
> > > +  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 6ed0be7203b..49fea22c092 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;
> > > @@ -969,4 +970,40 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
> > >  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
> > > +    (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 bbd21287b28..110864adac3 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 ff57c6e32b7..b5c6929fcf7 100644
> > > --- a/gdb/testsuite/gdb.debuginfod/section.exp
> > > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > > @@ -171,6 +171,27 @@ proc_with_prefix local_url { } {
> > >      # during backtrace.
> > >      set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
> > >      gdb_test "bt" $res "break backtrace"
> > > +
> > > +    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:37" $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"
> > >  }
> > >
> > >  # Create CACHE and DB directories ready for debuginfod to use.
> > > diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> > > index 9f643b1ce95..fde46bfbe94 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 70a7145741f..7de8796aee0 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.41.0
> > >


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

* [PING*4][PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo
  2023-11-30 16:29       ` [PING*3][PATCH " Aaron Merey
@ 2023-12-12 15:00         ` Aaron Merey
  2023-12-20 14:57           ` [PING*5][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-12-12 15:00 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Thu, Nov 30, 2023 at 11:29 AM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Mon, Nov 20, 2023 at 1:38 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Ping
> > >
> > > Thanks,
> > > Aaron
> > >
> > > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > >
> > > > v6: https://sourceware.org/pipermail/gdb-patches/2023-October/203147.html
> > > >
> > > > v7 adds support for buffering output stream flush().  Newline prefix
> > > > states have been removed from this patch and instead added to patch 4/4
> > > > in this series.
> > > >
> > > > Commit message:
> > > >
> > > > 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-mi.c       |   3 +
> > > >  gdb/stack.c              |  35 +++++---
> > > >  gdb/thread.c             | 171 ++++++++++++++++++++---------------
> > > >  gdb/ui-file.h            |   2 +-
> > > >  gdb/ui-out.c             | 144 ++++++++++++++++++++++++++++++
> > > >  gdb/ui-out.h             | 186 +++++++++++++++++++++++++++++++++++++++
> > > >  11 files changed, 493 insertions(+), 95 deletions(-)
> > > >
> > > > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > > > index 20d3d93f1ad..c919622d418 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 902af405cc6..b36fb8c35de 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;
> > > >      }
> > > > @@ -296,10 +297,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 4fde96800fb..7c1a7cca74f 100644
> > > > --- a/gdb/infrun.c
> > > > +++ b/gdb/infrun.c
> > > > @@ -8788,10 +8788,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;
> > > > @@ -8820,6 +8820,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-mi.c b/gdb/python/py-mi.c
> > > > index a7b4f4fa3cf..ba913bf1fee 100644
> > > > --- a/gdb/python/py-mi.c
> > > > +++ b/gdb/python/py-mi.c
> > > > @@ -61,6 +61,9 @@ class py_ui_out : public ui_out
> > > >      return current ().obj.release ();
> > > >    }
> > > >
> > > > +  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 0b35d62f82f..0560261144c 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 (gdb::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 c8145da59bc..f6cf2eb9cf4 100644
> > > > --- a/gdb/thread.c
> > > > +++ b/gdb/thread.c
> > > > @@ -1064,6 +1064,103 @@ 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;
> > > > +
> > > > +  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.  */
> > > > @@ -1147,82 +1244,12 @@ 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;
> > > >
> > > > -         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 defa8f9dfa4..9f643b1ce95 100644
> > > > --- a/gdb/ui-out.c
> > > > +++ b/gdb/ui-out.c
> > > > @@ -871,3 +871,147 @@ 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.  */ }
> > > > +
> > > > +buffer_group::~buffer_group ()
> > > > +{ /* 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..70a7145741f 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,187 @@ 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);
> > > > +
> > > > +  ~buffer_group ();
> > > > +
> > > > +  /* 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.  */
> > > > +  gdb::optional<buffering_file> m_buffered_current_uiout;
> > > > +
> > > > +  /* Additional ui_out being buffered.  */
> > > > +  ui_out *m_uiout;
> > > > +
> > > > +  /* Buffer for m_uiout's output stream.  */
> > > > +  gdb::optional<buffering_file> m_buffered_uiout;
> > > > +};
> > > > +
> > > >  #endif /* UI_OUT_H */
> > > > --
> > > > 2.41.0
> > > >


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

* [PING*4][PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator
  2023-11-30 16:30       ` [PING*3][PATCH " Aaron Merey
@ 2023-12-12 15:01         ` Aaron Merey
  2023-12-20 14:57           ` [PING*5][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-12-12 15:01 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Thu, Nov 30, 2023 at 11:30 AM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Mon, Nov 20, 2023 at 1:39 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Ping
> > >
> > > Thanks,
> > > Aaron
> > >
> > > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > >
> > > > v1: https://sourceware.org/pipermail/gdb-patches/2023-June/199984.html
> > > >
> > > > v2 removes unwrapping_reverse_objfile_iterator and adds
> > > > basic_safe_reverse_range and basic_safe_reverse_iterator.
> > > >
> > > > 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 becasue 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                          |   8 +-
> > > >  gdb/objfiles.h                          |   8 +-
> > > >  gdb/progspace.c                         |  19 ++++-
> > > >  gdb/progspace.h                         |  31 ++++---
> > > >  gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
> > > >  gdbsupport/safe-iterator.h              | 106 ++++++++++++++++++++++++
> > > >  7 files changed, 154 insertions(+), 27 deletions(-)
> > > >
> > > > diff --git a/gdb/jit.c b/gdb/jit.c
> > > > index 9e8325ab803..a39fdc5a96d 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..9822c179962 100644
> > > > --- a/gdb/objfiles.c
> > > > +++ b/gdb/objfiles.c
> > > > @@ -793,14 +793,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 4b8aa9bfcec..c20b63ceadf 100644
> > > > --- a/gdb/objfiles.h
> > > > +++ b/gdb/objfiles.h
> > > > @@ -698,13 +698,17 @@ struct objfile
> > > >
> > > >  private:
> > > >
> > > > +  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>;
> > > > +
> > > >    /* Ensure that partial symbols have been read and return the "quick" (aka
> > > >       partial) symbol functions for this symbol reader.  */
> > > > -  const std::forward_list<quick_symbol_functions_up> &
> > > > +  qf_safe_range
> > > >    qf_require_partial_symbols ()
> > > >    {
> > > >      this->require_partial_symbols (true);
> > > > -    return qf;
> > > > +    return qf_safe_range (qf_range (qf.begin (), qf.end ()));
> > > >    }
> > > >
> > > >  public:
> > > > diff --git a/gdb/progspace.c b/gdb/progspace.c
> > > > index 839707e9d71..c0fca1dace7 100644
> > > > --- a/gdb/progspace.c
> > > > +++ b/gdb/progspace.c
> > > > @@ -143,19 +143,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));
> > > >      }
> > > >  }
> > > >
> > > > @@ -184,6 +184,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 a22e427400e..17bb1710ccf 100644
> > > > --- a/gdb/progspace.h
> > > > +++ b/gdb/progspace.h
> > > > @@ -214,28 +214,32 @@ struct program_space
> > > >         unwrapping_objfile_iterator (objfiles_list.end ()));
> > > >    }
> > > >
> > > > -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> > > > +  using objfiles_safe_range = iterator_range<objfile_list::iterator>;
> > > > +  using objfiles_safe_reverse_range
> > > > +    = basic_safe_reverse_range<objfiles_safe_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_safe_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);
> > > > @@ -250,6 +254,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/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 ccd772ca2a5..9f57c1543cf 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 gdb::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.41.0
> > > >


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

* [PING*4][PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading
  2023-11-30 16:30       ` [PING*3][PATCH " Aaron Merey
@ 2023-12-12 15:01         ` Aaron Merey
  2023-12-20 14:57           ` [PING*5][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-12-12 15:01 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Thu, Nov 30, 2023 at 11:30 AM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Mon, Nov 20, 2023 at 1:39 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Ping
> > >
> > > Thanks,
> > > Aaron
> > >
> > > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > >
> > > > v3: https://sourceware.org/pipermail/gdb-patches/2023-June/199987.html
> > > >
> > > > v4 improves testcases when running with --target_board=native-gdbserver.
> > > >
> > > > v4 also fixes a bug where objfile observers could clear selected_frame
> > > > if debuginfo was downloaded during get_selected_frame.
> > > >
> > > > 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                | 156 +++++++++++++++--
> > > >  gdb/dwarf2/read.c                          | 146 +++++++++++++++-
> > > >  gdb/dwarf2/read.h                          |  10 ++
> > > >  gdb/dwarf2/section.c                       |   3 +-
> > > >  gdb/elfread.c                              |   2 +-
> > > >  gdb/frame.c                                |   7 +
> > > >  gdb/objfile-flags.h                        |   4 +
> > > >  gdb/objfiles.h                             |  20 +++
> > > >  gdb/quick-symbol.h                         |   4 +
> > > >  gdb/symfile.c                              |  13 +-
> > > >  gdb/symtab.c                               |  18 +-
> > > >  gdb/testsuite/gdb.debuginfod/libsection1.c |  40 +++++
> > > >  gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
> > > >  gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
> > > >  gdb/testsuite/gdb.debuginfod/section.exp   | 184 +++++++++++++++++++++
> > > >  gdb/testsuite/lib/debuginfod-support.exp   |  27 ++-
> > > >  21 files changed, 746 insertions(+), 24 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 abc8d613482..257f0316731 100644
> > > > --- a/gdb/dwarf2/frame.c
> > > > +++ b/gdb/dwarf2/frame.c
> > > > @@ -1617,6 +1617,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 cfa45435fbd..9d18717fe56 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 0e74857eb1a..4a44cdbc223 100644
> > > > --- a/gdb/dwarf2/public.h
> > > > +++ b/gdb/dwarf2/public.h
> > > > @@ -40,4 +40,11 @@ extern void dwarf2_initialize_objfile (struct objfile *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 e789e9c2654..da88a8b405c 100644
> > > > --- a/gdb/dwarf2/read-gdb-index.c
> > > > +++ b/gdb/dwarf2/read-gdb-index.c
> > > > @@ -139,6 +139,7 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > > >       gdb.dwarf2/gdb-index.exp testcase.  */
> > > >    void dump (struct objfile *objfile) override;
> > > >
> > > > +  /* Calls do_expand_matching_symbols and downloads debuginfo if necessary.  */
> > > >    void expand_matching_symbols
> > > >      (struct objfile *,
> > > >       const lookup_name_info &lookup_name,
> > > > @@ -146,6 +147,14 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > > >       int global,
> > > >       symbol_compare_ftype *ordered_compare) override;
> > > >
> > > > +  void do_expand_matching_symbols
> > > > +    (struct objfile *,
> > > > +     const lookup_name_info &lookup_name,
> > > > +     domain_enum domain,
> > > > +     int global,
> > > > +     symbol_compare_ftype *ordered_compare);
> > > > +
> > > > +  /* Calls do_expand_symtabs_matching and downloads debuginfo if necessary.  */
> > > >    bool expand_symtabs_matching
> > > >      (struct objfile *objfile,
> > > >       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > > > @@ -155,8 +164,59 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > > >       block_search_flags search_flags,
> > > >       domain_enum domain,
> > > >       enum search_domain kind) override;
> > > > +
> > > > +  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
> > > > @@ -318,7 +378,7 @@ dw2_symtab_iter_next (struct dw2_symtab_iterator *iter,
> > > >  }
> > > >
> > > >  void
> > > > -dwarf2_gdb_index::expand_matching_symbols
> > > > +dwarf2_gdb_index::do_expand_matching_symbols
> > > >    (struct objfile *objfile,
> > > >     const lookup_name_info &name, domain_enum domain,
> > > >     int global,
> > > > @@ -356,6 +416,29 @@ dwarf2_gdb_index::expand_matching_symbols
> > > >      }, per_objfile);
> > > >  }
> > > >
> > > > +void
> > > > +dwarf2_gdb_index::expand_matching_symbols
> > > > +  (struct objfile *objfile,
> > > > +   const lookup_name_info &lookup_name,
> > > > +   domain_enum domain,
> > > > +   int global,
> > > > +   symbol_compare_ftype *ordered_compare)
> > > > +{
> > > > +  try
> > > > +    {
> > > > +      do_expand_matching_symbols (objfile, lookup_name, domain,
> > > > +                                 global, ordered_compare);
> > > > +    }
> > > > +  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;
> > > > +    }
> > > > +}
> > > > +
> > > >  /* Helper for dw2_expand_matching symtabs.  Called on each symbol
> > > >     matched, to expand corresponding CUs that were marked.  IDX is the
> > > >     index of the symbol name that matched.  */
> > > > @@ -458,7 +541,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,
> > > > @@ -507,6 +590,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
> > > >  {
> > > > @@ -842,28 +958,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 ea0b2328a3e..0c5689c63ef 100644
> > > > --- a/gdb/dwarf2/read.c
> > > > +++ b/gdb/dwarf2/read.c
> > > > @@ -34,6 +34,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"
> > > > @@ -95,6 +96,8 @@
> > > >  #include "split-name.h"
> > > >  #include "gdbsupport/parallel-for.h"
> > > >  #include "gdbsupport/thread-pool.h"
> > > > +#include "inferior.h"
> > > > +#include "debuginfod-support.h"
> > > >
> > > >  /* When == 1, print basic high level tracing messages.
> > > >     When > 1, be more verbose.
> > > > @@ -3188,7 +3191,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,
> > > > @@ -3219,6 +3222,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,
> > > > @@ -3375,6 +3404,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 ();
> > > >
> > > >  /* See dwarf2/public.h.  */
> > > > @@ -3440,10 +3492,102 @@ dwarf2_initialize_objfile (struct objfile *objfile)
> > > >        return;
> > > >      }
> > > >
> > > > +  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;
> > > > +      return;
> > > > +    }
> > > > +
> > > >    global_index_cache.miss ();
> > > >    objfile->qf.push_front (make_cooked_index_funcs ());
> > > >  }
> > > >
> > > > +/* See read.h.  */
> > > > +
> > > > +void
> > > > +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> > > > +                                dwarf2_base_index_functions *fncs)
> > > > +{
> > > > +  gdb_assert (objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> > > > +
> > > > +  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)
> > > > +    goto unset;
> > > > +
> > > > +  filename = bfd_get_filename (objfile->obfd.get ());
> > > > +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> > > > +                                  filename, &symfile_path);
> > > > +  if (fd.get () < 0)
> > > > +    goto unset;
> > > > +
> > > > +  /* 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);
> > > > +      goto unset;
> > > > +    }
> > > > +
> > > > +  /* 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);
> > > > +
> > > > +unset:
> > > > +  objfile->remove_deferred_status ();
> > > > +}
> > > > +
> > > > +/* 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;
> > > > +
> > > > +  gdb::unique_xmalloc_ptr<char> index_path;
> > > > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > > +
> > > > +  if (build_id == nullptr)
> > > > +    return false;
> > > > +
> > > > +  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;
> > > > +}
> > > > +
> > > >
> > > >
> > > >  /* Build a partial symbol table.  */
> > > > diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> > > > index dc7abf23ba4..6ed0be7203b 100644
> > > > --- a/gdb/dwarf2/read.h
> > > > +++ b/gdb/dwarf2/read.h
> > > > @@ -883,6 +883,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
> > > >       CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
> > > >         override final;
> > > >
> > > > +  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
> > > >    {
> > > > @@ -959,4 +963,10 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
> > > >                                        dwarf2_section_info *section,
> > > >                                        addrmap *mutable_map);
> > > >
> > > > +/* 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 7900dfbc388..5c89598eee7 100644
> > > > --- a/gdb/elfread.c
> > > > +++ b/gdb/elfread.c
> > > > @@ -1235,7 +1235,7 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> > > >             symbol_file_add_separate (debug_bfd, debugfile.c_str (),
> > > >                                       symfile_flags, objfile);
> > > >         }
> > > > -      else
> > > > +      else if (!dwarf2_has_separate_index (objfile))
> > > >         {
> > > >           has_dwarf2 = false;
> > > >           const struct bfd_build_id *build_id
> > > > diff --git a/gdb/frame.c b/gdb/frame.c
> > > > index 7077016ccba..00dbffed1ef 100644
> > > > --- a/gdb/frame.c
> > > > +++ b/gdb/frame.c
> > > > @@ -1892,6 +1892,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 9dee2ee51a0..fb3f741c899 100644
> > > > --- a/gdb/objfile-flags.h
> > > > +++ b/gdb/objfile-flags.h
> > > > @@ -60,6 +60,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 c20b63ceadf..ea9bd2157dc 100644
> > > > --- a/gdb/objfiles.h
> > > > +++ b/gdb/objfiles.h
> > > > @@ -612,6 +612,26 @@ struct objfile
> > > >    /* See quick_symbol_functions.  */
> > > >    void require_partial_symbols (bool verbose);
> > > >
> > > > +  /* Indicate that the aquisition of this objfile's separate debug objfile
> > > > +     is no longer deferred.  Used when the debug objfile has been aquired
> > > > +     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 a7fea2ccb49..e7163503e39 100644
> > > > --- a/gdb/quick-symbol.h
> > > > +++ b/gdb/quick-symbol.h
> > > > @@ -225,6 +225,10 @@ struct quick_symbol_functions
> > > >    virtual void read_partial_symbols (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.c b/gdb/symfile.c
> > > > index eebc5ea44b9..0491a33e8f5 100644
> > > > --- a/gdb/symfile.c
> > > > +++ b/gdb/symfile.c
> > > > @@ -991,6 +991,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.  */
> > > > @@ -1001,7 +1005,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 ();
> > > >      }
> > > > @@ -1122,6 +1127,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);
> > > >
> > > >    bfd_cache_close_all ();
> > > > diff --git a/gdb/symtab.c b/gdb/symtab.c
> > > > index 5ec56f4f2af..bd01a75189d 100644
> > > > --- a/gdb/symtab.c
> > > > +++ b/gdb/symtab.c
> > > > @@ -2925,14 +2925,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..60824b415c6
> > > > --- /dev/null
> > > > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > > @@ -0,0 +1,40 @@
> > > > +/* 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 *);
> > > > +
> > > > +void
> > > > +libsection1_test ()
> > > > +{
> > > > +  pthread_t thr;
> > > > +
> > > > +  printf ("In libsection1\n");
> > > > +  libsection2_test ();
> > > > +
> > > > +  pthread_create (&thr, NULL, libsection2_thread_test, NULL);
> > > > +
> > > > +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> > > > +  sleep (3);
> > > > +  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..629a67f94a5
> > > > --- /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>
> > > > +
> > > > +void
> > > > +libsection2_test ()
> > > > +{
> > > > +  printf ("In libsection2\n");
> > > > +}
> > > > +
> > > > +void *
> > > > +libsection2_thread_test (void *arg)
> > > > +{
> > > > +  (void) arg;
> > > > +
> > > > +  printf ("In thread test\n");
> > > > +
> > > > +  while (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..d391a8f898e
> > > > --- /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..ff57c6e32b7
> > > > --- /dev/null
> > > > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > > > @@ -0,0 +1,184 @@
> > > > +# 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 { [have_index $lib_sl1] != "gdb_index"
> > > > +     && [add_gdb_index $lib_sl1] == 0 } {
> > > > +    untested "failed to add .gdb_index to $libfile1"
> > > > +    return -1
> > > > +}
> > > > +
> > > > +if { [have_index $lib_sl2] != "gdb_index"
> > > > +     && [add_gdb_index $lib_sl2] == 0 } {
> > > > +    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 res 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
> > > > +
> > > > +    if {![runto_main]} {
> > > > +       return
> > > > +    }
> > > > +}
> > > > +
> > > > +# 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
> > > > +
> > > > +    # Download .gdb_index for solibs.
> > > > +    set res ".*section \.gdb_index for $lib_sl1.*\
> > > > +       section \.gdb_index for $lib_sl2.*"
> > > > +    clean_restart_with_prompt $sectexec $res "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"
> > > > +
> > > > +    # Check that download progress message is correctly formatted
> > > > +    # during backtrace.
> > > > +    set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
> > > > +    gdb_test "bt" $res "break backtrace"
> > > > +}
> > > > +
> > > > +# 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
> > > > +       }
> > > > +    }
> > > > +}
> > > > --
> > > > 2.41.0
> > > >


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

* [PING*4][PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading
  2023-11-30 16:30       ` [PING*3][PATCH " Aaron Merey
@ 2023-12-12 15:08         ` Aaron Merey
  2023-12-20 14:58           ` [PING*5][PATCH " Aaron Merey
  0 siblings, 1 reply; 31+ messages in thread
From: Aaron Merey @ 2023-12-12 15:08 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Thu, Nov 30, 2023 at 11:30 AM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Mon, Nov 20, 2023 at 1:40 PM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Sun, Nov 12, 2023 at 3:21 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Ping
> > >
> > > Thanks,
> > > Aaron
> > >
> > > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > >
> > > > v4: https://sourceware.org/pipermail/gdb-patches/2023-August/201651.html
> > > >
> > > > v5 adds prefix_state to progress_update objects to track when
> > > > a newline prefix needs to be added to a download progress message.
> > > > This is used to correctly format progress messages that occur during
> > > > command autocompletion.
> > > >
> > > > Commit message:
> > > >
> > > > 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                 | 215 +++++++++++++++--------
> > > >  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 |  21 +++
> > > >  gdb/ui-out.c                             |   3 +
> > > >  gdb/ui-out.h                             |  20 +++
> > > >  11 files changed, 531 insertions(+), 81 deletions(-)
> > > >
> > > > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > > > index c919622d418..a570a2d939d 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 2abf3998345..9c299f3eb65 100644
> > > > --- a/gdb/completer.c
> > > > +++ b/gdb/completer.c
> > > > @@ -1345,6 +1345,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
> > > > @@ -1444,7 +1448,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,
> > > > @@ -1452,6 +1457,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);
> > > > @@ -1461,6 +1471,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 d072a91bac9..b9210d84f6b 100644
> > > > --- a/gdb/dwarf2/line-header.c
> > > > +++ b/gdb/dwarf2/line-header.c
> > > > @@ -102,50 +102,57 @@ 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;
> > > >  }
> > > >
> > > > -/* 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.  */
> > > > +
> > > > +/* Like read_formatted_entries but the .debug_line and .debug_line_str
> > > > +   are stored in LINE_BUFP and LINE_STR_DATA.  This is used for cases
> > > > +   where these sections are read from separate files without necessarily
> > > > +   having access to the entire debuginfo file they originate from.  */
> > > >
> > > >  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 *parent_bfd, 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;
> > > >
> > > > -  format_count = read_1_byte (abfd, buf);
> > > > +  format_count = read_1_byte (parent_bfd, buf);
> > > >    buf += 1;
> > > >    format_header_data = buf;
> > > >    for (formati = 0; formati < format_count; formati++)
> > > >      {
> > > > -      read_unsigned_leb128 (abfd, buf, &bytes_read);
> > > > +      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> > > >        buf += bytes_read;
> > > > -      read_unsigned_leb128 (abfd, buf, &bytes_read);
> > > > +      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> > > >        buf += bytes_read;
> > > >      }
> > > >
> > > > -  data_count = read_unsigned_leb128 (abfd, buf, &bytes_read);
> > > > +  data_count = read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> > > >    buf += bytes_read;
> > > >    for (datai = 0; datai < data_count; datai++)
> > > >      {
> > > > @@ -154,10 +161,10 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > > >
> > > >        for (formati = 0; formati < format_count; formati++)
> > > >         {
> > > > -         ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
> > > > +         ULONGEST content_type = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
> > > >           format += bytes_read;
> > > >
> > > > -         ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
> > > > +         ULONGEST form  = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
> > > >           format += bytes_read;
> > > >
> > > >           gdb::optional<const char *> string;
> > > > @@ -166,36 +173,48 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > > >           switch (form)
> > > >             {
> > > >             case DW_FORM_string:
> > > > -             string.emplace (read_direct_string (abfd, buf, &bytes_read));
> > > > +             string.emplace (read_direct_string (parent_bfd, buf, &bytes_read));
> > > >               buf += bytes_read;
> > > >               break;
> > > >
> > > >             case DW_FORM_line_strp:
> > > >               {
> > > > -               const char *str
> > > > -                 = per_objfile->read_line_string (buf, offset_size);
> > > > +               if (line_str_data.empty ())
> > > > +                 error (_("Dwarf Error: DW_FORM_line_strp used without " \
> > > > +                          "required section"));
> > > > +               if (line_str_data.size () <= offset_size)
> > > > +                 error (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
> > > > +                          "of section .debug_line"));
> > > > +
> > > > +               ULONGEST str_offset = read_offset (parent_bfd, 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));
> > > > +             uint.emplace (read_1_byte (parent_bfd, buf));
> > > >               buf += 1;
> > > >               break;
> > > >
> > > >             case DW_FORM_data2:
> > > > -             uint.emplace (read_2_bytes (abfd, buf));
> > > > +             uint.emplace (read_2_bytes (parent_bfd, buf));
> > > >               buf += 2;
> > > >               break;
> > > >
> > > >             case DW_FORM_data4:
> > > > -             uint.emplace (read_4_bytes (abfd, buf));
> > > > +             uint.emplace (read_4_bytes (parent_bfd, buf));
> > > >               buf += 4;
> > > >               break;
> > > >
> > > >             case DW_FORM_data8:
> > > > -             uint.emplace (read_8_bytes (abfd, buf));
> > > > +             uint.emplace (read_8_bytes (parent_bfd, buf));
> > > >               buf += 8;
> > > >               break;
> > > >
> > > > @@ -205,7 +224,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > > >               break;
> > > >
> > > >             case DW_FORM_udata:
> > > > -             uint.emplace (read_unsigned_leb128 (abfd, buf, &bytes_read));
> > > > +             uint.emplace (read_unsigned_leb128 (parent_bfd, buf, &bytes_read));
> > > >               buf += bytes_read;
> > > >               break;
> > > >
> > > > @@ -248,28 +267,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 *parent_bfd,
> > > > +                         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,62 +298,65 @@ 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 (parent_bfd, 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);
> > > > +  lh->version = read_2_bytes (parent_bfd, line_ptr);
> > > >    line_ptr += 2;
> > > >    if (lh->version > 5)
> > > >      {
> > > >        /* 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)
> > > >      {
> > > >        gdb_byte segment_selector_size;
> > > >
> > > >        /* Skip address size.  */
> > > > -      read_1_byte (abfd, line_ptr);
> > > > +      read_1_byte (parent_bfd, line_ptr);
> > > >        line_ptr += 1;
> > > >
> > > > -      segment_selector_size = read_1_byte (abfd, line_ptr);
> > > > +      segment_selector_size = read_1_byte (parent_bfd, line_ptr);
> > > >        line_ptr += 1;
> > > >        if (segment_selector_size != 0)
> > > >         {
> > > >           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);
> > > > +  LONGEST header_length = read_offset (parent_bfd, 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);
> > > > +
> > > > +  lh->minimum_instruction_length = read_1_byte (parent_bfd, line_ptr);
> > > >    line_ptr += 1;
> > > >
> > > >    if (lh->version >= 4)
> > > >      {
> > > > -      lh->maximum_ops_per_instruction = read_1_byte (abfd, line_ptr);
> > > > +      lh->maximum_ops_per_instruction = read_1_byte (parent_bfd, line_ptr);
> > > >        line_ptr += 1;
> > > >      }
> > > >    else
> > > > @@ -345,41 +369,47 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > > >                    "in `.debug_line' section"));
> > > >      }
> > > >
> > > > -  lh->default_is_stmt = read_1_byte (abfd, line_ptr);
> > > > +  lh->default_is_stmt = read_1_byte (parent_bfd, line_ptr);
> > > >    line_ptr += 1;
> > > > -  lh->line_base = read_1_signed_byte (abfd, line_ptr);
> > > > +
> > > > +  lh->line_base = read_1_signed_byte (parent_bfd, line_ptr);
> > > >    line_ptr += 1;
> > > > -  lh->line_range = read_1_byte (abfd, line_ptr);
> > > > +
> > > > +  lh->line_range = read_1_byte (parent_bfd, line_ptr);
> > > >    line_ptr += 1;
> > > > -  lh->opcode_base = read_1_byte (abfd, line_ptr);
> > > > +
> > > > +  lh->opcode_base = read_1_byte (parent_bfd, 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.  */
> > > >    for (i = 1; i < lh->opcode_base; ++i)
> > > >      {
> > > > -      lh->standard_opcode_lengths[i] = read_1_byte (abfd, line_ptr);
> > > > +      lh->standard_opcode_lengths[i] = read_1_byte (parent_bfd, line_ptr);
> > > >        line_ptr += 1;
> > > >      }
> > > >
> > > >    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
> > > > +       (parent_bfd, &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
> > > > +       (parent_bfd, &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 +417,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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
> > > >         {
> > > >           line_ptr += bytes_read;
> > > >           lh->add_include_dir (cur_dir);
> > > > @@ -395,17 +425,17 @@ 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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
> > > >         {
> > > >           unsigned int mod_time, length;
> > > >           dir_index d_index;
> > > >
> > > >           line_ptr += bytes_read;
> > > > -         d_index = (dir_index) read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > > > +         d_index = (dir_index) read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> > > >           line_ptr += bytes_read;
> > > > -         mod_time = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > > > +         mod_time = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> > > >           line_ptr += bytes_read;
> > > > -         length = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > > > +         length = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> > > >           line_ptr += bytes_read;
> > > >
> > > >           lh->add_file_name (cur_file, d_index, mod_time, length);
> > > > @@ -413,9 +443,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..22db9f9aa78 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 *parent_bfd, 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 da88a8b405c..c0e51357ce2 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.
> > > > @@ -175,6 +178,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;
> > > > @@ -182,6 +194,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
> > > > @@ -217,6 +238,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
> > > > @@ -590,6 +635,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,
> > > > @@ -618,6 +674,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 0c5689c63ef..876e3aedcf1 100644
> > > > --- a/gdb/dwarf2/read.c
> > > > +++ b/gdb/dwarf2/read.c
> > > > @@ -81,6 +81,7 @@
> > > >  #include "gdbsupport/gdb_optional.h"
> > > >  #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>
> > > > @@ -2135,6 +2136,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
> > > > +  (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
> > > > +  (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 (lh.release ());
> > > > +    }
> > > > +
> > > > +  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 6ed0be7203b..49fea22c092 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;
> > > > @@ -969,4 +970,40 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
> > > >  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
> > > > +    (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 bbd21287b28..110864adac3 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 ff57c6e32b7..b5c6929fcf7 100644
> > > > --- a/gdb/testsuite/gdb.debuginfod/section.exp
> > > > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > > > @@ -171,6 +171,27 @@ proc_with_prefix local_url { } {
> > > >      # during backtrace.
> > > >      set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
> > > >      gdb_test "bt" $res "break backtrace"
> > > > +
> > > > +    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:37" $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"
> > > >  }
> > > >
> > > >  # Create CACHE and DB directories ready for debuginfod to use.
> > > > diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> > > > index 9f643b1ce95..fde46bfbe94 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 70a7145741f..7de8796aee0 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.41.0
> > > >


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

* [PING*5][PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo
  2023-12-12 15:00         ` [PING*4][PATCH " Aaron Merey
@ 2023-12-20 14:57           ` Aaron Merey
  0 siblings, 0 replies; 31+ messages in thread
From: Aaron Merey @ 2023-12-20 14:57 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Tue, Dec 12, 2023 at 10:00 AM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Thu, Nov 30, 2023 at 11:29 AM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Mon, Nov 20, 2023 at 1:38 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Ping
> > >
> > > Thanks,
> > > Aaron
> > >
> > > On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > >
> > > > Ping
> > > >
> > > > Thanks,
> > > > Aaron
> > > >
> > > > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > > >
> > > > > v6: https://sourceware.org/pipermail/gdb-patches/2023-October/203147.html
> > > > >
> > > > > v7 adds support for buffering output stream flush().  Newline prefix
> > > > > states have been removed from this patch and instead added to patch 4/4
> > > > > in this series.
> > > > >
> > > > > Commit message:
> > > > >
> > > > > 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-mi.c       |   3 +
> > > > >  gdb/stack.c              |  35 +++++---
> > > > >  gdb/thread.c             | 171 ++++++++++++++++++++---------------
> > > > >  gdb/ui-file.h            |   2 +-
> > > > >  gdb/ui-out.c             | 144 ++++++++++++++++++++++++++++++
> > > > >  gdb/ui-out.h             | 186 +++++++++++++++++++++++++++++++++++++++
> > > > >  11 files changed, 493 insertions(+), 95 deletions(-)
> > > > >
> > > > > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > > > > index 20d3d93f1ad..c919622d418 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 902af405cc6..b36fb8c35de 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;
> > > > >      }
> > > > > @@ -296,10 +297,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 4fde96800fb..7c1a7cca74f 100644
> > > > > --- a/gdb/infrun.c
> > > > > +++ b/gdb/infrun.c
> > > > > @@ -8788,10 +8788,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;
> > > > > @@ -8820,6 +8820,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-mi.c b/gdb/python/py-mi.c
> > > > > index a7b4f4fa3cf..ba913bf1fee 100644
> > > > > --- a/gdb/python/py-mi.c
> > > > > +++ b/gdb/python/py-mi.c
> > > > > @@ -61,6 +61,9 @@ class py_ui_out : public ui_out
> > > > >      return current ().obj.release ();
> > > > >    }
> > > > >
> > > > > +  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 0b35d62f82f..0560261144c 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 (gdb::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 c8145da59bc..f6cf2eb9cf4 100644
> > > > > --- a/gdb/thread.c
> > > > > +++ b/gdb/thread.c
> > > > > @@ -1064,6 +1064,103 @@ 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;
> > > > > +
> > > > > +  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.  */
> > > > > @@ -1147,82 +1244,12 @@ 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;
> > > > >
> > > > > -         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 defa8f9dfa4..9f643b1ce95 100644
> > > > > --- a/gdb/ui-out.c
> > > > > +++ b/gdb/ui-out.c
> > > > > @@ -871,3 +871,147 @@ 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.  */ }
> > > > > +
> > > > > +buffer_group::~buffer_group ()
> > > > > +{ /* 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..70a7145741f 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,187 @@ 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);
> > > > > +
> > > > > +  ~buffer_group ();
> > > > > +
> > > > > +  /* 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.  */
> > > > > +  gdb::optional<buffering_file> m_buffered_current_uiout;
> > > > > +
> > > > > +  /* Additional ui_out being buffered.  */
> > > > > +  ui_out *m_uiout;
> > > > > +
> > > > > +  /* Buffer for m_uiout's output stream.  */
> > > > > +  gdb::optional<buffering_file> m_buffered_uiout;
> > > > > +};
> > > > > +
> > > > >  #endif /* UI_OUT_H */
> > > > > --
> > > > > 2.41.0
> > > > >


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

* [PING*5][PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator
  2023-12-12 15:01         ` [PING*4][PATCH " Aaron Merey
@ 2023-12-20 14:57           ` Aaron Merey
  0 siblings, 0 replies; 31+ messages in thread
From: Aaron Merey @ 2023-12-20 14:57 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Tue, Dec 12, 2023 at 10:01 AM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Thu, Nov 30, 2023 at 11:30 AM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Mon, Nov 20, 2023 at 1:39 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Ping
> > >
> > > Thanks,
> > > Aaron
> > >
> > > On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > >
> > > > Ping
> > > >
> > > > Thanks,
> > > > Aaron
> > > >
> > > > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > > >
> > > > > v1: https://sourceware.org/pipermail/gdb-patches/2023-June/199984.html
> > > > >
> > > > > v2 removes unwrapping_reverse_objfile_iterator and adds
> > > > > basic_safe_reverse_range and basic_safe_reverse_iterator.
> > > > >
> > > > > 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 becasue 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                          |   8 +-
> > > > >  gdb/objfiles.h                          |   8 +-
> > > > >  gdb/progspace.c                         |  19 ++++-
> > > > >  gdb/progspace.h                         |  31 ++++---
> > > > >  gdb/testsuite/gdb.python/py-objfile.exp |   2 +-
> > > > >  gdbsupport/safe-iterator.h              | 106 ++++++++++++++++++++++++
> > > > >  7 files changed, 154 insertions(+), 27 deletions(-)
> > > > >
> > > > > diff --git a/gdb/jit.c b/gdb/jit.c
> > > > > index 9e8325ab803..a39fdc5a96d 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..9822c179962 100644
> > > > > --- a/gdb/objfiles.c
> > > > > +++ b/gdb/objfiles.c
> > > > > @@ -793,14 +793,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 4b8aa9bfcec..c20b63ceadf 100644
> > > > > --- a/gdb/objfiles.h
> > > > > +++ b/gdb/objfiles.h
> > > > > @@ -698,13 +698,17 @@ struct objfile
> > > > >
> > > > >  private:
> > > > >
> > > > > +  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>;
> > > > > +
> > > > >    /* Ensure that partial symbols have been read and return the "quick" (aka
> > > > >       partial) symbol functions for this symbol reader.  */
> > > > > -  const std::forward_list<quick_symbol_functions_up> &
> > > > > +  qf_safe_range
> > > > >    qf_require_partial_symbols ()
> > > > >    {
> > > > >      this->require_partial_symbols (true);
> > > > > -    return qf;
> > > > > +    return qf_safe_range (qf_range (qf.begin (), qf.end ()));
> > > > >    }
> > > > >
> > > > >  public:
> > > > > diff --git a/gdb/progspace.c b/gdb/progspace.c
> > > > > index 839707e9d71..c0fca1dace7 100644
> > > > > --- a/gdb/progspace.c
> > > > > +++ b/gdb/progspace.c
> > > > > @@ -143,19 +143,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));
> > > > >      }
> > > > >  }
> > > > >
> > > > > @@ -184,6 +184,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 a22e427400e..17bb1710ccf 100644
> > > > > --- a/gdb/progspace.h
> > > > > +++ b/gdb/progspace.h
> > > > > @@ -214,28 +214,32 @@ struct program_space
> > > > >         unwrapping_objfile_iterator (objfiles_list.end ()));
> > > > >    }
> > > > >
> > > > > -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> > > > > +  using objfiles_safe_range = iterator_range<objfile_list::iterator>;
> > > > > +  using objfiles_safe_reverse_range
> > > > > +    = basic_safe_reverse_range<objfiles_safe_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_safe_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);
> > > > > @@ -250,6 +254,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/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 ccd772ca2a5..9f57c1543cf 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 gdb::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.41.0
> > > > >


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

* [PING*5][PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading
  2023-12-12 15:01         ` [PING*4][PATCH " Aaron Merey
@ 2023-12-20 14:57           ` Aaron Merey
  0 siblings, 0 replies; 31+ messages in thread
From: Aaron Merey @ 2023-12-20 14:57 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Tue, Dec 12, 2023 at 10:01 AM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Thu, Nov 30, 2023 at 11:30 AM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Mon, Nov 20, 2023 at 1:39 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Ping
> > >
> > > Thanks,
> > > Aaron
> > >
> > > On Sun, Nov 12, 2023 at 3:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > >
> > > > Ping
> > > >
> > > > Thanks,
> > > > Aaron
> > > >
> > > > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > > >
> > > > > v3: https://sourceware.org/pipermail/gdb-patches/2023-June/199987.html
> > > > >
> > > > > v4 improves testcases when running with --target_board=native-gdbserver.
> > > > >
> > > > > v4 also fixes a bug where objfile observers could clear selected_frame
> > > > > if debuginfo was downloaded during get_selected_frame.
> > > > >
> > > > > 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                | 156 +++++++++++++++--
> > > > >  gdb/dwarf2/read.c                          | 146 +++++++++++++++-
> > > > >  gdb/dwarf2/read.h                          |  10 ++
> > > > >  gdb/dwarf2/section.c                       |   3 +-
> > > > >  gdb/elfread.c                              |   2 +-
> > > > >  gdb/frame.c                                |   7 +
> > > > >  gdb/objfile-flags.h                        |   4 +
> > > > >  gdb/objfiles.h                             |  20 +++
> > > > >  gdb/quick-symbol.h                         |   4 +
> > > > >  gdb/symfile.c                              |  13 +-
> > > > >  gdb/symtab.c                               |  18 +-
> > > > >  gdb/testsuite/gdb.debuginfod/libsection1.c |  40 +++++
> > > > >  gdb/testsuite/gdb.debuginfod/libsection2.c |  37 +++++
> > > > >  gdb/testsuite/gdb.debuginfod/section.c     |  29 ++++
> > > > >  gdb/testsuite/gdb.debuginfod/section.exp   | 184 +++++++++++++++++++++
> > > > >  gdb/testsuite/lib/debuginfod-support.exp   |  27 ++-
> > > > >  21 files changed, 746 insertions(+), 24 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 abc8d613482..257f0316731 100644
> > > > > --- a/gdb/dwarf2/frame.c
> > > > > +++ b/gdb/dwarf2/frame.c
> > > > > @@ -1617,6 +1617,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 cfa45435fbd..9d18717fe56 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 0e74857eb1a..4a44cdbc223 100644
> > > > > --- a/gdb/dwarf2/public.h
> > > > > +++ b/gdb/dwarf2/public.h
> > > > > @@ -40,4 +40,11 @@ extern void dwarf2_initialize_objfile (struct objfile *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 e789e9c2654..da88a8b405c 100644
> > > > > --- a/gdb/dwarf2/read-gdb-index.c
> > > > > +++ b/gdb/dwarf2/read-gdb-index.c
> > > > > @@ -139,6 +139,7 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > > > >       gdb.dwarf2/gdb-index.exp testcase.  */
> > > > >    void dump (struct objfile *objfile) override;
> > > > >
> > > > > +  /* Calls do_expand_matching_symbols and downloads debuginfo if necessary.  */
> > > > >    void expand_matching_symbols
> > > > >      (struct objfile *,
> > > > >       const lookup_name_info &lookup_name,
> > > > > @@ -146,6 +147,14 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > > > >       int global,
> > > > >       symbol_compare_ftype *ordered_compare) override;
> > > > >
> > > > > +  void do_expand_matching_symbols
> > > > > +    (struct objfile *,
> > > > > +     const lookup_name_info &lookup_name,
> > > > > +     domain_enum domain,
> > > > > +     int global,
> > > > > +     symbol_compare_ftype *ordered_compare);
> > > > > +
> > > > > +  /* Calls do_expand_symtabs_matching and downloads debuginfo if necessary.  */
> > > > >    bool expand_symtabs_matching
> > > > >      (struct objfile *objfile,
> > > > >       gdb::function_view<expand_symtabs_file_matcher_ftype> file_matcher,
> > > > > @@ -155,8 +164,59 @@ struct dwarf2_gdb_index : public dwarf2_base_index_functions
> > > > >       block_search_flags search_flags,
> > > > >       domain_enum domain,
> > > > >       enum search_domain kind) override;
> > > > > +
> > > > > +  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
> > > > > @@ -318,7 +378,7 @@ dw2_symtab_iter_next (struct dw2_symtab_iterator *iter,
> > > > >  }
> > > > >
> > > > >  void
> > > > > -dwarf2_gdb_index::expand_matching_symbols
> > > > > +dwarf2_gdb_index::do_expand_matching_symbols
> > > > >    (struct objfile *objfile,
> > > > >     const lookup_name_info &name, domain_enum domain,
> > > > >     int global,
> > > > > @@ -356,6 +416,29 @@ dwarf2_gdb_index::expand_matching_symbols
> > > > >      }, per_objfile);
> > > > >  }
> > > > >
> > > > > +void
> > > > > +dwarf2_gdb_index::expand_matching_symbols
> > > > > +  (struct objfile *objfile,
> > > > > +   const lookup_name_info &lookup_name,
> > > > > +   domain_enum domain,
> > > > > +   int global,
> > > > > +   symbol_compare_ftype *ordered_compare)
> > > > > +{
> > > > > +  try
> > > > > +    {
> > > > > +      do_expand_matching_symbols (objfile, lookup_name, domain,
> > > > > +                                 global, ordered_compare);
> > > > > +    }
> > > > > +  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;
> > > > > +    }
> > > > > +}
> > > > > +
> > > > >  /* Helper for dw2_expand_matching symtabs.  Called on each symbol
> > > > >     matched, to expand corresponding CUs that were marked.  IDX is the
> > > > >     index of the symbol name that matched.  */
> > > > > @@ -458,7 +541,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,
> > > > > @@ -507,6 +590,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
> > > > >  {
> > > > > @@ -842,28 +958,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 ea0b2328a3e..0c5689c63ef 100644
> > > > > --- a/gdb/dwarf2/read.c
> > > > > +++ b/gdb/dwarf2/read.c
> > > > > @@ -34,6 +34,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"
> > > > > @@ -95,6 +96,8 @@
> > > > >  #include "split-name.h"
> > > > >  #include "gdbsupport/parallel-for.h"
> > > > >  #include "gdbsupport/thread-pool.h"
> > > > > +#include "inferior.h"
> > > > > +#include "debuginfod-support.h"
> > > > >
> > > > >  /* When == 1, print basic high level tracing messages.
> > > > >     When > 1, be more verbose.
> > > > > @@ -3188,7 +3191,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,
> > > > > @@ -3219,6 +3222,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,
> > > > > @@ -3375,6 +3404,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 ();
> > > > >
> > > > >  /* See dwarf2/public.h.  */
> > > > > @@ -3440,10 +3492,102 @@ dwarf2_initialize_objfile (struct objfile *objfile)
> > > > >        return;
> > > > >      }
> > > > >
> > > > > +  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;
> > > > > +      return;
> > > > > +    }
> > > > > +
> > > > >    global_index_cache.miss ();
> > > > >    objfile->qf.push_front (make_cooked_index_funcs ());
> > > > >  }
> > > > >
> > > > > +/* See read.h.  */
> > > > > +
> > > > > +void
> > > > > +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> > > > > +                                dwarf2_base_index_functions *fncs)
> > > > > +{
> > > > > +  gdb_assert (objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> > > > > +
> > > > > +  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)
> > > > > +    goto unset;
> > > > > +
> > > > > +  filename = bfd_get_filename (objfile->obfd.get ());
> > > > > +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> > > > > +                                  filename, &symfile_path);
> > > > > +  if (fd.get () < 0)
> > > > > +    goto unset;
> > > > > +
> > > > > +  /* 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);
> > > > > +      goto unset;
> > > > > +    }
> > > > > +
> > > > > +  /* 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);
> > > > > +
> > > > > +unset:
> > > > > +  objfile->remove_deferred_status ();
> > > > > +}
> > > > > +
> > > > > +/* 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;
> > > > > +
> > > > > +  gdb::unique_xmalloc_ptr<char> index_path;
> > > > > +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> > > > > +
> > > > > +  if (build_id == nullptr)
> > > > > +    return false;
> > > > > +
> > > > > +  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;
> > > > > +}
> > > > > +
> > > > >
> > > > >
> > > > >  /* Build a partial symbol table.  */
> > > > > diff --git a/gdb/dwarf2/read.h b/gdb/dwarf2/read.h
> > > > > index dc7abf23ba4..6ed0be7203b 100644
> > > > > --- a/gdb/dwarf2/read.h
> > > > > +++ b/gdb/dwarf2/read.h
> > > > > @@ -883,6 +883,10 @@ struct dwarf2_base_index_functions : public quick_symbol_functions
> > > > >       CORE_ADDR pc, struct obj_section *section, int warn_if_readin)
> > > > >         override final;
> > > > >
> > > > > +  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
> > > > >    {
> > > > > @@ -959,4 +963,10 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
> > > > >                                        dwarf2_section_info *section,
> > > > >                                        addrmap *mutable_map);
> > > > >
> > > > > +/* 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 7900dfbc388..5c89598eee7 100644
> > > > > --- a/gdb/elfread.c
> > > > > +++ b/gdb/elfread.c
> > > > > @@ -1235,7 +1235,7 @@ elf_symfile_read_dwarf2 (struct objfile *objfile,
> > > > >             symbol_file_add_separate (debug_bfd, debugfile.c_str (),
> > > > >                                       symfile_flags, objfile);
> > > > >         }
> > > > > -      else
> > > > > +      else if (!dwarf2_has_separate_index (objfile))
> > > > >         {
> > > > >           has_dwarf2 = false;
> > > > >           const struct bfd_build_id *build_id
> > > > > diff --git a/gdb/frame.c b/gdb/frame.c
> > > > > index 7077016ccba..00dbffed1ef 100644
> > > > > --- a/gdb/frame.c
> > > > > +++ b/gdb/frame.c
> > > > > @@ -1892,6 +1892,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 9dee2ee51a0..fb3f741c899 100644
> > > > > --- a/gdb/objfile-flags.h
> > > > > +++ b/gdb/objfile-flags.h
> > > > > @@ -60,6 +60,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 c20b63ceadf..ea9bd2157dc 100644
> > > > > --- a/gdb/objfiles.h
> > > > > +++ b/gdb/objfiles.h
> > > > > @@ -612,6 +612,26 @@ struct objfile
> > > > >    /* See quick_symbol_functions.  */
> > > > >    void require_partial_symbols (bool verbose);
> > > > >
> > > > > +  /* Indicate that the aquisition of this objfile's separate debug objfile
> > > > > +     is no longer deferred.  Used when the debug objfile has been aquired
> > > > > +     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 a7fea2ccb49..e7163503e39 100644
> > > > > --- a/gdb/quick-symbol.h
> > > > > +++ b/gdb/quick-symbol.h
> > > > > @@ -225,6 +225,10 @@ struct quick_symbol_functions
> > > > >    virtual void read_partial_symbols (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.c b/gdb/symfile.c
> > > > > index eebc5ea44b9..0491a33e8f5 100644
> > > > > --- a/gdb/symfile.c
> > > > > +++ b/gdb/symfile.c
> > > > > @@ -991,6 +991,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.  */
> > > > > @@ -1001,7 +1005,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 ();
> > > > >      }
> > > > > @@ -1122,6 +1127,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);
> > > > >
> > > > >    bfd_cache_close_all ();
> > > > > diff --git a/gdb/symtab.c b/gdb/symtab.c
> > > > > index 5ec56f4f2af..bd01a75189d 100644
> > > > > --- a/gdb/symtab.c
> > > > > +++ b/gdb/symtab.c
> > > > > @@ -2925,14 +2925,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..60824b415c6
> > > > > --- /dev/null
> > > > > +++ b/gdb/testsuite/gdb.debuginfod/libsection1.c
> > > > > @@ -0,0 +1,40 @@
> > > > > +/* 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 *);
> > > > > +
> > > > > +void
> > > > > +libsection1_test ()
> > > > > +{
> > > > > +  pthread_t thr;
> > > > > +
> > > > > +  printf ("In libsection1\n");
> > > > > +  libsection2_test ();
> > > > > +
> > > > > +  pthread_create (&thr, NULL, libsection2_thread_test, NULL);
> > > > > +
> > > > > +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> > > > > +  sleep (3);
> > > > > +  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..629a67f94a5
> > > > > --- /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>
> > > > > +
> > > > > +void
> > > > > +libsection2_test ()
> > > > > +{
> > > > > +  printf ("In libsection2\n");
> > > > > +}
> > > > > +
> > > > > +void *
> > > > > +libsection2_thread_test (void *arg)
> > > > > +{
> > > > > +  (void) arg;
> > > > > +
> > > > > +  printf ("In thread test\n");
> > > > > +
> > > > > +  while (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..d391a8f898e
> > > > > --- /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..ff57c6e32b7
> > > > > --- /dev/null
> > > > > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > > > > @@ -0,0 +1,184 @@
> > > > > +# 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 { [have_index $lib_sl1] != "gdb_index"
> > > > > +     && [add_gdb_index $lib_sl1] == 0 } {
> > > > > +    untested "failed to add .gdb_index to $libfile1"
> > > > > +    return -1
> > > > > +}
> > > > > +
> > > > > +if { [have_index $lib_sl2] != "gdb_index"
> > > > > +     && [add_gdb_index $lib_sl2] == 0 } {
> > > > > +    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 res 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
> > > > > +
> > > > > +    if {![runto_main]} {
> > > > > +       return
> > > > > +    }
> > > > > +}
> > > > > +
> > > > > +# 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
> > > > > +
> > > > > +    # Download .gdb_index for solibs.
> > > > > +    set res ".*section \.gdb_index for $lib_sl1.*\
> > > > > +       section \.gdb_index for $lib_sl2.*"
> > > > > +    clean_restart_with_prompt $sectexec $res "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"
> > > > > +
> > > > > +    # Check that download progress message is correctly formatted
> > > > > +    # during backtrace.
> > > > > +    set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
> > > > > +    gdb_test "bt" $res "break backtrace"
> > > > > +}
> > > > > +
> > > > > +# 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
> > > > > +       }
> > > > > +    }
> > > > > +}
> > > > > --
> > > > > 2.41.0
> > > > >


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

* [PING*5][PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading
  2023-12-12 15:08         ` [PING*4][PATCH " Aaron Merey
@ 2023-12-20 14:58           ` Aaron Merey
  0 siblings, 0 replies; 31+ messages in thread
From: Aaron Merey @ 2023-12-20 14:58 UTC (permalink / raw)
  To: gdb-patches; +Cc: Andrew Burgess

Ping

Thanks,
Aaron

On Tue, Dec 12, 2023 at 10:08 AM Aaron Merey <amerey@redhat.com> wrote:
>
> Ping
>
> Thanks,
> Aaron
>
> On Thu, Nov 30, 2023 at 11:30 AM Aaron Merey <amerey@redhat.com> wrote:
> >
> > Ping
> >
> > Thanks,
> > Aaron
> >
> > On Mon, Nov 20, 2023 at 1:40 PM Aaron Merey <amerey@redhat.com> wrote:
> > >
> > > Ping
> > >
> > > Thanks,
> > > Aaron
> > >
> > > On Sun, Nov 12, 2023 at 3:21 PM Aaron Merey <amerey@redhat.com> wrote:
> > > >
> > > > Ping
> > > >
> > > > Thanks,
> > > > Aaron
> > > >
> > > > On Fri, Oct 27, 2023 at 8:20 PM Aaron Merey <amerey@redhat.com> wrote:
> > > > >
> > > > > v4: https://sourceware.org/pipermail/gdb-patches/2023-August/201651.html
> > > > >
> > > > > v5 adds prefix_state to progress_update objects to track when
> > > > > a newline prefix needs to be added to a download progress message.
> > > > > This is used to correctly format progress messages that occur during
> > > > > command autocompletion.
> > > > >
> > > > > Commit message:
> > > > >
> > > > > 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                 | 215 +++++++++++++++--------
> > > > >  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 |  21 +++
> > > > >  gdb/ui-out.c                             |   3 +
> > > > >  gdb/ui-out.h                             |  20 +++
> > > > >  11 files changed, 531 insertions(+), 81 deletions(-)
> > > > >
> > > > > diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> > > > > index c919622d418..a570a2d939d 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 2abf3998345..9c299f3eb65 100644
> > > > > --- a/gdb/completer.c
> > > > > +++ b/gdb/completer.c
> > > > > @@ -1345,6 +1345,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
> > > > > @@ -1444,7 +1448,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,
> > > > > @@ -1452,6 +1457,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);
> > > > > @@ -1461,6 +1471,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 d072a91bac9..b9210d84f6b 100644
> > > > > --- a/gdb/dwarf2/line-header.c
> > > > > +++ b/gdb/dwarf2/line-header.c
> > > > > @@ -102,50 +102,57 @@ 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;
> > > > >  }
> > > > >
> > > > > -/* 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.  */
> > > > > +
> > > > > +/* Like read_formatted_entries but the .debug_line and .debug_line_str
> > > > > +   are stored in LINE_BUFP and LINE_STR_DATA.  This is used for cases
> > > > > +   where these sections are read from separate files without necessarily
> > > > > +   having access to the entire debuginfo file they originate from.  */
> > > > >
> > > > >  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 *parent_bfd, 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;
> > > > >
> > > > > -  format_count = read_1_byte (abfd, buf);
> > > > > +  format_count = read_1_byte (parent_bfd, buf);
> > > > >    buf += 1;
> > > > >    format_header_data = buf;
> > > > >    for (formati = 0; formati < format_count; formati++)
> > > > >      {
> > > > > -      read_unsigned_leb128 (abfd, buf, &bytes_read);
> > > > > +      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> > > > >        buf += bytes_read;
> > > > > -      read_unsigned_leb128 (abfd, buf, &bytes_read);
> > > > > +      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> > > > >        buf += bytes_read;
> > > > >      }
> > > > >
> > > > > -  data_count = read_unsigned_leb128 (abfd, buf, &bytes_read);
> > > > > +  data_count = read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
> > > > >    buf += bytes_read;
> > > > >    for (datai = 0; datai < data_count; datai++)
> > > > >      {
> > > > > @@ -154,10 +161,10 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > > > >
> > > > >        for (formati = 0; formati < format_count; formati++)
> > > > >         {
> > > > > -         ULONGEST content_type = read_unsigned_leb128 (abfd, format, &bytes_read);
> > > > > +         ULONGEST content_type = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
> > > > >           format += bytes_read;
> > > > >
> > > > > -         ULONGEST form  = read_unsigned_leb128 (abfd, format, &bytes_read);
> > > > > +         ULONGEST form  = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
> > > > >           format += bytes_read;
> > > > >
> > > > >           gdb::optional<const char *> string;
> > > > > @@ -166,36 +173,48 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > > > >           switch (form)
> > > > >             {
> > > > >             case DW_FORM_string:
> > > > > -             string.emplace (read_direct_string (abfd, buf, &bytes_read));
> > > > > +             string.emplace (read_direct_string (parent_bfd, buf, &bytes_read));
> > > > >               buf += bytes_read;
> > > > >               break;
> > > > >
> > > > >             case DW_FORM_line_strp:
> > > > >               {
> > > > > -               const char *str
> > > > > -                 = per_objfile->read_line_string (buf, offset_size);
> > > > > +               if (line_str_data.empty ())
> > > > > +                 error (_("Dwarf Error: DW_FORM_line_strp used without " \
> > > > > +                          "required section"));
> > > > > +               if (line_str_data.size () <= offset_size)
> > > > > +                 error (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
> > > > > +                          "of section .debug_line"));
> > > > > +
> > > > > +               ULONGEST str_offset = read_offset (parent_bfd, 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));
> > > > > +             uint.emplace (read_1_byte (parent_bfd, buf));
> > > > >               buf += 1;
> > > > >               break;
> > > > >
> > > > >             case DW_FORM_data2:
> > > > > -             uint.emplace (read_2_bytes (abfd, buf));
> > > > > +             uint.emplace (read_2_bytes (parent_bfd, buf));
> > > > >               buf += 2;
> > > > >               break;
> > > > >
> > > > >             case DW_FORM_data4:
> > > > > -             uint.emplace (read_4_bytes (abfd, buf));
> > > > > +             uint.emplace (read_4_bytes (parent_bfd, buf));
> > > > >               buf += 4;
> > > > >               break;
> > > > >
> > > > >             case DW_FORM_data8:
> > > > > -             uint.emplace (read_8_bytes (abfd, buf));
> > > > > +             uint.emplace (read_8_bytes (parent_bfd, buf));
> > > > >               buf += 8;
> > > > >               break;
> > > > >
> > > > > @@ -205,7 +224,7 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
> > > > >               break;
> > > > >
> > > > >             case DW_FORM_udata:
> > > > > -             uint.emplace (read_unsigned_leb128 (abfd, buf, &bytes_read));
> > > > > +             uint.emplace (read_unsigned_leb128 (parent_bfd, buf, &bytes_read));
> > > > >               buf += bytes_read;
> > > > >               break;
> > > > >
> > > > > @@ -248,28 +267,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 *parent_bfd,
> > > > > +                         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,62 +298,65 @@ 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 (parent_bfd, 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);
> > > > > +  lh->version = read_2_bytes (parent_bfd, line_ptr);
> > > > >    line_ptr += 2;
> > > > >    if (lh->version > 5)
> > > > >      {
> > > > >        /* 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)
> > > > >      {
> > > > >        gdb_byte segment_selector_size;
> > > > >
> > > > >        /* Skip address size.  */
> > > > > -      read_1_byte (abfd, line_ptr);
> > > > > +      read_1_byte (parent_bfd, line_ptr);
> > > > >        line_ptr += 1;
> > > > >
> > > > > -      segment_selector_size = read_1_byte (abfd, line_ptr);
> > > > > +      segment_selector_size = read_1_byte (parent_bfd, line_ptr);
> > > > >        line_ptr += 1;
> > > > >        if (segment_selector_size != 0)
> > > > >         {
> > > > >           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);
> > > > > +  LONGEST header_length = read_offset (parent_bfd, 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);
> > > > > +
> > > > > +  lh->minimum_instruction_length = read_1_byte (parent_bfd, line_ptr);
> > > > >    line_ptr += 1;
> > > > >
> > > > >    if (lh->version >= 4)
> > > > >      {
> > > > > -      lh->maximum_ops_per_instruction = read_1_byte (abfd, line_ptr);
> > > > > +      lh->maximum_ops_per_instruction = read_1_byte (parent_bfd, line_ptr);
> > > > >        line_ptr += 1;
> > > > >      }
> > > > >    else
> > > > > @@ -345,41 +369,47 @@ dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
> > > > >                    "in `.debug_line' section"));
> > > > >      }
> > > > >
> > > > > -  lh->default_is_stmt = read_1_byte (abfd, line_ptr);
> > > > > +  lh->default_is_stmt = read_1_byte (parent_bfd, line_ptr);
> > > > >    line_ptr += 1;
> > > > > -  lh->line_base = read_1_signed_byte (abfd, line_ptr);
> > > > > +
> > > > > +  lh->line_base = read_1_signed_byte (parent_bfd, line_ptr);
> > > > >    line_ptr += 1;
> > > > > -  lh->line_range = read_1_byte (abfd, line_ptr);
> > > > > +
> > > > > +  lh->line_range = read_1_byte (parent_bfd, line_ptr);
> > > > >    line_ptr += 1;
> > > > > -  lh->opcode_base = read_1_byte (abfd, line_ptr);
> > > > > +
> > > > > +  lh->opcode_base = read_1_byte (parent_bfd, 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.  */
> > > > >    for (i = 1; i < lh->opcode_base; ++i)
> > > > >      {
> > > > > -      lh->standard_opcode_lengths[i] = read_1_byte (abfd, line_ptr);
> > > > > +      lh->standard_opcode_lengths[i] = read_1_byte (parent_bfd, line_ptr);
> > > > >        line_ptr += 1;
> > > > >      }
> > > > >
> > > > >    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
> > > > > +       (parent_bfd, &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
> > > > > +       (parent_bfd, &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 +417,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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
> > > > >         {
> > > > >           line_ptr += bytes_read;
> > > > >           lh->add_include_dir (cur_dir);
> > > > > @@ -395,17 +425,17 @@ 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 (parent_bfd, line_ptr, &bytes_read)) != nullptr)
> > > > >         {
> > > > >           unsigned int mod_time, length;
> > > > >           dir_index d_index;
> > > > >
> > > > >           line_ptr += bytes_read;
> > > > > -         d_index = (dir_index) read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > > > > +         d_index = (dir_index) read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> > > > >           line_ptr += bytes_read;
> > > > > -         mod_time = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > > > > +         mod_time = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> > > > >           line_ptr += bytes_read;
> > > > > -         length = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
> > > > > +         length = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
> > > > >           line_ptr += bytes_read;
> > > > >
> > > > >           lh->add_file_name (cur_file, d_index, mod_time, length);
> > > > > @@ -413,9 +443,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..22db9f9aa78 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 *parent_bfd, 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 da88a8b405c..c0e51357ce2 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.
> > > > > @@ -175,6 +178,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;
> > > > > @@ -182,6 +194,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
> > > > > @@ -217,6 +238,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
> > > > > @@ -590,6 +635,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,
> > > > > @@ -618,6 +674,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 0c5689c63ef..876e3aedcf1 100644
> > > > > --- a/gdb/dwarf2/read.c
> > > > > +++ b/gdb/dwarf2/read.c
> > > > > @@ -81,6 +81,7 @@
> > > > >  #include "gdbsupport/gdb_optional.h"
> > > > >  #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>
> > > > > @@ -2135,6 +2136,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
> > > > > +  (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
> > > > > +  (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 (lh.release ());
> > > > > +    }
> > > > > +
> > > > > +  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 6ed0be7203b..49fea22c092 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;
> > > > > @@ -969,4 +970,40 @@ extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
> > > > >  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
> > > > > +    (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 bbd21287b28..110864adac3 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 ff57c6e32b7..b5c6929fcf7 100644
> > > > > --- a/gdb/testsuite/gdb.debuginfod/section.exp
> > > > > +++ b/gdb/testsuite/gdb.debuginfod/section.exp
> > > > > @@ -171,6 +171,27 @@ proc_with_prefix local_url { } {
> > > > >      # during backtrace.
> > > > >      set res ".*debug info for $lib_sl1\.\.\.\r\n\#1.*"
> > > > >      gdb_test "bt" $res "break backtrace"
> > > > > +
> > > > > +    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:37" $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"
> > > > >  }
> > > > >
> > > > >  # Create CACHE and DB directories ready for debuginfod to use.
> > > > > diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> > > > > index 9f643b1ce95..fde46bfbe94 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 70a7145741f..7de8796aee0 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.41.0
> > > > >


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

* Re: [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo
  2023-10-28  0:20 ` [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
  2023-11-12 20:20   ` Aaron Merey
@ 2023-12-26 16:28   ` Thiago Jung Bauermann
  2024-01-17 17:49     ` Aaron Merey
  2024-01-17 18:05   ` Andrew Burgess
  2 siblings, 1 reply; 31+ messages in thread
From: Thiago Jung Bauermann @ 2023-12-26 16:28 UTC (permalink / raw)
  To: Aaron Merey; +Cc: aburgess, gdb-patches


Hello,

Nice feature! Just a few small nits:

Aaron Merey <amerey@redhat.com> writes:

> +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)
> +  {

This curly brace should be at column 0. Which naturally affects the
indentation of the whole method.

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

There's a stray semicolon here.

> +
> +/* See ui-out.h.  */
> +
> +void
> +buffered_streams::remove_buffers ()
> +  {

This curly brace should be at column 0.

> +    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.  */ }
> +
> +buffer_group::~buffer_group ()
> +{ /* Nothing.  */ }

If the destructor does nothing, can it be removed?

-- 
Thiago

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

* Re: [PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator
  2023-10-28  0:20 ` [PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator Aaron Merey
  2023-11-12 20:20   ` Aaron Merey
@ 2023-12-26 17:09   ` Thiago Jung Bauermann
  1 sibling, 0 replies; 31+ messages in thread
From: Thiago Jung Bauermann @ 2023-12-26 17:09 UTC (permalink / raw)
  To: Aaron Merey; +Cc: aburgess, gdb-patches


Aaron Merey <amerey@redhat.com> writes:

> v1: https://sourceware.org/pipermail/gdb-patches/2023-June/199984.html
>
> v2 removes unwrapping_reverse_objfile_iterator and adds
> basic_safe_reverse_range and basic_safe_reverse_iterator.
>
> 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 becasue the separate

*because

> debug objfile has just been downloaded.  The use of a safe_range
> prevents this removal from causing iterator invalidation.

<snip>

> diff --git a/gdb/progspace.h b/gdb/progspace.h
> index a22e427400e..17bb1710ccf 100644
> --- a/gdb/progspace.h
> +++ b/gdb/progspace.h
> @@ -214,28 +214,32 @@ struct program_space
>         unwrapping_objfile_iterator (objfiles_list.end ()));
>    }
>  
> -  using objfiles_safe_range = basic_safe_range<objfiles_range>;
> +  using objfiles_safe_range = iterator_range<objfile_list::iterator>;

Does objfile_safe_range still need the safe qualifier? Or should it
be called objfiles_range now?

> +  using objfiles_safe_reverse_range
> +    = basic_safe_reverse_range<objfiles_safe_range>;

<snip>

> 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

Does the fact that this testcase needed change mean that there was an
API break? In my opinion it doesn't and this patch is ok. I think it
would be too much to guarantee a specific ordering of the returned
objfiles.

But I'm mentioning it anyway because perhaps someone has a different
view?

-- 
Thiago

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

* Re: [PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading
  2023-10-28  0:20 ` [PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading Aaron Merey
  2023-11-12 20:20   ` Aaron Merey
@ 2023-12-26 18:35   ` Thiago Jung Bauermann
  1 sibling, 0 replies; 31+ messages in thread
From: Thiago Jung Bauermann @ 2023-12-26 18:35 UTC (permalink / raw)
  To: Aaron Merey; +Cc: aburgess, gdb-patches


Aaron Merey <amerey@redhat.com> writes:

> +void
> +dwarf2_gdb_index::expand_matching_symbols
> +  (struct objfile *objfile,
> +   const lookup_name_info &lookup_name,
> +   domain_enum domain,
> +   int global,
> +   symbol_compare_ftype *ordered_compare)
> +{
> +  try
> +    {
> +      do_expand_matching_symbols (objfile, lookup_name, domain,
> +				  global, ordered_compare);
> +    }
> +  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;

"return" is redundant here. I suggest removing it.

> +    }
> +}
> +void
> +read_full_dwarf_from_debuginfod (struct objfile *objfile,
> +				 dwarf2_base_index_functions *fncs)
> +{
> +  gdb_assert (objfile->flags & OBJF_DOWNLOAD_DEFERRED);
> +
> +  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;

The "goto unset"s in this function make me wonder whether it is
exception-safe. If SCOPE_EXIT is used at this point like so:

SCOPE_EXIT { objfile->remove_deferred_status (); };

can it correctly replace the gotos?

> +
> +  if (build_id == nullptr)
> +    goto unset;
> +
> +  filename = bfd_get_filename (objfile->obfd.get ());
> +  fd = debuginfod_debuginfo_query (build_id->data, build_id->size,
> +				   filename, &symfile_path);
> +  if (fd.get () < 0)
> +    goto unset;
> +
> +  /* 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);
> +      goto unset;
> +    }
> +
> +  /* 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);
> +
> +unset:
> +  objfile->remove_deferred_status ();
> +}
> +
> +/* 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;
> +
> +  gdb::unique_xmalloc_ptr<char> index_path;

I hope this isn't too pedantic, but the index_path declaration can be
moved right above the call to debuginfod_section_query.

> +  const bfd_build_id *build_id = build_id_bfd_get (objfile->obfd.get ());
> +
> +  if (build_id == nullptr)
> +    return false;
> +
> +  scoped_fd fd = debuginfod_section_query (build_id->data,
> +					   build_id->size,
> +					   bfd_get_filename
> +					     (objfile->obfd.get ()),
> +					   ".gdb_index",
> +					   &index_path);

<snip>

> diff --git a/gdb/objfiles.h b/gdb/objfiles.h
> index c20b63ceadf..ea9bd2157dc 100644
> --- a/gdb/objfiles.h
> +++ b/gdb/objfiles.h
> @@ -612,6 +612,26 @@ struct objfile
>    /* See quick_symbol_functions.  */
>    void require_partial_symbols (bool verbose);
>  
> +  /* Indicate that the aquisition of this objfile's separate debug objfile
> +     is no longer deferred.  Used when the debug objfile has been aquired

*acquired

> +     or could not be found.  */
> +  void remove_deferred_status ()

<snip>

> +void
> +libsection1_test ()
> +{
> +  pthread_t thr;
> +
> +  printf ("In libsection1\n");
> +  libsection2_test ();
> +
> +  pthread_create (&thr, NULL, libsection2_thread_test, NULL);
> +
> +  /* Give the new thread a chance to actually enter libsection2_thread_test.  */
> +  sleep (3);

If the testcase is run in a slow simulator, it can take more than 3
seconds. Or in a heavily loaded machine. Can the test use
pthread_cond_wait/pthread_cond_signal instead?


> +  printf ("Cancelling thread\n");
> +
> +  pthread_cancel (thr);
> +}

<snip>

> +# 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 res testname } {

The res argument isn't used by the function.

> +    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
> +
> +    if {![runto_main]} {
> +       return

Isn't this return redundant? Suggest ending the procedure with just
"[runto_main]".

> +    }
> +}

-- 
Thiago

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

* Re: [PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading
  2023-10-28  0:20 ` [PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading Aaron Merey
  2023-11-12 20:21   ` Aaron Merey
@ 2023-12-27  0:30   ` Thiago Jung Bauermann
  1 sibling, 0 replies; 31+ messages in thread
From: Thiago Jung Bauermann @ 2023-12-27  0:30 UTC (permalink / raw)
  To: Aaron Merey; +Cc: aburgess, gdb-patches


Aaron Merey <amerey@redhat.com> writes:

> -/* 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.  */
> +
> +/* Like read_formatted_entries but the .debug_line and .debug_line_str

This function is also called read_formatted_entries, so the comment is a
bit confusing to me. Perhaps start with "Like the other
read_formatted_entries, but ..."?

> +   are stored in LINE_BUFP and LINE_STR_DATA.  This is used for cases
> +   where these sections are read from separate files without necessarily
> +   having access to the entire debuginfo file they originate from.  */
>  
>  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 *parent_bfd, 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))
>  {

<snip>

> @@ -166,36 +173,48 @@ read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
>  	  switch (form)
>  	    {
>  	    case DW_FORM_string:
> -	      string.emplace (read_direct_string (abfd, buf, &bytes_read));
> +	      string.emplace (read_direct_string (parent_bfd, buf, &bytes_read));
>  	      buf += bytes_read;
>  	      break;
>  
>  	    case DW_FORM_line_strp:
>  	      {
> -		const char *str
> -		  = per_objfile->read_line_string (buf, offset_size);
> +		if (line_str_data.empty ())
> +		  error (_("Dwarf Error: DW_FORM_line_strp used without " \
> +			   "required section"));

Is error the correct response here? Or should the bad form just be
complained about and ignored?

> +		if (line_str_data.size () <= offset_size)

I don't understand this check. Shouldn't it be the following?

  if (line_str_data.size () <= str_offset)

> +		  error (_("Dwarf Error: DW_FORM_line_strp pointing outside " \
> +			   "of section .debug_line"));

IIUC, the message should say ".debug_line_str" instead of ".debug_line".
Also, same question about error vs complaint.

> +
> +		ULONGEST str_offset = read_offset (parent_bfd, 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;

<snip>

> +gdb::array_view<const gdb_byte>
> +mapped_debug_line::read_debug_line_separate
> +  (char *filename, std::unique_ptr<index_cache_resource> *resource)

filename can be const char *.

> +{
> +  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 (lh.release ());

This line is destroying a line_header_up just to construct a new one to
put in the vector. I believe you can use std::move here instead to avoid
the extra work.

> +    }
> +
> +  return true;
> +}
> +#endif /* !HAVE_SYS_MMAN_H */

-- 
Thiago

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

* Re: [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo
  2023-12-26 16:28   ` [PATCH " Thiago Jung Bauermann
@ 2024-01-17 17:49     ` Aaron Merey
  0 siblings, 0 replies; 31+ messages in thread
From: Aaron Merey @ 2024-01-17 17:49 UTC (permalink / raw)
  To: Thiago Jung Bauermann; +Cc: gdb-patches

Hi Thiago,

On Tue, Dec 26, 2023 at 11:29 AM Thiago Jung Bauermann
<thiago.bauermann@linaro.org> wrote:
>
> Nice feature! Just a few small nits:

Thanks for reviewing this patch set. I've updated the patch set
with your suggestions. The revised patches have been posted
here: https://sourceware.org/pipermail/gdb-patches/2024-January/205953.html

> > --- 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
>
> Does the fact that this testcase needed change mean that there was an
> API break? In my opinion it doesn't and this patch is ok. I think it
> would be too much to guarantee a specific ordering of the returned
> objfiles.
>
> But I'm mentioning it anyway because perhaps someone has a different
> view?

I agree that it's too much to guarantee a specific ordering of the
objfiles list. I did not change objfile ordering in the revised patches.

Aaron


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

* Re: [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo
  2023-10-28  0:20 ` [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
  2023-11-12 20:20   ` Aaron Merey
  2023-12-26 16:28   ` [PATCH " Thiago Jung Bauermann
@ 2024-01-17 18:05   ` Andrew Burgess
  2 siblings, 0 replies; 31+ messages in thread
From: Andrew Burgess @ 2024-01-17 18:05 UTC (permalink / raw)
  To: Aaron Merey, gdb-patches; +Cc: Aaron Merey

Aaron Merey <amerey@redhat.com> writes:

> v6: https://sourceware.org/pipermail/gdb-patches/2023-October/203147.html
>
> v7 adds support for buffering output stream flush().  Newline prefix
> states have been removed from this patch and instead added to patch 4/4
> in this series.
>
> Commit message:
>
> 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-mi.c       |   3 +
>  gdb/stack.c              |  35 +++++---
>  gdb/thread.c             | 171 ++++++++++++++++++++---------------
>  gdb/ui-file.h            |   2 +-
>  gdb/ui-out.c             | 144 ++++++++++++++++++++++++++++++
>  gdb/ui-out.h             | 186 +++++++++++++++++++++++++++++++++++++++
>  11 files changed, 493 insertions(+), 95 deletions(-)
>
> diff --git a/gdb/cli-out.c b/gdb/cli-out.c
> index 20d3d93f1ad..c919622d418 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 902af405cc6..b36fb8c35de 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;
>      }
> @@ -296,10 +297,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 4fde96800fb..7c1a7cca74f 100644
> --- a/gdb/infrun.c
> +++ b/gdb/infrun.c
> @@ -8788,10 +8788,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;
> @@ -8820,6 +8820,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-mi.c b/gdb/python/py-mi.c
> index a7b4f4fa3cf..ba913bf1fee 100644
> --- a/gdb/python/py-mi.c
> +++ b/gdb/python/py-mi.c
> @@ -61,6 +61,9 @@ class py_ui_out : public ui_out
>      return current ().obj.release ();
>    }
>  
> +  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 0b35d62f82f..0560261144c 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 (gdb::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 c8145da59bc..f6cf2eb9cf4 100644
> --- a/gdb/thread.c
> +++ b/gdb/thread.c
> @@ -1064,6 +1064,103 @@ 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;
> +
> +  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.  */
> @@ -1147,82 +1244,12 @@ 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;
>  
> -	  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 without this change
for me.

>    /* The internal buffer.  */
>    std::string m_string;
>  
> diff --git a/gdb/ui-out.c b/gdb/ui-out.c
> index defa8f9dfa4..9f643b1ce95 100644
> --- a/gdb/ui-out.c
> +++ b/gdb/ui-out.c
> @@ -871,3 +871,147 @@ 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.  */ }
> +
> +buffer_group::~buffer_group ()
> +{ /* 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..70a7145741f 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,187 @@ 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);
> +
> +  ~buffer_group ();
> +
> +  /* 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)

I think you can declare EX as const here.

> +    {
> +      /* 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.  */

I think we could achieve the same result as the current code by making
use of std::uncaught_exceptions(), which would allow us to move the call
to flush into the buffer_group destructor.  Something like this:

  buffer_group::~buffer_group () noexcept(false)
  {
    try
      {
        this->flush ();
      }
    catch (const gdb_exception &ex)
      {
        if (std::uncaught_exceptions () == 0)
          throw;
        /* We're already handling an exception.  */
        warning (_("failed to flush buffered output: %s"),
                 ex.what ());
      }
  }

The only benefit I can see to this approach is we could get rid of
do_with_buffered_output, and instead we could have something like:

  scoped_buffer_output buffered_output;

Which could be just dropped into a scope and then any output after that
in the block would be buffered.

I'm not 100% if this would be better or not, so I don't think I'm
actually suggesting you should make this change .... but I wanted to
mention it, I guess just for the record.

> +      try
> +	{
> +	  g.flush ();
> +	}
> +      catch (const gdb_exception &)
> +	{
> +	}
> +
> +      throw_exception (std::move (ex));

And here you can just use `throw;` which will re-throw EX.  We only need
to call throw_exception if object slicing has occurred, e.g. if EX has
been returned from some other function.

Thanks,
Andrew


> +    }
> +
> +  /* 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.  */
> +  gdb::optional<buffering_file> m_buffered_current_uiout;
> +
> +  /* Additional ui_out being buffered.  */
> +  ui_out *m_uiout;
> +
> +  /* Buffer for m_uiout's output stream.  */
> +  gdb::optional<buffering_file> m_buffered_uiout;
> +};
> +
>  #endif /* UI_OUT_H */
> -- 
> 2.41.0


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

end of thread, other threads:[~2024-01-17 18:05 UTC | newest]

Thread overview: 31+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-10-28  0:20 [PATCH 0/4] On-demand debuginfo downloading Aaron Merey
2023-10-28  0:20 ` [PATCH 1/4 v7] gdb: Buffer output streams during events that might download debuginfo Aaron Merey
2023-11-12 20:20   ` Aaron Merey
2023-11-20 18:38     ` [PING*2][PATCH " Aaron Merey
2023-11-30 16:29       ` [PING*3][PATCH " Aaron Merey
2023-12-12 15:00         ` [PING*4][PATCH " Aaron Merey
2023-12-20 14:57           ` [PING*5][PATCH " Aaron Merey
2023-12-26 16:28   ` [PATCH " Thiago Jung Bauermann
2024-01-17 17:49     ` Aaron Merey
2024-01-17 18:05   ` Andrew Burgess
2023-10-28  0:20 ` [PATCH 2/4 v2] gdb/progspace: Add reverse safe iterator and template for unwrapping iterator Aaron Merey
2023-11-12 20:20   ` Aaron Merey
2023-11-20 18:39     ` [PING*2][PATCH " Aaron Merey
2023-11-30 16:30       ` [PING*3][PATCH " Aaron Merey
2023-12-12 15:01         ` [PING*4][PATCH " Aaron Merey
2023-12-20 14:57           ` [PING*5][PATCH " Aaron Merey
2023-12-26 17:09   ` [PATCH " Thiago Jung Bauermann
2023-10-28  0:20 ` [PATCH 3/4 v4] gdb/debuginfod: Support on-demand debuginfo downloading Aaron Merey
2023-11-12 20:20   ` Aaron Merey
2023-11-20 18:39     ` [PING*2][PATCH " Aaron Merey
2023-11-30 16:30       ` [PING*3][PATCH " Aaron Merey
2023-12-12 15:01         ` [PING*4][PATCH " Aaron Merey
2023-12-20 14:57           ` [PING*5][PATCH " Aaron Merey
2023-12-26 18:35   ` [PATCH " Thiago Jung Bauermann
2023-10-28  0:20 ` [PATCH 4/4 v5] gdb/debuginfod: Add .debug_line downloading Aaron Merey
2023-11-12 20:21   ` Aaron Merey
2023-11-20 18:40     ` [PING*2][PATCH " Aaron Merey
2023-11-30 16:30       ` [PING*3][PATCH " Aaron Merey
2023-12-12 15:08         ` [PING*4][PATCH " Aaron Merey
2023-12-20 14:58           ` [PING*5][PATCH " Aaron Merey
2023-12-27  0:30   ` [PATCH " Thiago Jung Bauermann

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