Commit: Add support for DWARF-5 .debug_line sections

Message ID 877dzrqjqd.fsf@redhat.com
State New
Headers show
Series
  • Commit: Add support for DWARF-5 .debug_line sections
Related show

Commit Message

H.J. Lu via Binutils March 11, 2020, 10:16 a.m.
Hi Guys,

  I am applying the attached patch to add support for DWARF-5
  .debug_line sections.  DWARF v5 changes the format of the
  section's header so that the layout of the directory table and the
  file table are explicitly specified.  This allow for extra fields
  and also the possibility of reducing the size of the tables by not
  storing unneeded fields.

  The patch also extended the .file directive so that it can take an
  optional directory name and md5 sum, like this:

    .file <fileno> [<dirname>] <filename> [md5 <value>]

  The md5 sum is a hash of the source file, so that consumers can verify
  that any file that they locate matches the debug information.

  As part of the patch I have also updated the DWARF decoder used by
  readelf and objdump to enhance its display of version 5 directory and
  file name tables.
  
Cheers
  Nick

binutils/ChangeLog
2020-03-11  Nick Clifton  <nickc@redhat.com>

	PR 25611
	PR 25614
	* dwarf.h (DWARF2_Internal_LineInfo): Add li_address_size and
	li_segment_size fields.
	* dwarf.c (read_debug_line_header): Record the address size and
	segment selector size values (if present) in the lineinfo
	structure.
	(display_formatted_table): Warn if the format count is empty but
	the table itself is not empty.
	Display the format count and entry count at the start of the table
	dump.
	(display_debug_lines_raw): Display the address size and segement
	selector size fields, if present.
	* testsuite/binutils-all/dw5.W: Update expected output.

gas/ChangeLog
2020-03-11  Nick Clifton  <nickc@redhat.com>

	PR 25611
	PR 25614
	* dwarf2dbg.c (DWARF2_FILE_TIME_NAME): Default to -1.
	(DWARF2_FILE_SIZE_NAME): Default to -1.
	(DWARF2_LINE_VERSION): Default to the current dwarf level or 3,
	whichever is higher.
	(DWARF2_LINE_MAX_OPS_PER_INSN): Provide a default value of 1.
	(NUM_MD5_BYTES): Define.
	(struct file entry): Add md5 field.
	(get_filenum): Delete and replace with...
	(get_basename): New function.
	(get_directory_table_entry): New function.
	(allocate_filenum): New function.
	(allocate_filename_to_slot): New function.
	(dwarf2_where): Use new functions.
	(dwarf2_directive_filename): Add support for extended .file
	pseudo-op.
	(dwarf2_directive_loc): Allow the use of file number zero with
	DWARF 5 or higher.
	(out_file_list): Rename to...
	(out_dir_and_file_list): Add DWARF 5 support.
	(out_debug_line): Emit extra values into the section header for
	DWARF 5.
	(out_debug_str): Allow for file 0 to be used with DWARF 5.
	* doc/as.texi (.file): Update the description of this pseudo-op.
	* testsuite/gas/elf-dwarf-5-file0.s: Add more lines.
	* testsuite/gas/elf-dwarf-5-file0.d: Update expected dump output.
	* testsuite/gas/lns/lns-diag-1.l: Update expected error message.
	* NEWS: Mention the new feature.

Patch

diff --git a/binutils/dwarf.c b/binutils/dwarf.c
index 2403ac7174..c75059bd93 100644
--- a/binutils/dwarf.c
+++ b/binutils/dwarf.c
@@ -3622,7 +3622,6 @@  read_debug_line_header (struct dwarf_section * section,
 {
   unsigned char *hdrptr;
   unsigned int initial_length_size;
-  unsigned char address_size, segment_selector_size;
 
   /* Extract information from the Line Number Program Header.
      (section 6.2.4 in the Dwarf3 doc).  */
@@ -3679,15 +3678,15 @@  read_debug_line_header (struct dwarf_section * section,
 
   if (linfo->li_version >= 5)
     {
-      SAFE_BYTE_GET_AND_INC (address_size, hdrptr, 1, end);
+      SAFE_BYTE_GET_AND_INC (linfo->li_address_size, hdrptr, 1, end);
 
-      SAFE_BYTE_GET_AND_INC (segment_selector_size, hdrptr, 1, end);
-      if (segment_selector_size != 0)
+      SAFE_BYTE_GET_AND_INC (linfo->li_segment_size, hdrptr, 1, end);
+      if (linfo->li_segment_size != 0)
 	{
 	  warn (_("The %s section contains "
 		  "unsupported segment selector size: %d.\n"),
-		section->name, segment_selector_size);
-	  return 0;
+		section->name, linfo->li_segment_size);
+	  return NULL;
 	}
     }
 
@@ -3737,7 +3736,8 @@  display_formatted_table (unsigned char *                   data,
   unsigned char *format_start, format_count, *format, formati;
   dwarf_vma data_count, datai;
   unsigned int namepass, last_entry = 0;
-
+  const char * table_name = is_dir ? N_("Directory Table") : N_("File Name Table");
+  
   SAFE_BYTE_GET_AND_INC (format_count, data, 1, end);
   format_start = data;
   for (formati = 0; formati < format_count; formati++)
@@ -3746,10 +3746,7 @@  display_formatted_table (unsigned char *                   data,
       SKIP_ULEB (data, end);
       if (data == end)
 	{
-	  if (is_dir)
-	    warn (_("Corrupt directory format table entry\n"));
-	  else
-	    warn (_("Corrupt file name format table entry\n"));
+	  warn (_("%s: Corrupt format description entry\n"), table_name);
 	  return data;
 	}
     }
@@ -3757,28 +3754,25 @@  display_formatted_table (unsigned char *                   data,
   READ_ULEB (data_count, data, end);
   if (data == end)
     {
-      if (is_dir)
-	warn (_("Corrupt directory list\n"));
-      else
-	warn (_("Corrupt file name list\n"));
+      warn (_("%s: Corrupt entry count\n"), table_name);
       return data;
     }
 
   if (data_count == 0)
     {
-      if (is_dir)
-	printf (_("\n The Directory Table is empty.\n"));
-      else
-	printf (_("\n The File Name Table is empty.\n"));
+      printf (_("\n The %s is empty.\n"), table_name);
       return data;
     }
+  else if (format_count == 0)
+    {
+      warn (_("%s: format count is zero, but the table is not empty\n"),
+	    table_name);
+      return end;
+    }
 
-  if (is_dir)
-    printf (_("\n The Directory Table (offset 0x%lx):\n"),
-	    (long) (data - start));
-  else
-    printf (_("\n The File Name Table (offset 0x%lx):\n"),
-	    (long) (data - start));
+  printf (_("\n The %s (offset 0x%lx, lines %s, columns %u):\n"),
+	  table_name, (long) (data - start), dwarf_vmatoa ("u", data_count),
+	  format_count);
 
   printf (_("  Entry"));
   /* Delay displaying name as the last entry for better screen layout.  */ 
@@ -3806,7 +3800,7 @@  display_formatted_table (unsigned char *                   data,
 		printf (_("\tSize"));
 		break;
 	      case DW_LNCT_MD5:
-		printf (_("\tMD5"));
+		printf (_("\tMD5\t\t\t"));
 		break;
 	      default:
 		printf (_("\t(Unknown format content type %s)"),
@@ -3840,12 +3834,10 @@  display_formatted_table (unsigned char *                   data,
 						  section, NULL, '\t', -1);
 	    }
 	}
-      if (data == end)
+
+      if (data == end && (datai < data_count - 1))
 	{
-	  if (is_dir)
-	    warn (_("Corrupt directory entries list\n"));
-	  else
-	    warn (_("Corrupt file name entries list\n"));
+	  warn (_("\n%s: Corrupt entries list\n"), table_name);
 	  return data;
 	}
       putchar ('\n');
@@ -3909,6 +3901,11 @@  display_debug_lines_raw (struct dwarf_section *  section,
 	  printf (_("  Offset:                      0x%lx\n"), (long)(data - start));
 	  printf (_("  Length:                      %ld\n"), (long) linfo.li_length);
 	  printf (_("  DWARF Version:               %d\n"), linfo.li_version);
+	  if (linfo.li_version >= 5)
+	    {
+	      printf (_("  Address size (bytes):        %d\n"), linfo.li_address_size);
+	      printf (_("  Segment selector (bytes):    %d\n"), linfo.li_segment_size);
+	    }
 	  printf (_("  Prologue Length:             %d\n"), (int) linfo.li_prologue_length);
 	  printf (_("  Minimum Instruction Length:  %d\n"), linfo.li_min_insn_length);
 	  if (linfo.li_version >= 4)
diff --git a/binutils/dwarf.h b/binutils/dwarf.h
index 6956692676..2249750f87 100644
--- a/binutils/dwarf.h
+++ b/binutils/dwarf.h
@@ -29,6 +29,8 @@  typedef struct
 {
   dwarf_vma	 li_length;
   unsigned short li_version;
+  unsigned char  li_address_size;
+  unsigned char  li_segment_size;
   dwarf_vma      li_prologue_length;
   unsigned char  li_min_insn_length;
   unsigned char  li_max_ops_per_insn;
diff --git a/binutils/testsuite/binutils-all/dw5.W b/binutils/testsuite/binutils-all/dw5.W
index 74c02566ee..2eccb03c5a 100644
--- a/binutils/testsuite/binutils-all/dw5.W
+++ b/binutils/testsuite/binutils-all/dw5.W
@@ -291,6 +291,8 @@  Raw dump of debug contents of section .debug_line:
   Offset:                      0x0
   Length:                      144
   DWARF Version:               5
+  Address size \(bytes\):        8
+  Segment selector \(bytes\):    0
   Prologue Length:             60
   Minimum Instruction Length:  1
   Maximum Ops per Instruction: 1
@@ -313,13 +315,13 @@  Raw dump of debug contents of section .debug_line:
   Opcode 11 has 0 args
   Opcode 12 has 1 arg
 
- The Directory Table \(offset 0x22\):
+ The Directory Table \(offset 0x22, lines 3, columns 1\):
   Entry	Name
   0	\(indirect line string, offset: 0x0\): 
   1	\(indirect line string, offset: 0x1\): 
   2	\(indirect line string, offset: 0x22\): /usr/include
 
- The File Name Table \(offset 0x34\):
+ The File Name Table \(offset 0x34, lines 4, columns 2\):
   Entry	Dir	Name
   0	0	\(indirect line string, offset: 0x14\): main.c
   1	1	\(indirect line string, offset: 0x1b\): main.c
diff --git a/gas/NEWS b/gas/NEWS
index 9a3f352108..5623db6435 100644
--- a/gas/NEWS
+++ b/gas/NEWS
@@ -1,5 +1,9 @@ 
 -*- text -*-
 
+* Add --gdwarf-5 option to the assembler to generate DWARF 5 debug output
+  (if such output is being generated).  Added the ability to generate
+  version 5 .debug_line sections.
+
 Changes in 2.34:
 
 * Add -malign-branch-boundary=NUM, -malign-branch=TYPE[+TYPE...],
diff --git a/gas/doc/as.texi b/gas/doc/as.texi
index 711578fc76..0a6727ef84 100644
--- a/gas/doc/as.texi
+++ b/gas/doc/as.texi
@@ -5366,13 +5366,29 @@  to the @code{.debug_line} file name table.  The syntax is:
 
 The @var{fileno} operand should be a unique positive integer to use as the
 index of the entry in the table.  The @var{filename} operand is a C string
-literal.
+literal enclosed in double quotes.  The @var{filename} can include directory
+elements.  If it does, then the directory will be added to the directory table
+and the basename will be added to the file table.
 
 The detail of filename indices is exposed to the user because the filename
 table is shared with the @code{.debug_info} section of the DWARF2 debugging
 information, and thus the user must know the exact indices that table
 entries will have.
 
+If DWARF-5 support has been enabled via the @option{-gdwarf-5} option then
+an extended version of the @code{file} is also allowed:
+
+@smallexample
+.file @var{fileno} [@var{dirname}] @var{filename} [md5 @var{value}]
+@end smallexample
+
+With this version a separate directory name is allowed, although if this is
+used then @var{filename} should not contain any directory components.  In
+addtion an md5 hash value of the contents of @var{filename} can be provided.
+This will be stored in the the file table as well, and can be used by tools
+reading the debug information to verify that the contents of the source file
+match the contents of the compiled file.
+
 @node Fill
 @section @code{.fill @var{repeat} , @var{size} , @var{value}}
 
diff --git a/gas/dwarf2dbg.c b/gas/dwarf2dbg.c
index 181401300e..9c4f321f76 100644
--- a/gas/dwarf2dbg.c
+++ b/gas/dwarf2dbg.c
@@ -30,6 +30,7 @@ 
 
 #include "as.h"
 #include "safe-ctype.h"
+#include "bignum.h"
 
 #ifdef HAVE_LIMITS_H
 #include <limits.h>
@@ -81,11 +82,11 @@ 
 #endif
 
 #ifndef DWARF2_FILE_TIME_NAME
-#define DWARF2_FILE_TIME_NAME(FILENAME,DIRNAME) 0
+#define DWARF2_FILE_TIME_NAME(FILENAME,DIRNAME) -1
 #endif
 
 #ifndef DWARF2_FILE_SIZE_NAME
-#define DWARF2_FILE_SIZE_NAME(FILENAME,DIRNAME) 0
+#define DWARF2_FILE_SIZE_NAME(FILENAME,DIRNAME) -1
 #endif
 
 #ifndef DWARF2_VERSION
@@ -99,7 +100,7 @@ 
 
 /* This implementation outputs version 3 .debug_line information.  */
 #ifndef DWARF2_LINE_VERSION
-#define DWARF2_LINE_VERSION 3
+#define DWARF2_LINE_VERSION (dwarf_level > 3 ? dwarf_level : 3)
 #endif
 
 #include "subsegs.h"
@@ -147,6 +148,10 @@ 
 /* Flag that indicates the initial value of the is_stmt_start flag.  */
 #define	DWARF2_LINE_DEFAULT_IS_STMT	1
 
+#ifndef DWARF2_LINE_MAX_OPS_PER_INSN
+#define DWARF2_LINE_MAX_OPS_PER_INSN	1
+#endif
+
 /* Given a special op, return the line skip amount.  */
 #define SPECIAL_LINE(op) \
 	(((op) - DWARF2_LINE_OPCODE_BASE)%DWARF2_LINE_RANGE + DWARF2_LINE_BASE)
@@ -196,10 +201,13 @@  struct line_seg
 static struct line_seg *all_segs;
 static struct line_seg **last_seg_ptr;
 
+#define NUM_MD5_BYTES       16
+
 struct file_entry
 {
-  const char *filename;
-  unsigned int dir;
+  const char *   filename;
+  unsigned int   dir;
+  unsigned char  md5[NUM_MD5_BYTES];
 };
 
 /* Table of files used by .debug_line.  */
@@ -208,9 +216,9 @@  static unsigned int files_in_use;
 static unsigned int files_allocated;
 
 /* Table of directories used by .debug_line.  */
-static char **dirs;
-static unsigned int dirs_in_use;
-static unsigned int dirs_allocated;
+static char **       dirs = NULL;
+static unsigned int  dirs_in_use = 0;
+static unsigned int  dirs_allocated = 0;
 
 /* TRUE when we've seen a .loc directive recently.  Used to avoid
    doing work when there's nothing to do.  */
@@ -239,8 +247,6 @@  static symbolS *view_assert_failed;
 /* The size of an address on the target.  */
 static unsigned int sizeof_address;
 
-static unsigned int get_filenum (const char *, unsigned int);
-
 #ifndef TC_DWARF2_EMIT_OFFSET
 #define TC_DWARF2_EMIT_OFFSET  generic_dwarf2_emit_offset
 
@@ -280,6 +286,7 @@  get_line_subseg (segT seg, subsegT subseg, bfd_boolean create_p)
       last_seg_ptr = &s->next;
       seg_info (seg)->dwarf2_line_seg = s;
     }
+
   gas_assert (seg == s->seg);
 
   for (pss = &s->head; (lss = *pss) != NULL ; pss = &lss->next)
@@ -516,7 +523,9 @@  dwarf2_gen_line_info (addressT ofs, struct dwarf2_line_info *loc)
   symbolS *sym;
 
   /* Early out for as-yet incomplete location information.  */
-  if (loc->filenum == 0 || loc->line == 0)
+  if (loc->line == 0)
+    return;
+  if (loc->filenum == 0 && DWARF2_LINE_VERSION < 5)
     return;
 
   /* Don't emit sequences of line symbols for the same line when the
@@ -544,6 +553,322 @@  dwarf2_gen_line_info (addressT ofs, struct dwarf2_line_info *loc)
   dwarf2_gen_line_info_1 (sym, loc);
 }
 
+static const char *
+get_basename (const char * pathname)
+{
+  const char * file;
+
+  file = lbasename (pathname);
+  /* Don't make empty string from / or A: from A:/ .  */
+#ifdef HAVE_DOS_BASED_FILE_SYSTEM
+  if (file <= pathname + 3)
+    file = pathname;
+#else
+  if (file == pathname + 1)
+    file = pathname;
+#endif
+  return file;
+}
+
+static unsigned int
+get_directory_table_entry (const char *  dirname,
+			   size_t        dirlen,
+			   bfd_boolean   can_use_zero)
+{
+  unsigned int d;
+
+  if (dirlen == 0)
+    return 0;
+
+#ifndef DWARF2_DIR_SHOULD_END_WITH_SEPARATOR
+  if (IS_DIR_SEPARATOR (dirname[dirlen - 1]))
+    {
+      -- dirlen;
+      if (dirlen == 0)
+	return 0;
+    }
+#endif
+
+  for (d = 0; d < dirs_in_use; ++d)
+    {
+      if (dirs[d] != NULL
+	  && filename_ncmp (dirname, dirs[d], dirlen) == 0
+	  && dirs[d][dirlen] == '\0')
+	return d;
+    }
+
+  if (can_use_zero)
+    {
+      if (dirs == NULL || dirs[0] == NULL)
+	d = 0;
+    }
+  else if (d == 0)
+    d = 1;
+
+  if (d >= dirs_allocated)
+    {
+      unsigned int old = dirs_allocated;
+
+      dirs_allocated = d + 32;
+      dirs = XRESIZEVEC (char *, dirs, dirs_allocated);
+      memset (dirs + old, 0, (dirs_allocated - old) * sizeof (char *));
+    }
+
+  dirs[d] = xmemdup0 (dirname, dirlen);
+  if (dirs_in_use <= d)
+    dirs_in_use = d + 1;
+
+  return d;  
+}
+
+/* Get a .debug_line file number for PATHNAME.  If there is a
+   directory component to PATHNAME, then this will be stored
+   in the directory table, if it is not already present.
+   Returns the slot number allocated to that filename or -1
+   if there was a problem.  */
+
+static signed int
+allocate_filenum (const char * pathname)
+{
+  static signed int last_used = -1, last_used_dir_len = 0;
+  const char *file;
+  size_t dir_len;
+  unsigned int i, dir;
+
+  /* Short circuit the common case of adding the same pathname
+     as last time.  */
+  if (last_used != -1)
+    {
+      const char * dirname = NULL;
+
+      if (dirs != NULL)
+	dirname = dirs[files[last_used].dir];
+
+      if (dirname == NULL)
+	{
+	  if (filename_cmp (pathname, files[last_used].filename) == 0)
+	    return last_used;
+	}
+      else
+	{
+	  if (filename_ncmp (pathname, dirname, last_used_dir_len) == 0
+	      && IS_DIR_SEPARATOR (pathname [last_used_dir_len])
+	      && filename_cmp (pathname + last_used_dir_len + 1,
+			       files[last_used].filename) == 0)
+	    return last_used;
+	}
+    }
+
+  file = get_basename (pathname);
+  dir_len = file - pathname;
+
+  dir = get_directory_table_entry (pathname, dir_len, FALSE);
+
+  /* Do not use slot-0.  That is specificailly reserved for use by
+     the '.file 0 "name"' directive.  */
+  for (i = 1; i < files_in_use; ++i)
+    if (files[i].dir == dir
+	&& files[i].filename
+	&& filename_cmp (file, files[i].filename) == 0)
+      {
+	last_used = i;
+	last_used_dir_len = dir_len;
+	return i;
+      }
+
+  if (i >= files_allocated)
+    {
+      unsigned int old = files_allocated;
+
+      files_allocated = i + 32;
+      /* Catch wraparound.  */
+      if (files_allocated <= old)
+	{
+	  as_bad (_("file number %lu is too big"), (unsigned long) i);
+	  return -1;
+	}
+
+      files = XRESIZEVEC (struct file_entry, files, files_allocated);
+      memset (files + old, 0, (i + 32 - old) * sizeof (struct file_entry));
+    }
+
+  files[i].filename = file;
+  files[i].dir = dir;
+  memset (files[i].md5, 0, NUM_MD5_BYTES);
+
+  if (files_in_use < i + 1)
+    files_in_use = i + 1;  
+  last_used = i;
+  last_used_dir_len = dir_len;
+
+  return i;
+}
+
+/* Allocate slot NUM in the .debug_line file table to FILENAME.
+   If DIRNAME is not NULL or there is a directory component to FILENAME
+   then this will be stored in the directory table, if not already present.
+   if WITH_MD5 is TRUE then there is a md5 value in generic_bignum.
+   Returns TRUE if allocation succeeded, FALSE otherwise.  */
+
+static bfd_boolean
+allocate_filename_to_slot (const char *  dirname,
+			   const char *  filename,
+			   unsigned int  num,
+			   bfd_boolean   with_md5)
+{
+  const char *file;
+  size_t dirlen;
+  unsigned int i, d;
+
+  /* Short circuit the common case of adding the same pathname
+     as last time.  */
+  if (num < files_allocated && files[num].filename != NULL)
+    {
+      const char * dir = NULL;
+
+      if (dirs)
+	dir = dirs[files[num].dir];
+
+      if (with_md5 && memcmp (generic_bignum, files[num].md5, NUM_MD5_BYTES) != 0)
+	goto fail;
+
+      if (dirname != NULL)
+	{
+	  if (dir != NULL && filename_cmp (dir, dirname) != 0)
+	    goto fail;
+      
+	  if (filename_cmp (filename, files[num].filename) != 0)
+	    goto fail;
+
+	  /* If the filenames match, but the directory table entry was
+	     empty, then fill it with the provided directory name.  */
+	  if (dir == NULL)
+	    dirs[files[num].dir] = xmemdup0 (dirname, strlen (dirname));
+	    
+	  return TRUE;
+	}
+      else if (dir != NULL) 
+	{
+	  dirlen = strlen (dir);
+	  if (filename_ncmp (filename, dir, dirlen) == 0
+	      && IS_DIR_SEPARATOR (filename [dirlen])
+	      && filename_cmp (filename + dirlen + 1, files[num].filename) == 0)
+	    return TRUE;
+	}
+      else /* dir == NULL  */
+	{
+	  file = get_basename (filename);
+	  if (filename_cmp (file, files[num].filename) == 0)
+	    {
+	      if (file > filename)
+		/* The filenames match, but the directory table entry is empty.
+		   Fill it with the provided directory name.  */
+		dirs[files[num].dir] = xmemdup0 (filename, file - filename);
+	      return TRUE;
+	    }
+	}
+
+    fail:
+      as_bad (_("file table slot %u is already occupied by a different file (%s%s%s vs %s%s%s)"),
+	      num,
+	      dir == NULL ? "" : dir,
+	      dir == NULL ? "" : "/",
+	      files[num].filename,
+	      dirname == NULL ? "" : dirname,
+	      dirname == NULL ? "" : "/",
+	      filename);
+      return FALSE;
+    }
+
+  if (dirname == NULL)
+    {
+      dirname = filename;
+      file = get_basename (filename);
+      dirlen = file - filename;
+    }
+  else
+    {
+      dirlen = strlen (dirname);
+      file = filename;
+    }
+  
+  d = get_directory_table_entry (dirname, dirlen, num == 0);
+  i = num;
+
+  if (i >= files_allocated)
+    {
+      unsigned int old = files_allocated;
+
+      files_allocated = i + 32;
+      /* Catch wraparound.  */
+      if (files_allocated <= old)
+	{
+	  as_bad (_("file number %lu is too big"), (unsigned long) i);
+	  return FALSE;
+	}
+
+      files = XRESIZEVEC (struct file_entry, files, files_allocated);
+      memset (files + old, 0, (i + 32 - old) * sizeof (struct file_entry));
+    }
+
+  files[i].filename = file;
+  files[i].dir = d;
+  if (with_md5)
+    {
+      if (target_big_endian)
+	{
+	  /* md5's are stored in litte endian format.  */
+	  unsigned int     bits_remaining = NUM_MD5_BYTES * BITS_PER_CHAR;
+	  unsigned int     byte = NUM_MD5_BYTES;
+	  unsigned int     bignum_index = 0;
+
+	  while (bits_remaining)
+	    {
+	      unsigned int bignum_bits_remaining = LITTLENUM_NUMBER_OF_BITS;
+	      valueT       bignum_value = generic_bignum [bignum_index];
+	      bignum_index ++;
+
+	      while (bignum_bits_remaining)
+		{
+		  files[i].md5[--byte] = bignum_value & 0xff;
+		  bignum_value >>= 8;
+		  bignum_bits_remaining -= 8;
+		  bits_remaining -= 8;
+		}
+	    }
+	}
+      else
+	{
+	  unsigned int     bits_remaining = NUM_MD5_BYTES * BITS_PER_CHAR;
+	  unsigned int     byte = 0;
+	  unsigned int     bignum_index = 0;
+
+	  while (bits_remaining)
+	    {
+	      unsigned int bignum_bits_remaining = LITTLENUM_NUMBER_OF_BITS;
+	      valueT       bignum_value = generic_bignum [bignum_index];
+
+	      bignum_index ++;
+
+	      while (bignum_bits_remaining)
+		{
+		  files[i].md5[byte++] = bignum_value & 0xff;
+		  bignum_value >>= 8;
+		  bignum_bits_remaining -= 8;
+		  bits_remaining -= 8;
+		}
+	    }
+	}
+    }
+  else
+    memset (files[i].md5, 0, NUM_MD5_BYTES);
+
+  if (files_in_use < i + 1)
+    files_in_use = i + 1;
+
+  return TRUE;
+}
+
 /* Returns the current source information.  If .file directives have
    been encountered, the info for the corresponding source file is
    returned.  Otherwise, the info for the assembly source file is
@@ -558,7 +883,8 @@  dwarf2_where (struct dwarf2_line_info *line)
 
       memset (line, 0, sizeof (*line));
       filename = as_where (&line->line);
-      line->filenum = get_filenum (filename, 0);
+      line->filenum = allocate_filenum (filename);
+      /* FIXME: We should check the return value from allocate_filenum.  */
       line->column = 0;
       line->flags = DWARF2_FLAG_IS_STMT;
       line->isa = current.isa;
@@ -670,108 +996,6 @@  dwarf2_emit_label (symbolS *label)
   dwarf2_consume_line_info ();
 }
 
-/* Get a .debug_line file number for FILENAME.  If NUM is nonzero,
-   allocate it on that file table slot, otherwise return the first
-   empty one.  */
-
-static unsigned int
-get_filenum (const char *filename, unsigned int num)
-{
-  static unsigned int last_used, last_used_dir_len;
-  const char *file;
-  size_t dir_len;
-  unsigned int i, dir;
-
-  if (num == 0 && last_used)
-    {
-      if (! files[last_used].dir
-	  && filename_cmp (filename, files[last_used].filename) == 0)
-	return last_used;
-      if (files[last_used].dir
-	  && filename_ncmp (filename, dirs[files[last_used].dir],
-			    last_used_dir_len) == 0
-	  && IS_DIR_SEPARATOR (filename [last_used_dir_len])
-	  && filename_cmp (filename + last_used_dir_len + 1,
-			   files[last_used].filename) == 0)
-	return last_used;
-    }
-
-  file = lbasename (filename);
-  /* Don't make empty string from / or A: from A:/ .  */
-#ifdef HAVE_DOS_BASED_FILE_SYSTEM
-  if (file <= filename + 3)
-    file = filename;
-#else
-  if (file == filename + 1)
-    file = filename;
-#endif
-  dir_len = file - filename;
-
-  dir = 0;
-  if (dir_len)
-    {
-#ifndef DWARF2_DIR_SHOULD_END_WITH_SEPARATOR
-      --dir_len;
-#endif
-      for (dir = 1; dir < dirs_in_use; ++dir)
-	if (filename_ncmp (filename, dirs[dir], dir_len) == 0
-	    && dirs[dir][dir_len] == '\0')
-	  break;
-
-      if (dir >= dirs_in_use)
-	{
-	  if (dir >= dirs_allocated)
-	    {
-	      dirs_allocated = dir + 32;
-	      dirs = XRESIZEVEC (char *, dirs, dirs_allocated);
-	    }
-
-	  dirs[dir] = xmemdup0 (filename, dir_len);
-	  dirs_in_use = dir + 1;
-	}
-    }
-
-  if (num == 0)
-    {
-      for (i = 1; i < files_in_use; ++i)
-	if (files[i].dir == dir
-	    && files[i].filename
-	    && filename_cmp (file, files[i].filename) == 0)
-	  {
-	    last_used = i;
-	    last_used_dir_len = dir_len;
-	    return i;
-	  }
-    }
-  else
-    i = num;
-
-  if (i >= files_allocated)
-    {
-      unsigned int old = files_allocated;
-
-      files_allocated = i + 32;
-      /* Catch wraparound.  */
-      if (files_allocated <= old)
-	{
-	  as_bad (_("file number %lu is too big"), (unsigned long) i);
-	  return 0;
-	}
-
-      files = XRESIZEVEC (struct file_entry, files, files_allocated);
-      memset (files + old, 0, (i + 32 - old) * sizeof (struct file_entry));
-    }
-
-  files[i].filename = file;
-  files[i].dir = dir;
-  if (files_in_use < i + 1)
-    files_in_use = i + 1;
-  last_used = i;
-  last_used_dir_len = dir_len;
-
-  return i;
-}
-
 /* Handle two forms of .file directive:
    - Pass .file "source.c" to s_app_file
    - Handle .file 1 "source.c" by adding an entry to the DWARF-2 file table
@@ -781,8 +1005,10 @@  get_filenum (const char *filename, unsigned int num)
 char *
 dwarf2_directive_filename (void)
 {
+  bfd_boolean with_md5 = TRUE;
   valueT num;
   char *filename;
+  const char * dirname = NULL;
   int filename_len;
 
   /* Continue to accept a bare string and pass it off.  */
@@ -795,24 +1021,48 @@  dwarf2_directive_filename (void)
 
   num = get_absolute_expression ();
 
-  if ((offsetT) num < 1 && dwarf_level < 5)
+  if ((offsetT) num < 1 && DWARF2_LINE_VERSION < 5)
     {
       as_bad (_("file number less than one"));
       ignore_rest_of_line ();
       return NULL;
     }
 
-  if (num == 0)
-    {
-      demand_empty_rest_of_line ();
-      return NULL;
-    }
+  /* FIXME: Should we allow ".file <N>\n" as an expression meaning
+     "switch back to the already allocated file <N> as the current
+     file" ?  */
 
   filename = demand_copy_C_string (&filename_len);
   if (filename == NULL)
     /* demand_copy_C_string will have already generated an error message.  */
     return NULL;
 
+  /* For DWARF-5 support we also accept:
+     .file <NUM> ["<dir>"] "<file>" [md5 <NUM>]  */
+  if (DWARF2_LINE_VERSION > 4)
+    {
+      SKIP_WHITESPACE ();
+      if (*input_line_pointer == '"')
+	{
+	  dirname = filename;
+	  filename = demand_copy_C_string (&filename_len);
+	  SKIP_WHITESPACE ();
+	}
+
+      if (strncmp (input_line_pointer, "md5", 3) == 0)
+	{
+	  input_line_pointer += 3;
+	  SKIP_WHITESPACE ();
+
+	  expressionS exp;
+	  expression_and_evaluate (& exp);
+	  if (exp.X_op != O_big)
+	    as_bad (_("md5 value too small or not a constant"));
+	  else
+	    with_md5 = TRUE;
+	}
+    }
+
   demand_empty_rest_of_line ();
 
   /* A .file directive implies compiler generated debug information is
@@ -825,13 +1075,9 @@  dwarf2_directive_filename (void)
       as_bad (_("file number %lu is too big"), (unsigned long) num);
       return NULL;
     }
-  if (num < files_in_use && files[num].filename != 0)
-    {
-      as_bad (_("file number %u already allocated"), (unsigned int) num);
-      return NULL;
-    }
 
-  (void) get_filenum (filename, (unsigned int) num);
+  if (! allocate_filename_to_slot (dirname, filename, (unsigned int) num, with_md5))
+    return NULL;
 
   return filename;
 }
@@ -861,10 +1107,14 @@  dwarf2_directive_loc (int dummy ATTRIBUTE_UNUSED)
 
   if (filenum < 1)
     {
-      as_bad (_("file number less than one"));
-      return;
+      if (filenum != 0 || DWARF2_LINE_VERSION < 5)
+	{
+	  as_bad (_("file number less than one"));
+	  return;
+	}
     }
-  if (filenum >= (int) files_in_use || files[filenum].filename == 0)
+
+  if (filenum >= (int) files_in_use || files[filenum].filename == NULL)
     {
       as_bad (_("unassigned file number %ld"), (long) filenum);
       return;
@@ -1710,14 +1960,38 @@  process_entries (segT seg, struct line_entry *e)
 /* Emit the directory and file tables for .debug_line.  */
 
 static void
-out_file_list (void)
+out_dir_and_file_list (void)
 {
   size_t size;
   const char *dir;
   char *cp;
   unsigned int i;
+  bfd_boolean emit_md5 = FALSE;
+  bfd_boolean emit_timestamps = TRUE;
+  bfd_boolean emit_filesize = TRUE;
 
+  /* Output the Directory Table.  */
+
+  if (DWARF2_LINE_VERSION >= 5)
+    {
+      out_byte (1);
+      out_uleb128 (DW_LNCT_path);
+      out_uleb128 (DW_FORM_string);
+      out_uleb128 (dirs_in_use);
+    }
+      
   /* Emit directory list.  */
+  if (DWARF2_LINE_VERSION >= 5)
+    {
+      if (dirs[0] == NULL)
+	dir = remap_debug_filename (".");
+      else
+	dir = remap_debug_filename (dirs[0]);
+	
+      size = strlen (dir) + 1;
+      cp = frag_more (size);
+      memcpy (cp, dir, size);
+    }
   for (i = 1; i < dirs_in_use; ++i)
     {
       dir = remap_debug_filename (dirs[i]);
@@ -1725,19 +1999,87 @@  out_file_list (void)
       cp = frag_more (size);
       memcpy (cp, dir, size);
     }
-  /* Terminate it.  */
-  out_byte ('\0');
 
-  for (i = 1; i < files_in_use; ++i)
+  if (DWARF2_LINE_VERSION < 5)
+    /* Terminate it.  */
+    out_byte ('\0');
+
+  /* Output the File Name Table.  */
+
+  if (DWARF2_LINE_VERSION >= 5)
+    {
+      unsigned int columns = 4;
+
+      if (((unsigned long) DWARF2_FILE_TIME_NAME ("", "")) == -1UL)
+	{
+	  emit_timestamps = FALSE;
+	  -- columns;
+	}
+
+      if (DWARF2_FILE_SIZE_NAME ("", "") == -1)
+	{
+	  emit_filesize = FALSE;
+	  -- columns;
+	}
+
+      for (i = 0; i < files_in_use; ++i)
+	if (files[i].md5[0] != 0)
+	  break;
+      if (i < files_in_use)
+	{
+	  emit_md5 = TRUE;
+	  ++ columns;
+	}
+      
+      /* The number of format entries to follow.  */
+      out_byte (columns);
+
+      /* The format of the file name.  */
+      out_uleb128 (DW_LNCT_path);
+      out_uleb128 (DW_FORM_string);
+
+      /* The format of the directory index.  */
+      out_uleb128 (DW_LNCT_directory_index);
+      out_uleb128 (DW_FORM_udata);
+
+      if (emit_timestamps)
+	{
+	  /* The format of the timestamp.  */
+	  out_uleb128 (DW_LNCT_timestamp);
+	  out_uleb128 (DW_FORM_udata);
+	}
+
+      if (emit_filesize)
+	{
+	  /* The format of the file size.  */
+	  out_uleb128 (DW_LNCT_size);
+	  out_uleb128 (DW_FORM_udata);
+	}
+
+      if (emit_md5)
+	{
+	  /* The format of the MD5 sum.  */
+	  out_uleb128 (DW_LNCT_MD5);
+	  out_uleb128 (DW_FORM_data16);
+	}
+
+      /* The number of entries in the table.  */
+      out_uleb128 (files_in_use);
+   }
+      
+  for (i = DWARF2_LINE_VERSION > 4 ? 0 : 1; i < files_in_use; ++i)
     {
       const char *fullfilename;
 
       if (files[i].filename == NULL)
 	{
-	  as_bad (_("unassigned file number %ld"), (long) i);
 	  /* Prevent a crash later, particularly for file 1.  */
 	  files[i].filename = "";
-	  continue;
+	  if (DWARF2_LINE_VERSION < 5 || i != 0)
+	    {
+	      as_bad (_("unassigned file number %ld"), (long) i);
+	      continue;
+	    }
 	}
 
       fullfilename = DWARF2_FILE_NAME (files[i].filename,
@@ -1746,17 +2088,45 @@  out_file_list (void)
       cp = frag_more (size);
       memcpy (cp, fullfilename, size);
 
-      out_uleb128 (files[i].dir);	/* directory number */
+      /* Directory number.  */
+      out_uleb128 (files[i].dir);
+
       /* Output the last modification timestamp.  */
-      out_uleb128 (DWARF2_FILE_TIME_NAME (files[i].filename,
-					  files[i].dir ? dirs [files [i].dir] : ""));
+      if (emit_timestamps)
+	{
+	  offsetT timestamp;
+
+	  timestamp = DWARF2_FILE_TIME_NAME (files[i].filename,
+					     files[i].dir ? dirs [files [i].dir] : "");
+	  if (timestamp == -1)
+	    timestamp = 0;
+	  out_uleb128 (timestamp);
+	}
+
       /* Output the filesize.  */
-      out_uleb128 (DWARF2_FILE_SIZE_NAME (files[i].filename,
-					  files[i].dir ? dirs [files [i].dir] : ""));
+      if (emit_filesize)
+	{
+	  offsetT filesize;
+	  filesize = DWARF2_FILE_SIZE_NAME (files[i].filename,
+					    files[i].dir ? dirs [files [i].dir] : "");
+	  if (filesize == -1)
+	    filesize = 0;
+	  out_uleb128 (filesize);
+	}
+
+      /* Output the md5 sum.  */
+      if (emit_md5)
+	{
+	  int b;
+
+	  for (b = 0; b < NUM_MD5_BYTES; b++)
+	    out_byte (files[i].md5[b]);
+	}
     }
 
-  /* Terminate filename list.  */
-  out_byte (0);
+  if (DWARF2_LINE_VERSION < 5)
+    /* Terminate filename list.  */
+    out_byte (0);
 }
 
 /* Switch to SEC and output a header length field.  Return the size of
@@ -1833,6 +2203,11 @@  out_debug_line (segT line_seg)
   /* Version.  */
   out_two (DWARF2_LINE_VERSION);
 
+  if (DWARF2_LINE_VERSION >= 5)
+    {
+      out_byte (sizeof_address);
+      out_byte (0); /* Segment Selector size.  */
+    }
   /* Length of the prologue following this length.  */
   prologue_start = symbol_temp_make ();
   prologue_end = symbol_temp_make ();
@@ -1845,6 +2220,8 @@  out_debug_line (segT line_seg)
 
   /* Parameters of the state machine.  */
   out_byte (DWARF2_LINE_MIN_INSN_LENGTH);
+  if (DWARF2_LINE_VERSION >= 4)
+    out_byte (DWARF2_LINE_MAX_OPS_PER_INSN);
   out_byte (DWARF2_LINE_DEFAULT_IS_STMT);
   out_byte (DWARF2_LINE_BASE);
   out_byte (DWARF2_LINE_RANGE);
@@ -1863,8 +2240,11 @@  out_debug_line (segT line_seg)
   out_byte (0);			/* DW_LNS_set_prologue_end */
   out_byte (0);			/* DW_LNS_set_epilogue_begin */
   out_byte (1);			/* DW_LNS_set_isa */
+  /* We have emitted 12 opcode lengths, so make that this
+     matches up to the opcode base value we have been using.  */
+  gas_assert (DWARF2_LINE_OPCODE_BASE == 13);
 
-  out_file_list ();
+  out_dir_and_file_list ();
 
   symbol_set_value_now (prologue_end);
 
@@ -2134,19 +2514,20 @@  out_debug_str (segT str_seg, symbolS **name_sym, symbolS **comp_dir_sym,
   const char *dirname;
   char *p;
   int len;
+  int first_file = DWARF2_LINE_VERSION > 4 ? 0 : 1;
 
   subseg_set (str_seg, 0);
 
   /* DW_AT_name.  We don't have the actual file name that was present
-     on the command line, so assume files[1] is the main input file.
+     on the command line, so assume files[first_file] is the main input file.
      We're not supposed to get called unless at least one line number
      entry was emitted, so this should always be defined.  */
   *name_sym = symbol_temp_new_now_octets ();
   if (files_in_use == 0)
     abort ();
-  if (files[1].dir)
+  if (files[first_file].dir)
     {
-      dirname = remap_debug_filename (dirs[files[1].dir]);
+      dirname = remap_debug_filename (dirs[files[first_file].dir]);
       len = strlen (dirname);
 #ifdef TE_VMS
       /* Already has trailing slash.  */
@@ -2158,9 +2539,9 @@  out_debug_str (segT str_seg, symbolS **name_sym, symbolS **comp_dir_sym,
       INSERT_DIR_SEPARATOR (p, len);
 #endif
     }
-  len = strlen (files[1].filename) + 1;
+  len = strlen (files[first_file].filename) + 1;
   p = frag_more (len);
-  memcpy (p, files[1].filename, len);
+  memcpy (p, files[first_file].filename, len);
 
   /* DW_AT_comp_dir */
   *comp_dir_sym = symbol_temp_new_now_octets ();
diff --git a/gas/testsuite/gas/elf/dwarf-5-file0.d b/gas/testsuite/gas/elf/dwarf-5-file0.d
index a3d1fdfb6e..3dffa63965 100644
--- a/gas/testsuite/gas/elf/dwarf-5-file0.d
+++ b/gas/testsuite/gas/elf/dwarf-5-file0.d
@@ -2,5 +2,18 @@ 
 #name: DWARF5 .line 0
 #readelf: -wl
 
+#...
+ The Directory Table \(offset 0x.*, lines 3, columns 1\):
+  Entry	Name
+  0	master directory
+  1	secondary directory
+  2	/tmp
+
+ The File Name Table \(offset 0x.*, lines 3, columns 3\):
+  Entry	Dir	MD5				Name
+  0	0 0x00000000000000000000000000000000	master source file
+  1	1 0x00000000000000000000000000000000	secondary source file
+  2	2 0x95828e8bc4f7404dbf7526fb7bd0f192	foo.c
 #pass
 
+
diff --git a/gas/testsuite/gas/elf/dwarf-5-file0.s b/gas/testsuite/gas/elf/dwarf-5-file0.s
index 1f99c58923..2792ba8c44 100644
--- a/gas/testsuite/gas/elf/dwarf-5-file0.s
+++ b/gas/testsuite/gas/elf/dwarf-5-file0.s
@@ -1,17 +1,23 @@ 
-	.text
+	.section	.debug_info,"",%progbits
+	.4byte	0x8a
+	.2byte  0x2
+	.4byte	.Ldebug_abbrev0
+	.byte	0x4
+	.uleb128 0x1
 
- .file 0
- .line 1
-	.long 3
+	.file 0 "master directory/master source file"
+	.line 1
+	.text
+	.word 0
 
- .file 1 "asdf"
- .line 2
-	.long 5
+	.file 1 "secondary directory/secondary source file"
+	.line 2
+	.word 2
 
- .file 0
- .line 4
-	.long 3
+	.file "master source file"
+	.line 4
+	.word 4
 
- .file 2 "def"
- .line 5
-	.long 3
+	.file 2 "/tmp" "foo.c" md5 0x95828e8bc4f7404dbf7526fb7bd0f192
+	.line 5
+	.word 6
diff --git a/gas/testsuite/gas/i386/debug1.d b/gas/testsuite/gas/i386/debug1.d
index f78ad06bf6..cb6d23bf54 100644
--- a/gas/testsuite/gas/i386/debug1.d
+++ b/gas/testsuite/gas/i386/debug1.d
@@ -29,7 +29,7 @@  Raw dump of debug contents of section \.z?debug_line:
   Opcode 12 has 1 arg
 
  The Directory Table \(offset 0x.*\):
-  .*
+  1	.*
 
  The File Name Table \(offset 0x.*\):
   Entry	Dir	Time	Size	Name
diff --git a/gas/testsuite/gas/lns/lns-diag-1.l b/gas/testsuite/gas/lns/lns-diag-1.l
index 53f993605d..1256e85cfc 100644
--- a/gas/testsuite/gas/lns/lns-diag-1.l
+++ b/gas/testsuite/gas/lns/lns-diag-1.l
@@ -1,7 +1,7 @@ 
 .*: Assembler messages:
 .*:2: Error: file number less than one
 .*:3: Error: missing string
-.*:4: Error: file number 1 already allocated
+.*:4: Error: file table slot 1 is already occupied.*
 .*:8: Error: unassigned file number 3
 .*:9: Error: junk at end of line, first unrecognized character is `1'
 .*:12: Error: junk at end of line, first unrecognized character is `0'