[RFC,1/1] gdb: Support embedded source in DWARF

Message ID 20240318162209.468831-2-hawkinsw@obs.cr
State Dropped, archived
Headers
Series Add support for embedded source in GDB |

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-arm fail Testing failed
linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 fail Testing failed

Commit Message

Will Hawkins March 18, 2024, 4:22 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.

ChangeLog:

	* gdb/dwarf2/line-header.c (line_header::add_file_name): Add
	  source as parameter.
	(read_formatted_entries): Handle additional source parameter.
	(dwarf_decode_line_header): Read DW_LNCT_[llvm]_source.
	* gdb/dwarf2/line-header.h (struct file_entry): Add source
	  member.
	* gdb/dwarf2/read.c (dwarf_decode_lines_1): Pass through source.
	(dwarf_decode_lines): Assign source, if present.
	* gdb/source.c (open_embedded_source): New function for
	  "opening" an embedded source file.
	(open_source_file): Call open_embedded_source.
	* gdb/source.h (open_embedded_source): Add declaration for
	  open_embedded_source.
	* gdb/symtab.h (struct symtab): Add source member.
	* gdb/testsuite/lib/dwarf.exp: Update testing library to support
	  adding embedded source to a file entry in a line table
	  program.
	* gdb/testsuite/gdb.dwarf2/dw2-lnct-source.c: New test.
	* gdb/testsuite/gdb.dwarf2/dw2-lnct-source.exp: New test.

Signed-off-by: Will Hawkins <hawkinsw@obs.cr>
---
 gdb/dwarf2/line-header.c                     | 22 +++--
 gdb/dwarf2/line-header.h                     |  9 ++-
 gdb/dwarf2/read.c                            | 11 ++-
 gdb/source.c                                 | 73 ++++++++++++++++-
 gdb/source.h                                 |  4 +
 gdb/symtab.h                                 |  2 +
 gdb/testsuite/gdb.dwarf2/dw2-lnct-source.c   | 24 ++++++
 gdb/testsuite/gdb.dwarf2/dw2-lnct-source.exp | 85 ++++++++++++++++++++
 gdb/testsuite/lib/dwarf.exp                  | 14 +++-
 include/dwarf2.h                             |  2 +
 10 files changed, 227 insertions(+), 19 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 Tromey March 18, 2024, 5:24 p.m. UTC | #1
>>>>> "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 for the patch.

Will> ChangeLog:

gdb doesn't use ChangeLogs any more, so you can delete this part.

Will> +	  if (fe.source)
Will> +	    sf->symtab->source = cu->per_objfile->objfile->intern (fe.source);

IIUC the 'source' here is the full text of the source code.

In this case you don't want to use intern.  That will make a copy of the
text.  Instead, because the section data is read-in and not unmapped or
freed until the objfile is destroyed, you can just use the pointer
directly.

Will> +/* Open an embedded  source file given a symtab S.  Returns a file descriptor
Will> +   or negative errno for error.  */
Will> +
Will> +scoped_fd
Will> +open_embedded_source(struct symtab *s,
Will> +		     gdb::unique_xmalloc_ptr<char> *fullname)
Will> +{

I think some other refactoring should be done so that gdb doesn't have
to write the source to a file and then re-read it.

Some kind of abstraction here would be appropriate.

Will> --- a/gdb/symtab.h
Will> +++ b/gdb/symtab.h
Will> @@ -1755,6 +1755,8 @@ struct symtab
 
Will>    const char *filename;
 
Will> +  const char *source;

This line could use an explanatory comment.

Will> diff --git a/include/dwarf2.h b/include/dwarf2.h
Will> index b3d3731ee83..abe0359926b 100644
Will> --- a/include/dwarf2.h
Will> +++ b/include/dwarf2.h
Will> @@ -288,7 +288,9 @@ enum dwarf_line_number_content_type
Will>      DW_LNCT_timestamp = 0x3,
Will>      DW_LNCT_size = 0x4,
Will>      DW_LNCT_MD5 = 0x5,
Will> +    DW_LNCT_SOURCE = 0x6,
Will>      DW_LNCT_lo_user = 0x2000,
Will> +    DW_LNCT_llvm_SOURCE = 0x2001,

This should probably use "LLVM" and not "llvm".
Also for vendor extensions we like to have some kind of comment or
documentation explaining what it is about -- in the more distant past
this wasn't done and as a result there are some mystery extensions.

Also I would not assign DW_LNCT_SOURCE until DWARF officially blesses
it.

Tom
  
Will Hawkins March 18, 2024, 9:18 p.m. UTC | #2
On Mon, Mar 18, 2024 at 1:24 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 for the patch.
>
> Will> ChangeLog:
>
> gdb doesn't use ChangeLogs any more, so you can delete this part.

Tom,

Thank you so much for the comments! I am glad to see that (based on
the volume of comments) I am on the right track. I really appreciate
you taking the time to respond!

And, yes, I *thought* that gdb had "retired" ChangeLogs, but I wasn't
sure. I was trying to be as "correct" as possible. I will definitely
drop it in v2!

>
> Will> +   if (fe.source)
> Will> +     sf->symtab->source = cu->per_objfile->objfile->intern (fe.source);
>
> IIUC the 'source' here is the full text of the source code.
>
> In this case you don't want to use intern.  That will make a copy of the
> text.  Instead, because the section data is read-in and not unmapped or
> freed until the objfile is destroyed, you can just use the pointer
> directly.

Thank you for confirming! I thought that was the case (that the
lifetime of the objfile outlives the symtab) but I wasn't sure.

>
> Will> +/* Open an embedded  source file given a symtab S.  Returns a file descriptor
> Will> +   or negative errno for error.  */
> Will> +
> Will> +scoped_fd
> Will> +open_embedded_source(struct symtab *s,
> Will> +              gdb::unique_xmalloc_ptr<char> *fullname)
> Will> +{
>
> I think some other refactoring should be done so that gdb doesn't have
> to write the source to a file and then re-read it.
>
> Some kind of abstraction here would be appropriate.

Absolutely! I will do that!

>
> Will> --- a/gdb/symtab.h
> Will> +++ b/gdb/symtab.h
> Will> @@ -1755,6 +1755,8 @@ struct symtab
>
> Will>    const char *filename;
>
> Will> +  const char *source;
>
> This line could use an explanatory comment.

I *thought* I added one -- sorry!

>
> Will> diff --git a/include/dwarf2.h b/include/dwarf2.h
> Will> index b3d3731ee83..abe0359926b 100644
> Will> --- a/include/dwarf2.h
> Will> +++ b/include/dwarf2.h
> Will> @@ -288,7 +288,9 @@ enum dwarf_line_number_content_type
> Will>      DW_LNCT_timestamp = 0x3,
> Will>      DW_LNCT_size = 0x4,
> Will>      DW_LNCT_MD5 = 0x5,
> Will> +    DW_LNCT_SOURCE = 0x6,
> Will>      DW_LNCT_lo_user = 0x2000,
> Will> +    DW_LNCT_llvm_SOURCE = 0x2001,
>
> This should probably use "LLVM" and not "llvm".
> Also for vendor extensions we like to have some kind of comment or
> documentation explaining what it is about -- in the more distant past
> this wasn't done and as a result there are some mystery extensions.

You got it!

>
> Also I would not assign DW_LNCT_SOURCE until DWARF officially blesses
> it.
>

Will do!!

Thank you, again, Tom!

Will



> Tom
  

Patch

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