diff --git a/gdb/dwarf2/line-header.c b/gdb/dwarf2/line-header.c
index a3ca49b64f5..837d9b5cfa4 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:
+	    case DW_LNCT_SOURCE:
+	      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 4afb026b8ce..80f60a54f8e 100644
--- a/gdb/dwarf2/read.c
+++ b/gdb/dwarf2/read.c
@@ -18426,7 +18426,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:
@@ -18581,9 +18581,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 = cu->per_objfile->objfile->intern (fe.source);
+	}
       fe.symtab = sf->symtab;
     }
 }
diff --git a/gdb/source.c b/gdb/source.c
index bbeb4154258..b7f3aa65883 100644
--- a/gdb/source.c
+++ b/gdb/source.c
@@ -29,9 +29,11 @@
 #include "gdbsupport/filestuff.h"
 
 #include <sys/types.h>
+#include <sys/time.h>
 #include <fcntl.h>
 #include "gdbcore.h"
 #include "gdbsupport/gdb_regex.h"
+#include "gdbsupport/gdb_unlinker.h"
 #include "symfile.h"
 #include "objfiles.h"
 #include "annotate.h"
@@ -1135,6 +1137,67 @@ find_and_open_source (const char *filename,
   return scoped_fd (result);
 }
 
+/* Open an embedded  source file given a symtab S.  Returns a file descriptor
+   or negative errno for error.  */
+
+scoped_fd
+open_embedded_source(struct symtab *s,
+		     gdb::unique_xmalloc_ptr<char> *fullname)
+{
+  if (!s->source || !strlen (s->source))
+    return scoped_fd (-1);
+
+  /* Make a temporary file in the same directory as the object file. */
+  std::string dirname = ldirname (objfile_name (s->compunit ()-> objfile
+						()));
+  std::string filename = lbasename (s->filename);
+  gdb::char_vector temp_name = make_temp_filename (dirname +
+						   SLASH_STRING +
+						   filename);
+  scoped_fd fd = gdb_mkostemp_cloexec (temp_name.data (),
+				       O_TEXT);
+  if (fd.get () < 0)
+    return fd;
+
+  /* Always unlink. Noone else needs the file by name.  */
+  gdb::unlinker unlink (temp_name.data ());
+
+  /* Write the source contents into it.  */
+  size_t source_len = strlen (s->source);
+  if (source_len != write(fd.get (), s->source, source_len))
+    {
+      close (fd.get ());
+      return scoped_fd (-1);
+    }
+
+  if (lseek(fd.get (), 0, SEEK_SET))
+    {
+      close (fd.get ());
+      return scoped_fd (-1);
+    }
+
+  /* Adjust its time(s) so that gdb doesn't print an awkward
+     message about it being more recent than the exe.  */
+  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;
+
+  struct timeval tvs[2] = {};
+  tvs[0].tv_sec = mtime;
+  tvs[1].tv_sec = mtime;
+  if (futimes (fd.get (), tvs) < 0)
+    {
+      close (fd.get ());
+      return scoped_fd (-1);
+    }
+
+  *fullname = make_unique_xstrdup (temp_name.data ());
+  return fd;
+}
+
 /* Open a source file given a symtab S.  Returns a file descriptor or
    negative errno for error.
    
@@ -1148,7 +1211,15 @@ open_source_file (struct symtab *s)
 
   gdb::unique_xmalloc_ptr<char> fullname (s->fullname);
   s->fullname = NULL;
-  scoped_fd fd = find_and_open_source (s->filename, s->compunit ()->dirname (),
+
+  scoped_fd fd = open_embedded_source(s, &fullname);
+  if (fd.get () >= 0)
+    {
+      s->fullname = fullname.release ();
+      return fd;
+    }
+
+  fd = find_and_open_source (s->filename, s->compunit ()->dirname (),
 				       &fullname);
 
   if (fd.get () < 0)
diff --git a/gdb/source.h b/gdb/source.h
index 144ee48f722..711d49749df 100644
--- a/gdb/source.h
+++ b/gdb/source.h
@@ -85,6 +85,10 @@ extern gdb::unique_xmalloc_ptr<char> find_source_or_rewrite
    negative errno indicating the reason for the failure.  */
 extern scoped_fd open_source_file (struct symtab *s);
 
+extern scoped_fd
+open_embedded_source(struct symtab *s,
+		     gdb::unique_xmalloc_ptr<char> *fullname);
+
 extern gdb::unique_xmalloc_ptr<char> rewrite_source_path (const char *path);
 
 extern const char *symtab_to_fullname (struct symtab *s);
diff --git a/gdb/symtab.h b/gdb/symtab.h
index 5bd63979132..4a925050d6f 100644
--- a/gdb/symtab.h
+++ b/gdb/symtab.h
@@ -1755,6 +1755,8 @@ struct symtab
 
   const char *filename;
 
+  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..6c03c84d4e7
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-lnct-source.c
@@ -0,0 +1,24 @@
+/* Copyright 2022-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..19deebfbbc2
--- /dev/null
+++ b/gdb/testsuite/gdb.dwarf2/dw2-lnct-source.exp
@@ -0,0 +1,85 @@
+# Copyright 2022-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.*"
diff --git a/gdb/testsuite/lib/dwarf.exp b/gdb/testsuite/lib/dwarf.exp
index d085f835f07..43ee29a7185 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,6 +2494,11 @@ namespace eval Dwarf {
 			    "directory_entry_format (form: DW_FORM_line_strp)"
 		    }
 		}
+		_op .uleb128 0x2001 \
+		    "file_name_entry_format (content type code:
+		  DW_LNCT_llvm_source)"
+		_op .uleb128 0x08 \
+		  "directory_entry_format (form: DW_FORM_string)"
 		_op .uleb128 2 \
 		    "file_name_entry_format (content type code: DW_LNCT_directory_index)"
 		_op .uleb128 0x0f \
@@ -2502,7 +2507,7 @@ namespace eval Dwarf {
 		set nr_files [expr [llength $_line_file_names] / 2]
 		_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]
@@ -2516,6 +2521,7 @@ namespace eval Dwarf {
 			    _op_offset [expr $_line_is_64 ? 8 : 4] $string_ptr
 			}
 		    }
+		    _op .ascii [_quote [string map { "\"" "\\\"" "\n" "\\n" } "$source"]]
 		    _op .uleb128 $diridx
 		}
 	    } else {
diff --git a/include/dwarf2.h b/include/dwarf2.h
index b3d3731ee83..abe0359926b 100644
--- a/include/dwarf2.h
+++ b/include/dwarf2.h
@@ -288,7 +288,9 @@ enum dwarf_line_number_content_type
     DW_LNCT_timestamp = 0x3,
     DW_LNCT_size = 0x4,
     DW_LNCT_MD5 = 0x5,
+    DW_LNCT_SOURCE = 0x6,
     DW_LNCT_lo_user = 0x2000,
+    DW_LNCT_llvm_SOURCE = 0x2001,
     DW_LNCT_hi_user = 0x3fff
   };
 
