[7/7] gdb/debuginfod: Add .debug_line downloading

Message ID 20230227194212.348003-7-amerey@redhat.com
State New
Headers
Series [1/7] gdb/debuginfod: Add debuginfod_section_query |

Commit Message

Aaron Merey Feb. 27, 2023, 7:42 p.m. UTC
  'set debuginfod enabled lazy' 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 gdb can search the
debuginfo for matching source filenames.

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 is saved
by checking these sections before choosing to download a deferred debuginfo.

This patch adds functions read_formatted_entries_separate and
dwarf_decode_line_header_separate.  They are similar to
read_formatted_entries and dwarf_decode_line_header except that they are
able to work with .debug_line sections originating from separately
downloaded files.

dwarf2_gdb_index::expand_symtabs_matching initiates the downloading and
checking of an index's associated .debug_line when there is a filename
argument that must be matched.  The .debug_line information is managed
by the new structs line_resource_mmap and mapped_debug_line.
---
 gdb/dwarf2/line-header.c    | 332 ++++++++++++++++++++++++++++++++++++
 gdb/dwarf2/line-header.h    |  10 ++
 gdb/dwarf2/read-gdb-index.c |  26 +++
 gdb/dwarf2/read.c           | 164 ++++++++++++++++++
 gdb/dwarf2/read.h           |  31 ++++
 5 files changed, 563 insertions(+)
  

Comments

Tom Tromey March 7, 2023, 8:36 p.m. UTC | #1
>>>>> "Aaron" == Aaron Merey via Gdb-patches <gdb-patches@sourceware.org> writes:

Aaron> This patch adds functions read_formatted_entries_separate and
Aaron> dwarf_decode_line_header_separate.  They are similar to
Aaron> read_formatted_entries and dwarf_decode_line_header except that they are
Aaron> able to work with .debug_line sections originating from separately
Aaron> downloaded files.

I think there has to be some other refactoring to avoid duplicating code
in this patch.  Copying ~300 lines like that seems bad, especially
considering they'll need parallel updates whenever we find bugs, when
DWARF changes, etc.

Aaron> +  gdb::unique_xmalloc_ptr<char> line_path;
Aaron> +  scoped_fd line_fd = debuginfod_section_query (build_id->data,
Aaron> +						build_id->size,
Aaron> +						bfd_get_filename
Aaron> +						  (objfile->obfd.get ()),
Aaron> +						".debug_line",
Aaron> +						&line_path);

For gdb's purposes, it's a shame debuginfod works explicitly on sections
and not as more of a locally-caching filesystem-like API.  With the
latter we would perhaps have very little or nothing to do to make this
work, provided we were careful to keep the gdb-index code lazy about
reading sections.  Also, inside gdb, all the sharing across inferiors
and such would automatically work.  The cost would be whatever BFD
requests when identifying a file, not sure how much data that is.

Anyway, back to current reality -- the DWARF reader already does try to
lazily map section data.  So I think one big question is, why can't this
be the mechanism for all the sections with debuginfod?  That is, stick
the debuginfod calls into dwarf2_section_info::read.  I don't know what
the debuginfod client does under the hood, but if it doesn't cache this
data somewhere, perhaps gdb could.

Tom
  
Aaron Merey March 9, 2023, 12:26 a.m. UTC | #2
On Tue, Mar 7, 2023 at 3:36 PM Tom Tromey <tom@tromey.com> wrote:
>
> >>>>> "Aaron" == Aaron Merey via Gdb-patches <gdb-patches@sourceware.org> writes:
>
> Aaron> This patch adds functions read_formatted_entries_separate and
> Aaron> dwarf_decode_line_header_separate.  They are similar to
> Aaron> read_formatted_entries and dwarf_decode_line_header except that they are
> Aaron> able to work with .debug_line sections originating from separately
> Aaron> downloaded files.
>
> I think there has to be some other refactoring to avoid duplicating code
> in this patch.  Copying ~300 lines like that seems bad, especially
> considering they'll need parallel updates whenever we find bugs, when
> DWARF changes, etc.

Will do.

>
> Aaron> +  gdb::unique_xmalloc_ptr<char> line_path;
> Aaron> +  scoped_fd line_fd = debuginfod_section_query (build_id->data,
> Aaron> +                                                build_id->size,
> Aaron> +                                                bfd_get_filename
> Aaron> +                                                  (objfile->obfd.get ()),
> Aaron> +                                                ".debug_line",
> Aaron> +                                                &line_path);
>
> For gdb's purposes, it's a shame debuginfod works explicitly on sections
> and not as more of a locally-caching filesystem-like API.  With the
> latter we would perhaps have very little or nothing to do to make this
> work, provided we were careful to keep the gdb-index code lazy about
> reading sections.  Also, inside gdb, all the sharing across inferiors
> and such would automatically work.  The cost would be whatever BFD
> requests when identifying a file, not sure how much data that is.

That's an interesting idea.  Though I wonder if the extra latency from
possibly many more small, frequent transfers outweighs the benefits
over downloading just .gdb_index and maybe .debug_line and the
full debuginfo.

>
> Anyway, back to current reality -- the DWARF reader already does try to
> lazily map section data.  So I think one big question is, why can't this
> be the mechanism for all the sections with debuginfod?  That is, stick
> the debuginfod calls into dwarf2_section_info::read.  I don't know what
> the debuginfod client does under the hood, but if it doesn't cache this
> data somewhere, perhaps gdb could.

Debuginfod does cache sections.  But before the full debuginfo is downloaded
gdb only knows about sections present in the solib binary.  This won't
include any .debug_* sections (otherwise gdb wouldn't be using debuginfod
here).  I think to make this work debuginfod would have to provide the
section header table too?

Since .debug_info is so connected to the other .debug_* sections do we
have much to gain by downloading sections separately once we know we'll
need the .debug_info?  At that point we might as well just download the
whole file.

Thanks for reviewing these patches.

Aaron
  

Patch

diff --git a/gdb/dwarf2/line-header.c b/gdb/dwarf2/line-header.c
index 9d74c8fe75b..7c51fb5bb12 100644
--- a/gdb/dwarf2/line-header.c
+++ b/gdb/dwarf2/line-header.c
@@ -113,6 +113,156 @@  read_checked_initial_length_and_offset (bfd *abfd, const gdb_byte *buf,
   return length;
 }
 
+
+/* 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_separate
+  (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 = *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 (parent_bfd, buf);
+  buf += 1;
+  format_header_data = buf;
+  for (formati = 0; formati < format_count; formati++)
+    {
+      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
+      buf += bytes_read;
+      read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
+      buf += bytes_read;
+    }
+
+  data_count = read_unsigned_leb128 (parent_bfd, buf, &bytes_read);
+  buf += bytes_read;
+  for (datai = 0; datai < data_count; datai++)
+    {
+      const gdb_byte *format = format_header_data;
+      struct file_entry fe;
+
+      for (formati = 0; formati < format_count; formati++)
+	{
+	  ULONGEST content_type = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
+	  format += bytes_read;
+
+	  ULONGEST form  = read_unsigned_leb128 (parent_bfd, format, &bytes_read);
+	  format += bytes_read;
+
+	  gdb::optional<const char *> string;
+	  gdb::optional<unsigned int> uint;
+
+	  switch (form)
+	    {
+	    case DW_FORM_string:
+	      string.emplace (read_direct_string (parent_bfd, buf, &bytes_read));
+	      buf += bytes_read;
+	      break;
+
+	    case DW_FORM_line_strp:
+	      {
+		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;
+	      }
+
+	    case DW_FORM_data1:
+	      uint.emplace (read_1_byte (parent_bfd, buf));
+	      buf += 1;
+	      break;
+
+	    case DW_FORM_data2:
+	      uint.emplace (read_2_bytes (parent_bfd, buf));
+	      buf += 2;
+	      break;
+
+	    case DW_FORM_data4:
+	      uint.emplace (read_4_bytes (parent_bfd, buf));
+	      buf += 4;
+	      break;
+
+	    case DW_FORM_data8:
+	      uint.emplace (read_8_bytes (parent_bfd, buf));
+	      buf += 8;
+	      break;
+
+	    case DW_FORM_data16:
+	      /*  This is used for MD5, but file_entry does not record MD5s. */
+	      buf += 16;
+	      break;
+
+	    case DW_FORM_udata:
+	      uint.emplace (read_unsigned_leb128 (parent_bfd, buf, &bytes_read));
+	      buf += bytes_read;
+	      break;
+
+	    case DW_FORM_block:
+	      /* It is valid only for DW_LNCT_timestamp which is ignored by
+		 current GDB.  */
+	      break;
+	    }
+
+	  switch (content_type)
+	    {
+	    case DW_LNCT_path:
+	      if (string.has_value ())
+		fe.name = *string;
+	      break;
+	    case DW_LNCT_directory_index:
+	      if (uint.has_value ())
+		fe.d_index = (dir_index) *uint;
+	      break;
+	    case DW_LNCT_timestamp:
+	      if (uint.has_value ())
+		fe.mod_time = *uint;
+	      break;
+	    case DW_LNCT_size:
+	      if (uint.has_value ())
+		fe.length = *uint;
+	      break;
+	    case DW_LNCT_MD5:
+	      break;
+	    default:
+	      complaint (_("Unknown format content type %s"),
+			 pulongest (content_type));
+	    }
+	}
+
+      callback (lh, fe.name, fe.d_index, fe.mod_time, fe.length);
+    }
+
+  *line_bufp = buf;
+}
+
 /* Read directory or file name entry format, starting with byte of
    format count entries, ULEB128 pairs of entry formats, ULEB128 of
    entries count and the entries themselves in the described entry
@@ -247,6 +397,188 @@  read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
   *bufp = buf;
 }
 
+/* See line-header.h.  */
+
+line_header_up
+dwarf_decode_line_header_separate (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)
+{
+  const gdb_byte *line_ptr, *buf;
+  unsigned int bytes_read, offset_size;
+  int i;
+  const char *cur_dir, *cur_file;
+
+  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 (buf + 4 >= line_data.data () + line_data.size ())
+    {
+      dwarf2_statement_list_fits_in_line_number_section_complaint (); //  need to print
+      return 0;
+    }
+
+  /* We don't have access to the CU DIE yet so we don't know the comp_dir.  */
+  line_header_up lh (new line_header (nullptr));
+
+  lh->sect_off = (sect_offset) (buf - line_data.data ());
+  lh->offset_in_dwz = false;
+
+  line_ptr = buf;
+
+  /* Read in the header.  */
+
+  LONGEST unit_length = read_initial_length (parent_bfd, buf, &bytes_read);
+  offset_size = (bytes_read == 4) ? 4 : 8;
+
+  line_ptr += bytes_read;
+
+  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 (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 nullptr;
+    }
+  if (lh->version >= 5)
+    {
+      gdb_byte segment_selector_size;
+
+      /* Skip address size.  */
+      read_1_byte (parent_bfd, line_ptr);
+      line_ptr += 1;
+
+      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 nullptr;
+	}
+    }
+
+  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 (parent_bfd, line_ptr);
+  line_ptr += 1;
+
+  if (lh->version >= 4)
+    {
+      lh->maximum_ops_per_instruction = read_1_byte (parent_bfd, line_ptr);
+      line_ptr += 1;
+    }
+  else
+    lh->maximum_ops_per_instruction = 1;
+
+  if (lh->maximum_ops_per_instruction == 0)
+    {
+      lh->maximum_ops_per_instruction = 1;
+      complaint (_("invalid maximum_ops_per_instruction "
+		   "in `.debug_line' section"));
+    }
+
+  lh->default_is_stmt = read_1_byte (parent_bfd, line_ptr);
+  line_ptr += 1;
+
+  lh->line_base = read_1_signed_byte (parent_bfd, line_ptr);
+  line_ptr += 1;
+
+  lh->line_range = read_1_byte (parent_bfd, line_ptr);
+  line_ptr += 1;
+
+  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 (parent_bfd, line_ptr);
+      line_ptr += 1;
+    }
+
+  if (lh->version >= 5)
+    {
+      /* Read directory table.  */
+      read_formatted_entries_separate
+	(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_separate
+	(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);
+	});
+    }
+  else
+    {
+      /* Read directory table.  */
+      while ((cur_dir = read_direct_string (parent_bfd, line_ptr, &bytes_read)) != nullptr)
+	{
+	  line_ptr += bytes_read;
+	  lh->add_include_dir (cur_dir);
+	}
+      line_ptr += bytes_read;
+
+      /* Read file name table.  */
+      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 (parent_bfd, line_ptr, &bytes_read);
+	  line_ptr += bytes_read;
+	  mod_time = read_unsigned_leb128 (parent_bfd, line_ptr, &bytes_read);
+	  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);
+	}
+      line_ptr += bytes_read;
+    }
+
+  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;
+}
+
+
 /* See line-header.h.  */
 
 line_header_up
diff --git a/gdb/dwarf2/line-header.h b/gdb/dwarf2/line-header.h
index 59a42e336f5..18af953343c 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 dwarf_decode_line_header but the .debug_line and .debug_line_str
+   are stored in LINE_DATA and LINE_STR_DATA.  This is used when these
+   sections are read from separate files without necessarily having
+   access to the entire debuginfo file they originate from.  */
+
+extern line_header_up dwarf_decode_line_header_separate
+  (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);
+
 #endif /* DWARF2_LINE_HEADER_H */
diff --git a/gdb/dwarf2/read-gdb-index.c b/gdb/dwarf2/read-gdb-index.c
index f066d55bd1a..15f7d79e2d3 100644
--- a/gdb/dwarf2/read-gdb-index.c
+++ b/gdb/dwarf2/read-gdb-index.c
@@ -128,6 +128,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.
@@ -165,6 +168,15 @@  struct dwarf2_gdb_index : public dwarf2_base_index_functions
      block_search_flags search_flags,
      domain_enum domain,
      enum search_domain kind);
+
+  /* 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);
 };
 
 /* This dumps minimal information about the index.
@@ -517,6 +529,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,
@@ -547,6 +570,9 @@  dwarf2_gdb_index::expand_symtabs_matching
 
       /* Objfile is a stub holding only index information.  Try to reinitialize
 	 objfile with the full debuginfo.  */
+      if (file_matcher != nullptr
+	  && !filename_in_debug_line (objfile, file_matcher))
+	return true;
       if (!read_full_dwarf_from_debuginfod (objfile))
 	return false;
       return do_expand_symtabs_matching (objfile, file_matcher, lookup_name,
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 12b0dbd1ee8..8bcdf9824cc 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>
@@ -2103,6 +2104,169 @@  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;
+}
+
+#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.  */
+
+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);
+
+  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);
+
+  if (line_fd.get () < 0)
+    return false;
+
+  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_separate (objfile->obfd.get (),
+					     line_data, line_str_data,
+					     &line_ptr);
+      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 4e6c35cba5e..63d804f7b44 100644
--- a/gdb/dwarf2/read.h
+++ b/gdb/dwarf2/read.h
@@ -34,6 +34,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;
@@ -951,4 +952,34 @@  extern bool read_addrmap_from_aranges (dwarf2_per_objfile *per_objfile,
 
 extern bool read_full_dwarf_from_debuginfod (struct objfile *objfile);
 
+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);
+
+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 */