[v5] gdb: Support embedded source in DWARF

Message ID 20240412185525.171292-1-hawkinsw@obs.cr
State New
Headers
Series [v5] gdb: Support embedded source in DWARF |

Checks

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

Commit Message

Will Hawkins April 12, 2024, 6:55 p.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:
    v4 -> v5
      - Fix a bug caused by an indeterminate value in the embeddeds
        member of the quick_file_names struct.
    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
  

Comments

Tom de Vries April 12, 2024, 8:12 p.m. UTC | #1
On 4/12/24 20:55, Will Hawkins wrote:
> +	    DW_LNE_set_address main_end
> +	    line [gdb_get_line_number "main end"]
> +	    DW_LNS_copy
> +
> +	    DW_LNE_end_sequence
> +	}
I've just submitted a patch series designed to catch this ( 
https://sourceware.org/pipermail/gdb-patches/2024-April/207922.html ).

The copy produces a line table entry with empty address range (because 
the end_sequence is at the same address),.

This is probably not what you intended.

Thanks,
- Tom
  
Will Hawkins April 14, 2024, 2:50 a.m. UTC | #2
On Fri, Apr 12, 2024 at 4:12 PM Tom de Vries <tdevries@suse.de> wrote:
>
> On 4/12/24 20:55, Will Hawkins wrote:
> > +         DW_LNE_set_address main_end
> > +         line [gdb_get_line_number "main end"]
> > +         DW_LNS_copy
> > +
> > +         DW_LNE_end_sequence
> > +     }
> I've just submitted a patch series designed to catch this (
> https://sourceware.org/pipermail/gdb-patches/2024-April/207922.html ).
>
> The copy produces a line table entry with empty address range (because
> the end_sequence is at the same address),.
>
> This is probably not what you intended.

What I get for doing copy/paste! A v6 is on its way! Thank you for the
eagle eyes!

On a semi-related note, it appears that "many" of the dwarf2 tests do
not properly specify

add_dummy_cus 0

in their invocation of Dwarf::assemble resulting in two CUs being
generated in the .S file. This does not appear to cause problems for
the tests, but if you run the generated binaries through a "dwarf
validator", e.g., llvm-dwarfdump, they will report errors. I have
requested an account on the gdb bugzilla to report a bug and will file
a related patch as a fix, if that is okay with you?

Will

>
> Thanks,
> - Tom
>
  
Tom de Vries April 14, 2024, 7:01 a.m. UTC | #3
On 4/14/24 04:50, Will Hawkins wrote:
> On Fri, Apr 12, 2024 at 4:12 PM Tom de Vries <tdevries@suse.de> wrote:
>>
>> On 4/12/24 20:55, Will Hawkins wrote:
>>> +         DW_LNE_set_address main_end
>>> +         line [gdb_get_line_number "main end"]
>>> +         DW_LNS_copy
>>> +
>>> +         DW_LNE_end_sequence
>>> +     }
>> I've just submitted a patch series designed to catch this (
>> https://sourceware.org/pipermail/gdb-patches/2024-April/207922.html ).
>>
>> The copy produces a line table entry with empty address range (because
>> the end_sequence is at the same address),.
>>
>> This is probably not what you intended.
> 
> What I get for doing copy/paste! A v6 is on its way! Thank you for the
> eagle eyes!
> 

It's just that I spent some time fixing all test-cases with this 
problem, so I happened to spot it.  Anyway, thanks for following up.

> On a semi-related note, it appears that "many" of the dwarf2 tests do
> not properly specify
> 
> add_dummy_cus 0
> 
> in their invocation of Dwarf::assemble resulting in two CUs being
> generated in the .S file. This does not appear to cause problems for
> the tests, but if you run the generated binaries through a "dwarf
> validator", e.g., llvm-dwarfdump, they will report errors. I have
> requested an account on the gdb bugzilla to report a bug and will file
> a related patch as a fix, if that is okay with you?

The dummy cu's are there by default, intentionally, as explained here ( 
https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=5ef670d81fd222ae5edfa1428ad48710f5e10d35 
).  In brief, it tries to make sure that when writing a dwarf assembly 
test-case on one platform it will work on another.

If this causes problems with llvm-dwarfdump, we can look into fixing 
that.  So, yes, please file a PR.

But I'd look into a way of fixing this that doesn't require 
"add_dummy_cus 0".

Thanks,
- Tom
  
Will Hawkins April 14, 2024, 7:15 a.m. UTC | #4
On Sun, Apr 14, 2024 at 3:01 AM Tom de Vries <tdevries@suse.de> wrote:
>
> On 4/14/24 04:50, Will Hawkins wrote:
> > On Fri, Apr 12, 2024 at 4:12 PM Tom de Vries <tdevries@suse.de> wrote:
> >>
> >> On 4/12/24 20:55, Will Hawkins wrote:
> >>> +         DW_LNE_set_address main_end
> >>> +         line [gdb_get_line_number "main end"]
> >>> +         DW_LNS_copy
> >>> +
> >>> +         DW_LNE_end_sequence
> >>> +     }
> >> I've just submitted a patch series designed to catch this (
> >> https://sourceware.org/pipermail/gdb-patches/2024-April/207922.html ).
> >>
> >> The copy produces a line table entry with empty address range (because
> >> the end_sequence is at the same address),.
> >>
> >> This is probably not what you intended.
> >
> > What I get for doing copy/paste! A v6 is on its way! Thank you for the
> > eagle eyes!
> >
>
> It's just that I spent some time fixing all test-cases with this
> problem, so I happened to spot it.  Anyway, thanks for following up.
>
> > On a semi-related note, it appears that "many" of the dwarf2 tests do
> > not properly specify
> >
> > add_dummy_cus 0
> >
> > in their invocation of Dwarf::assemble resulting in two CUs being
> > generated in the .S file. This does not appear to cause problems for
> > the tests, but if you run the generated binaries through a "dwarf
> > validator", e.g., llvm-dwarfdump, they will report errors. I have
> > requested an account on the gdb bugzilla to report a bug and will file
> > a related patch as a fix, if that is okay with you?
>
> The dummy cu's are there by default, intentionally, as explained here (
> https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=5ef670d81fd222ae5edfa1428ad48710f5e10d35
> ).  In brief, it tries to make sure that when writing a dwarf assembly
> test-case on one platform it will work on another.
>
> If this causes problems with llvm-dwarfdump, we can look into fixing
> that.  So, yes, please file a PR.
>
> But I'd look into a way of fixing this that doesn't require
> "add_dummy_cus 0".

Interesting. So, as I understand it, forcing CUs for the file being
compiled (hello.c in the example you cited) to be somewhere other than
the beginning of the .debug_info sections allowed you to catch
additional bugs. In order to draw out similar bugs on platforms where
the "main" CU goes first by default, you added the dummy CU feature.
Am I understanding that correctly? If so, that's really neat.

I will file a bug and then work on PR that makes the dummy cu feature
"compatible" with llvm-dwarfdump. It could obviously also be an issue
with the llvm-dwarfdump tool, too. I have contributed to LLDB, too,
and would be more than happy to work with them if the problems is
really in their tool.

Thank you for the follow up!
Will


>
> Thanks,
> - Tom
>
  
Will Hawkins April 15, 2024, 4:13 p.m. UTC | #5
On Sun, Apr 14, 2024 at 3:15 AM Will Hawkins <hawkinsw@obs.cr> wrote:
>
> On Sun, Apr 14, 2024 at 3:01 AM Tom de Vries <tdevries@suse.de> wrote:
> >
> > On 4/14/24 04:50, Will Hawkins wrote:
> > > On Fri, Apr 12, 2024 at 4:12 PM Tom de Vries <tdevries@suse.de> wrote:
> > >>
> > >> On 4/12/24 20:55, Will Hawkins wrote:
> > >>> +         DW_LNE_set_address main_end
> > >>> +         line [gdb_get_line_number "main end"]
> > >>> +         DW_LNS_copy
> > >>> +
> > >>> +         DW_LNE_end_sequence
> > >>> +     }
> > >> I've just submitted a patch series designed to catch this (
> > >> https://sourceware.org/pipermail/gdb-patches/2024-April/207922.html ).
> > >>
> > >> The copy produces a line table entry with empty address range (because
> > >> the end_sequence is at the same address),.
> > >>
> > >> This is probably not what you intended.
> > >
> > > What I get for doing copy/paste! A v6 is on its way! Thank you for the
> > > eagle eyes!
> > >
> >
> > It's just that I spent some time fixing all test-cases with this
> > problem, so I happened to spot it.  Anyway, thanks for following up.
> >
> > > On a semi-related note, it appears that "many" of the dwarf2 tests do
> > > not properly specify
> > >
> > > add_dummy_cus 0
> > >
> > > in their invocation of Dwarf::assemble resulting in two CUs being
> > > generated in the .S file. This does not appear to cause problems for
> > > the tests, but if you run the generated binaries through a "dwarf
> > > validator", e.g., llvm-dwarfdump, they will report errors. I have
> > > requested an account on the gdb bugzilla to report a bug and will file
> > > a related patch as a fix, if that is okay with you?
> >
> > The dummy cu's are there by default, intentionally, as explained here (
> > https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=5ef670d81fd222ae5edfa1428ad48710f5e10d35
> > ).  In brief, it tries to make sure that when writing a dwarf assembly
> > test-case on one platform it will work on another.
> >
> > If this causes problems with llvm-dwarfdump, we can look into fixing
> > that.  So, yes, please file a PR.
> >
> > But I'd look into a way of fixing this that doesn't require
> > "add_dummy_cus 0".
>
> Interesting. So, as I understand it, forcing CUs for the file being
> compiled (hello.c in the example you cited) to be somewhere other than
> the beginning of the .debug_info sections allowed you to catch
> additional bugs. In order to draw out similar bugs on platforms where
> the "main" CU goes first by default, you added the dummy CU feature.
> Am I understanding that correctly? If so, that's really neat.

FYI for Tom, the problem is with llvm-dwarfdump and I am currently in
the process of opening a PR with a fix.

Sincerely,
Will


>
> I will file a bug and then work on PR that makes the dummy cu feature
> "compatible" with llvm-dwarfdump. It could obviously also be an issue
> with the llvm-dwarfdump tool, too. I have contributed to LLDB, too,
> and would be more than happy to work with them if the problems is
> really in their tool.
>
> Thank you for the follow up!
> Will
>
>
> >
> > Thanks,
> > - Tom
> >
  
Tom Tromey April 19, 2024, 8:17 p.m. UTC | #6
>>>>> "Will" == Will Hawkins <hawkinsw@obs.cr> writes:

Will> While DW_LNCT_source is not yet finalized in the DWARF standard
Will> (https://dwarfstd.org/issues/180201.1.html), LLVM does emit it.

Will> This patch adds support for it in gdb.

Thanks.

Will> -      m_fullname = find_source_or_rewrite (get_name (), get_comp_dir ());
Will> +      {
Will> +	if (m_is_embedded)
Will> +	  m_fullname = make_unique_xstrdup (embedded_fullname (
Will> +							       get_name (),
Will> +							       get_comp_dir ()));

No line break before the get_name call.

Will> +	  else if (entry.source != nullptr)
Will> +	    {
Will> +	      /* We have an embedded source for the CU.  */
Will> +	      gdb_assert (offset == 1);
Will> +	      cu_file_embedded = true;

This assert makes me a bit nervous.  Can this be fooled by bad input
data?  Or is it really testing some internal invariant?  I couldn't
convince myself it was the latter.

Will> +	fullname.reset (embedded_fullname (dirname, qfn->file_names[index]));

fullname = ...

Will> +  /* Because the line header may tell us information about the CU
Will> +     filename (e.g., whether it is embedded) which will affect other
Will> +     calculations, we have to read that information here.  */
Will> +  line_header *lh = cu->line_header;
Will> +  struct attribute *attr = dwarf2_attr (die, DW_AT_stmt_list, cu);
Will> +  if (lh == nullptr && attr != nullptr && attr->form_is_unsigned ())
Will> +    {
Will> +      sect_offset line_offset = (sect_offset) attr->as_unsigned ();
Will> +      line_header_up lhu = dwarf_decode_line_header (line_offset, cu,
Will> +				     res.get_comp_dir ());
Will> +      if (lhu != nullptr)
Will> +	  lh = lhu.release();

This will leak memory.

However if this is the first time reading the header, should it be
stashed somewhere?  It seems to me it shouldn't have to be read more
than once.

IOW, how does this case get reached?

Will> +/* See source.h.  */
Will> +
Will> +char *
Will> +embedded_fullname (const char *dirname, const char *filename)

Should return a unique_xmalloc_ptr<char>

Will> +{
Will> +  if (dirname != nullptr)
Will> +    {
Will> +      return concat (dirname, SLASH_STRING, filename, (char *) nullptr);
Will> +    }

No braces needed.

Will> +
Will> +  return xstrdup (filename);

make_unique_xstrdup

thanks,
Tom
  
Will Hawkins April 26, 2024, 6:12 a.m. UTC | #7
On Fri, Apr 19, 2024 at 4:18 PM Tom Tromey <tom@tromey.com> wrote:
>
> >>>>> "Will" == Will Hawkins <hawkinsw@obs.cr> writes:
>
> Will> While DW_LNCT_source is not yet finalized in the DWARF standard
> Will> (https://dwarfstd.org/issues/180201.1.html), LLVM does emit it.
>
> Will> This patch adds support for it in gdb.
>
> Thanks.

Sorry for the delay responding. I went back and did a much deeper dive
into the source code so that I could get a better idea of how each of
the components interacted to make myself feel more confident that I am
doing "the right thing".

>
> Will> -      m_fullname = find_source_or_rewrite (get_name (), get_comp_dir ());
> Will> +      {
> Will> + if (m_is_embedded)
> Will> +   m_fullname = make_unique_xstrdup (embedded_fullname (
> Will> +                                                        get_name (),
> Will> +                                                        get_comp_dir ()));
>
> No line break before the get_name call.
>
> Will> +   else if (entry.source != nullptr)
> Will> +     {
> Will> +       /* We have an embedded source for the CU.  */
> Will> +       gdb_assert (offset == 1);
> Will> +       cu_file_embedded = true;
>
> This assert makes me a bit nervous.  Can this be fooled by bad input
> data?  Or is it really testing some internal invariant?  I couldn't
> convince myself it was the latter.
>
> Will> + fullname.reset (embedded_fullname (dirname, qfn->file_names[index]));
>
> fullname = ...
>
> Will> +  /* Because the line header may tell us information about the CU
> Will> +     filename (e.g., whether it is embedded) which will affect other
> Will> +     calculations, we have to read that information here.  */
> Will> +  line_header *lh = cu->line_header;
> Will> +  struct attribute *attr = dwarf2_attr (die, DW_AT_stmt_list, cu);
> Will> +  if (lh == nullptr && attr != nullptr && attr->form_is_unsigned ())
> Will> +    {
> Will> +      sect_offset line_offset = (sect_offset) attr->as_unsigned ();
> Will> +      line_header_up lhu = dwarf_decode_line_header (line_offset, cu,
> Will> +                              res.get_comp_dir ());
> Will> +      if (lhu != nullptr)
> Will> +   lh = lhu.release();
>
> This will leak memory.
>
> However if this is the first time reading the header, should it be
> stashed somewhere?  It seems to me it shouldn't have to be read more
> than once.
>
> IOW, how does this case get reached?

As I was doing my additional research and attempting to figure out a
good response to this question, I came to the conclusion that there
are several places where dwarf_decode_line_header is invoked and the
results of those invocations are inconsistently stored for later
reuse. Like there is the line_header_hash_table for deduplicating line
header decoding in a partial unit, I am working on a change that will
give dwarf2_per_cu the power to own a line_header that can be shared
among each of dwarf2_cus instantiated for the various cutu_reader
instances. As it stands (still being debugged), the ability to own a
decoded line header in a per CU is a supplement to the line header
hash table in the per objfile -- if there is a line header hash table
and the line header belongs to a partial unit, then that owns the
decoded line header; if not (or there is a conflict in the hash
table), the per CU owns the decoded line header. With that change,
there is no need for instances of dwarf2_cu to own a decoded line
header which means that it is possible to remove the process_die_scope
class.

You all are the experts here and I am a new person looking at this
code. I would love to hear what you think about that possible
approach. It seems to increase the reuse of a data structure that is
expensive to build and reduce some code complexity by removing the
RAII process_die_scope.

Again, though, I am not nearly as smart as you all. That said, I have
had a tremendously enjoyable time studying the code and would be more
than willing to either

1. Incorporate this change into the changes needed to support embedded
source code in DWARF, or
2. Make it a separate patch entirely.

Sorry again for the delay responding!
Will


Will

>
> Will> +/* See source.h.  */
> Will> +
> Will> +char *
> Will> +embedded_fullname (const char *dirname, const char *filename)
>
> Should return a unique_xmalloc_ptr<char>
>
> Will> +{
> Will> +  if (dirname != nullptr)
> Will> +    {
> Will> +      return concat (dirname, SLASH_STRING, filename, (char *) nullptr);
> Will> +    }
>
> No braces needed.
>
> Will> +
> Will> +  return xstrdup (filename);
>
> make_unique_xstrdup
>
> thanks,
> Tom
  

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..4300160c706 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 (offset != 0)
+    embeddeds[0] = cu_file_embedded;
+  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
   };