[v4] gdb: Support embedded source in DWARF

Message ID 20240409045735.32562-1-hawkinsw@obs.cr
State Dropped, archived
Headers
Series [v4] gdb: Support embedded source in DWARF |

Checks

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

Commit Message

Will Hawkins April 9, 2024, 4:57 a.m. UTC
  While DW_LNCT_source is not yet finalized in the DWARF standard
(https://dwarfstd.org/issues/180201.1.html), LLVM does emit it.

This patch adds support for it in gdb.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
---

Notes:
    v3 -> v4
      - Initialize the source pointer in the symtab to NULL.
      - Fix a bug that caused an error generating formatted output.
    v2 -> v3
      - Address feedback from v2.
      - Fixed up a bug where non-CU embedded files were incorrectly
        marked as embedded.
      - Revamped source access through the cache to avoid copies.
    
    v1 -> v2
      - Address feedback from original PR
      - Add support for maintenance commands to see embedded source status
      - Prevent access to the filesystem for symtabs with embedded source
      - Add additional unit tests

 gdb/dwarf2/file-and-dir.h                    | 23 ++++-
 gdb/dwarf2/line-header.c                     | 22 +++--
 gdb/dwarf2/line-header.h                     |  9 +-
 gdb/dwarf2/read.c                            | 83 +++++++++++++++---
 gdb/extension.c                              |  7 +-
 gdb/extension.h                              |  9 +-
 gdb/source-cache.c                           | 87 ++++++++++++-------
 gdb/source-cache.h                           | 12 +--
 gdb/source.c                                 | 74 +++++++++++-----
 gdb/source.h                                 |  4 +
 gdb/symfile.c                                |  1 +
 gdb/symmisc.c                                | 17 ++--
 gdb/symtab.h                                 |  5 ++
 gdb/testsuite/gdb.dwarf2/dw2-lnct-source.c   | 24 ++++++
 gdb/testsuite/gdb.dwarf2/dw2-lnct-source.exp | 88 ++++++++++++++++++++
 gdb/testsuite/lib/dwarf.exp                  | 19 +++--
 include/dwarf2.h                             |  5 ++
 17 files changed, 391 insertions(+), 98 deletions(-)
 create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-lnct-source.c
 create mode 100644 gdb/testsuite/gdb.dwarf2/dw2-lnct-source.exp
  

Patch

diff --git a/gdb/dwarf2/file-and-dir.h b/gdb/dwarf2/file-and-dir.h
index a5b1d8a3a21..84a7367e973 100644
--- a/gdb/dwarf2/file-and-dir.h
+++ b/gdb/dwarf2/file-and-dir.h
@@ -95,7 +95,14 @@  struct file_and_directory
   const char *get_fullname ()
   {
     if (m_fullname == nullptr)
-      m_fullname = find_source_or_rewrite (get_name (), get_comp_dir ());
+      {
+	if (m_is_embedded)
+	  m_fullname = make_unique_xstrdup (embedded_fullname (
+							       get_name (),
+							       get_comp_dir ()));
+	else
+	  m_fullname = find_source_or_rewrite (get_name (), get_comp_dir ());
+      }
     return m_fullname.get ();
   }
 
@@ -105,6 +112,17 @@  struct file_and_directory
     m_fullname.reset ();
   }
 
+  /* Set whether the file's source is embedded in the dwarf.  */
+  void set_embedded (bool is_embedded)
+  {
+    m_is_embedded = is_embedded;
+  }
+
+  /* Return true if the file's source is embedded in the dwarf.  */
+  bool is_embedded () const
+  {
+    return m_is_embedded;
+  }
 private:
 
   /* The filename.  */
@@ -124,6 +142,9 @@  struct file_and_directory
 
   /* The full name.  */
   gdb::unique_xmalloc_ptr<char> m_fullname;
+
+  /* Whether the file's source is embedded in the dwarf.  */
+  bool m_is_embedded = false;
 };
 
 #endif /* GDB_DWARF2_FILE_AND_DIR_H */
diff --git a/gdb/dwarf2/line-header.c b/gdb/dwarf2/line-header.c
index a3ca49b64f5..3bc707e999e 100644
--- a/gdb/dwarf2/line-header.c
+++ b/gdb/dwarf2/line-header.c
@@ -45,6 +45,7 @@  line_header::add_include_dir (const char *include_dir)
 void
 line_header::add_file_name (const char *name,
 			    dir_index d_index,
+			    const char *source,
 			    unsigned int mod_time,
 			    unsigned int length)
 {
@@ -54,7 +55,7 @@  line_header::add_file_name (const char *name,
   if (dwarf_line_debug >= 2)
     gdb_printf (gdb_stdlog, "Adding file %d: %s\n", index, name);
 
-  m_file_names.emplace_back (name, index, d_index, mod_time, length);
+  m_file_names.emplace_back (name, index, d_index, source, mod_time, length);
 }
 
 std::string
@@ -125,6 +126,7 @@  read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
 			void (*callback) (struct line_header *lh,
 					  const char *name,
 					  dir_index d_index,
+					  const char *source,
 					  unsigned int mod_time,
 					  unsigned int length))
 {
@@ -239,13 +241,17 @@  read_formatted_entries (dwarf2_per_objfile *per_objfile, bfd *abfd,
 	      break;
 	    case DW_LNCT_MD5:
 	      break;
+	    case DW_LNCT_LLVM_SOURCE:
+	      if (string.has_value () && (*string)[0] != '\0')
+		fe.source = *string;
+	      break;
 	    default:
 	      complaint (_("Unknown format content type %s"),
 			 pulongest (content_type));
 	    }
 	}
 
-      callback (lh, fe.name, fe.d_index, fe.mod_time, fe.length);
+      callback (lh, fe.name, fe.d_index, fe.source, fe.mod_time, fe.length);
     }
 
   *bufp = buf;
@@ -368,8 +374,8 @@  dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
       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)
+				  dir_index d_index, const char *source,
+				  unsigned int mod_time, unsigned int length)
 	{
 	  header->add_include_dir (name);
 	});
@@ -378,10 +384,10 @@  dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
       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)
+				  dir_index d_index, const char *source,
+				  unsigned int mod_time, unsigned int length)
 	{
-	  header->add_file_name (name, d_index, mod_time, length);
+	  header->add_file_name (name, d_index, source, mod_time, length);
 	});
     }
   else
@@ -408,7 +414,7 @@  dwarf_decode_line_header  (sect_offset sect_off, bool is_dwz,
 	  length = read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
 	  line_ptr += bytes_read;
 
-	  lh->add_file_name (cur_file, d_index, mod_time, length);
+	  lh->add_file_name (cur_file, d_index, nullptr, mod_time, length);
 	}
       line_ptr += bytes_read;
     }
diff --git a/gdb/dwarf2/line-header.h b/gdb/dwarf2/line-header.h
index c068dff70a3..abc95f3ee87 100644
--- a/gdb/dwarf2/line-header.h
+++ b/gdb/dwarf2/line-header.h
@@ -35,9 +35,10 @@  struct file_entry
   file_entry () = default;
 
   file_entry (const char *name_, file_name_index index_, dir_index d_index_,
-	      unsigned int mod_time_, unsigned int length_)
+	      const char *source_, unsigned int mod_time_, unsigned int length_)
     : name (name_),
       index (index_),
+      source (source_),
       d_index (d_index_),
       mod_time (mod_time_),
       length (length_)
@@ -54,6 +55,10 @@  struct file_entry
   /* The index of this file in the file table.  */
   file_name_index index {};
 
+  /* The file's contents (if not null).  Note this is an observing pointer.
+     The memory is owned by debug_line_buffer.  */
+  const char *source {};
+
   /* The directory index (1-based).  */
   dir_index d_index {};
 
@@ -88,7 +93,7 @@  struct line_header
   void add_include_dir (const char *include_dir);
 
   /* Add an entry to the file name table.  */
-  void add_file_name (const char *name, dir_index d_index,
+  void add_file_name (const char *name, dir_index d_index, const char *source,
 		      unsigned int mod_time, unsigned int length);
 
   /* Return the include dir at INDEX (0-based in DWARF 5 and 1-based before).
diff --git a/gdb/dwarf2/read.c b/gdb/dwarf2/read.c
index 7442094874c..e35ff019acf 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -1635,6 +1635,10 @@  struct quick_file_names
   /* The file names from the line table after being run through
      gdb_realpath.  These are computed lazily.  */
   const char **real_names;
+
+  /* Whether or not the file names refer to sources embedded
+     in the dwarf.  */
+  const bool *embeddeds;
 };
 
 /* With OBJF_READNOW, the DWARF reader expands all CUs immediately.
@@ -1908,27 +1912,39 @@  dw2_get_file_names_reader (const struct die_reader_specs *reader,
   if (slot != nullptr)
     *slot = qfn;
 
+
+  bool cu_file_embedded = false;
+  std::vector<bool> embeddedv;
+
   std::vector<const char *> include_names;
   if (lh != nullptr)
     {
       for (const auto &entry : lh->file_names ())
 	{
 	  std::string name_holder;
-	  const char *include_name =
-	    compute_include_file_name (lh.get (), entry, fnd, name_holder);
+	  const char *include_name
+	    = compute_include_file_name (lh.get (), entry, fnd, name_holder);
 	  if (include_name != nullptr)
 	    {
 	      include_name = per_objfile->objfile->intern (include_name);
 	      include_names.push_back (include_name);
+	      embeddedv.push_back (entry.source != nullptr);
 	    }
+	  else if (entry.source != nullptr)
+	    {
+	      /* We have an embedded source for the CU.  */
+	      gdb_assert (offset == 1);
+	      cu_file_embedded = true;
+	    }
+
 	}
     }
 
   qfn->num_file_names = offset + include_names.size ();
   qfn->comp_dir = fnd.intern_comp_dir (per_objfile->objfile);
-  qfn->file_names =
-    XOBNEWVEC (&per_objfile->per_bfd->obstack, const char *,
-	       qfn->num_file_names);
+  qfn->file_names
+    = XOBNEWVEC (&per_objfile->per_bfd->obstack, const char *,
+		 qfn->num_file_names);
   if (offset != 0)
     qfn->file_names[0] = per_objfile->objfile->intern (fnd.get_name ());
 
@@ -1936,7 +1952,16 @@  dw2_get_file_names_reader (const struct die_reader_specs *reader,
     memcpy (&qfn->file_names[offset], include_names.data (),
 	    include_names.size () * sizeof (const char *));
 
-  qfn->real_names = NULL;
+  bool *embeddeds
+    = XOBNEWVEC (&per_objfile->per_bfd->obstack, bool,
+		 qfn->num_file_names);
+  if (cu_file_embedded)
+    embeddeds[0] = true;
+  for (size_t i = 0; i < embeddedv.size (); i++)
+    embeddeds[offset + i] = embeddedv[i];
+  qfn->embeddeds = embeddeds;
+
+  qfn->real_names = nullptr;
 
   lh_cu->file_names = qfn;
 }
@@ -1980,7 +2005,11 @@  dw2_get_real_path (dwarf2_per_objfile *per_objfile,
 	dirname = qfn->comp_dir;
 
       gdb::unique_xmalloc_ptr<char> fullname;
-      fullname = find_source_or_rewrite (qfn->file_names[index], dirname);
+
+      if (qfn->embeddeds[index])
+	fullname.reset (embedded_fullname (dirname, qfn->file_names[index]));
+      else
+	fullname = find_source_or_rewrite (qfn->file_names[index], dirname);
 
       qfn->real_names[index] = fullname.release ();
     }
@@ -7311,6 +7340,35 @@  find_file_and_directory (struct die_info *die, struct dwarf2_cu *cu)
   file_and_directory res (dwarf2_string_attr (die, DW_AT_name, cu),
 			  dwarf2_string_attr (die, DW_AT_comp_dir, cu));
 
+  /* Because the line header may tell us information about the CU
+     filename (e.g., whether it is embedded) which will affect other
+     calculations, we have to read that information here.  */
+  line_header *lh = cu->line_header;
+  struct attribute *attr = dwarf2_attr (die, DW_AT_stmt_list, cu);
+  if (lh == nullptr && attr != nullptr && attr->form_is_unsigned ())
+    {
+      sect_offset line_offset = (sect_offset) attr->as_unsigned ();
+      line_header_up lhu = dwarf_decode_line_header (line_offset, cu,
+				     res.get_comp_dir ());
+      if (lhu != nullptr)
+	  lh = lhu.release();
+    }
+
+  if (lh != nullptr)
+    {
+      for (const auto &entry : lh->file_names ())
+	{
+	  if (entry.source == nullptr)
+	    continue;
+
+	  std::string name_holder;
+	  const char *include_name =
+	    compute_include_file_name (lh, entry, res, name_holder);
+	  if (include_name == nullptr)
+	    res.set_embedded (true);
+	}
+    }
+
   if (res.get_comp_dir () == nullptr
       && producer_is_gcc_lt_4_3 (cu)
       && res.get_name () != nullptr
@@ -18448,7 +18506,7 @@  dwarf_decode_lines_1 (struct line_header *lh, struct dwarf2_cu *cu,
 		    length =
 		      read_unsigned_leb128 (abfd, line_ptr, &bytes_read);
 		    line_ptr += bytes_read;
-		    lh->add_file_name (cur_file, dindex, mod_time, length);
+		    lh->add_file_name (cur_file, dindex, nullptr, mod_time, length);
 		  }
 		  break;
 		case DW_LNE_set_discriminator:
@@ -18603,9 +18661,12 @@  dwarf_decode_lines (struct line_header *lh, struct dwarf2_cu *cu,
       subfile *sf = builder->get_current_subfile ();
 
       if (sf->symtab == nullptr)
-	sf->symtab = allocate_symtab (cust, sf->name.c_str (),
-				      sf->name_for_id.c_str ());
-
+	{
+	  sf->symtab = allocate_symtab (cust, sf->name.c_str (),
+					sf->name_for_id.c_str ());
+	  if (fe.source)
+	    sf->symtab->source = fe.source;
+	}
       fe.symtab = sf->symtab;
     }
 }
diff --git a/gdb/extension.c b/gdb/extension.c
index 9db8b53a087..f17991a3751 100644
--- a/gdb/extension.c
+++ b/gdb/extension.c
@@ -991,16 +991,19 @@  xmethod_worker::get_result_type (value *object, gdb::array_view<value *> args)
 /* See extension.h.  */
 
 std::optional<std::string>
-ext_lang_colorize (const std::string &filename, const std::string &contents)
+ext_lang_colorize (const std::string &filename, const std::string_view contents)
 {
   std::optional<std::string> result;
 
+  /* We avoided copies as long as possible. The external colorization API
+     requires a std::string.  */
+  std::string contents_storage = std::string (contents);
   for (const struct extension_language_defn *extlang : extension_languages)
     {
       if (extlang->ops == nullptr
 	  || extlang->ops->colorize == nullptr)
 	continue;
-      result = extlang->ops->colorize (filename, contents);
+      result = extlang->ops->colorize (filename, contents_storage);
       if (result.has_value ())
 	return result;
     }
diff --git a/gdb/extension.h b/gdb/extension.h
index 5260bcbde00..e5a5333b7c3 100644
--- a/gdb/extension.h
+++ b/gdb/extension.h
@@ -319,12 +319,13 @@  extern void get_matching_xmethod_workers
    std::vector<xmethod_worker_up> *workers);
 
 /* Try to colorize some source code.  FILENAME is the name of the file
-   holding the code.  CONTENTS is the source code itself.  This will
-   either a colorized (using ANSI terminal escapes) version of the
-   source code, or an empty value if colorizing could not be done.  */
+   holding the code.  CONTENTS is a view of the source code itself.
+   This will either generate a colorized (using ANSI terminal escapes)
+   version of the source code, or an empty value if colorizing could not
+   be done.  */
 
 extern std::optional<std::string> ext_lang_colorize
-  (const std::string &filename, const std::string &contents);
+  (const std::string &filename, const std::string_view contents);
 
 /* Try to colorize a single line of disassembler output, CONTENT for
    GDBARCH.  This will return either a colorized (using ANSI terminal
diff --git a/gdb/source-cache.c b/gdb/source-cache.c
index 8b5bd84d19a..f5168921f99 100644
--- a/gdb/source-cache.c
+++ b/gdb/source-cache.c
@@ -93,32 +93,45 @@  set_use_gnu_source_highlight_enabled (const char *ignore_args,
 
 /* See source-cache.h.  */
 
-std::string
+std::string_view
 source_cache::get_plain_source_lines (struct symtab *s,
-				      const std::string &fullname)
+				      const std::string &fullname,
+				      std::string &source_storage)
 {
-  scoped_fd desc (open_source_file (s));
-  if (desc.get () < 0)
-    perror_with_name (symtab_to_filename_for_display (s), -desc.get ());
+  std::string_view lines;
+  if (!s->source)
+    {
+
+      scoped_fd desc (open_source_file (s));
+      if (desc.get () < 0)
+	perror_with_name (symtab_to_filename_for_display (s), -desc.get ());
 
-  struct stat st;
-  if (fstat (desc.get (), &st) < 0)
-    perror_with_name (symtab_to_filename_for_display (s));
+      struct stat st;
+      if (fstat (desc.get (), &st) < 0)
+	perror_with_name (symtab_to_filename_for_display (s));
 
-  std::string lines;
-  lines.resize (st.st_size);
-  if (myread (desc.get (), &lines[0], lines.size ()) < 0)
-    perror_with_name (symtab_to_filename_for_display (s));
+      source_storage.resize (st.st_size);
+      if (myread (desc.get (),
+		  &source_storage[0],
+		  source_storage.size ()) < 0)
+	perror_with_name (symtab_to_filename_for_display (s));
 
-  time_t mtime = 0;
-  if (s->compunit ()->objfile () != NULL
-      && s->compunit ()->objfile ()->obfd != NULL)
-    mtime = s->compunit ()->objfile ()->mtime;
-  else if (current_program_space->exec_bfd ())
-    mtime = current_program_space->ebfd_mtime;
+      time_t mtime = 0;
+      if (s->compunit ()->objfile () != nullptr
+	  && s->compunit ()->objfile ()->obfd != nullptr)
+	mtime = s->compunit ()->objfile ()->mtime;
+      else if (current_program_space->exec_bfd ())
+	mtime = current_program_space->ebfd_mtime;
 
-  if (mtime && mtime < st.st_mtime)
-    warning (_("Source file is more recent than executable."));
+      if (mtime && mtime < st.st_mtime)
+	warning (_("Source file is more recent than executable."));
+
+      lines = source_storage;
+    }
+  else
+    {
+      lines = s->source;
+    }
 
   std::vector<off_t> offsets;
   offsets.push_back (0);
@@ -200,9 +213,10 @@  get_language_name (enum language lang)
    succeeded.  */
 
 static bool
-try_source_highlight (std::string &contents ATTRIBUTE_UNUSED,
+try_source_highlight (std::string_view &contents ATTRIBUTE_UNUSED,
 		      enum language lang ATTRIBUTE_UNUSED,
-		      const std::string &fullname ATTRIBUTE_UNUSED)
+		      const std::string &fullname ATTRIBUTE_UNUSED,
+		      std::string &contents_storage)
 {
 #ifdef HAVE_SOURCE_HIGHLIGHT
   if (!use_gnu_source_highlight)
@@ -240,10 +254,14 @@  try_source_highlight (std::string &contents ATTRIBUTE_UNUSED,
 	  lang_name = detected_lang.c_str ();
 	}
 
-      std::istringstream input (contents);
+      /* We waited as long as possible but now we need a string.  */
+      contents_storage = contents;
+      std::istringstream input (contents_storage);
       std::ostringstream output;
       highlighter->highlight (input, output, lang_name, fullname);
-      contents = std::move (output).str ();
+      /* Use the given storage for the contents and set the
+	 view appropriately.  */
+      contents = contents_storage = std::move (output).str ();
       styled = true;
     }
   catch (...)
@@ -275,13 +293,16 @@  static void gnu_source_highlight_test ()
        "}\n");
   const std::string fullname = "test.c";
   std::string styled_prog;
+  std::string_view styled_prog_view;
 
   bool res = false;
   bool saw_exception = false;
   styled_prog = prog;
+  styled_prog_view = styled_prog;
   try
     {
-      res = try_source_highlight (styled_prog, language_c, fullname);
+      res = try_source_highlight (styled_prog_view, language_c, fullname,
+				  styled_prog);
     }
   catch (...)
     {
@@ -324,10 +345,12 @@  source_cache::ensure (struct symtab *s)
 	}
     }
 
-  std::string contents;
+  std::string_view source_contents;
+  std::string source_contents_storage;
   try
     {
-      contents = get_plain_source_lines (s, fullname);
+      source_contents = get_plain_source_lines (s, fullname,
+						source_contents_storage);
     }
   catch (const gdb_exception_error &e)
     {
@@ -339,15 +362,17 @@  source_cache::ensure (struct symtab *s)
       && m_no_styling_files.count (fullname) == 0)
     {
       bool already_styled
-	= try_source_highlight (contents, s->language (), fullname);
+	= try_source_highlight (source_contents, s->language (), fullname,
+				source_contents_storage);
 
       if (!already_styled)
 	{
 	  std::optional<std::string> ext_contents;
-	  ext_contents = ext_lang_colorize (fullname, contents);
+	  ext_contents = ext_lang_colorize (fullname, source_contents);
 	  if (ext_contents.has_value ())
 	    {
-	      contents = std::move (*ext_contents);
+	      source_contents = source_contents_storage
+		= std::move (*ext_contents);
 	      already_styled = true;
 	    }
 	}
@@ -369,7 +394,7 @@  source_cache::ensure (struct symtab *s)
 	}
     }
 
-  source_text result = { std::move (fullname), std::move (contents) };
+  source_text result = { std::move (fullname), std::string (source_contents) };
   m_source_map.push_back (std::move (result));
 
   if (m_source_map.size () > MAX_ENTRIES)
diff --git a/gdb/source-cache.h b/gdb/source-cache.h
index d4cb7d00ae8..84996a6a5a1 100644
--- a/gdb/source-cache.h
+++ b/gdb/source-cache.h
@@ -80,11 +80,13 @@  class source_cache
     std::string contents;
   };
 
-  /* A helper function for get_source_lines reads a source file.
-     Returns the contents of the file; or throws an exception on
-     error.  This also updates m_offset_cache.  */
-  std::string get_plain_source_lines (struct symtab *s,
-				      const std::string &fullname);
+  /* A helper function for get_source_lines that reads a source file.
+     Returns a view of the contents of the file using SOURCE_STORAGE
+     as necessary (i.e., if the source is stored on disk);  or throws
+     an exception on error.  This also updates m_offset_cache.  */
+  std::string_view get_plain_source_lines (struct symtab *s,
+					   const std::string &fullname,
+					   std::string &source_storage);
 
   /* A helper function that the data for the given symtab is entered
      into both caches.  Returns false on error.  */
diff --git a/gdb/source.c b/gdb/source.c
index bbeb4154258..8bfaffe43a8 100644
--- a/gdb/source.c
+++ b/gdb/source.c
@@ -684,9 +684,11 @@  info_source_command (const char *ignore, int from_tty)
 
   cust = s->compunit ();
   gdb_printf (_("Current source file is %s\n"), s->filename);
-  if (s->compunit ()->dirname () != NULL)
+  if (s->compunit ()->dirname () != nullptr)
     gdb_printf (_("Compilation directory is %s\n"), s->compunit ()->dirname ());
-  if (s->fullname)
+  if (s->source != nullptr)
+    gdb_printf (_("With embedded source.\n"));
+  else if (s->fullname)
     gdb_printf (_("Located in %s\n"), s->fullname);
   const std::vector<off_t> *offsets;
   if (g_source_cache.get_line_charpos (s, &offsets))
@@ -960,6 +962,19 @@  source_full_path_of (const char *filename,
   return 1;
 }
 
+/* See source.h.  */
+
+char *
+embedded_fullname (const char *dirname, const char *filename)
+{
+  if (dirname != nullptr)
+    {
+      return concat (dirname, SLASH_STRING, filename, (char *) nullptr);
+    }
+
+  return xstrdup (filename);
+}
+
 /* Return non-zero if RULE matches PATH, that is if the rule can be
    applied to PATH.  */
 
@@ -1237,27 +1252,35 @@  symtab_to_fullname (struct symtab *s)
   /* Use cached copy if we have it.
      We rely on forget_cached_source_info being called appropriately
      to handle cases like the file being moved.  */
-  if (s->fullname == NULL)
+  if (s->fullname == nullptr)
     {
-      scoped_fd fd = open_source_file (s);
-
-      if (fd.get () < 0)
+      if (s->source)
+	  s->fullname = embedded_fullname (s->compunit ()->dirname (),
+					   s->filename);
+      else
 	{
-	  gdb::unique_xmalloc_ptr<char> fullname;
+	  scoped_fd fd = open_source_file (s);
 
-	  /* rewrite_source_path would be applied by find_and_open_source, we
-	     should report the pathname where GDB tried to find the file.  */
+	  if (fd.get () < 0)
+	    {
+	      gdb::unique_xmalloc_ptr<char> fullname;
 
-	  if (s->compunit ()->dirname () == nullptr
-	      || IS_ABSOLUTE_PATH (s->filename))
-	    fullname.reset (xstrdup (s->filename));
-	  else
-	    fullname.reset (concat (s->compunit ()->dirname (), SLASH_STRING,
-				    s->filename, (char *) NULL));
+	      /* rewrite_source_path would be applied by find_and_open_source,
+		 we should report the pathname where GDB tried to find the
+		 file.  */
 
-	  s->fullname = rewrite_source_path (fullname.get ()).release ();
-	  if (s->fullname == NULL)
-	    s->fullname = fullname.release ();
+	      if (s->compunit ()->dirname () == nullptr
+		  || IS_ABSOLUTE_PATH (s->filename))
+		fullname.reset (xstrdup (s->filename));
+	      else
+		fullname.reset (concat (s->compunit ()->dirname (),
+					SLASH_STRING, s->filename,
+					(char *) nullptr));
+
+	      s->fullname = rewrite_source_path (fullname.get ()).release ();
+	      if (s->fullname == nullptr)
+		s->fullname = fullname.release ();
+	    }
 	}
     } 
 
@@ -1317,12 +1340,17 @@  print_source_lines_base (struct symtab *s, int line, int stopline,
       else
 	{
 	  last_source_visited = s;
-	  scoped_fd desc = open_source_file (s);
-	  last_source_error = desc.get () < 0;
-	  if (last_source_error)
+	  /* Do not attempt to open a source file for a symtab
+	     with an embedded source.  */
+	  if (!s->source)
 	    {
-	      noprint = true;
-	      errcode = -desc.get ();
+	      scoped_fd desc = open_source_file (s);
+	      last_source_error = desc.get () < 0;
+	      if (last_source_error)
+		{
+		  noprint = true;
+		  errcode = -desc.get ();
+		}
 	    }
 	}
     }
diff --git a/gdb/source.h b/gdb/source.h
index 144ee48f722..f27caf1a9b5 100644
--- a/gdb/source.h
+++ b/gdb/source.h
@@ -216,4 +216,8 @@  extern void forget_cached_source_info (void);
    need to would make things slower than necessary.  */
 extern void select_source_symtab ();
 
+/* Compute the fullname for a source file whose source is embedded
+   in the dwarf file.  */
+extern char *embedded_fullname (const char *dirname,
+				const char *filename);
 #endif
diff --git a/gdb/symfile.c b/gdb/symfile.c
index db6d76e78bf..48b297b540b 100644
--- a/gdb/symfile.c
+++ b/gdb/symfile.c
@@ -2835,6 +2835,7 @@  allocate_symtab (struct compunit_symtab *cust, const char *filename,
   symtab->filename = objfile->intern (filename);
   symtab->filename_for_id = objfile->intern (filename_for_id);
   symtab->fullname = NULL;
+  symtab->source = NULL;
   symtab->set_language (deduce_language_from_filename (filename));
 
   /* This can be very verbose with lots of headers.
diff --git a/gdb/symmisc.c b/gdb/symmisc.c
index 49b9674f77a..91d9ca906a5 100644
--- a/gdb/symmisc.c
+++ b/gdb/symmisc.c
@@ -810,9 +810,11 @@  maintenance_info_symtabs (const char *regexp, int from_tty)
 		    gdb_printf ("((struct symtab *) %s)\n",
 				host_address_to_string (symtab));
 		    gdb_printf ("\t  fullname %s\n",
-				symtab->fullname != NULL
+				symtab->fullname != nullptr
 				? symtab->fullname
 				: "(null)");
+		    if (symtab->source != nullptr)
+		      gdb_printf ("\t  source embedded in DWARF\n");
 		    gdb_printf ("\t  "
 				"linetable ((struct linetable *) %s)\n",
 				host_address_to_string
@@ -955,15 +957,20 @@  maintenance_print_one_line_table (struct symtab *symtab, void *data)
   gdb_printf (_("compunit_symtab: %s ((struct compunit_symtab *) %s)\n"),
 	      symtab->compunit ()->name,
 	      host_address_to_string (symtab->compunit ()));
+  styled_string_s styled_symtab_fullname;
+  if (symtab->source)
+    styled_symtab_fullname = *styled_string (metadata_style.style (),
+					    _("Source embedded in DWARF"));
+  else
+    styled_symtab_fullname = *styled_string (file_name_style.style (),
+					    symtab_to_fullname (symtab));
   gdb_printf (_("symtab: %ps ((struct symtab *) %s)\n"),
-	      styled_string (file_name_style.style (),
-			     symtab_to_fullname (symtab)),
+	      &styled_symtab_fullname,
 	      host_address_to_string (symtab));
   linetable = symtab->linetable ();
   gdb_printf (_("linetable: ((struct linetable *) %s):\n"),
 	      host_address_to_string (linetable));
-
-  if (linetable == NULL)
+  if (linetable == nullptr)
     gdb_printf (_("No line table.\n"));
   else if (linetable->nitems <= 0)
     gdb_printf (_("Line table has no lines.\n"));
diff --git a/gdb/symtab.h b/gdb/symtab.h
index bf9a3cfb79f..90c2f202390 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -1755,6 +1755,11 @@  struct symtab
 
   const char *filename;
 
+  /* When the contents of the source file were/are embedded with the
+     debugging info, this pointer will refer to that source. Can be nullptr
+     if the source for this source file is on disk.  */
+  const char *source;
+
   /* Filename for this source file, used as an identifier to link with
      related objects such as associated macro_source_file objects.  It must
      therefore match the name of any macro_source_file object created for this
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-lnct-source.c b/gdb/testsuite/gdb.dwarf2/dw2-lnct-source.c
new file mode 100644
index 00000000000..86cdb9655b7
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-lnct-source.c
@@ -0,0 +1,24 @@ 
+/* Copyright 2024 Free Software Foundation, Inc.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
+
+
+int
+main (void)
+{							/* main prologue */
+  asm ("main_label: .global main_label");
+  int m = 42;						/* main assign m */
+  asm ("main_end: .global main_end");			/* main end */
+  return m;
+}
diff --git a/gdb/testsuite/gdb.dwarf2/dw2-lnct-source.exp b/gdb/testsuite/gdb.dwarf2/dw2-lnct-source.exp
new file mode 100644
index 00000000000..73a06bd7b9c
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-lnct-source.exp
@@ -0,0 +1,88 @@ 
+# Copyright 2024 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Check that GDB can honor LNCT_llvm_SOURCE.
+
+load_lib dwarf.exp
+
+# This test can only be run on targets which support DWARF-2 and use gas.
+require dwarf2_support
+
+standard_testfile .c .S
+
+set asm_file [standard_output_file $srcfile2]
+
+set fp [open "${srcdir}/${subdir}/${srcfile}" r]
+set srcfile_data [read $fp]
+close $fp
+
+Dwarf::assemble $asm_file {
+    global srcdir subdir srcfile srcfile2 srcfile_data
+    declare_labels lines_label
+
+    get_func_info main
+
+    cu {} {
+	compile_unit {
+	    {language @DW_LANG_C}
+	    {name missing-file.c}
+	    {stmt_list ${lines_label} DW_FORM_sec_offset}
+	} {
+	    subprogram {
+		{external 1 flag}
+		{name main}
+		{low_pc $main_start addr}
+		{high_pc "$main_start + $main_len" addr}
+	    }
+	}
+    }
+
+    lines {version 5} lines_label {
+	set diridx [include_dir "${srcdir}/${subdir}"]
+	file_name "missing-file.c" $diridx "${srcfile_data}"
+
+	program {
+	    DW_LNS_set_file $diridx
+	    DW_LNE_set_address $main_start
+	    line [gdb_get_line_number "main prologue"]
+	    DW_LNS_copy
+
+	    DW_LNE_set_address main_label
+	    line [gdb_get_line_number "main assign m"]
+	    DW_LNS_copy
+
+	    DW_LNE_set_address main_end
+	    line [gdb_get_line_number "main end"]
+	    DW_LNS_copy
+
+	    DW_LNE_end_sequence
+	}
+    }
+}
+
+if { [prepare_for_testing "failed to prepare" ${testfile} \
+	  [list $srcfile $asm_file] {nodebug}] } {
+    return -1
+}
+
+if ![runto_main] {
+    return -1
+}
+
+set assign_m_line [gdb_get_line_number "main assign m"]
+gdb_test "frame" ".*main \\\(\\\) at \[^\r\n\]*:$assign_m_line\r\n.*"
+gdb_test "maintenance info symtabs missing-file.c" ".*source embedded in DWARF.*"
+gdb_test "maintenance info line-table missing-file.c" ".*symtab: Source embedded in DWARF.*"
+gdb_test "info source" ".*With embedded source.*"
diff --git a/gdb/testsuite/lib/dwarf.exp b/gdb/testsuite/lib/dwarf.exp
index d085f835f07..b1f09eab3d3 100644
--- a/gdb/testsuite/lib/dwarf.exp
+++ b/gdb/testsuite/lib/dwarf.exp
@@ -2423,9 +2423,9 @@  namespace eval Dwarf {
 	# Add a file name entry to the line table header's file names table.
 	#
 	# Return the index by which this entry can be referred to.
-	proc file_name {filename diridx} {
+	proc file_name {filename diridx { source "" } } {
 	    variable _line_file_names
-	    lappend _line_file_names $filename $diridx
+	    lappend _line_file_names $filename $diridx $source
 
 	    if { $Dwarf::_line_unit_version >= 5 } {
 		return [expr [llength $_line_file_names] - 1]
@@ -2481,7 +2481,7 @@  namespace eval Dwarf {
 		    }
 		}
 
-		_op .byte 2 "file_name_entry_format_count"
+		_op .byte 3 "file_name_entry_format_count"
 		_op .uleb128 1 \
 		    "file_name_entry_format (content type code: DW_LNCT_path)"
 		switch $_line_string_form {
@@ -2494,15 +2494,21 @@  namespace eval Dwarf {
 			    "directory_entry_format (form: DW_FORM_line_strp)"
 		    }
 		}
+
 		_op .uleb128 2 \
 		    "file_name_entry_format (content type code: DW_LNCT_directory_index)"
 		_op .uleb128 0x0f \
 		    "file_name_entry_format (form: DW_FORM_udata)"
 
-		set nr_files [expr [llength $_line_file_names] / 2]
+		_op .uleb128 0x2001 \
+		    "file_name_entry_format (content type code: DW_LNCT_LLVM_SOURCE)"
+		_op .uleb128 0x08 \
+		    "file_name_entry_format (form: DW_FORM_string)"
+
+		set nr_files [expr [llength $_line_file_names] / 3]
 		_op .byte $nr_files "file_names_count"
 
-		foreach { filename diridx } $_line_file_names {
+		foreach { filename diridx source } $_line_file_names {
 		    switch $_line_string_form {
 			string {
 			    _op .ascii [_quote $filename]
@@ -2517,6 +2523,7 @@  namespace eval Dwarf {
 			}
 		    }
 		    _op .uleb128 $diridx
+		    _op .ascii [_quote [string map { "\"" "\\\"" "\n" "\\n" } $source]]
 		}
 	    } else {
 		foreach dirname $_line_include_dirs {
@@ -2525,7 +2532,7 @@  namespace eval Dwarf {
 
 		_op .byte 0 "Terminator (include_directories)"
 
-		foreach { filename diridx } $_line_file_names {
+		foreach { filename diridx source } $_line_file_names {
 		    _op .ascii [_quote $filename]
 		    _op .sleb128 $diridx
 		    _op .sleb128 0 "mtime"
diff --git a/include/dwarf2.h b/include/dwarf2.h
index b3d3731ee83..3823c041bab 100644
--- a/include/dwarf2.h
+++ b/include/dwarf2.h
@@ -289,6 +289,11 @@  enum dwarf_line_number_content_type
     DW_LNCT_size = 0x4,
     DW_LNCT_MD5 = 0x5,
     DW_LNCT_lo_user = 0x2000,
+    /* LLVM has implemented DW_LNCT_source (see
+       https://dwarfstd.org/issues/180201.1.html) as DW_LNCT_LLVM_SOURCE as
+       a vendor extension until the DWARF standard is updated (see
+       https://github.com/llvm/llvm-project/blob/08bb121835be432ac52372f92845950628ce9a4a/llvm/include/llvm/BinaryFormat/Dwarf.def#L1080 .  */
+    DW_LNCT_LLVM_SOURCE = 0x2001,
     DW_LNCT_hi_user = 0x3fff
   };