public inbox for binutils@sourceware.org
 help / color / mirror / Atom feed
* [PATCH v2] ld: Generate PDB string table
@ 2022-11-25  2:53 Mark Harmstone
  2022-11-25  2:54 ` [PATCH] ld: Write DEBUG_S_FILECHKSMS entries in PDBs Mark Harmstone
  0 siblings, 1 reply; 19+ messages in thread
From: Mark Harmstone @ 2022-11-25  2:53 UTC (permalink / raw)
  To: jbeulich, binutils; +Cc: Mark Harmstone

This populates the "/names" named stream of the PDB, which contains the
string table.

We loop through the .debug$S sections of the object files, deduplicate the
strings found in the DEBUG_S_STRINGTABLE subsection, and add them to the
table.

---
 ld/pdb.c                          | 296 +++++++++++++++++++++++++++++-
 ld/pdb.h                          |  12 ++
 ld/testsuite/ld-pe/pdb-strings.d  |  10 +
 ld/testsuite/ld-pe/pdb-strings1.s |  19 ++
 ld/testsuite/ld-pe/pdb-strings2.s |  19 ++
 ld/testsuite/ld-pe/pdb.exp        | 122 ++++++++++++
 6 files changed, 472 insertions(+), 6 deletions(-)
 create mode 100644 ld/testsuite/ld-pe/pdb-strings.d
 create mode 100644 ld/testsuite/ld-pe/pdb-strings1.s
 create mode 100644 ld/testsuite/ld-pe/pdb-strings2.s

diff --git a/ld/pdb.c b/ld/pdb.c
index 6f69574289d..dc008bc38bb 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -41,6 +41,23 @@ struct public
   uint32_t address;
 };
 
+struct string
+{
+  struct string *next;
+  uint32_t hash;
+  uint32_t offset;
+  size_t len;
+  char s[];
+};
+
+struct string_table
+{
+  struct string *strings_head;
+  struct string *strings_tail;
+  uint32_t strings_len;
+  htab_t hashmap;
+};
+
 /* Add a new stream to the PDB archive, and return its BFD.  */
 static bfd *
 add_stream (bfd *pdb, const char *name, uint16_t *stream_num)
@@ -383,15 +400,170 @@ get_arch_number (bfd *abfd)
   return IMAGE_FILE_MACHINE_I386;
 }
 
+/* Add a string to the strings table, if it's not already there.  */
+static void
+add_string (char *str, size_t len, struct string_table *strings)
+{
+  uint32_t hash = calc_hash (str, len);
+  void **slot;
+
+  slot = htab_find_slot_with_hash (strings->hashmap, str, hash, INSERT);
+
+  if (slot && !*slot)
+    {
+      struct string *s;
+
+      *slot = xmalloc (offsetof (struct string, s) + len);
+
+      s = (struct string *) *slot;
+
+      s->next = NULL;
+      s->hash = hash;
+      s->offset = strings->strings_len;
+      s->len = len;
+      memcpy (s->s, str, len);
+
+      if (strings->strings_tail)
+	strings->strings_tail->next = s;
+      else
+	strings->strings_head = s;
+
+      strings->strings_tail = s;
+
+      strings->strings_len += len + 1;
+    }
+}
+
+/* Return the hash of an entry in the string table.  */
+static hashval_t
+hash_string_table_entry (const void *p)
+{
+  const struct string *s = (const struct string *) p;
+
+  return s->hash;
+}
+
+/* Compare an entry in the string table with a string.  */
+static int
+eq_string_table_entry (const void *a, const void *b)
+{
+  const struct string *s1 = (const struct string *) a;
+  const char *s2 = (const char *) b;
+  size_t s2_len = strlen (s2);
+
+  if (s2_len != s1->len)
+    return 0;
+
+  return memcmp (s1->s, s2, s2_len) == 0;
+}
+
+/* Parse the string table within the .debug$S section.  */
+static void
+parse_string_table (bfd_byte *data, size_t size,
+		    struct string_table *strings)
+{
+  while (true)
+    {
+      size_t len = strnlen ((char *) data, size);
+
+      add_string ((char *) data, len, strings);
+
+      data += len + 1;
+
+      if (size <= len + 1)
+	break;
+
+      size -= len + 1;
+    }
+}
+
+/* Parse the .debug$S section within an object file.  */
+static bool
+handle_debugs_section (asection *s, bfd *mod, struct string_table *strings)
+{
+  bfd_byte *data = NULL;
+  size_t off;
+
+  if (!bfd_get_full_section_contents (mod, s, &data))
+    return false;
+
+  if (!data)
+    return false;
+
+  if (bfd_getl32 (data) != CV_SIGNATURE_C13)
+    {
+      free (data);
+      return true;
+    }
+
+  off = sizeof (uint32_t);
+
+  while (off + sizeof (uint32_t) <= s->size)
+    {
+      uint32_t type, size;
+
+      type = bfd_getl32 (data + off);
+
+      off += sizeof (uint32_t);
+
+      if (off + sizeof (uint32_t) > s->size)
+	{
+	  free (data);
+	  bfd_set_error (bfd_error_bad_value);
+	  return false;
+	}
+
+      size = bfd_getl32 (data + off);
+
+      off += sizeof (uint32_t);
+
+      if (off + size > s->size)
+	{
+	  free (data);
+	  bfd_set_error (bfd_error_bad_value);
+	  return false;
+	}
+
+      switch (type)
+	{
+	case DEBUG_S_STRINGTABLE:
+	  parse_string_table (data + off, size, strings);
+
+	  break;
+	}
+
+      off += size;
+
+      if (off % sizeof (uint32_t))
+	off += sizeof (uint32_t) - (off % sizeof (uint32_t));
+    }
+
+  free (data);
+
+  return true;
+}
+
 /* Populate the module stream, which consists of the transformed .debug$S
    data for each object file.  */
 static bool
-populate_module_stream (bfd *stream, uint32_t *sym_byte_size)
+populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
+			struct string_table *strings)
 {
   uint8_t int_buf[sizeof (uint32_t)];
 
   *sym_byte_size = sizeof (uint32_t);
 
+  /* Process .debug$S section(s).  */
+
+  for (asection *s = mod->sections; s; s = s->next)
+    {
+      if (!strcmp (s->name, ".debug$S") && s->size >= sizeof (uint32_t))
+	{
+	  if (!handle_debugs_section (s, mod, strings))
+	      return false;
+	}
+    }
+
   /* Write the signature.  */
 
   bfd_putl32 (CV_SIGNATURE_C13, int_buf);
@@ -412,7 +584,7 @@ populate_module_stream (bfd *stream, uint32_t *sym_byte_size)
 /* Create the module info substream within the DBI.  */
 static bool
 create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
-			      uint32_t *size)
+			      uint32_t *size, struct string_table *strings)
 {
   uint8_t *ptr;
 
@@ -482,7 +654,8 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 	  return false;
 	}
 
-      if (!populate_module_stream (stream, &sym_byte_size))
+      if (!populate_module_stream (stream, in, &sym_byte_size,
+				   strings))
 	{
 	  free (*data);
 	  return false;
@@ -687,14 +860,16 @@ static bool
 populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
 		     uint16_t section_header_stream_num,
 		     uint16_t sym_rec_stream_num,
-		     uint16_t publics_stream_num)
+		     uint16_t publics_stream_num,
+		     struct string_table *strings)
 {
   struct pdb_dbi_stream_header h;
   struct optional_dbg_header opt;
   void *mod_info, *sc;
   uint32_t mod_info_size, sc_size;
 
-  if (!create_module_info_substream (abfd, pdb, &mod_info, &mod_info_size))
+  if (!create_module_info_substream (abfd, pdb, &mod_info, &mod_info_size,
+				     strings))
     return false;
 
   if (!create_section_contrib_substream (abfd, &sc, &sc_size))
@@ -1107,6 +1282,95 @@ create_section_header_stream (bfd *pdb, bfd *abfd, uint16_t *num)
   return true;
 }
 
+/* Populate the "/names" named stream, which contains the string table.  */
+static bool
+populate_names_stream (bfd *stream, struct string_table *strings)
+{
+  char int_buf[sizeof (uint32_t)];
+  struct string_table_header h;
+  uint32_t num_strings = 0, num_buckets;
+  struct string **buckets;
+
+  bfd_putl32 (STRING_TABLE_SIGNATURE, &h.signature);
+  bfd_putl32 (STRING_TABLE_VERSION, &h.version);
+
+  if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h))
+    return false;
+
+  bfd_putl32 (strings->strings_len, int_buf);
+
+  if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
+    return false;
+
+  int_buf[0] = 0;
+
+  if (bfd_bwrite (int_buf, 1, stream) != 1)
+    return false;
+
+  for (struct string *s = strings->strings_head; s; s = s->next)
+    {
+      if (bfd_bwrite (s->s, s->len, stream) != s->len)
+	return false;
+
+      if (bfd_bwrite (int_buf, 1, stream) != 1)
+	return false;
+
+      num_strings++;
+    }
+
+  num_buckets = num_strings * 2;
+
+  buckets = xmalloc (sizeof (struct string *) * num_buckets);
+  memset (buckets, 0, sizeof (struct string *) * num_buckets);
+
+  for (struct string *s = strings->strings_head; s; s = s->next)
+    {
+      uint32_t bucket_num = s->hash % num_buckets;
+
+      while (buckets[bucket_num])
+	{
+	  bucket_num++;
+
+	  if (bucket_num == num_buckets)
+	    bucket_num = 0;
+	}
+
+      buckets[bucket_num] = s;
+    }
+
+  bfd_putl32 (num_buckets, int_buf);
+
+  if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
+    {
+      free (buckets);
+      return false;
+    }
+
+  for (unsigned int i = 0; i < num_buckets; i++)
+    {
+      if (buckets[i])
+	bfd_putl32 (buckets[i]->offset, int_buf);
+      else
+	bfd_putl32 (0, int_buf);
+
+      if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) !=
+	  sizeof (uint32_t))
+	{
+	  free (buckets);
+	  return false;
+	}
+    }
+
+  free (buckets);
+
+  bfd_putl32 (num_strings, int_buf);
+
+  if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
+    return false;
+
+  return true;
+}
+
 /* Create a PDB debugging file for the PE image file abfd with the build ID
    guid, stored at pdb_name.  */
 bool
@@ -1117,6 +1381,7 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
   bfd *info_stream, *dbi_stream, *names_stream, *sym_rec_stream,
     *publics_stream;
   uint16_t section_header_stream_num, sym_rec_stream_num, publics_stream_num;
+  struct string_table strings;
 
   pdb = bfd_openw (pdb_name, "pdb");
   if (!pdb)
@@ -1125,6 +1390,13 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
       return false;
     }
 
+  strings.strings_head = NULL;
+  strings.strings_tail = NULL;
+  strings.strings_len = 1;
+  strings.hashmap = htab_create_alloc (0, hash_string_table_entry,
+				       eq_string_table_entry, free,
+				       xcalloc, free);
+
   bfd_set_format (pdb, bfd_archive);
 
   if (!create_old_directory_stream (pdb))
@@ -1201,13 +1473,23 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
     }
 
   if (!populate_dbi_stream (dbi_stream, abfd, pdb, section_header_stream_num,
-			    sym_rec_stream_num, publics_stream_num))
+			    sym_rec_stream_num, publics_stream_num,
+			    &strings))
     {
       einfo (_("%P: warning: cannot populate DBI stream "
 	       "in PDB file: %E\n"));
       goto end;
     }
 
+  add_string ("", 0, &strings);
+
+  if (!populate_names_stream (names_stream, &strings))
+    {
+      einfo (_("%P: warning: cannot populate names stream "
+	       "in PDB file: %E\n"));
+      goto end;
+    }
+
   if (!populate_publics_stream (publics_stream, abfd, sym_rec_stream))
     {
       einfo (_("%P: warning: cannot populate publics stream "
@@ -1227,5 +1509,7 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
 end:
   bfd_close (pdb);
 
+  htab_delete (strings.hashmap);
+
   return ret;
 }
diff --git a/ld/pdb.h b/ld/pdb.h
index e22dea18eca..611f71041c0 100644
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -155,6 +155,18 @@ struct optional_dbg_header
 
 #define CV_SIGNATURE_C13		4
 
+#define DEBUG_S_STRINGTABLE		0xf3
+
+#define STRING_TABLE_SIGNATURE		0xeffeeffe
+#define STRING_TABLE_VERSION		1
+
+/* VHdr in nmt.h */
+struct string_table_header
+{
+  uint32_t signature;
+  uint32_t version;
+};
+
 #define SECTION_CONTRIB_VERSION_60	0xf12eba2d
 
 /* SC in dbicommon.h */
diff --git a/ld/testsuite/ld-pe/pdb-strings.d b/ld/testsuite/ld-pe/pdb-strings.d
new file mode 100644
index 00000000000..8be853efb72
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-strings.d
@@ -0,0 +1,10 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 feeffeef 01000000 17000000 0000666f  ..............fo
+ 0010 6f006261 72006261 7a007175 78007175  o.bar.baz.qux.qu
+ 0020 7578000c 00000001 0000000a 00000000  ux..............
+ 0030 00000000 00000000 00000012 00000000  ................
+ 0040 00000000 00000002 00000006 00000000  ................
+ 0050 0000000e 00000006 000000             ...........     
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-strings1.s b/ld/testsuite/ld-pe/pdb-strings1.s
new file mode 100644
index 00000000000..09eedd93fb3
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-strings1.s
@@ -0,0 +1,19 @@
+.equ CV_SIGNATURE_C13, 4
+.equ DEBUG_S_STRINGTABLE, 0xf3
+
+.section ".debug$S", "rn"
+.long CV_SIGNATURE_C13
+.long DEBUG_S_STRINGTABLE
+.long .strings_end - .strings_start
+
+.strings_start:
+
+.asciz ""
+.asciz "foo"
+.asciz "bar"
+.asciz "baz"
+.asciz "qux"
+
+.strings_end:
+
+.balign 4
diff --git a/ld/testsuite/ld-pe/pdb-strings2.s b/ld/testsuite/ld-pe/pdb-strings2.s
new file mode 100644
index 00000000000..33d9215e4c8
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-strings2.s
@@ -0,0 +1,19 @@
+.equ CV_SIGNATURE_C13, 4
+.equ DEBUG_S_STRINGTABLE, 0xf3
+
+.section ".debug$S", "rn"
+.long CV_SIGNATURE_C13
+.long DEBUG_S_STRINGTABLE
+.long .strings_end - .strings_start
+
+.strings_start:
+
+.asciz ""
+.asciz "bar"
+.asciz "baz"
+.asciz "qux"
+.asciz "quux"
+
+.strings_end:
+
+.balign 4
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index 0be65e22fb6..09e9b4a8809 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -703,5 +703,127 @@ proc test2 { } {
     test_section_contrib $section_contrib
 }
 
+proc find_named_stream { pdb name } {
+    global ar
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb 0001"]
+
+    if ![string match "" $exec_output] {
+	return 0
+    }
+
+    set fi [open tmpdir/0001]
+    fconfigure $fi -translation binary
+
+    seek $fi 0x1c
+
+    set data [read $fi 4]
+    binary scan $data i string_len
+
+    set strings [read $fi $string_len]
+
+    set string_off 0
+
+    while {[string first \000 $strings $string_off] != -1 } {
+	set str [string range $strings $string_off [expr [string first \000 $strings $string_off] - 1]]
+
+	if { $str eq $name } {
+	    break
+	}
+
+	incr string_off [expr [string length $str] + 1]
+    }
+
+    if { [string length $strings] == $string_off } { # string not found
+	close $fi
+	return 0
+    }
+
+    set data [read $fi 4]
+    binary scan $data i num_entries
+
+    seek $fi 4 current
+
+    set data [read $fi 4]
+    binary scan $data i present_bitmap_len
+
+    seek $fi [expr $present_bitmap_len * 4] current
+
+    set data [read $fi 4]
+    binary scan $data i deleted_bitmap_len
+
+    seek $fi [expr $deleted_bitmap_len * 4] current
+
+    for {set i 0} {$i < $num_entries} {incr i} {
+	set data [read $fi 4]
+	binary scan $data i offset
+
+	if { $offset == $string_off } {
+	    set data [read $fi 4]
+	    binary scan $data i value
+	    close $fi
+
+	    return $value
+	}
+
+	seek $fi 4 current
+    }
+
+    close $fi
+
+    return 0
+}
+
+proc test3 { } {
+    global as
+    global ar
+    global ld
+    global objdump
+    global srcdir
+    global subdir
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-strings1.s tmpdir/pdb-strings1.o] {
+	unsupported "Build pdb-strings1.o"
+	return
+    }
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-strings2.s tmpdir/pdb-strings2.o] {
+	unsupported "Build pdb-strings2.o"
+	return
+    }
+
+    if ![ld_link $ld "tmpdir/pdb-strings.exe" "--pdb=tmpdir/pdb-strings.pdb tmpdir/pdb-strings1.o tmpdir/pdb-strings2.o"] {
+	unsupported "Create PE image with PDB file"
+	return
+    }
+
+    set index [find_named_stream "tmpdir/pdb-strings.pdb" "/names"]
+
+    if { $index == 0 } {
+	fail "Could not find /names stream"
+	return
+    } else {
+	pass "Found /names stream"
+    }
+
+    set index_str [format "%04x" $index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-strings.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	return 0
+    }
+
+    set exp [file_contents "$srcdir/$subdir/pdb-strings.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]
+
+    if ![string match $exp $got] {
+	fail "Strings table was not as expected"
+    } else {
+	pass "Strings table was as expected"
+    }
+}
+
 test1
 test2
+test3
-- 
2.37.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH] ld: Write DEBUG_S_FILECHKSMS entries in PDBs
  2022-11-25  2:53 [PATCH v2] ld: Generate PDB string table Mark Harmstone
@ 2022-11-25  2:54 ` Mark Harmstone
  2022-11-27  2:38   ` [PATCH] ld: Fix segfault in populate_publics_stream Mark Harmstone
  0 siblings, 1 reply; 19+ messages in thread
From: Mark Harmstone @ 2022-11-25  2:54 UTC (permalink / raw)
  To: binutils; +Cc: Mark Harmstone

Here we parse the DEBUG_S_FILECHKSMS subsections of the .debug$S input
sections, which contain the names and checksums of the input source
files.

Copies are stored in the "C13" substream of the symbol stream for each
module. We also populate the source info substream of the DBI stream,
which contains a deduplicated list of all source files.

---
 ld/pdb.c                              | 433 +++++++++++++++++++++++++-
 ld/pdb.h                              |   9 +
 ld/testsuite/ld-pe/pdb.exp            | 154 +++++++++
 ld/testsuite/ld-pe/pdb3-c13-info1.d   |   8 +
 ld/testsuite/ld-pe/pdb3-c13-info2.d   |   8 +
 ld/testsuite/ld-pe/pdb3-source-info.d |   7 +
 ld/testsuite/ld-pe/pdb3a.s            |  52 ++++
 ld/testsuite/ld-pe/pdb3b.s            |  52 ++++
 8 files changed, 711 insertions(+), 12 deletions(-)
 create mode 100644 ld/testsuite/ld-pe/pdb3-c13-info1.d
 create mode 100644 ld/testsuite/ld-pe/pdb3-c13-info2.d
 create mode 100644 ld/testsuite/ld-pe/pdb3-source-info.d
 create mode 100644 ld/testsuite/ld-pe/pdb3a.s
 create mode 100644 ld/testsuite/ld-pe/pdb3b.s

diff --git a/ld/pdb.c b/ld/pdb.c
index dc008bc38bb..d133b3e1aaa 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -46,6 +46,7 @@ struct string
   struct string *next;
   uint32_t hash;
   uint32_t offset;
+  uint32_t source_file_offset;
   size_t len;
   char s[];
 };
@@ -58,6 +59,18 @@ struct string_table
   htab_t hashmap;
 };
 
+struct mod_source_files
+{
+  uint16_t files_count;
+  struct string **files;
+};
+
+struct source_files_info
+{
+  uint16_t mod_count;
+  struct mod_source_files *mods;
+};
+
 /* Add a new stream to the PDB archive, and return its BFD.  */
 static bfd *
 add_stream (bfd *pdb, const char *name, uint16_t *stream_num)
@@ -400,6 +413,134 @@ get_arch_number (bfd *abfd)
   return IMAGE_FILE_MACHINE_I386;
 }
 
+/* Validate the DEBUG_S_FILECHKSMS entry within a module's .debug$S
+   section, and copy it to the module's symbol stream.  */
+static bool
+copy_filechksms (uint8_t *data, uint32_t size, char *string_table,
+		 struct string_table *strings, uint8_t *out,
+		 struct mod_source_files *mod_source)
+{
+  uint8_t *orig_data = data;
+  uint32_t orig_size = size;
+  uint16_t num_files = 0;
+  struct string **strptr;
+
+  bfd_putl32 (DEBUG_S_FILECHKSMS, out);
+  out += sizeof (uint32_t);
+
+  bfd_putl32 (size, out);
+  out += sizeof (uint32_t);
+
+  /* Calculate the number of files, and check for any overflows.  */
+
+  while (size > 0)
+    {
+      struct file_checksum *fc = (struct file_checksum *) data;
+      uint8_t padding;
+      size_t len;
+
+      if (size < sizeof (struct file_checksum))
+	{
+	  bfd_set_error (bfd_error_bad_value);
+	  return false;
+	}
+
+      len = sizeof (struct file_checksum) + fc->checksum_length;
+
+      if (size < len)
+	{
+	  bfd_set_error (bfd_error_bad_value);
+	  return false;
+	}
+
+      data += len;
+      size -= len;
+
+      if (len % sizeof (uint32_t))
+	padding = sizeof (uint32_t) - (len % sizeof (uint32_t));
+      else
+	padding = 0;
+
+      if (size < padding)
+	{
+	  bfd_set_error (bfd_error_bad_value);
+	  return false;
+	}
+
+      num_files++;
+
+      data += padding;
+      size -= padding;
+    }
+
+  /* Add the files to mod_source, so that they'll appear in the source
+     info substream.  */
+
+  if (num_files > 0)
+    {
+      uint16_t new_count = num_files + mod_source->files_count;
+
+      mod_source->files = xrealloc (mod_source->files,
+				    sizeof (struct string *) * new_count);
+
+      strptr = mod_source->files + mod_source->files_count;
+
+      mod_source->files_count += num_files;
+    }
+
+  /* Actually copy the data.  */
+
+  data = orig_data;
+  size = orig_size;
+
+  while (size > 0)
+    {
+      struct file_checksum *fc = (struct file_checksum *) data;
+      uint32_t string_off;
+      uint8_t padding;
+      size_t len;
+      struct string *str = NULL;
+
+      string_off = bfd_getl32 (&fc->file_id);
+      len = sizeof (struct file_checksum) + fc->checksum_length;
+
+      if (len % sizeof (uint32_t))
+	padding = sizeof (uint32_t) - (len % sizeof (uint32_t));
+      else
+	padding = 0;
+
+      /* Remap the "file ID", i.e. the offset in the module's string table,
+         so it points to the right place in the main string table.  */
+
+      if (string_table)
+	{
+	  char *fn = string_table + string_off;
+	  size_t fn_len = strlen (fn);
+	  uint32_t hash = calc_hash (fn, fn_len);
+	  void **slot;
+
+	  slot = htab_find_slot_with_hash (strings->hashmap, fn, hash,
+					   NO_INSERT);
+
+	  if (slot)
+	    str = (struct string *) *slot;
+	}
+
+      *strptr = str;
+      strptr++;
+
+      bfd_putl32 (str ? str->offset : 0, &fc->file_id);
+
+      memcpy (out, data, len + padding);
+
+      data += len + padding;
+      size -= len + padding;
+      out += len + padding;
+    }
+
+  return true;
+}
+
 /* Add a string to the strings table, if it's not already there.  */
 static void
 add_string (char *str, size_t len, struct string_table *strings)
@@ -420,6 +561,7 @@ add_string (char *str, size_t len, struct string_table *strings)
       s->next = NULL;
       s->hash = hash;
       s->offset = strings->strings_len;
+      s->source_file_offset = 0xffffffff;
       s->len = len;
       memcpy (s->s, str, len);
 
@@ -479,10 +621,15 @@ parse_string_table (bfd_byte *data, size_t size,
 
 /* Parse the .debug$S section within an object file.  */
 static bool
-handle_debugs_section (asection *s, bfd *mod, struct string_table *strings)
+handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
+		       uint8_t **dataptr, uint32_t *sizeptr,
+		       struct mod_source_files *mod_source)
 {
   bfd_byte *data = NULL;
   size_t off;
+  uint32_t c13_size = 0;
+  char *string_table = NULL;
+  uint8_t *buf, *bufptr;
 
   if (!bfd_get_full_section_contents (mod, s, &data))
     return false;
@@ -498,6 +645,8 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings)
 
   off = sizeof (uint32_t);
 
+  /* calculate size */
+
   while (off + sizeof (uint32_t) <= s->size)
     {
       uint32_t type, size;
@@ -526,9 +675,64 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings)
 
       switch (type)
 	{
+	case DEBUG_S_FILECHKSMS:
+	  c13_size += sizeof (uint32_t) + sizeof (uint32_t) + size;
+
+	  if (c13_size % sizeof (uint32_t))
+	    c13_size += sizeof (uint32_t) - (c13_size % sizeof (uint32_t));
+
+	  break;
+
 	case DEBUG_S_STRINGTABLE:
 	  parse_string_table (data + off, size, strings);
 
+	  string_table = (char *) data + off;
+
+	  break;
+	}
+
+      off += size;
+
+      if (off % sizeof (uint32_t))
+	off += sizeof (uint32_t) - (off % sizeof (uint32_t));
+    }
+
+  if (c13_size == 0)
+    {
+      free (data);
+      return true;
+    }
+
+  /* copy data */
+
+  buf = xmalloc (c13_size);
+  bufptr = buf;
+
+  off = sizeof (uint32_t);
+
+  while (off + sizeof (uint32_t) <= s->size)
+    {
+      uint32_t type, size;
+
+      type = bfd_getl32 (data + off);
+      off += sizeof (uint32_t);
+
+      size = bfd_getl32 (data + off);
+      off += sizeof (uint32_t);
+
+      switch (type)
+	{
+	case DEBUG_S_FILECHKSMS:
+	  if (!copy_filechksms (data + off, size, string_table,
+				strings, bufptr,
+				mod_source))
+	    {
+	      free (data);
+	      return false;
+	    }
+
+	  bufptr += sizeof (uint32_t) + sizeof (uint32_t) + size;
+
 	  break;
 	}
 
@@ -540,6 +744,23 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings)
 
   free (data);
 
+  if (*dataptr)
+    {
+      /* Append the C13 info to what's already there, if the module has
+	 multiple .debug$S sections.  */
+
+      *dataptr = xrealloc (*dataptr, *sizeptr + c13_size);
+      memcpy (*dataptr + *sizeptr, buf, c13_size);
+
+      free (buf);
+    }
+  else
+    {
+      *dataptr = buf;
+    }
+
+  *sizeptr += c13_size;
+
   return true;
 }
 
@@ -547,11 +768,15 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings)
    data for each object file.  */
 static bool
 populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
-			struct string_table *strings)
+			struct string_table *strings,
+			uint32_t *c13_info_size,
+			struct mod_source_files *mod_source)
 {
   uint8_t int_buf[sizeof (uint32_t)];
+  uint8_t *c13_info = NULL;
 
   *sym_byte_size = sizeof (uint32_t);
+  *c13_info_size = 0;
 
   /* Process .debug$S section(s).  */
 
@@ -559,8 +784,13 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
     {
       if (!strcmp (s->name, ".debug$S") && s->size >= sizeof (uint32_t))
 	{
-	  if (!handle_debugs_section (s, mod, strings))
+	  if (!handle_debugs_section (s, mod, strings, &c13_info,
+				      c13_info_size, mod_source))
+	    {
+	      free (c13_info);
+	      free (mod_source->files);
 	      return false;
+	    }
 	}
     }
 
@@ -569,7 +799,22 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
   bfd_putl32 (CV_SIGNATURE_C13, int_buf);
 
   if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
-    return false;
+    {
+      free (c13_info);
+      return false;
+    }
+
+  if (c13_info)
+    {
+      if (bfd_bwrite (c13_info, *c13_info_size, stream)
+	  != *c13_info_size)
+	{
+	  free (c13_info);
+	  return false;
+	}
+
+      free (c13_info);
+    }
 
   /* Write the global refs size.  */
 
@@ -584,9 +829,11 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 /* Create the module info substream within the DBI.  */
 static bool
 create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
-			      uint32_t *size, struct string_table *strings)
+			      uint32_t *size, struct string_table *strings,
+			      struct source_files_info *source)
 {
   uint8_t *ptr;
+  unsigned int mod_num;
 
   static const char linker_fn[] = "* Linker *";
 
@@ -631,32 +878,54 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 	len += 4 - (len % 4);
 
       *size += len;
+
+      source->mod_count++;
     }
 
   *data = xmalloc (*size);
 
   ptr = *data;
 
+  source->mods = xmalloc (source->mod_count
+			  * sizeof (struct mod_source_files));
+  memset (source->mods, 0,
+	  source->mod_count * sizeof (struct mod_source_files));
+
+  mod_num = 0;
+
   for (bfd *in = coff_data (abfd)->link_info->input_bfds; in;
        in = in->link.next)
     {
       struct module_info *mod = (struct module_info *) ptr;
       uint16_t stream_num;
       bfd *stream;
-      uint32_t sym_byte_size;
+      uint32_t sym_byte_size, c13_info_size;
       uint8_t *start = ptr;
 
       stream = add_stream (pdb, NULL, &stream_num);
 
       if (!stream)
 	{
+	  for (unsigned int i = 0; i < source->mod_count; i++)
+	    {
+	      free (source->mods[i].files);
+	    }
+
+	  free (source->mods);
 	  free (*data);
 	  return false;
 	}
 
       if (!populate_module_stream (stream, in, &sym_byte_size,
-				   strings))
+				   strings, &c13_info_size,
+				   &source->mods[mod_num]))
 	{
+	  for (unsigned int i = 0; i < source->mod_count; i++)
+	    {
+	      free (source->mods[i].files);
+	    }
+
+	  free (source->mods);
 	  free (*data);
 	  return false;
 	}
@@ -679,7 +948,7 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
       bfd_putl16 (stream_num, &mod->module_sym_stream);
       bfd_putl32 (sym_byte_size, &mod->sym_byte_size);
       bfd_putl32 (0, &mod->c11_byte_size);
-      bfd_putl32 (0, &mod->c13_byte_size);
+      bfd_putl32 (c13_info_size, &mod->c13_byte_size);
       bfd_putl16 (0, &mod->source_file_count);
       bfd_putl16 (0, &mod->padding);
       bfd_putl32 (0, &mod->unused2);
@@ -741,6 +1010,8 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 	  memset (ptr, 0, 4 - ((ptr - start) % 4));
 	  ptr += 4 - ((ptr - start) % 4);
 	}
+
+      mod_num++;
     }
 
   return true;
@@ -855,6 +1126,114 @@ create_section_contrib_substream (bfd *abfd, void **data, uint32_t *size)
   return true;
 }
 
+/* The source info substream lives within the DBI stream, and lists the
+   source files for each object file (i.e. it's derived from the
+   DEBUG_S_FILECHKSMS parts of the .debug$S sections).  This is a bit
+   superfluous, as the filenames are also available in the C13 parts of
+   the module streams, but MSVC relies on it to work properly.  */
+static void
+create_source_info_substream (void **data, uint32_t *size,
+			      struct source_files_info *source)
+{
+  uint16_t dedupe_source_files_count = 0;
+  uint16_t source_files_count = 0;
+  uint32_t strings_len = 0;
+  uint8_t *ptr;
+
+  /* Loop through the source files, marking unique filenames.  The pointers
+     here are for entries in the main string table, and so have already
+     been deduplicated.  */
+
+  for (uint16_t i = 0; i < source->mod_count; i++)
+    {
+      for (uint16_t j = 0; j < source->mods[i].files_count; j++)
+	{
+	  if (source->mods[i].files[j])
+	    {
+	      if (source->mods[i].files[j]->source_file_offset == 0xffffffff)
+		{
+		  source->mods[i].files[j]->source_file_offset = strings_len;
+		  strings_len += source->mods[i].files[j]->len + 1;
+		  dedupe_source_files_count++;
+		}
+
+	      source_files_count++;
+	    }
+	}
+    }
+
+  *size = sizeof (uint16_t) + sizeof (uint16_t);
+  *size += (sizeof (uint16_t) + sizeof (uint16_t)) * source->mod_count;
+  *size += sizeof (uint32_t) * source_files_count;
+  *size += strings_len;
+
+  *data = xmalloc (*size);
+
+  ptr = (uint8_t *) *data;
+
+  /* Write header (module count and source file count).  */
+
+  bfd_putl16 (source->mod_count, ptr);
+  ptr += sizeof (uint16_t);
+
+  bfd_putl16 (dedupe_source_files_count, ptr);
+  ptr += sizeof (uint16_t);
+
+  /* Write "ModIndices".  As the LLVM documentation puts it, "this array is
+     present, but does not appear to be useful".  */
+
+  for (uint16_t i = 0; i < source->mod_count; i++)
+    {
+      bfd_putl16 (i, ptr);
+      ptr += sizeof (uint16_t);
+    }
+
+  /* Write source file count for each module.  */
+
+  for (uint16_t i = 0; i < source->mod_count; i++)
+    {
+      bfd_putl16 (source->mods[i].files_count, ptr);
+      ptr += sizeof (uint16_t);
+    }
+
+  /* For each module, write the offsets within the string table
+     for each source file.  */
+
+  for (uint16_t i = 0; i < source->mod_count; i++)
+    {
+      for (uint16_t j = 0; j < source->mods[i].files_count; j++)
+	{
+	  if (source->mods[i].files[j])
+	    {
+	      bfd_putl32 (source->mods[i].files[j]->source_file_offset, ptr);
+	      ptr += sizeof (uint32_t);
+	    }
+	}
+    }
+
+  /* Write the string table.  We set source_file_offset to a dummy value for
+     each entry we write, so we don't write duplicate filenames.  */
+
+  for (uint16_t i = 0; i < source->mod_count; i++)
+    {
+      for (uint16_t j = 0; j < source->mods[i].files_count; j++)
+	{
+	  if (source->mods[i].files[j]
+	      && source->mods[i].files[j]->source_file_offset != 0xffffffff)
+	    {
+	      memcpy (ptr, source->mods[i].files[j]->s,
+		      source->mods[i].files[j]->len);
+	      ptr += source->mods[i].files[j]->len;
+
+	      *ptr = 0;
+	      ptr++;
+
+	      source->mods[i].files[j]->source_file_offset = 0xffffffff;
+	    }
+	}
+    }
+}
+
 /* Stream 4 is the debug information (DBI) stream.  */
 static bool
 populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
@@ -865,19 +1244,38 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
 {
   struct pdb_dbi_stream_header h;
   struct optional_dbg_header opt;
-  void *mod_info, *sc;
-  uint32_t mod_info_size, sc_size;
+  void *mod_info, *sc, *source_info;
+  uint32_t mod_info_size, sc_size, source_info_size;
+  struct source_files_info source;
+
+  source.mod_count = 0;
+  source.mods = NULL;
 
   if (!create_module_info_substream (abfd, pdb, &mod_info, &mod_info_size,
-				     strings))
+				     strings, &source))
     return false;
 
   if (!create_section_contrib_substream (abfd, &sc, &sc_size))
     {
+      for (unsigned int i = 0; i < source.mod_count; i++)
+	{
+	  free (source.mods[i].files);
+	}
+      free (source.mods);
+
       free (mod_info);
       return false;
     }
 
+  create_source_info_substream (&source_info, &source_info_size,
+				&source);
+
+  for (unsigned int i = 0; i < source.mod_count; i++)
+    {
+      free (source.mods[i].files);
+    }
+  free (source.mods);
+
   bfd_putl32 (0xffffffff, &h.version_signature);
   bfd_putl32 (DBI_STREAM_VERSION_70, &h.version_header);
   bfd_putl32 (1, &h.age);
@@ -890,7 +1288,7 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
   bfd_putl32 (mod_info_size, &h.mod_info_size);
   bfd_putl32 (sc_size, &h.section_contribution_size);
   bfd_putl32 (0, &h.section_map_size);
-  bfd_putl32 (0, &h.source_info_size);
+  bfd_putl32 (source_info_size, &h.source_info_size);
   bfd_putl32 (0, &h.type_server_map_size);
   bfd_putl32 (0, &h.mfc_type_server_index);
   bfd_putl32 (sizeof (opt), &h.optional_dbg_header_size);
@@ -901,6 +1299,7 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
 
   if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h))
     {
+      free (source_info);
       free (sc);
       free (mod_info);
       return false;
@@ -908,6 +1307,7 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
 
   if (bfd_bwrite (mod_info, mod_info_size, stream) != mod_info_size)
     {
+      free (source_info);
       free (sc);
       free (mod_info);
       return false;
@@ -917,12 +1317,21 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
 
   if (bfd_bwrite (sc, sc_size, stream) != sc_size)
     {
+      free (source_info);
       free (sc);
       return false;
     }
 
   free (sc);
 
+  if (bfd_bwrite (source_info, source_info_size, stream) != source_info_size)
+    {
+      free (source_info);
+      return false;
+    }
+
+  free (source_info);
+
   bfd_putl16 (0xffff, &opt.fpo_stream);
   bfd_putl16 (0xffff, &opt.exception_stream);
   bfd_putl16 (0xffff, &opt.fixup_stream);
diff --git a/ld/pdb.h b/ld/pdb.h
index 611f71041c0..e8f673c24a0 100644
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -156,6 +156,7 @@ struct optional_dbg_header
 #define CV_SIGNATURE_C13		4
 
 #define DEBUG_S_STRINGTABLE		0xf3
+#define DEBUG_S_FILECHKSMS		0xf4
 
 #define STRING_TABLE_SIGNATURE		0xeffeeffe
 #define STRING_TABLE_VERSION		1
@@ -200,6 +201,14 @@ struct module_info
   uint32_t pdb_file_path_name_index;
 };
 
+/* filedata in dumpsym7.cpp */
+struct file_checksum
+{
+  uint32_t file_id;
+  uint8_t checksum_length;
+  uint8_t checksum_type;
+} ATTRIBUTE_PACKED;
+
 extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
 
 #endif
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index 09e9b4a8809..9dab41110ac 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -824,6 +824,160 @@ proc test3 { } {
     }
 }
 
+proc extract_c13_info { pdb mod_info } {
+    global ar
+
+    binary scan [string range $mod_info 34 35] s module_sym_stream
+    binary scan [string range $mod_info 36 39] i sym_byte_size
+    binary scan [string range $mod_info 40 43] i c11_byte_size
+    binary scan [string range $mod_info 44 47] i c13_byte_size
+
+    set index_str [format "%04x" $module_sym_stream]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir $pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	return ""
+    }
+
+    set fi [open tmpdir/$index_str]
+    fconfigure $fi -translation binary
+
+    seek $fi [expr $sym_byte_size + $c11_byte_size]
+
+    set data [read $fi $c13_byte_size]
+
+    close $fi
+
+    return $data
+}
+
+proc test4 { } {
+    global as
+    global ar
+    global ld
+    global objdump
+    global srcdir
+    global subdir
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb3a.s tmpdir/pdb3a.o] {
+	unsupported "Build pdb3a.o"
+	return
+    }
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb3b.s tmpdir/pdb3b.o] {
+	unsupported "Build pdb3b.o"
+	return
+    }
+
+    if ![ld_link $ld "tmpdir/pdb3.exe" "--pdb=tmpdir/pdb3.pdb tmpdir/pdb3a.o tmpdir/pdb3b.o"] {
+	unsupported "Create PE image with PDB file"
+	return
+    }
+
+    # read relevant bits from DBI stream
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb3.pdb 0003"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract DBI stream"
+	return
+    } else {
+	pass "Extracted DBI stream"
+    }
+
+    set fi [open tmpdir/0003]
+    fconfigure $fi -translation binary
+
+    seek $fi 24
+
+    # read substream sizes
+
+    set data [read $fi 4]
+    binary scan $data i mod_info_size
+
+    set data [read $fi 4]
+    binary scan $data i section_contribution_size
+
+    set data [read $fi 4]
+    binary scan $data i section_map_size
+
+    set data [read $fi 4]
+    binary scan $data i source_info_size
+
+    seek $fi 24 current
+
+    set mod_info [read $fi $mod_info_size]
+
+    seek $fi [expr $section_contribution_size + $section_map_size] current
+
+    set source_info [read $fi $source_info_size]
+
+    close $fi
+
+    # check source info substream
+
+    set fi [open tmpdir/pdb3-source-info w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $source_info
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb3-source-info.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb3-source-info"]
+
+    if [string match $exp $got] {
+	pass "Correct source info substream"
+    } else {
+	fail "Incorrect source info substream"
+    }
+
+    # check C13 info in first module
+
+    set c13_info [extract_c13_info "tmpdir/pdb3.pdb" [string range $mod_info 0 63]]
+
+    set fi [open tmpdir/pdb3-c13-info1 w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $c13_info
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb3-c13-info1.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb3-c13-info1"]
+
+    if [string match $exp $got] {
+	pass "Correct C13 info for first module"
+    } else {
+	fail "Incorrect C13 info for first module"
+    }
+
+    # check C13 info in second module
+
+    set fn1_end [string first \000 $mod_info 64]
+    set fn2_end [string first \000 $mod_info [expr $fn1_end + 1]]
+
+    set off [expr $fn2_end + 1]
+
+    if { [expr $off % 4] != 0 } {
+	set off [expr $off + 4 - ($off % 4)]
+    }
+
+    set c13_info [extract_c13_info "tmpdir/pdb3.pdb" [string range $mod_info $off [expr $off + 63]]]
+
+    set fi [open tmpdir/pdb3-c13-info2 w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $c13_info
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb3-c13-info2.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb3-c13-info2"]
+
+    if [string match $exp $got] {
+	pass "Correct C13 info for second module"
+    } else {
+	fail "Incorrect C13 info for second module"
+    }
+}
+
 test1
 test2
 test3
+test4
diff --git a/ld/testsuite/ld-pe/pdb3-c13-info1.d b/ld/testsuite/ld-pe/pdb3-c13-info1.d
new file mode 100644
index 00000000000..f92062ba4e5
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb3-c13-info1.d
@@ -0,0 +1,8 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 f4000000 30000000 02000000 10016745  ....0.........gE
+ 0010 2301efcd ab8998ba dcfe1023 45670000  #..........#Eg..
+ 0020 06000000 100198ba dcfe1023 45676745  ...........#EggE
+ 0030 2301efcd ab890000                    #.......        
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb3-c13-info2.d b/ld/testsuite/ld-pe/pdb3-c13-info2.d
new file mode 100644
index 00000000000..1c33ce1e798
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb3-c13-info2.d
@@ -0,0 +1,8 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 f4000000 30000000 06000000 100198ba  ....0...........
+ 0010 dcfe1023 45676745 2301efcd ab890000  ...#EggE#.......
+ 0020 0a000000 10013b2a 19087f6e 5d4c4c5d  ......;*...n]LL]
+ 0030 6e7f0819 2a3b0000                    n...*;..        
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb3-source-info.d b/ld/testsuite/ld-pe/pdb3-source-info.d
new file mode 100644
index 00000000000..5b7d58cfa0c
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb3-source-info.d
@@ -0,0 +1,7 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 03000300 00000100 02000200 02000000  ................
+ 0010 00000000 04000000 04000000 08000000  ................
+ 0020 666f6f00 62617200 62617a00           foo.bar.baz.    
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb3a.s b/ld/testsuite/ld-pe/pdb3a.s
new file mode 100644
index 00000000000..71795b53a66
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb3a.s
@@ -0,0 +1,52 @@
+.equ CV_SIGNATURE_C13, 4
+.equ DEBUG_S_STRINGTABLE, 0xf3
+.equ DEBUG_S_FILECHKSMS, 0xf4
+.equ CHKSUM_TYPE_MD5, 1
+
+.equ NUM_MD5_BYTES, 16
+
+.section ".debug$S", "rn"
+.long CV_SIGNATURE_C13
+.long DEBUG_S_STRINGTABLE
+.long .strings_end - .strings_start
+
+.strings_start:
+
+.asciz ""
+
+.src1:
+.asciz "foo"
+
+.src2:
+.asciz "bar"
+
+.strings_end:
+
+.balign 4
+
+.long DEBUG_S_FILECHKSMS
+.long .chksms_end - .chksms_start
+
+.chksms_start:
+
+.long .src1 - .strings_start
+.byte NUM_MD5_BYTES
+.byte CHKSUM_TYPE_MD5
+.long 0x01234567
+.long 0x89abcdef
+.long 0xfedcba98
+.long 0x67452310
+.short 0 # padding
+
+.long .src2 - .strings_start
+.byte NUM_MD5_BYTES
+.byte CHKSUM_TYPE_MD5
+.long 0xfedcba98
+.long 0x67452310
+.long 0x01234567
+.long 0x89abcdef
+.short 0 # padding
+
+.chksms_end:
+
+.balign 4
diff --git a/ld/testsuite/ld-pe/pdb3b.s b/ld/testsuite/ld-pe/pdb3b.s
new file mode 100644
index 00000000000..fffb1150c88
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb3b.s
@@ -0,0 +1,52 @@
+.equ CV_SIGNATURE_C13, 4
+.equ DEBUG_S_STRINGTABLE, 0xf3
+.equ DEBUG_S_FILECHKSMS, 0xf4
+.equ CHKSUM_TYPE_MD5, 1
+
+.equ NUM_MD5_BYTES, 16
+
+.section ".debug$S", "rn"
+.long CV_SIGNATURE_C13
+.long DEBUG_S_STRINGTABLE
+.long .strings_end - .strings_start
+
+.strings_start:
+
+.asciz ""
+
+.src1:
+.asciz "bar"
+
+.src2:
+.asciz "baz"
+
+.strings_end:
+
+.balign 4
+
+.long DEBUG_S_FILECHKSMS
+.long .chksms_end - .chksms_start
+
+.chksms_start:
+
+.long .src1 - .strings_start
+.byte NUM_MD5_BYTES
+.byte CHKSUM_TYPE_MD5
+.long 0xfedcba98
+.long 0x67452310
+.long 0x01234567
+.long 0x89abcdef
+.short 0 # padding
+
+.long .src2 - .strings_start
+.byte NUM_MD5_BYTES
+.byte CHKSUM_TYPE_MD5
+.long 0x08192a3b
+.long 0x4c5d6e7f
+.long 0x7f6e5d4c
+.long 0x3b2a1908
+.short 0 # padding
+
+.chksms_end:
+
+.balign 4
-- 
2.37.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH] ld: Fix segfault in populate_publics_stream
  2022-11-25  2:54 ` [PATCH] ld: Write DEBUG_S_FILECHKSMS entries in PDBs Mark Harmstone
@ 2022-11-27  2:38   ` Mark Harmstone
  2022-11-27  2:38     ` [PATCH] ld: Write DEBUG_S_LINES entries in PDB file Mark Harmstone
  2022-11-28 14:54     ` [PATCH] ld: Fix segfault in populate_publics_stream Jan Beulich
  0 siblings, 2 replies; 19+ messages in thread
From: Mark Harmstone @ 2022-11-27  2:38 UTC (permalink / raw)
  To: binutils; +Cc: Mark Harmstone

---
 ld/pdb.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/ld/pdb.c b/ld/pdb.c
index d133b3e1aaa..42bb1b3a91b 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -1413,6 +1413,9 @@ populate_publics_stream (bfd *stream, bfd *abfd, bfd *sym_rec_stream)
   for (bfd *in = coff_data (abfd)->link_info->input_bfds; in;
        in = in->link.next)
     {
+      if (!in->outsymbols)
+	continue;
+
       for (unsigned int i = 0; i < in->symcount; i++)
 	{
 	  struct bfd_symbol *sym = in->outsymbols[i];
-- 
2.37.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH] ld: Write DEBUG_S_LINES entries in PDB file
  2022-11-27  2:38   ` [PATCH] ld: Fix segfault in populate_publics_stream Mark Harmstone
@ 2022-11-27  2:38     ` Mark Harmstone
  2022-11-29  0:10       ` [PATCH] ld: Write types into TPI stream of PDB Mark Harmstone
  2022-11-28 14:54     ` [PATCH] ld: Fix segfault in populate_publics_stream Jan Beulich
  1 sibling, 1 reply; 19+ messages in thread
From: Mark Harmstone @ 2022-11-27  2:38 UTC (permalink / raw)
  To: binutils; +Cc: Mark Harmstone

This extracts the DEBUG_S_LINES subsections of the .debug$S section of
the input files, resolves the relocations, and copies them into the
module symbol stream of the PDB.

---
 ld/pdb.c                            | 111 +++++++++++++++++++++++++++-
 ld/pdb.h                            |   1 +
 ld/testsuite/ld-pe/pdb.exp          |   2 +-
 ld/testsuite/ld-pe/pdb3-c13-info1.d |   8 +-
 ld/testsuite/ld-pe/pdb3a.s          |  88 ++++++++++++++++++++++
 5 files changed, 204 insertions(+), 6 deletions(-)

diff --git a/ld/pdb.c b/ld/pdb.c
index 42bb1b3a91b..cfc76004227 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -623,7 +623,8 @@ parse_string_table (bfd_byte *data, size_t size,
 static bool
 handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 		       uint8_t **dataptr, uint32_t *sizeptr,
-		       struct mod_source_files *mod_source)
+		       struct mod_source_files *mod_source,
+		       bfd *abfd)
 {
   bfd_byte *data = NULL;
   size_t off;
@@ -637,6 +638,59 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
   if (!data)
     return false;
 
+  /* Resolve relocations.  Addresses are stored within the .debug$S section as
+     a .secidx, .secrel32 pair.  */
+
+  if (s->flags & SEC_RELOC)
+    {
+      struct internal_reloc *relocs;
+      struct internal_syment *symbols;
+      asection **sectlist;
+      unsigned int syment_count;
+      int sect_num;
+      struct external_syment *ext;
+
+      syment_count = obj_raw_syment_count (mod);
+
+      relocs =
+	_bfd_coff_read_internal_relocs (mod, s, false, NULL, true, NULL);
+
+      symbols = xmalloc (sizeof (struct internal_syment) * syment_count);
+      sectlist = xmalloc (sizeof (asection *) * syment_count);
+
+      ext = (struct external_syment *) (coff_data (mod)->external_syms);
+
+      for (unsigned int i = 0; i < syment_count; i++)
+	{
+	  bfd_coff_swap_sym_in (mod, &ext[i], &symbols[i]);
+	}
+
+      sect_num = 1;
+
+      for (asection *sect = mod->sections; sect; sect = sect->next)
+	{
+	  for (unsigned int i = 0; i < syment_count; i++)
+	    {
+	      if (symbols[i].n_scnum == sect_num)
+		sectlist[i] = sect;
+	    }
+
+	  sect_num++;
+	}
+
+      if (!bfd_coff_relocate_section (abfd, coff_data (abfd)->link_info, mod,
+				      s, data, relocs, symbols, sectlist))
+	{
+	  free (sectlist);
+	  free (symbols);
+	  free (data);
+	  return false;
+	}
+
+      free (sectlist);
+      free (symbols);
+    }
+
   if (bfd_getl32 (data) != CV_SIGNATURE_C13)
     {
       free (data);
@@ -689,6 +743,32 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 	  string_table = (char *) data + off;
 
 	  break;
+
+	case DEBUG_S_LINES:
+	  {
+	    uint16_t sect;
+
+	    if (size < sizeof (uint32_t) + sizeof (uint16_t))
+	      {
+		free (data);
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    sect = bfd_getl16 (data + off + sizeof (uint32_t));
+
+	    /* Skip GC'd symbols.  */
+	    if (sect != 0)
+	      {
+		c13_size += sizeof (uint32_t) + sizeof (uint32_t) + size;
+
+		if (c13_size % sizeof (uint32_t))
+		  c13_size +=
+		    sizeof (uint32_t) - (c13_size % sizeof (uint32_t));
+	      }
+
+	    break;
+	  }
 	}
 
       off += size;
@@ -734,6 +814,28 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 	  bufptr += sizeof (uint32_t) + sizeof (uint32_t) + size;
 
 	  break;
+
+	case DEBUG_S_LINES:
+	  {
+	    uint16_t sect;
+
+	    sect = bfd_getl16 (data + off + sizeof (uint32_t));
+
+	    /* Skip if GC'd.  */
+	    if (sect != 0)
+	      {
+		bfd_putl32 (type, bufptr);
+		bufptr += sizeof (uint32_t);
+
+		bfd_putl32 (size, bufptr);
+		bufptr += sizeof (uint32_t);
+
+		memcpy (bufptr, data + off, size);
+		bufptr += size;
+	      }
+
+	    break;
+	  }
 	}
 
       off += size;
@@ -770,7 +872,8 @@ static bool
 populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 			struct string_table *strings,
 			uint32_t *c13_info_size,
-			struct mod_source_files *mod_source)
+			struct mod_source_files *mod_source,
+			bfd *abfd)
 {
   uint8_t int_buf[sizeof (uint32_t)];
   uint8_t *c13_info = NULL;
@@ -785,7 +888,7 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
       if (!strcmp (s->name, ".debug$S") && s->size >= sizeof (uint32_t))
 	{
 	  if (!handle_debugs_section (s, mod, strings, &c13_info,
-				      c13_info_size, mod_source))
+				      c13_info_size, mod_source, abfd))
 	    {
 	      free (c13_info);
 	      free (mod_source->files);
@@ -918,7 +1021,7 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 
       if (!populate_module_stream (stream, in, &sym_byte_size,
 				   strings, &c13_info_size,
-				   &source->mods[mod_num]))
+				   &source->mods[mod_num], abfd))
 	{
 	  for (unsigned int i = 0; i < source->mod_count; i++)
 	    {
diff --git a/ld/pdb.h b/ld/pdb.h
index e8f673c24a0..bbb106043c4 100644
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -155,6 +155,7 @@ struct optional_dbg_header
 
 #define CV_SIGNATURE_C13		4
 
+#define DEBUG_S_LINES			0xf2
 #define DEBUG_S_STRINGTABLE		0xf3
 #define DEBUG_S_FILECHKSMS		0xf4
 
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index 9dab41110ac..2e5f83477aa 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -870,7 +870,7 @@ proc test4 { } {
 	return
     }
 
-    if ![ld_link $ld "tmpdir/pdb3.exe" "--pdb=tmpdir/pdb3.pdb tmpdir/pdb3a.o tmpdir/pdb3b.o"] {
+    if ![ld_link $ld "tmpdir/pdb3.exe" "--pdb=tmpdir/pdb3.pdb --gc-sections -e main tmpdir/pdb3a.o tmpdir/pdb3b.o"] {
 	unsupported "Create PE image with PDB file"
 	return
     }
diff --git a/ld/testsuite/ld-pe/pdb3-c13-info1.d b/ld/testsuite/ld-pe/pdb3-c13-info1.d
index f92062ba4e5..5a4f94861c7 100644
--- a/ld/testsuite/ld-pe/pdb3-c13-info1.d
+++ b/ld/testsuite/ld-pe/pdb3-c13-info1.d
@@ -5,4 +5,10 @@ Contents of section .data:
  0000 f4000000 30000000 02000000 10016745  ....0.........gE
  0010 2301efcd ab8998ba dcfe1023 45670000  #..........#Eg..
  0020 06000000 100198ba dcfe1023 45676745  ...........#EggE
- 0030 2301efcd ab890000                    #.......        
\ No newline at end of file
+ 0030 2301efcd ab890000 f2000000 58000000  #...........X...
+ 0040 00000000 01000000 14000000 00000000  ................
+ 0050 02000000 1c000000 00000000 01000080  ................
+ 0060 04000000 02000080 18000000 02000000  ................
+ 0070 1c000000 08000000 03000080 0c000000  ................
+ 0080 04000080 00000000 01000000 14000000  ................
+ 0090 10000000 05000080                    ........        
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb3a.s b/ld/testsuite/ld-pe/pdb3a.s
index 71795b53a66..1df84a344f6 100644
--- a/ld/testsuite/ld-pe/pdb3a.s
+++ b/ld/testsuite/ld-pe/pdb3a.s
@@ -1,4 +1,5 @@
 .equ CV_SIGNATURE_C13, 4
+.equ DEBUG_S_LINES, 0xf2
 .equ DEBUG_S_STRINGTABLE, 0xf3
 .equ DEBUG_S_FILECHKSMS, 0xf4
 .equ CHKSUM_TYPE_MD5, 1
@@ -50,3 +51,90 @@
 .chksms_end:
 
 .balign 4
+
+.long DEBUG_S_LINES
+.long .lines_end - .lines_start
+
+.lines_start:
+
+.secrel32 main
+.secidx main
+.short 0 # flags
+.long .main_end - main # length of region
+
+.lines_block1:
+
+.long 0 # file ID 0 (foo)
+.long 2 # no. lines
+.long .lines_block2 - .lines_block1 # length
+
+.long .line1 - main
+.long 0x80000001 # line 1
+.long .line2 - main
+.long 0x80000002 # line 2
+
+.lines_block2:
+
+.long 0x18 # file ID 18 (bar)
+.long 2 # no. lines
+.long .lines_block3 - .lines_block2 # length
+
+.long .line3 - main
+.long 0x80000003 # line 3
+.long .line4 - main
+.long 0x80000004 # line 4
+
+.lines_block3:
+
+.long 0 # file ID 0 (foo)
+.long 1 # no. lines
+.long .lines_end - .lines_block3 # length
+
+.long .line5 - main
+.long 0x80000005 # line 5
+
+.lines_end:
+
+.long DEBUG_S_LINES
+.long .lines_end2 - .lines_start2
+
+.lines_start2:
+
+.secrel32 gcfunc
+.secidx gcfunc
+.short 0 # flags
+.long .gcfunc_end - gcfunc # length of region
+
+.lines_block4:
+
+.long 0 # file ID 0 (foo)
+.long 1 # no. lines
+.long .lines_end2 - .lines_block4 # length
+
+.long .line6 - gcfunc
+.long 0x80000006 # line 6
+
+.lines_end2:
+
+.text
+
+.global main
+main:
+.line1:
+	.long 0x12345678
+.line2:
+	.long 0x12345678
+.line3:
+	.long 0x12345678
+.line4:
+	.long 0x12345678
+.line5:
+	.long 0x12345678
+.main_end:
+
+.section "gcsect"
+
+gcfunc:
+.line6:
+	.long 0x12345678
+.gcfunc_end:
-- 
2.37.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] ld: Fix segfault in populate_publics_stream
  2022-11-27  2:38   ` [PATCH] ld: Fix segfault in populate_publics_stream Mark Harmstone
  2022-11-27  2:38     ` [PATCH] ld: Write DEBUG_S_LINES entries in PDB file Mark Harmstone
@ 2022-11-28 14:54     ` Jan Beulich
  2022-11-28 17:53       ` Mark Harmstone
  1 sibling, 1 reply; 19+ messages in thread
From: Jan Beulich @ 2022-11-28 14:54 UTC (permalink / raw)
  To: Mark Harmstone; +Cc: binutils

On 27.11.2022 03:38, Mark Harmstone wrote:
> --- a/ld/pdb.c
> +++ b/ld/pdb.c
> @@ -1413,6 +1413,9 @@ populate_publics_stream (bfd *stream, bfd *abfd, bfd *sym_rec_stream)

Out of curiosity - which tree was this diff generated against? The
line number here looks to be off by several hundred from what I
see in the repo right now.

>    for (bfd *in = coff_data (abfd)->link_info->input_bfds; in;
>         in = in->link.next)
>      {
> +      if (!in->outsymbols)
> +	continue;
> +
>        for (unsigned int i = 0; i < in->symcount; i++)
>  	{
>  	  struct bfd_symbol *sym = in->outsymbols[i];

Why / when would in->outsymbols be NULL but in->symcount be non-zero?
And if that was possible, why would it not also be possible that the
array is smaller than in->symcount? (This is the kind of questions
which arise when there's no description at all for a patch. Such a
description could have clarified under what special conditions a NULL
deref could happen despite it not being obviously possible.)

Jan

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] ld: Fix segfault in populate_publics_stream
  2022-11-28 14:54     ` [PATCH] ld: Fix segfault in populate_publics_stream Jan Beulich
@ 2022-11-28 17:53       ` Mark Harmstone
  2022-11-29  9:00         ` Jan Beulich
  0 siblings, 1 reply; 19+ messages in thread
From: Mark Harmstone @ 2022-11-28 17:53 UTC (permalink / raw)
  To: Jan Beulich; +Cc: binutils

 > Out of curiosity - which tree was this diff generated against? The
 > line number here looks to be off by several hundred from what I
 > see in the repo right now.

This is inteded to be applied with the other patches in the series, the ones
beginning with "[PATCH v2] ld: Generate PDB string table" at
https://sourceware.org/pipermail/binutils/2022-November/thread.html.

Sorry, this probably wasn't obvious from a mail client. I've not numbered
them as I'm not sure how many more there'll be, and if I wait for the previous
patches to be accepted before submitting the next, I'll almost certainly miss
the cut-off for the code freeze.

 > Why / when would in->outsymbols be NULL but in->symcount be non-zero?

Try running the test in the DEBUG_S_LINES patch without this one - it'll fail
because ld segfaults. outsymbols doesn't get set from within generate_reloc
for the second object file, as it only has one non-loadable section. The
"symbols" come from the .equs I'm using like #defines.

 > And if that was possible, why would it not also be possible that the
 > array is smaller than in->symcount?

bfd_generic_link_read_symbols is called for each loadable section, and
allocates the outsymbols array once. It was my mistake when I submitted my
original patch for populate_publics_stream, in not realizing that it would
break for object files without any loadable sections.

Mark



^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH] ld: Write types into TPI stream of PDB
  2022-11-27  2:38     ` [PATCH] ld: Write DEBUG_S_LINES entries in PDB file Mark Harmstone
@ 2022-11-29  0:10       ` Mark Harmstone
  2022-11-29  0:10         ` [PATCH] ld: Write types into IPI " Mark Harmstone
  2022-11-29  0:10         ` [PATCH] ld: Parse LF_UDT_SRC_LINE records when creating PDB file Mark Harmstone
  0 siblings, 2 replies; 19+ messages in thread
From: Mark Harmstone @ 2022-11-29  0:10 UTC (permalink / raw)
  To: binutils; +Cc: Mark Harmstone

This parses the .debug$T section of the object files, and uses them to
populate the type information stream. This and the following patches
ought to cover all the CodeView types that Clang will emit, and all the
types that I've seen MSVC emit.

Sorry, I know this is massive, but there didn't seem much sense in
splitting it up - most of it is just parsing arbitrary structs. The
actual meaning of the records doesn't matter that much here, merely how
they reference other types and how their hashes are calculated.

As with the other patches, Microsoft's cvdump.exe can be used to
generate a textual version of what's output here.

---
 ld/pdb.c                                 | 1284 +++++++++++++++++++++-
 ld/pdb.h                                 |  264 +++++
 ld/testsuite/ld-pe/pdb-types1-hashlist.d |   13 +
 ld/testsuite/ld-pe/pdb-types1-skiplist.d |    5 +
 ld/testsuite/ld-pe/pdb-types1-typelist.d |   60 +
 ld/testsuite/ld-pe/pdb-types1a.s         |   27 +
 ld/testsuite/ld-pe/pdb-types1b.s         |  461 ++++++++
 ld/testsuite/ld-pe/pdb.exp               |  172 +++
 8 files changed, 2264 insertions(+), 22 deletions(-)
 create mode 100644 ld/testsuite/ld-pe/pdb-types1-hashlist.d
 create mode 100644 ld/testsuite/ld-pe/pdb-types1-skiplist.d
 create mode 100644 ld/testsuite/ld-pe/pdb-types1-typelist.d
 create mode 100644 ld/testsuite/ld-pe/pdb-types1a.s
 create mode 100644 ld/testsuite/ld-pe/pdb-types1b.s

diff --git a/ld/pdb.c b/ld/pdb.c
index cfc76004227..979ea126aa5 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -71,6 +71,69 @@ struct source_files_info
   struct mod_source_files *mods;
 };
 
+struct type_entry
+{
+  struct type_entry *next;
+  uint32_t index;
+  uint32_t cv_hash;
+  uint8_t data[];
+};
+
+struct types
+{
+  htab_t hashmap;
+  uint32_t num_types;
+  struct type_entry *first;
+  struct type_entry *last;
+};
+
+static const uint32_t crc_table[] =
+{
+  0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
+  0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
+  0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
+  0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
+  0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
+  0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
+  0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
+  0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
+  0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
+  0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
+  0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
+  0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
+  0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
+  0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
+  0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
+  0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
+  0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
+  0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
+  0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
+  0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
+  0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
+  0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
+  0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
+  0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
+  0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
+  0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
+  0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
+  0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
+  0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
+  0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
+  0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
+  0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
+  0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
+  0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
+  0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
+  0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
+  0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
+  0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
+  0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
+  0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
+  0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
+  0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
+  0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
+};
+
 /* Add a new stream to the PDB archive, and return its BFD.  */
 static bfd *
 add_stream (bfd *pdb, const char *name, uint16_t *stream_num)
@@ -365,38 +428,142 @@ end:
   return ret;
 }
 
+/* Calculate the CRC32 used for type hashes.  */
+static uint32_t
+crc32 (const uint8_t *data, size_t len)
+{
+  uint32_t crc = 0;
+
+  while (len > 0)
+    {
+      crc = (crc >> 8) ^ crc_table[(crc & 0xff) ^ *data];
+
+      data++;
+      len--;
+    }
+
+  return crc;
+}
+
 /* Stream 2 is the type information (TPI) stream, and stream 4 is
    the ID information (IPI) stream.  They differ only in which records
    go in which stream. */
 static bool
-create_type_stream (bfd *pdb)
+populate_type_stream (bfd *pdb, bfd *stream, struct types *types)
 {
-  bfd *stream;
   struct pdb_tpi_stream_header h;
+  struct type_entry *e;
+  uint32_t len = 0, index_offset_len, off;
+  struct bfd *hash_stream = NULL;
+  uint16_t hash_stream_index;
 
-  stream = add_stream (pdb, NULL, NULL);
-  if (!stream)
+  static const uint32_t index_skip = 0x2000;
+
+  e = types->first;
+
+  index_offset_len = 0;
+
+  while (e)
+    {
+      uint32_t old_len = len;
+
+      len += sizeof (uint16_t) + bfd_getl16 (e->data);
+
+      if (old_len == 0 || old_len / index_skip != len / index_skip)
+	index_offset_len += sizeof (uint32_t) * 2;
+
+      e = e->next;
+    }
+
+  /* Each type stream also has a stream which holds the hash value for each
+     type, along with a skip list to speed up searching.  */
+
+  hash_stream = add_stream (pdb, "", &hash_stream_index);
+
+  if (!hash_stream)
     return false;
 
   bfd_putl32 (TPI_STREAM_VERSION_80, &h.version);
   bfd_putl32 (sizeof (h), &h.header_size);
   bfd_putl32 (TPI_FIRST_INDEX, &h.type_index_begin);
-  bfd_putl32 (TPI_FIRST_INDEX, &h.type_index_end);
-  bfd_putl32 (0, &h.type_record_bytes);
-  bfd_putl16 (0xffff, &h.hash_stream_index);
+  bfd_putl32 (TPI_FIRST_INDEX + types->num_types, &h.type_index_end);
+  bfd_putl32 (len, &h.type_record_bytes);
+  bfd_putl16 (hash_stream_index, &h.hash_stream_index);
   bfd_putl16 (0xffff, &h.hash_aux_stream_index);
-  bfd_putl32 (4, &h.hash_key_size);
-  bfd_putl32 (0x3ffff, &h.num_hash_buckets);
+  bfd_putl32 (sizeof (uint32_t), &h.hash_key_size);
+  bfd_putl32 (NUM_TPI_HASH_BUCKETS, &h.num_hash_buckets);
   bfd_putl32 (0, &h.hash_value_buffer_offset);
-  bfd_putl32 (0, &h.hash_value_buffer_length);
-  bfd_putl32 (0, &h.index_offset_buffer_offset);
-  bfd_putl32 (0, &h.index_offset_buffer_length);
-  bfd_putl32 (0, &h.hash_adj_buffer_offset);
+  bfd_putl32 (types->num_types * sizeof (uint32_t),
+	      &h.hash_value_buffer_length);
+  bfd_putl32 (types->num_types * sizeof (uint32_t),
+	      &h.index_offset_buffer_offset);
+  bfd_putl32 (index_offset_len, &h.index_offset_buffer_length);
+  bfd_putl32 ((types->num_types * sizeof (uint32_t)) + index_offset_len,
+	      &h.hash_adj_buffer_offset);
   bfd_putl32 (0, &h.hash_adj_buffer_length);
 
   if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h))
     return false;
 
+  /* Write the type definitions into the main stream, and the hashes
+     into the hash stream.  The hashes have already been calculated
+     in handle_type.  */
+
+  e = types->first;
+
+  while (e)
+    {
+      uint8_t buf[sizeof (uint32_t)];
+      uint16_t size;
+
+      size = bfd_getl16 (e->data);
+
+      if (bfd_bwrite (e->data, size + sizeof (uint16_t), stream)
+	  != size + sizeof (uint16_t))
+	return false;
+
+      bfd_putl32 (e->cv_hash % NUM_TPI_HASH_BUCKETS, buf);
+
+      if (bfd_bwrite (buf, sizeof (uint32_t), hash_stream)
+	  != sizeof (uint32_t))
+	return false;
+
+      e = e->next;
+    }
+
+  /* Write the index offsets, i.e. the skip list, into the hash stream.  We
+     copy MSVC here by writing a new entry for every 8192 bytes.  */
+
+  e = types->first;
+  off = 0;
+
+  while (e)
+    {
+      uint32_t old_off = off;
+      uint16_t size = bfd_getl16 (e->data);
+
+      off += size + sizeof (uint16_t);
+
+      if (old_off == 0 || old_off / index_skip != len / index_skip)
+	{
+	  uint8_t buf[sizeof (uint32_t)];
+
+	  bfd_putl32 (TPI_FIRST_INDEX + e->index, buf);
+
+	  if (bfd_bwrite (buf, sizeof (uint32_t), hash_stream)
+	      != sizeof (uint32_t))
+	    return false;
+
+	  bfd_putl32 (old_off, buf);
+
+	  if (bfd_bwrite (buf, sizeof (uint32_t), hash_stream)
+	      != sizeof (uint32_t))
+	    return false;
+	}
+
+      e = e->next;
+    }
+
   return true;
 }
 
@@ -866,6 +1033,1003 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
   return true;
 }
 
+/* Remap the type number stored in data from the per-module numbering to
+   that of the deduplicated output list.  */
+static bool remap_type (void *data, struct type_entry **map,
+			uint32_t type_num, uint32_t num_types)
+{
+  uint32_t type = bfd_getl32 (data);
+
+  /* Ignore builtin types (those with IDs below 0x1000).  */
+  if (type < TPI_FIRST_INDEX)
+    return true;
+
+  if (type >= TPI_FIRST_INDEX + type_num)
+    {
+      einfo (_("%P: CodeView type %v references other type %v not yet "
+	       "declared\n"), TPI_FIRST_INDEX + type_num, type);
+      return false;
+    }
+
+  if (type >= TPI_FIRST_INDEX + num_types)
+    {
+      einfo (_("%P: CodeView type %v references out of range type %v\n"),
+	     TPI_FIRST_INDEX + type_num, type);
+      return false;
+    }
+
+  type = TPI_FIRST_INDEX + map[type - TPI_FIRST_INDEX]->index;
+  bfd_putl32 (type, data);
+
+  return true;
+}
+
+/* Determines whether the name of a struct, class, or union counts as
+   "anonymous".  Non-anonymous types have a hash based on just the name,
+   rather than the whole structure.  */
+static bool
+is_name_anonymous (char *name, size_t len)
+{
+  static const char tag1[] = "<unnamed-tag>";
+  static const char tag2[] = "__unnamed";
+  static const char tag3[] = "::<unnamed-tag>";
+  static const char tag4[] = "::__unnamed";
+
+  if (len == sizeof (tag1) - 1 && !memcmp (name, tag1, sizeof (tag1) - 1))
+    return true;
+
+  if (len == sizeof (tag2) - 1 && !memcmp (name, tag2, sizeof (tag2) - 1))
+    return true;
+
+  if (len >= sizeof (tag3) - 1
+      && !memcmp (name + len - sizeof (tag3) + 1, tag3, sizeof (tag3) - 1))
+    return true;
+
+  if (len >= sizeof (tag4) - 1
+      && !memcmp (name + len - sizeof (tag4) + 1, tag4, sizeof (tag4) - 1))
+    return true;
+
+  return false;
+}
+
+/* Parse a type definition in the .debug$T section.  We remap the numbers
+   of any referenced types, and if the type is not a duplicate of one
+   already seen add it to types (for TPI types) or ids (for IPI types).  */
+static bool
+handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
+	     uint32_t num_types, struct types *types)
+{
+  uint16_t size, type;
+  void **slot;
+  hashval_t hash;
+  bool other_hash = false;
+  uint32_t cv_hash;
+
+  size = bfd_getl16 (data) + sizeof (uint16_t);
+  type = bfd_getl16 (data + sizeof (uint16_t));
+
+  switch (type)
+    {
+    case LF_MODIFIER:
+      {
+	struct lf_modifier *mod = (struct lf_modifier *) data;
+
+	if (size < offsetof (struct lf_modifier, modifier))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record "
+		     "LF_MODIFIER\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&mod->base_type, map, type_num, num_types))
+	  return false;
+
+	break;
+      }
+
+    case LF_POINTER:
+      {
+	struct lf_pointer *ptr = (struct lf_pointer *) data;
+
+	if (size < offsetof (struct lf_pointer, attributes))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_POINTER\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&ptr->base_type, map, type_num, num_types))
+	  return false;
+
+	break;
+      }
+
+    case LF_PROCEDURE:
+      {
+	struct lf_procedure *proc = (struct lf_procedure *) data;
+
+	if (size < sizeof (struct lf_procedure))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_PROCEDURE\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&proc->return_type, map, type_num, num_types))
+	  return false;
+
+	if (!remap_type (&proc->arglist, map, type_num, num_types))
+	  return false;
+
+	break;
+      }
+
+    case LF_MFUNCTION:
+      {
+	struct lf_mfunction *func = (struct lf_mfunction *) data;
+
+	if (size < sizeof (struct lf_procedure))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_MFUNCTION\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&func->return_type, map, type_num, num_types))
+	  return false;
+
+	if (!remap_type (&func->containing_class_type, map, type_num,
+			 num_types))
+	  return false;
+
+	if (!remap_type (&func->this_type, map, type_num, num_types))
+	  return false;
+
+	if (!remap_type (&func->arglist, map, type_num, num_types))
+	  return false;
+
+	break;
+      }
+
+    case LF_ARGLIST:
+      {
+	uint32_t num_entries;
+	struct lf_arglist *al = (struct lf_arglist *) data;
+
+	if (size < offsetof (struct lf_arglist, args))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_ARGLIST\n"));
+	    return false;
+	  }
+
+	num_entries = bfd_getl32 (&al->num_entries);
+
+	if (size < offsetof (struct lf_arglist, args)
+		   + (num_entries * sizeof (uint32_t)))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_ARGLIST\n"));
+	    return false;
+	  }
+
+	for (uint32_t i = 0; i < num_entries; i++)
+	  {
+	    if (!remap_type (&al->args[i], map, type_num, num_types))
+	      return false;
+	  }
+
+	break;
+      }
+
+    case LF_FIELDLIST:
+      {
+	uint16_t left = size - sizeof (uint16_t) - sizeof (uint16_t);
+	uint8_t *ptr = data + sizeof (uint16_t) + sizeof (uint16_t);
+
+	while (left > 0)
+	  {
+	    uint16_t subtype;
+
+	    if (left < sizeof (uint16_t))
+	      {
+		einfo (_("%P: warning: truncated CodeView type record"
+			 " LF_FIELDLIST\n"));
+		return false;
+	      }
+
+	    subtype = bfd_getl16 (ptr);
+
+	    switch (subtype)
+	      {
+	      case LF_MEMBER:
+		{
+		  struct lf_member *mem = (struct lf_member *) ptr;
+		  size_t name_len, subtype_len;
+
+		  if (left < offsetof (struct lf_member, name))
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_MEMBER\n"));
+		      return false;
+		    }
+
+		  if (!remap_type (&mem->type, map, type_num, num_types))
+		    return false;
+
+		  name_len =
+		    strnlen (mem->name,
+			     left - offsetof (struct lf_member, name));
+
+		  if (name_len == left - offsetof (struct lf_member, name))
+		    {
+		      einfo (_("%P: warning: name for LF_MEMBER has no"
+			       " terminating zero\n"));
+		      return false;
+		    }
+
+		  name_len++;
+
+		  subtype_len = offsetof (struct lf_member, name) + name_len;
+
+		  if (subtype_len % 4 != 0)
+		    subtype_len += 4 - (subtype_len % 4);
+
+		  if (left < subtype_len)
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_FIELDLIST\n"));
+		      return false;
+		    }
+
+		  ptr += subtype_len;
+		  left -= subtype_len;
+
+		  break;
+		}
+
+	      case LF_ENUMERATE:
+		{
+		  struct lf_enumerate *en = (struct lf_enumerate *) ptr;
+		  size_t name_len, subtype_len;
+		  uint16_t val;
+
+		  if (left < offsetof (struct lf_enumerate, name))
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_ENUMERATE\n"));
+		      return false;
+		    }
+
+		  subtype_len = offsetof (struct lf_enumerate, name);
+
+		  val = bfd_getl16 (&en->value);
+
+		  /* If val >= 0x8000, the actual value immediate follows.  */
+		  if (val >= 0x8000)
+		    {
+		      size_t param_len;
+
+		      switch (val) {
+			case LF_CHAR:
+			  param_len = 1;
+			  break;
+
+			case LF_SHORT:
+			case LF_USHORT:
+			  param_len = 2;
+			  break;
+
+			case LF_LONG:
+			case LF_ULONG:
+			  param_len = 4;
+			  break;
+
+			case LF_QUADWORD:
+			case LF_UQUADWORD:
+			  param_len = 8;
+			  break;
+
+			default:
+			  einfo (_("%P: warning: unhandled type %v within"
+				   " LF_ENUMERATE\n"), val);
+			  return false;
+		      }
+
+		      if (left < subtype_len + param_len)
+			{
+			  einfo (_("%P: warning: truncated CodeView type"
+				  " record LF_ENUMERATE\n"));
+			  return false;
+			}
+
+		      subtype_len += param_len;
+		    }
+
+		  name_len = strnlen ((char *) ptr + subtype_len,
+				      left - subtype_len);
+
+		  if (name_len == left - offsetof (struct lf_enumerate, name))
+		    {
+		      einfo (_("%P: warning: name for LF_ENUMERATE has no"
+			       " terminating zero\n"));
+		      return false;
+		    }
+
+		  name_len++;
+
+		  subtype_len += name_len;
+
+		  if (subtype_len % 4 != 0)
+		    subtype_len += 4 - (subtype_len % 4);
+
+		  if (left < subtype_len)
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_ENUMERATE\n"));
+		      return false;
+		    }
+
+		  ptr += subtype_len;
+		  left -= subtype_len;
+
+		  break;
+		}
+
+	      case LF_INDEX:
+		{
+		  struct lf_index *ind = (struct lf_index *) ptr;
+
+		  if (left < sizeof (struct lf_index))
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_INDEX\n"));
+		      return false;
+		    }
+
+		  if (!remap_type (&ind->index, map, type_num, num_types))
+		    return false;
+
+		  ptr += sizeof (struct lf_index);
+		  left -= sizeof (struct lf_index);
+
+		  break;
+		}
+
+	      case LF_ONEMETHOD:
+		{
+		  struct lf_onemethod *meth = (struct lf_onemethod *) ptr;
+		  size_t name_len, subtype_len;
+
+		  if (left < offsetof (struct lf_onemethod, name))
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_ONEMETHOD\n"));
+		      return false;
+		    }
+
+		  if (!remap_type (&meth->method_type, map, type_num,
+				   num_types))
+		    return false;
+
+		  name_len =
+		    strnlen (meth->name,
+			     left - offsetof (struct lf_onemethod, name));
+
+		  if (name_len == left - offsetof (struct lf_onemethod, name))
+		    {
+		      einfo (_("%P: warning: name for LF_ONEMETHOD has no"
+			       " terminating zero\n"));
+		      return false;
+		    }
+
+		  name_len++;
+
+		  subtype_len = offsetof (struct lf_onemethod, name)
+				+ name_len;
+
+		  if (subtype_len % 4 != 0)
+		    subtype_len += 4 - (subtype_len % 4);
+
+		  if (left < subtype_len)
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_FIELDLIST\n"));
+		      return false;
+		    }
+
+		  ptr += subtype_len;
+		  left -= subtype_len;
+
+		  break;
+		}
+
+	      case LF_METHOD:
+		{
+		  struct lf_method *meth = (struct lf_method *) ptr;
+		  size_t name_len, subtype_len;
+
+		  if (left < offsetof (struct lf_method, name))
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_METHOD\n"));
+		      return false;
+		    }
+
+		  if (!remap_type (&meth->method_list, map, type_num,
+				   num_types))
+		    return false;
+
+		  name_len =
+		    strnlen (meth->name,
+			     left - offsetof (struct lf_method, name));
+
+		  if (name_len == left - offsetof (struct lf_method, name))
+		    {
+		      einfo (_("%P: warning: name for LF_METHOD has no"
+			       " terminating zero\n"));
+		      return false;
+		    }
+
+		  name_len++;
+
+		  subtype_len = offsetof (struct lf_method, name) + name_len;
+
+		  if (subtype_len % 4 != 0)
+		    subtype_len += 4 - (subtype_len % 4);
+
+		  if (left < subtype_len)
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_FIELDLIST\n"));
+		      return false;
+		    }
+
+		  ptr += subtype_len;
+		  left -= subtype_len;
+
+		  break;
+		}
+
+	      case LF_BCLASS:
+		{
+		  struct lf_bclass *bc = (struct lf_bclass *) ptr;
+
+		  if (left < sizeof (struct lf_bclass))
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_BCLASS\n"));
+		      return false;
+		    }
+
+		  if (!remap_type (&bc->base_class_type, map, type_num,
+				   num_types))
+		    return false;
+
+		  ptr += sizeof (struct lf_bclass);
+		  left -= sizeof (struct lf_bclass);
+
+		  break;
+		}
+
+	      case LF_VFUNCTAB:
+		{
+		  struct lf_vfunctab *vft = (struct lf_vfunctab *) ptr;
+
+		  if (left < sizeof (struct lf_vfunctab))
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_VFUNCTAB\n"));
+		      return false;
+		    }
+
+		  if (!remap_type (&vft->type, map, type_num, num_types))
+		    return false;
+
+		  ptr += sizeof (struct lf_vfunctab);
+		  left -= sizeof (struct lf_vfunctab);
+
+		  break;
+		}
+
+	      case LF_VBCLASS:
+	      case LF_IVBCLASS:
+		{
+		  struct lf_vbclass *vbc = (struct lf_vbclass *) ptr;
+
+		  if (left < sizeof (struct lf_vbclass))
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_VBCLASS/LF_IVBCLASS\n"));
+		      return false;
+		    }
+
+		  if (!remap_type (&vbc->base_class_type, map, type_num,
+				   num_types))
+		    return false;
+
+		  if (!remap_type (&vbc->virtual_base_pointer_type, map,
+				   type_num, num_types))
+		    return false;
+
+		  ptr += sizeof (struct lf_vbclass);
+		  left -= sizeof (struct lf_vbclass);
+
+		  break;
+		}
+
+	      case LF_STMEMBER:
+		{
+		  struct lf_static_member *st =
+		    (struct lf_static_member *) ptr;
+		  size_t name_len, subtype_len;
+
+		  if (left < offsetof (struct lf_static_member, name))
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_STMEMBER\n"));
+		      return false;
+		    }
+
+		  if (!remap_type (&st->type, map, type_num, num_types))
+		    return false;
+
+		  name_len =
+		    strnlen (st->name,
+			     left - offsetof (struct lf_static_member, name));
+
+		  if (name_len == left
+				    - offsetof (struct lf_static_member, name))
+		    {
+		      einfo (_("%P: warning: name for LF_STMEMBER has no"
+			       " terminating zero\n"));
+		      return false;
+		    }
+
+		  name_len++;
+
+		  subtype_len = offsetof (struct lf_static_member, name)
+				+ name_len;
+
+		  if (subtype_len % 4 != 0)
+		    subtype_len += 4 - (subtype_len % 4);
+
+		  if (left < subtype_len)
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_FIELDLIST\n"));
+		      return false;
+		    }
+
+		  ptr += subtype_len;
+		  left -= subtype_len;
+
+		  break;
+		}
+
+	      case LF_NESTTYPE:
+		{
+		  struct lf_nest_type *nest = (struct lf_nest_type *) ptr;
+		  size_t name_len, subtype_len;
+
+		  if (left < offsetof (struct lf_nest_type, name))
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_NESTTYPE\n"));
+		      return false;
+		    }
+
+		  if (!remap_type (&nest->type, map, type_num, num_types))
+		    return false;
+
+		  name_len =
+		    strnlen (nest->name,
+			     left - offsetof (struct lf_nest_type, name));
+
+		  if (name_len == left
+				    - offsetof (struct lf_nest_type, name))
+		    {
+		      einfo (_("%P: warning: name for LF_NESTTYPE has no"
+			       " terminating zero\n"));
+		      return false;
+		    }
+
+		  name_len++;
+
+		  subtype_len = offsetof (struct lf_nest_type, name)
+				+ name_len;
+
+		  if (subtype_len % 4 != 0)
+		    subtype_len += 4 - (subtype_len % 4);
+
+		  if (left < subtype_len)
+		    {
+		      einfo (_("%P: warning: truncated CodeView type record"
+			       " LF_FIELDLIST\n"));
+		      return false;
+		    }
+
+		  ptr += subtype_len;
+		  left -= subtype_len;
+
+		  break;
+		}
+
+	      default:
+		einfo (_("%P: warning: unrecognized CodeView subtype %v\n"),
+		       subtype);
+		return false;
+	      }
+	  }
+
+	break;
+      }
+
+    case LF_BITFIELD:
+      {
+	struct lf_bitfield *bf = (struct lf_bitfield *) data;
+
+	if (size < offsetof (struct lf_bitfield, length))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_BITFIELD\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&bf->base_type, map, type_num, num_types))
+	  return false;
+
+	break;
+      }
+
+    case LF_METHODLIST:
+      {
+	struct lf_methodlist *ml = (struct lf_methodlist *) data;
+	unsigned int num_entries;
+
+	if (size < offsetof (struct lf_methodlist, entries))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_METHODLIST\n"));
+	    return false;
+	  }
+
+	if ((size - offsetof (struct lf_methodlist, entries))
+	    % sizeof (struct lf_methodlist_entry))
+	  {
+	    einfo (_("%P: warning: malformed CodeView type record"
+		     " LF_METHODLIST\n"));
+	    return false;
+	  }
+
+	num_entries = (size - offsetof (struct lf_methodlist, entries))
+		      / sizeof (struct lf_methodlist_entry);
+
+	for (unsigned int i = 0; i < num_entries; i++)
+	  {
+	    if (!remap_type (&ml->entries[i].method_type, map,
+			     type_num, num_types))
+	      return false;
+	  }
+
+	break;
+      }
+
+    case LF_ARRAY:
+      {
+	struct lf_array *arr = (struct lf_array *) data;
+
+	if (size < offsetof (struct lf_array, length_in_bytes))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_ARRAY\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&arr->element_type, map, type_num, num_types))
+	  return false;
+
+	if (!remap_type (&arr->index_type, map, type_num, num_types))
+	  return false;
+
+	break;
+      }
+
+    case LF_CLASS:
+    case LF_STRUCTURE:
+      {
+	struct lf_class *cl = (struct lf_class *) data;
+	uint16_t prop;
+	size_t name_len;
+
+	if (size < offsetof (struct lf_class, name))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_CLASS/LF_STRUCTURE\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&cl->field_list, map, type_num, num_types))
+	  return false;
+
+	if (!remap_type (&cl->derived_from, map, type_num, num_types))
+	  return false;
+
+	if (!remap_type (&cl->vshape, map, type_num, num_types))
+	  return false;
+
+	name_len = strnlen (cl->name, size - offsetof (struct lf_class, name));
+
+	if (name_len == size - offsetof (struct lf_class, name))
+	  {
+	    einfo (_("%P: warning: name for LF_CLASS/LF_STRUCTURE has no"
+		     " terminating zero\n"));
+	    return false;
+	  }
+
+	prop = bfd_getl16 (&cl->properties);
+
+	if (prop & CV_PROP_HAS_UNIQUE_NAME)
+	  {
+	    /* Structure has another name following first one.  */
+
+	    size_t len = offsetof (struct lf_class, name) + name_len + 1;
+	    size_t unique_name_len;
+
+	    unique_name_len = strnlen (cl->name + name_len + 1, size - len);
+
+	    if (unique_name_len == size - len)
+	      {
+		einfo (_("%P: warning: unique name for LF_CLASS/LF_STRUCTURE"
+			 " has no terminating zero\n"));
+		return false;
+	      }
+	  }
+
+	if (!(prop & (CV_PROP_FORWARD_REF | CV_PROP_SCOPED))
+	    && !is_name_anonymous (cl->name, name_len))
+	  {
+	    other_hash = true;
+	    cv_hash = crc32 ((uint8_t *) cl->name, name_len);
+	  }
+
+	break;
+      }
+
+    case LF_UNION:
+      {
+	struct lf_union *un = (struct lf_union *) data;
+	uint16_t prop;
+	size_t name_len;
+
+	if (size < offsetof (struct lf_union, name))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_UNION\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&un->field_list, map, type_num, num_types))
+	  return false;
+
+	name_len = strnlen (un->name, size - offsetof (struct lf_union, name));
+
+	if (name_len == size - offsetof (struct lf_union, name))
+	  {
+	    einfo (_("%P: warning: name for LF_UNION has no"
+		     " terminating zero\n"));
+	    return false;
+	  }
+
+	prop = bfd_getl16 (&un->properties);
+
+	if (prop & CV_PROP_HAS_UNIQUE_NAME)
+	  {
+	    /* Structure has another name following first one.  */
+
+	    size_t len = offsetof (struct lf_union, name) + name_len + 1;
+	    size_t unique_name_len;
+
+	    unique_name_len = strnlen (un->name + name_len + 1, size - len);
+
+	    if (unique_name_len == size - len)
+	      {
+		einfo (_("%P: warning: unique name for LF_UNION has"
+			 " no terminating zero\n"));
+		return false;
+	      }
+	  }
+
+	if (!(prop & (CV_PROP_FORWARD_REF | CV_PROP_SCOPED))
+	    && !is_name_anonymous (un->name, name_len))
+	  {
+	    other_hash = true;
+	    cv_hash = crc32 ((uint8_t *) un->name, name_len);
+	  }
+
+	break;
+      }
+
+    case LF_ENUM:
+      {
+	struct lf_enum *en = (struct lf_enum *) data;
+	uint16_t prop;
+	size_t name_len;
+
+	if (size < offsetof (struct lf_enum, name))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_ENUM\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&en->underlying_type, map, type_num, num_types))
+	  return false;
+
+	if (!remap_type (&en->field_list, map, type_num, num_types))
+	  return false;
+
+	name_len = strnlen (en->name, size - offsetof (struct lf_enum, name));
+
+	if (name_len == size - offsetof (struct lf_enum, name))
+	  {
+	    einfo (_("%P: warning: name for LF_ENUM has no"
+		     " terminating zero\n"));
+	    return false;
+	  }
+
+	prop = bfd_getl16 (&en->properties);
+
+	if (prop & CV_PROP_HAS_UNIQUE_NAME)
+	  {
+	    /* Structure has another name following first one.  */
+
+	    size_t len = offsetof (struct lf_enum, name) + name_len + 1;
+	    size_t unique_name_len;
+
+	    unique_name_len = strnlen (en->name + name_len + 1, size - len);
+
+	    if (unique_name_len == size - len)
+	      {
+		einfo (_("%P: warning: unique name for LF_ENUM has"
+			 " no terminating zero\n"));
+		return false;
+	      }
+	  }
+
+	break;
+      }
+
+    case LF_VTSHAPE:
+      /* Does not reference any types, nothing to be done.  */
+      break;
+
+    default:
+      einfo (_("%P: warning: unrecognized CodeView type %v\n"), type);
+      return false;
+    }
+
+  hash = iterative_hash (data, size, 0);
+
+  slot = htab_find_slot_with_hash (types->hashmap, data, hash, INSERT);
+  if (!slot)
+    return false;
+
+  if (!*slot) /* new entry */
+    {
+      struct type_entry *e;
+
+      *slot = xmalloc (offsetof (struct type_entry, data) + size);
+
+      e = (struct type_entry *) *slot;
+
+      e->next = NULL;
+      e->index = types->num_types;
+
+      if (other_hash)
+	e->cv_hash = cv_hash;
+      else
+	e->cv_hash = crc32 (data, size);
+
+      memcpy (e->data, data, size);
+
+      if (types->last)
+	types->last->next = e;
+      else
+	types->first = e;
+
+      types->last = e;
+
+      map[type_num] = e;
+
+      types->num_types++;
+    }
+  else /* duplicate */
+    {
+      map[type_num] = (struct type_entry *) *slot;
+    }
+
+  return true;
+}
+
+/* Parse the .debug$T section of a module, and pass any type definitions
+   found to handle_type.  */
+static bool
+handle_debugt_section (asection *s, bfd *mod, struct types *types)
+{
+  bfd_byte *data = NULL;
+  size_t off;
+  unsigned int num_types = 0;
+  struct type_entry **map;
+  uint32_t type_num;
+
+  if (!bfd_get_full_section_contents (mod, s, &data))
+    return false;
+
+  if (!data)
+    return false;
+
+  if (bfd_getl32 (data) != CV_SIGNATURE_C13)
+    {
+      free (data);
+      return true;
+    }
+
+  off = sizeof (uint32_t);
+
+  while (off + sizeof (uint16_t) <= s->size)
+    {
+      uint16_t size;
+
+      size = bfd_getl16 (data + off);
+      off += sizeof (uint16_t);
+
+      if (size + off > s->size || size <= sizeof (uint16_t))
+	{
+	  free (data);
+	  bfd_set_error (bfd_error_bad_value);
+	  return false;
+	}
+
+      num_types++;
+      off += size;
+    }
+
+  if (num_types == 0)
+    {
+      free (data);
+      return true;
+    }
+
+  map = xcalloc (num_types, sizeof (struct type_entry *));
+
+  off = sizeof (uint32_t);
+  type_num = 0;
+
+  while (off + sizeof (uint16_t) <= s->size)
+    {
+      uint16_t size;
+
+      size = bfd_getl16 (data + off);
+
+      if (!handle_type (data + off, map, type_num, num_types, types))
+	{
+	  free (data);
+	  free (map);
+	  bfd_set_error (bfd_error_bad_value);
+	  return false;
+	}
+
+      off += sizeof (uint16_t) + size;
+      type_num++;
+    }
+
+  free (data);
+  free (map);
+
+  return true;
+}
+
 /* Populate the module stream, which consists of the transformed .debug$S
    data for each object file.  */
 static bool
@@ -873,7 +2037,7 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 			struct string_table *strings,
 			uint32_t *c13_info_size,
 			struct mod_source_files *mod_source,
-			bfd *abfd)
+			bfd *abfd, struct types *types)
 {
   uint8_t int_buf[sizeof (uint32_t)];
   uint8_t *c13_info = NULL;
@@ -895,6 +2059,15 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 	      return false;
 	    }
 	}
+      else if (!strcmp (s->name, ".debug$T") && s->size >= sizeof (uint32_t))
+	{
+	  if (!handle_debugt_section (s, mod, types))
+	    {
+	      free (c13_info);
+	      free (mod_source->files);
+	      return false;
+	    }
+	}
     }
 
   /* Write the signature.  */
@@ -933,7 +2106,8 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 static bool
 create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 			      uint32_t *size, struct string_table *strings,
-			      struct source_files_info *source)
+			      struct source_files_info *source,
+			      struct types *types)
 {
   uint8_t *ptr;
   unsigned int mod_num;
@@ -1021,7 +2195,8 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 
       if (!populate_module_stream (stream, in, &sym_byte_size,
 				   strings, &c13_info_size,
-				   &source->mods[mod_num], abfd))
+				   &source->mods[mod_num], abfd,
+				   types))
 	{
 	  for (unsigned int i = 0; i < source->mod_count; i++)
 	    {
@@ -1343,7 +2518,8 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
 		     uint16_t section_header_stream_num,
 		     uint16_t sym_rec_stream_num,
 		     uint16_t publics_stream_num,
-		     struct string_table *strings)
+		     struct string_table *strings,
+		     struct types *types)
 {
   struct pdb_dbi_stream_header h;
   struct optional_dbg_header opt;
@@ -1355,7 +2531,7 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
   source.mods = NULL;
 
   if (!create_module_info_substream (abfd, pdb, &mod_info, &mod_info_size,
-				     strings, &source))
+				     strings, &source, types))
     return false;
 
   if (!create_section_contrib_substream (abfd, &sc, &sc_size))
@@ -1886,6 +3062,32 @@ populate_names_stream (bfd *stream, struct string_table *strings)
   return true;
 }
 
+/* Calculate the hash of a type_entry.  */
+static hashval_t
+hash_type_entry (const void *p)
+{
+  const struct type_entry *e = (const struct type_entry *)p;
+  uint16_t size = bfd_getl16 (e->data) + sizeof (uint16_t);
+
+  return iterative_hash (e->data, size, 0);
+}
+
+/* Compare a type_entry with a type.  */
+static int
+eq_type_entry (const void *a, const void *b)
+{
+  const struct type_entry *e = (const struct type_entry *)a;
+  uint16_t size_a = bfd_getl16 (e->data);
+  uint16_t size_b = bfd_getl16 (b);
+
+  if (size_a != size_b)
+    return 0;
+
+  return memcmp (e->data + sizeof (uint16_t),
+		 (const uint8_t *)b + sizeof (uint16_t),
+		 size_a) == 0;
+}
+
 /* Create a PDB debugging file for the PE image file abfd with the build ID
    guid, stored at pdb_name.  */
 bool
@@ -1894,9 +3096,10 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
   bfd *pdb;
   bool ret = false;
   bfd *info_stream, *dbi_stream, *names_stream, *sym_rec_stream,
-    *publics_stream;
+    *publics_stream, *tpi_stream, *ipi_stream;
   uint16_t section_header_stream_num, sym_rec_stream_num, publics_stream_num;
   struct string_table strings;
+  struct types types, ids;
 
   pdb = bfd_openw (pdb_name, "pdb");
   if (!pdb)
@@ -1930,7 +3133,9 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
       goto end;
     }
 
-  if (!create_type_stream (pdb))
+  tpi_stream = add_stream (pdb, NULL, NULL);
+
+  if (!tpi_stream)
     {
       einfo (_("%P: warning: cannot create TPI stream "
 	       "in PDB file: %E\n"));
@@ -1946,7 +3151,9 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
       goto end;
     }
 
-  if (!create_type_stream (pdb))
+  ipi_stream = add_stream (pdb, NULL, NULL);
+
+  if (!ipi_stream)
     {
       einfo (_("%P: warning: cannot create IPI stream "
 	       "in PDB file: %E\n"));
@@ -1987,15 +3194,48 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
       goto end;
     }
 
+  types.num_types = 0;
+  types.hashmap = htab_create_alloc (0, hash_type_entry, eq_type_entry,
+				     free, xcalloc, free);
+  types.first = types.last = NULL;
+
+  ids.num_types = 0;
+  ids.hashmap = htab_create_alloc (0, hash_type_entry, eq_type_entry,
+				   free, xcalloc, free);
+  ids.first = ids.last = NULL;
+
   if (!populate_dbi_stream (dbi_stream, abfd, pdb, section_header_stream_num,
 			    sym_rec_stream_num, publics_stream_num,
-			    &strings))
+			    &strings, &types))
     {
       einfo (_("%P: warning: cannot populate DBI stream "
 	       "in PDB file: %E\n"));
+      htab_delete (types.hashmap);
+      htab_delete (ids.hashmap);
+      goto end;
+    }
+
+  if (!populate_type_stream (pdb, tpi_stream, &types))
+    {
+      einfo (_("%P: warning: cannot populate TPI stream "
+	       "in PDB file: %E\n"));
+      htab_delete (types.hashmap);
+      htab_delete (ids.hashmap);
       goto end;
     }
 
+  htab_delete (types.hashmap);
+
+  if (!populate_type_stream (pdb, ipi_stream, &ids))
+    {
+      einfo (_("%P: warning: cannot populate IPI stream "
+	       "in PDB file: %E\n"));
+      htab_delete (ids.hashmap);
+      goto end;
+    }
+
+  htab_delete (ids.hashmap);
+
   add_string ("", 0, &strings);
 
   if (!populate_names_stream (names_stream, &strings))
diff --git a/ld/pdb.h b/ld/pdb.h
index bbb106043c4..ecc26c1c87a 100644
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -27,6 +27,41 @@
 #include "sysdep.h"
 #include "bfd.h"
 #include <stdbool.h>
+#include <stddef.h>
+
+#define LF_VTSHAPE			0x000a
+#define LF_MODIFIER			0x1001
+#define LF_POINTER			0x1002
+#define LF_PROCEDURE			0x1008
+#define LF_MFUNCTION			0x1009
+#define LF_ARGLIST			0x1201
+#define LF_FIELDLIST			0x1203
+#define LF_BITFIELD			0x1205
+#define LF_METHODLIST			0x1206
+#define LF_BCLASS			0x1400
+#define LF_VBCLASS			0x1401
+#define LF_IVBCLASS			0x1402
+#define LF_INDEX			0x1404
+#define LF_VFUNCTAB			0x1409
+#define LF_ENUMERATE			0x1502
+#define LF_ARRAY			0x1503
+#define LF_CLASS			0x1504
+#define LF_STRUCTURE			0x1505
+#define LF_UNION			0x1506
+#define LF_ENUM				0x1507
+#define LF_MEMBER			0x150d
+#define LF_STMEMBER			0x150e
+#define LF_METHOD			0x150f
+#define LF_NESTTYPE			0x1510
+#define LF_ONEMETHOD			0x1511
+
+#define LF_CHAR				0x8000
+#define LF_SHORT			0x8001
+#define LF_USHORT			0x8002
+#define LF_LONG				0x8003
+#define LF_ULONG			0x8004
+#define LF_QUADWORD			0x8009
+#define LF_UQUADWORD			0x800a
 
 #define S_PUB32				0x110e
 
@@ -65,6 +100,7 @@ struct pdb_tpi_stream_header
 #define TPI_STREAM_VERSION_80		20040203
 
 #define TPI_FIRST_INDEX			0x1000
+#define NUM_TPI_HASH_BUCKETS		0x3ffff
 
 /* NewDBIHdr in dbi.h */
 struct pdb_dbi_stream_header
@@ -210,6 +246,234 @@ struct file_checksum
   uint8_t checksum_type;
 } ATTRIBUTE_PACKED;
 
+/* lfModifier in cvinfo.h */
+struct lf_modifier
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t base_type;
+  uint16_t modifier;
+  uint16_t padding;
+} ATTRIBUTE_PACKED;
+
+/* lfPointer in cvinfo.h */
+struct lf_pointer
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t base_type;
+  uint32_t attributes;
+} ATTRIBUTE_PACKED;
+
+/* lfArgList in cvinfo.h */
+struct lf_arglist
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t num_entries;
+  uint32_t args[];
+} ATTRIBUTE_PACKED;
+
+/* lfProc in cvinfo.h */
+struct lf_procedure
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t return_type;
+  uint8_t calling_convention;
+  uint8_t attributes;
+  uint16_t num_parameters;
+  uint32_t arglist;
+} ATTRIBUTE_PACKED;
+
+/* lfMFunc in cvinfo.h */
+struct lf_mfunction
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t return_type;
+  uint32_t containing_class_type;
+  uint32_t this_type;
+  uint8_t calling_convention;
+  uint8_t attributes;
+  uint16_t num_parameters;
+  uint32_t arglist;
+  int32_t this_adjustment;
+} ATTRIBUTE_PACKED;
+
+/* lfArray in cvinfo.h */
+struct lf_array
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t element_type;
+  uint32_t index_type;
+  uint16_t length_in_bytes;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* lfBitfield in cvinfo.h */
+struct lf_bitfield
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t base_type;
+  uint8_t length;
+  uint8_t position;
+} ATTRIBUTE_PACKED;
+
+/* lfMember in cvinfo.h */
+struct lf_member
+{
+  uint16_t kind;
+  uint16_t attributes;
+  uint32_t type;
+  uint16_t offset;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* from bitfield structure CV_prop_t in cvinfo.h */
+#define CV_PROP_FORWARD_REF	0x80
+#define CV_PROP_SCOPED		0x100
+#define CV_PROP_HAS_UNIQUE_NAME	0x200
+
+/* lfClass in cvinfo.h */
+struct lf_class
+{
+  uint16_t size;
+  uint16_t kind;
+  uint16_t num_members;
+  uint16_t properties;
+  uint32_t field_list;
+  uint32_t derived_from;
+  uint32_t vshape;
+  uint16_t length;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* lfUnion in cvinfo.h */
+struct lf_union
+{
+  uint16_t size;
+  uint16_t kind;
+  uint16_t num_members;
+  uint16_t properties;
+  uint32_t field_list;
+  uint16_t length;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* lfEnumerate in cvinfo.h */
+struct lf_enumerate
+{
+  uint16_t kind;
+  uint16_t attributes;
+  uint16_t value;
+  /* then actual value if value >= 0x8000 */
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* lfEnum in cvinfo.h */
+struct lf_enum
+{
+  uint16_t size;
+  uint16_t kind;
+  uint16_t num_elements;
+  uint16_t properties;
+  uint32_t underlying_type;
+  uint32_t field_list;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* lfIndex in cvinfo.h */
+struct lf_index
+{
+  uint16_t kind;
+  uint16_t padding;
+  uint32_t index;
+} ATTRIBUTE_PACKED;
+
+/* lfOneMethod in cvinfo.h */
+struct lf_onemethod
+{
+  uint16_t kind;
+  uint16_t method_attribute;
+  uint32_t method_type;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* mlMethod in cvinfo.h */
+struct lf_methodlist_entry
+{
+  uint16_t method_attribute;
+  uint16_t padding;
+  uint32_t method_type;
+} ATTRIBUTE_PACKED;
+
+/* lfMethodList in cvinfo.h */
+struct lf_methodlist
+{
+  uint16_t size;
+  uint16_t kind;
+  struct lf_methodlist_entry entries[];
+} ATTRIBUTE_PACKED;
+
+/* lfMethod in cvinfo.h */
+struct lf_method
+{
+  uint16_t kind;
+  uint16_t count;
+  uint32_t method_list;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* lfBClass in cvinfo.h */
+struct lf_bclass
+{
+  uint16_t kind;
+  uint16_t attributes;
+  uint32_t base_class_type;
+  uint16_t offset;
+  uint16_t padding;
+} ATTRIBUTE_PACKED;
+
+/* lfVFuncTab in cvinfo.h */
+struct lf_vfunctab
+{
+  uint16_t kind;
+  uint16_t padding;
+  uint32_t type;
+} ATTRIBUTE_PACKED;
+
+/* lfVBClass in cvinfo.h */
+struct lf_vbclass
+{
+  uint16_t kind;
+  uint16_t attributes;
+  uint32_t base_class_type;
+  uint32_t virtual_base_pointer_type;
+  uint16_t virtual_base_pointer_offset;
+  uint16_t virtual_base_vbtable_offset;
+} ATTRIBUTE_PACKED;
+
+/* lfSTMember in cvinfo.h */
+struct lf_static_member
+{
+  uint16_t kind;
+  uint16_t attributes;
+  uint32_t type;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* lfNestType in cvinfo.h */
+struct lf_nest_type
+{
+  uint16_t kind;
+  uint16_t padding;
+  uint32_t type;
+  char name[];
+} ATTRIBUTE_PACKED;
+
 extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
 
 #endif
diff --git a/ld/testsuite/ld-pe/pdb-types1-hashlist.d b/ld/testsuite/ld-pe/pdb-types1-hashlist.d
new file mode 100644
index 00000000000..b75f08c1de7
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types1-hashlist.d
@@ -0,0 +1,13 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 5b4f0200 e29b0300 a99a0300 90160300  *
+ 0010 32970300 d6540100 fad50000 c3b00000  *
+ 0020 6a270200 7b000100 31de0000 2bf50200  *
+ 0030 b59b0300 73cf0300 10b90000 84240300  *
+ 0040 64150100 2e8a0000 88fe0000 c0660000  *
+ 0050 ffd80200 b0260100 7c060200 e3240200  *
+ 0060 63ff0100 fb6b0300 0ad90100 523c0200  *
+ 0070 4d5e0200 8a940200 4b710300 6aa90300  *
+ 0080 0a2c0300 67e10300 4a3d0300           *
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types1-skiplist.d b/ld/testsuite/ld-pe/pdb-types1-skiplist.d
new file mode 100644
index 00000000000..52c10fa501d
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types1-skiplist.d
@@ -0,0 +1,5 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 00100000 00000000                    *
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types1-typelist.d b/ld/testsuite/ld-pe/pdb-types1-typelist.d
new file mode 100644
index 00000000000..ff2d91c311e
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types1-typelist.d
@@ -0,0 +1,60 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 0a000110 12000000 02000000 0a000110  ................
+ 0010 12000000 01000000 0a000110 22000000  ............"...
+ 0020 02000000 0a000110 74000000 03000000  ........t.......
+ 0030 0a000210 01100000 0c000100 0a000210  ................
+ 0040 02100000 0a800000 12000112 03000000  ................
+ 0050 01100000 02100000 03100000 0e000810  ................
+ 0060 02100000 00000300 06100000 0e000315  ................
+ 0070 04100000 74000000 180000f1 0a000512  ....t...........
+ 0080 75000000 0100f2f1 0a000512 75000000  u...........u...
+ 0090 1f01f2f1 22000312 0d150300 09100000  ...."...........
+ 00a0 00006e75 6d3100f1 0d150300 0a100000  ..num1..........
+ 00b0 00006e75 6d3200f1 22000515 02000000  ..num2..".......
+ 00c0 0b100000 00000000 00000000 04003c75  ..............<u
+ 00d0 6e6e616d 65642d74 61673e00 1e000515  nnamed-tag>.....
+ 00e0 00008002 00000000 00000000 00000000  ................
+ 00f0 0000666f 6f006261 7200f2f1 0a000210  ..foo.bar.......
+ 0100 0d100000 0c000100 06000112 00000000  ................
+ 0110 1a000910 02100000 0d100000 0e100000  ................
+ 0120 00000000 0f100000 00000000 1a000910  ................
+ 0130 02100000 0d100000 0e100000 00000300  ................
+ 0140 06100000 00000000 12000612 00000000  ................
+ 0150 10100000 00000000 11100000 32000312  ............2...
+ 0160 0d150300 75000000 00006e75 6d00f2f1  ....u.....num...
+ 0170 11150000 10100000 6d657468 6f6400f1  ........method..
+ 0180 0f150200 12100000 6d657468 6f643200  ........method2.
+ 0190 1e000515 02000002 13100000 00000000  ................
+ 01a0 00000000 0400666f 6f006261 7200f2f1  ......foo.bar...
+ 01b0 22000312 0d150300 75000000 00006e75  ".......u.....nu
+ 01c0 6d3100f1 0d150300 10000000 00006e75  m1............nu
+ 01d0 6d3200f1 1a000615 02000000 15100000  m2..............
+ 01e0 04003c75 6e6e616d 65642d74 61673e00  ..<unnamed-tag>.
+ 01f0 16000615 00008002 00000000 00006261  ..............ba
+ 0200 7a007175 7800f2f1 16000615 02000002  z.qux...........
+ 0210 15100000 04006261 7a007175 7800f2f1  ......baz.qux...
+ 0220 52000312 02150300 00007265 6400f2f1  R.........red...
+ 0230 02150300 01006772 65656e00 02150300  ......green.....
+ 0240 0380ffff ffff626c 756500f1 02150300  ......blue......
+ 0250 02800080 79656c6c 6f7700f1 02150300  ....yellow......
+ 0260 0a800000 00000100 00007075 72706c65  ..........purple
+ 0270 00f3f2f1 1e000715 00008002 23000000  ............#...
+ 0280 00000000 636f6c6f 75720063 6f6c6f75  ....colour.colou
+ 0290 723200f1 1e000715 05000002 23000000  r2..........#...
+ 02a0 19100000 636f6c6f 75720063 6f6c6f75  ....colour.colou
+ 02b0 723200f1 0a000312 04140000 19100000  r2..............
+ 02c0 06000a00 010000f1 0a000210 1d100000  ................
+ 02d0 0c000100 0a000312 02150300 00006100  ..............a.
+ 02e0 22000715 01000800 75000000 1f100000  ".......u.......
+ 02f0 71757578 3a3a6e65 73746564 5f656e75  quux::nested_enu
+ 0300 6d00f2f1 52000312 00140000 14100000  m...R...........
+ 0310 0400f2f1 09140000 1e100000 01140000  ................
+ 0320 14100000 1e100000 00000000 0e150000  ................
+ 0330 02100000 73746174 69635f6d 656d6265  ....static_membe
+ 0340 7200f2f1 10150000 20100000 6e657374  r....... ...nest
+ 0350 65645f65 6e756d00 1a000515 01000000  ed_enum.........
+ 0360 21100000 00000000 00000000 04007175  !.............qu
+ 0370 757800f1                             ux..            
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types1a.s b/ld/testsuite/ld-pe/pdb-types1a.s
new file mode 100644
index 00000000000..a2ee9a9972f
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types1a.s
@@ -0,0 +1,27 @@
+.equ CV_SIGNATURE_C13, 4
+
+.equ T_LONG, 0x0012
+
+.equ LF_MODIFIER, 0x1001
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, volatile long
+.mod1:
+.short .mod2 - .mod1 - 2
+.short LF_MODIFIER
+.long T_LONG
+.short 2 # volatile
+.p2align 2
+
+# Type 1001, const long
+.mod2:
+.short .types_end - .mod2 - 2
+.short LF_MODIFIER
+.long T_LONG
+.short 1 # const
+.p2align 2
+
+.types_end:
diff --git a/ld/testsuite/ld-pe/pdb-types1b.s b/ld/testsuite/ld-pe/pdb-types1b.s
new file mode 100644
index 00000000000..89ee6e3840f
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types1b.s
@@ -0,0 +1,461 @@
+.equ CV_SIGNATURE_C13, 4
+
+.equ T_CHAR, 0x0010
+.equ T_LONG, 0x0012
+.equ T_ULONG, 0x0022
+.equ T_INT4, 0x0074
+.equ T_UINT4, 0x0075
+.equ T_UQUAD, 0x0023
+
+.equ LF_VTSHAPE, 0x000a
+.equ LF_MODIFIER, 0x1001
+.equ LF_POINTER, 0x1002
+.equ LF_PROCEDURE, 0x1008
+.equ LF_MFUNCTION, 0x1009
+.equ LF_ARGLIST, 0x1201
+.equ LF_FIELDLIST, 0x1203
+.equ LF_BITFIELD, 0x1205
+.equ LF_METHODLIST, 0x1206
+.equ LF_BCLASS, 0x1400
+.equ LF_VBCLASS, 0x1401
+.equ LF_INDEX, 0x1404
+.equ LF_VFUNCTAB, 0x1409
+.equ LF_ENUMERATE, 0x1502
+.equ LF_ARRAY, 0x1503
+.equ LF_STRUCTURE, 0x1505
+.equ LF_UNION, 0x1506
+.equ LF_ENUM, 0x1507
+.equ LF_MEMBER, 0x150d
+.equ LF_STMEMBER, 0x150e
+.equ LF_METHOD, 0x150f
+.equ LF_NESTTYPE, 0x1510
+.equ LF_ONEMETHOD, 0x1511
+
+.equ LF_USHORT, 0x8002
+.equ LF_LONG, 0x8003
+.equ LF_UQUADWORD, 0x800a
+
+.equ CV_PTR_NEAR32, 0xa
+.equ CV_PTR_64, 0xc
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, const long
+.mod1:
+.short .mod2 - .mod1 - 2
+.short LF_MODIFIER
+.long T_LONG
+.short 1 # const
+.p2align 2
+
+# Type 1001, volatile unsigned long
+.mod2:
+.short .mod3 - .mod2 - 2
+.short LF_MODIFIER
+.long T_ULONG
+.short 2 # volatile
+.p2align 2
+
+# Type 1002, const volatile int
+.mod3:
+.short .ptr1 - .mod3 - 2
+.short LF_MODIFIER
+.long T_INT4
+.short 3 # const volatile
+.p2align 2
+
+# Type 1003, const long * (64-bit pointer)
+.ptr1:
+.short .ptr2 - .ptr1 - 2
+.short LF_POINTER
+.long 0x1000
+.long (8 << 13) | CV_PTR_64
+
+# Type 1004, volatile unsigned long * (32-bit pointer)
+.ptr2:
+.short .arglist1 - .ptr2 - 2
+.short LF_POINTER
+.long 0x1001
+.long (4 << 13) | CV_PTR_NEAR32
+
+# Type 1005, arg list of types 1000, 1001, 1002
+.arglist1:
+.short .proc1 - .arglist1 - 2
+.short LF_ARGLIST
+.long 3 # no. entries
+.long 0x1000
+.long 0x1001
+.long 0x1002
+
+# Type 1006, procedure, return type 1001, arg list 1005
+.proc1:
+.short .arr1 - .proc1 - 2
+.short LF_PROCEDURE
+.long 0x1001
+.byte 0 # calling convention
+.byte 0 # attributes
+.short 3 # no. parameters
+.long 0x1005
+
+# Type 1007, array[3] of const long *
+.arr1:
+.short .bitfield1 - .arr1 - 2
+.short LF_ARRAY
+.long 0x1003 # element type
+.long T_INT4 # index type
+.short 24 # length in bytes
+.byte 0 # name
+.byte 0xf1 # padding
+
+# Type 1008, bitfield of uint32_t, position 0, length 1
+.bitfield1:
+.short .bitfield2 - .bitfield1 - 2
+.short LF_BITFIELD
+.long T_UINT4
+.byte 1
+.byte 0
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1009, bitfield of uint32_t, position 1, length 31
+.bitfield2:
+.short .fieldlist1 - .bitfield2 - 2
+.short LF_BITFIELD
+.long T_UINT4
+.byte 31
+.byte 1
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 100a, field list (1008 as num1, 1009 as num2)
+.fieldlist1:
+.short .struct1 - .fieldlist1 - 2
+.short LF_FIELDLIST
+.short LF_MEMBER
+.short 3 # public
+.long 0x1008
+.short 0 # offset
+.asciz "num1"
+.byte 0xf1 # padding
+.short LF_MEMBER
+.short 3 # public
+.long 0x1009
+.short 0 # offset
+.asciz "num2"
+.byte 0xf1 # padding
+
+# Type 100b, anonymous struct, field list 100a
+.struct1:
+.short .struct2 - .struct1 - 2
+.short LF_STRUCTURE
+.short 2 # no. members
+.short 0 # property
+.long 0x100a # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 4 # size
+.asciz "<unnamed-tag>"
+
+# Type 100c, forward declaration of struct foo
+.struct2:
+.short .ptr3 - .struct2 - 2
+.short LF_STRUCTURE
+.short 0 # no. members
+.short 0x280 # property (has unique name, forward declaration)
+.long 0 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 0 # size
+.asciz "foo" # name
+.asciz "bar" # unique name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 100d, pointer to 100c
+.ptr3:
+.short .arglist2 - .ptr3 - 2
+.short LF_POINTER
+.long 0x100c
+.long (8 << 13) | CV_PTR_64
+
+# Type 100e, empty arg list
+.arglist2:
+.short .mfunc1 - .arglist2 - 2
+.short LF_ARGLIST
+.long 0 # no. entries
+
+# Type 100f, member function of 100c, return type 1001
+.mfunc1:
+.short .mfunc2 - .mfunc1 - 2
+.short LF_MFUNCTION
+.long 0x1001
+.long 0x100c
+.long 0x100d # type of "this" pointer
+.byte 0 # calling convention
+.byte 0 # attributes
+.short 0 # no. parameters
+.long 0x100e # arg list
+.long 0 # "this" adjustment
+
+# Type 1010, member function of 100c, return type 1001, arg list 1005
+.mfunc2:
+.short .methodlist1 - .mfunc2 - 2
+.short LF_MFUNCTION
+.long 0x1001
+.long 0x100c
+.long 0x100d # type of "this" pointer
+.byte 0 # calling convention
+.byte 0 # attributes
+.short 3 # no. parameters
+.long 0x1005 # arg list
+.long 0 # "this" adjustment
+
+# Type 1011, method list for both member functions 100f and 1010
+.methodlist1:
+.short .fieldlist2 - .methodlist1 - 2
+.short LF_METHODLIST
+.short 0 # attributes
+.short 0 # padding
+.long 0x100f
+.short 0 # attributes
+.short 0 # padding
+.long 0x1010
+
+# Type 1012, field list (uint32_t as num1)
+.fieldlist2:
+.short .struct3 - .fieldlist2 - 2
+.short LF_FIELDLIST
+.short LF_MEMBER
+.short 3 # public
+.long T_UINT4
+.short 0 # offset
+.asciz "num"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+.short LF_ONEMETHOD
+.short 0 # method attribute
+.long 0x100f # method type
+.asciz "method"
+.byte 0xf1
+.short LF_METHOD
+.short 2 # no. overloads
+.long 0x1011 # method list
+.asciz "method2"
+
+# Type 1013, struct foo, field list 1012
+.struct3:
+.short .fieldlist3 - .struct3 - 2
+.short LF_STRUCTURE
+.short 2 # no. members
+.short 0x200 # property (has unique name)
+.long 0x1012 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 4 # size
+.asciz "foo" # name
+.asciz "bar" # unique name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1014, field list (uint32_t as num1, char as num2)
+.fieldlist3:
+.short .union1 - .fieldlist3 - 2
+.short LF_FIELDLIST
+.short LF_MEMBER
+.short 3 # public
+.long T_UINT4
+.short 0 # offset
+.asciz "num1"
+.byte 0xf1 # padding
+.short LF_MEMBER
+.short 3 # public
+.long T_CHAR
+.short 0 # offset
+.asciz "num2"
+.byte 0xf1 # padding
+
+# Type 1015, anonymous union (field list 1014)
+.union1:
+.short .union2 - .union1 - 2
+.short LF_UNION
+.short 2 # no. members
+.short 0 # property
+.long 0x1014
+.short 4 # size
+.asciz "<unnamed-tag>"
+
+# Type 1016, forward declaration of union baz
+.union2:
+.short .union3 - .union2 - 2
+.short LF_UNION
+.short 0 # no. members
+.short 0x280 # property (has unique name, forward declaration)
+.long 0 # field list
+.short 0 # size
+.asciz "baz"
+.asciz "qux"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1017, union baz (field list 1014)
+.union3:
+.short .fieldlist4 - .union3 - 2
+.short LF_UNION
+.short 2 # no. members
+.short 0x200 # property (has unique name, forward declaration)
+.long 0x1014 # field list
+.short 4 # size
+.asciz "baz"
+.asciz "qux"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1018, field list for enum (red = 0, green = 1, blue = -1, yellow = 0x8000, purple = 0x100000000)
+.fieldlist4:
+.short .enum1 - .fieldlist4 - 2
+.short LF_FIELDLIST
+.short LF_ENUMERATE
+.short 3 # public
+.short 0 # value
+.asciz "red"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+.short LF_ENUMERATE
+.short 3 # public
+.short 1 # value
+.asciz "green"
+.short LF_ENUMERATE
+.short 3 # public
+.short LF_LONG
+.long 0xffffffff # value
+.asciz "blue"
+.byte 0xf1 # padding
+.short LF_ENUMERATE
+.short 3 # public
+.short LF_USHORT
+.short 0x8000 # value
+.asciz "yellow"
+.byte 0xf1 # padding
+.short LF_ENUMERATE
+.short 3 # public
+.short LF_UQUADWORD
+.quad 0x100000000 # value
+.asciz "purple"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1019, forward reference to enum
+.enum1:
+.short .enum2 - .enum1 - 2
+.short LF_ENUM
+.short 0 # no. elements
+.short 0x280 # property (has unique name, forward ref)
+.long T_UQUAD # underlying type
+.long 0 # field list
+.asciz "colour"
+.asciz "colour2"
+.byte 0xf1 # padding
+
+# Type 101a, enum (field list 1018)
+.enum2:
+.short .fieldlist5 - .enum2 - 2
+.short LF_ENUM
+.short 5 # no. elements
+.short 0x200 # property (has unique name)
+.long T_UQUAD # underlying type
+.long 0x1018 # field list
+.asciz "colour"
+.asciz "colour2"
+.byte 0xf1 # padding
+
+# Type 101b, field list referencing other field list 1018
+.fieldlist5:
+.short .vtshape1 - .fieldlist5 - 2
+.short LF_FIELDLIST
+.short LF_INDEX
+.short 0 # padding
+.long 0x1018
+
+# Type 101c, virtual function table shape
+.vtshape1:
+.short .ptr4 - .vtshape1 - 2
+.short LF_VTSHAPE
+.short 1 # no. descriptors
+.byte 0 # descriptor (CV_VTS_near)
+.byte 0xf1 # padding
+
+# Type 101d, pointer to 101c
+.ptr4:
+.short .fieldlist6 - .ptr4 - 2
+.short LF_POINTER
+.long 0x101c
+.long (8 << 13) | CV_PTR_64
+
+# Type 101e, fieldlist for enum
+.fieldlist6:
+.short .enum3 - .fieldlist6 - 2
+.short LF_FIELDLIST
+.short LF_ENUMERATE
+.short 3 # public
+.short 0 # value
+.asciz "a"
+
+# Type 101f, nested enum
+.enum3:
+.short .fieldlist7 - .enum3 - 2
+.short LF_ENUM
+.short 1 # no. elements
+.short 0x8 # property (is nested)
+.long T_UINT4 # underlying type
+.long 0x101e # field list
+.asciz "quux::nested_enum"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1020, field list for struct quux
+.fieldlist7:
+.short .struct4 - .fieldlist7 - 2
+.short LF_FIELDLIST
+.short LF_BCLASS
+.short 0 # attributes
+.long 0x1013 # base class
+.short 4 # offset within class
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+.short LF_VFUNCTAB
+.short 0 # padding
+.long 0x101d # pointer to vtshape
+.short LF_VBCLASS
+.short 0 # attribute
+.long 0x1013 # type index of direct virtual base class
+.long 0x101d # type index of virtual base pointer
+.short 0 # virtual base pointer offset
+.short 0 # virtual base offset from vbtable
+.short LF_STMEMBER
+.short 0 # attribute
+.long 0x1001 # volatile unsigned long
+.asciz "static_member"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+.short LF_NESTTYPE
+.short 0 # padding
+.long 0x101f # enum type
+.asciz "nested_enum"
+
+# Type 1021, struct quux, field list 1020
+.struct4:
+.short .types_end - .struct4 - 2
+.short LF_STRUCTURE
+.short 1 # no. members
+.short 0 # property
+.long 0x1020 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 4 # size
+.asciz "quux" # name
+.byte 0xf1 # padding
+
+.types_end:
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index 2e5f83477aa..5661438c9e9 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -977,7 +977,179 @@ proc test4 { } {
     }
 }
 
+proc test5 { } {
+    global as
+    global ar
+    global ld
+    global objdump
+    global srcdir
+    global subdir
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-types1a.s tmpdir/pdb-types1a.o] {
+	unsupported "Build pdb-types1a.o"
+	return
+    }
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-types1b.s tmpdir/pdb-types1b.o] {
+	unsupported "Build pdb-types1b.o"
+	return
+    }
+
+    if ![ld_link $ld "tmpdir/pdb-types1.exe" "--pdb=tmpdir/pdb-types1.pdb tmpdir/pdb-types1a.o tmpdir/pdb-types1b.o"] {
+	unsupported "Create PE image with PDB file"
+	return
+    }
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types1.pdb 0002"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract TPI stream"
+	return
+    } else {
+	pass "Extracted TPI stream"
+    }
+
+    # check values in TPI header, and save anything interesting
+
+    set fi [open tmpdir/0002]
+    fconfigure $fi -translation binary
+
+    seek $fi 8 current
+
+    set data [read $fi 4]
+    binary scan $data i first_type
+
+    if { $first_type != 0x1000 } {
+	fail "Incorrect first type value in TPI stream."
+    } else {
+	pass "Correct first type value in TPI stream."
+    }
+
+    set data [read $fi 4]
+    binary scan $data i end_type
+
+    # end_type is one greater than the last type in the stream
+    if { $end_type != 0x1023 } {
+	fail "Incorrect end type value in TPI stream."
+    } else {
+	pass "Correct end type value in TPI stream."
+    }
+
+    set data [read $fi 4]
+    binary scan $data i type_list_size
+
+    set data [read $fi 2]
+    binary scan $data s hash_stream_index
+
+    seek $fi 2 current
+
+    set data [read $fi 4]
+    binary scan $data i hash_size
+
+    if { $hash_size != 4 } {
+	fail "Incorrect hash size in TPI stream."
+    } else {
+	pass "Correct hash size in TPI stream."
+    }
+
+    set data [read $fi 4]
+    binary scan $data i num_buckets
+
+    if { $num_buckets != 0x3ffff } {
+	fail "Incorrect number of buckets in TPI stream."
+    } else {
+	pass "Correct number of buckets in TPI stream."
+    }
+
+    set data [read $fi 4]
+    binary scan $data i hash_list_offset
+
+    set data [read $fi 4]
+    binary scan $data i hash_list_size
+
+    set data [read $fi 4]
+    binary scan $data i skip_list_offset
+
+    set data [read $fi 4]
+    binary scan $data i skip_list_size
+
+    seek $fi 8 current
+
+    set type_list [read $fi $type_list_size]
+
+    close $fi
+
+    set fi [open tmpdir/pdb-types1-typelist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $type_list
+    close $fi
+
+    # check type list
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types1-typelist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types1-typelist"]
+    if ![string match $exp $got] {
+	fail "Incorrect type list in TPI stream."
+    } else {
+	pass "Correct type list in TPI stream."
+    }
+
+    # extract hash list and skip list
+
+    set index_str [format "%04x" $hash_stream_index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types1.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract TPI hash stream."
+    } else {
+	pass "Extracted TPI hash stream."
+    }
+
+    set fi [open tmpdir/$index_str]
+    fconfigure $fi -translation binary
+
+    seek $fi $hash_list_offset
+    set hash_list [read $fi $hash_list_size]
+
+    seek $fi $skip_list_offset
+    set skip_list [read $fi $skip_list_size]
+
+    close $fi
+
+    # check hash list
+
+    set fi [open tmpdir/pdb-types1-hashlist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $hash_list
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types1-hashlist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types1-hashlist"]
+    if ![string match $exp $got] {
+	fail "Incorrect hash list in TPI stream."
+    } else {
+	pass "Correct hash list in TPI stream."
+    }
+
+    # check skip list
+
+    set fi [open tmpdir/pdb-types1-skiplist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $skip_list
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types1-skiplist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types1-skiplist"]
+    if ![string match $exp $got] {
+	fail "Incorrect skip list in TPI stream."
+    } else {
+	pass "Correct skip list in TPI stream."
+    }
+}
+
 test1
 test2
 test3
 test4
+test5
-- 
2.37.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH] ld: Write types into IPI stream of PDB
  2022-11-29  0:10       ` [PATCH] ld: Write types into TPI stream of PDB Mark Harmstone
@ 2022-11-29  0:10         ` Mark Harmstone
  2022-11-29  0:10         ` [PATCH] ld: Parse LF_UDT_SRC_LINE records when creating PDB file Mark Harmstone
  1 sibling, 0 replies; 19+ messages in thread
From: Mark Harmstone @ 2022-11-29  0:10 UTC (permalink / raw)
  To: binutils; +Cc: Mark Harmstone

This covers the other types, that are instead written to the ID
information stream.

---
 ld/pdb.c                                 | 204 +++++++++++++++++++--
 ld/pdb.h                                 |  45 ++++-
 ld/testsuite/ld-pe/pdb-types2-hashlist.d |   8 +
 ld/testsuite/ld-pe/pdb-types2-skiplist.d |   5 +
 ld/testsuite/ld-pe/pdb-types2-typelist.d |  20 ++
 ld/testsuite/ld-pe/pdb-types2a.s         |  42 +++++
 ld/testsuite/ld-pe/pdb-types2b.s         | 221 +++++++++++++++++++++++
 ld/testsuite/ld-pe/pdb.exp               | 172 ++++++++++++++++++
 8 files changed, 699 insertions(+), 18 deletions(-)
 create mode 100644 ld/testsuite/ld-pe/pdb-types2-hashlist.d
 create mode 100644 ld/testsuite/ld-pe/pdb-types2-skiplist.d
 create mode 100644 ld/testsuite/ld-pe/pdb-types2-typelist.d
 create mode 100644 ld/testsuite/ld-pe/pdb-types2a.s
 create mode 100644 ld/testsuite/ld-pe/pdb-types2b.s

diff --git a/ld/pdb.c b/ld/pdb.c
index 979ea126aa5..6584ccd9911 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -1097,13 +1097,16 @@ is_name_anonymous (char *name, size_t len)
    already seen add it to types (for TPI types) or ids (for IPI types).  */
 static bool
 handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
-	     uint32_t num_types, struct types *types)
+	     uint32_t num_types, struct types *types,
+	     struct types *ids)
 {
   uint16_t size, type;
   void **slot;
   hashval_t hash;
   bool other_hash = false;
   uint32_t cv_hash;
+  struct types *t;
+  bool ipi = false;
 
   size = bfd_getl16 (data) + sizeof (uint16_t);
   type = bfd_getl16 (data + sizeof (uint16_t));
@@ -1904,6 +1907,168 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
       /* Does not reference any types, nothing to be done.  */
       break;
 
+    case LF_STRING_ID:
+      {
+	struct lf_string_id *str = (struct lf_string_id *) data;
+	size_t string_len;
+
+	if (size < offsetof (struct lf_string_id, string))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_STRING_ID\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&str->substring, map, type_num, num_types))
+	  return false;
+
+	string_len = strnlen (str->string,
+			      size - offsetof (struct lf_string_id, string));
+
+	if (string_len == size - offsetof (struct lf_string_id, string))
+	  {
+	    einfo (_("%P: warning: string for LF_STRING_ID has no"
+		     " terminating zero\n"));
+	    return false;
+	  }
+
+	ipi = true;
+
+	break;
+      }
+
+    case LF_SUBSTR_LIST:
+      {
+	uint32_t num_entries;
+	struct lf_arglist *ssl = (struct lf_arglist *) data;
+
+	if (size < offsetof (struct lf_arglist, args))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_SUBSTR_LIST\n"));
+	    return false;
+	  }
+
+	num_entries = bfd_getl32 (&ssl->num_entries);
+
+	if (size < offsetof (struct lf_arglist, args)
+		   + (num_entries * sizeof (uint32_t)))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_SUBSTR_LIST\n"));
+	    return false;
+	  }
+
+	for (uint32_t i = 0; i < num_entries; i++)
+	  {
+	    if (!remap_type (&ssl->args[i], map, type_num, num_types))
+	      return false;
+	  }
+
+	ipi = true;
+
+	break;
+      }
+
+    case LF_BUILDINFO:
+      {
+	uint16_t num_entries;
+	struct lf_build_info *bi = (struct lf_build_info *) data;
+
+	if (size < offsetof (struct lf_build_info, strings))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_BUILDINFO\n"));
+	    return false;
+	  }
+
+	num_entries = bfd_getl16 (&bi->count);
+
+	if (size < offsetof (struct lf_build_info, strings)
+		   + (num_entries * sizeof (uint32_t)))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_BUILDINFO\n"));
+	    return false;
+	  }
+
+	for (uint32_t i = 0; i < num_entries; i++)
+	  {
+	    if (!remap_type (&bi->strings[i], map, type_num, num_types))
+	      return false;
+	  }
+
+	ipi = true;
+
+	break;
+      }
+
+    case LF_FUNC_ID:
+      {
+	struct lf_func_id *func = (struct lf_func_id *) data;
+	size_t name_len;
+
+	if (size < offsetof (struct lf_func_id, name))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_FUNC_ID\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&func->parent_scope, map, type_num, num_types))
+	  return false;
+
+	if (!remap_type (&func->function_type, map, type_num, num_types))
+	  return false;
+
+	name_len = strnlen (func->name,
+			    size - offsetof (struct lf_func_id, name));
+
+	if (name_len == size - offsetof (struct lf_func_id, name))
+	  {
+	    einfo (_("%P: warning: string for LF_FUNC_ID has no"
+		     " terminating zero\n"));
+	    return false;
+	  }
+
+	ipi = true;
+
+	break;
+      }
+
+    case LF_MFUNC_ID:
+      {
+	struct lf_mfunc_id *mfunc = (struct lf_mfunc_id *) data;
+	size_t name_len;
+
+	if (size < offsetof (struct lf_mfunc_id, name))
+	  {
+	    einfo (_("%P: warning: truncated CodeView type record"
+		     " LF_MFUNC_ID\n"));
+	    return false;
+	  }
+
+	if (!remap_type (&mfunc->parent_type, map, type_num, num_types))
+	  return false;
+
+	if (!remap_type (&mfunc->function_type, map, type_num, num_types))
+	  return false;
+
+	name_len = strnlen (mfunc->name,
+			    size - offsetof (struct lf_mfunc_id, name));
+
+	if (name_len == size - offsetof (struct lf_mfunc_id, name))
+	  {
+	    einfo (_("%P: warning: string for LF_MFUNC_ID has no"
+		     " terminating zero\n"));
+	    return false;
+	  }
+
+	ipi = true;
+
+	break;
+      }
+
     default:
       einfo (_("%P: warning: unrecognized CodeView type %v\n"), type);
       return false;
@@ -1911,7 +2076,9 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
 
   hash = iterative_hash (data, size, 0);
 
-  slot = htab_find_slot_with_hash (types->hashmap, data, hash, INSERT);
+  t = ipi ? ids : types;
+
+  slot = htab_find_slot_with_hash (t->hashmap, data, hash, INSERT);
   if (!slot)
     return false;
 
@@ -1924,7 +2091,7 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
       e = (struct type_entry *) *slot;
 
       e->next = NULL;
-      e->index = types->num_types;
+      e->index = t->num_types;
 
       if (other_hash)
 	e->cv_hash = cv_hash;
@@ -1933,16 +2100,16 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
 
       memcpy (e->data, data, size);
 
-      if (types->last)
-	types->last->next = e;
+      if (t->last)
+	t->last->next = e;
       else
-	types->first = e;
+	t->first = e;
 
-      types->last = e;
+      t->last = e;
 
       map[type_num] = e;
 
-      types->num_types++;
+      t->num_types++;
     }
   else /* duplicate */
     {
@@ -1955,7 +2122,8 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
 /* Parse the .debug$T section of a module, and pass any type definitions
    found to handle_type.  */
 static bool
-handle_debugt_section (asection *s, bfd *mod, struct types *types)
+handle_debugt_section (asection *s, bfd *mod, struct types *types,
+		       struct types *ids)
 {
   bfd_byte *data = NULL;
   size_t off;
@@ -2012,7 +2180,7 @@ handle_debugt_section (asection *s, bfd *mod, struct types *types)
 
       size = bfd_getl16 (data + off);
 
-      if (!handle_type (data + off, map, type_num, num_types, types))
+      if (!handle_type (data + off, map, type_num, num_types, types, ids))
 	{
 	  free (data);
 	  free (map);
@@ -2037,7 +2205,8 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 			struct string_table *strings,
 			uint32_t *c13_info_size,
 			struct mod_source_files *mod_source,
-			bfd *abfd, struct types *types)
+			bfd *abfd, struct types *types,
+			struct types *ids)
 {
   uint8_t int_buf[sizeof (uint32_t)];
   uint8_t *c13_info = NULL;
@@ -2061,7 +2230,7 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 	}
       else if (!strcmp (s->name, ".debug$T") && s->size >= sizeof (uint32_t))
 	{
-	  if (!handle_debugt_section (s, mod, types))
+	  if (!handle_debugt_section (s, mod, types, ids))
 	    {
 	      free (c13_info);
 	      free (mod_source->files);
@@ -2107,7 +2276,7 @@ static bool
 create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 			      uint32_t *size, struct string_table *strings,
 			      struct source_files_info *source,
-			      struct types *types)
+			      struct types *types, struct types *ids)
 {
   uint8_t *ptr;
   unsigned int mod_num;
@@ -2196,7 +2365,7 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
       if (!populate_module_stream (stream, in, &sym_byte_size,
 				   strings, &c13_info_size,
 				   &source->mods[mod_num], abfd,
-				   types))
+				   types, ids))
 	{
 	  for (unsigned int i = 0; i < source->mod_count; i++)
 	    {
@@ -2519,7 +2688,8 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
 		     uint16_t sym_rec_stream_num,
 		     uint16_t publics_stream_num,
 		     struct string_table *strings,
-		     struct types *types)
+		     struct types *types,
+		     struct types *ids)
 {
   struct pdb_dbi_stream_header h;
   struct optional_dbg_header opt;
@@ -2531,7 +2701,7 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
   source.mods = NULL;
 
   if (!create_module_info_substream (abfd, pdb, &mod_info, &mod_info_size,
-				     strings, &source, types))
+				     strings, &source, types, ids))
     return false;
 
   if (!create_section_contrib_substream (abfd, &sc, &sc_size))
@@ -3206,7 +3376,7 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
 
   if (!populate_dbi_stream (dbi_stream, abfd, pdb, section_header_stream_num,
 			    sym_rec_stream_num, publics_stream_num,
-			    &strings, &types))
+			    &strings, &types, &ids))
     {
       einfo (_("%P: warning: cannot populate DBI stream "
 	       "in PDB file: %E\n"));
diff --git a/ld/pdb.h b/ld/pdb.h
index ecc26c1c87a..7d87a3fef07 100644
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -54,6 +54,11 @@
 #define LF_METHOD			0x150f
 #define LF_NESTTYPE			0x1510
 #define LF_ONEMETHOD			0x1511
+#define LF_FUNC_ID			0x1601
+#define LF_MFUNC_ID			0x1602
+#define LF_BUILDINFO			0x1603
+#define LF_SUBSTR_LIST			0x1604
+#define LF_STRING_ID			0x1605
 
 #define LF_CHAR				0x8000
 #define LF_SHORT			0x8001
@@ -265,7 +270,7 @@ struct lf_pointer
   uint32_t attributes;
 } ATTRIBUTE_PACKED;
 
-/* lfArgList in cvinfo.h */
+/* lfArgList in cvinfo.h (used for both LF_ARGLIST and LF_SUBSTR_LIST) */
 struct lf_arglist
 {
   uint16_t size;
@@ -474,6 +479,44 @@ struct lf_nest_type
   char name[];
 } ATTRIBUTE_PACKED;
 
+/* lfStringId in cvinfo.h */
+struct lf_string_id
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t substring;
+  char string[];
+} ATTRIBUTE_PACKED;
+
+/* lfBuildInfo in cvinfo.h */
+struct lf_build_info
+{
+  uint16_t size;
+  uint16_t kind;
+  uint16_t count;
+  uint32_t strings[];
+} ATTRIBUTE_PACKED;
+
+/* lfFuncId in cvinfo.h */
+struct lf_func_id
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t parent_scope;
+  uint32_t function_type;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* lfMFuncId in cvinfo.h */
+struct lf_mfunc_id
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t parent_type;
+  uint32_t function_type;
+  char name[];
+} ATTRIBUTE_PACKED;
+
 extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
 
 #endif
diff --git a/ld/testsuite/ld-pe/pdb-types2-hashlist.d b/ld/testsuite/ld-pe/pdb-types2-hashlist.d
new file mode 100644
index 00000000000..71d9045fbbd
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types2-hashlist.d
@@ -0,0 +1,8 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 75cf0100 8d660300 f2a20300 aea00000  *
+ 0010 ef990300 223d0000 d6b60000 24070100  *
+ 0020 7f220100 f6d10200 16100200 010a0300  *
+ 0030 0b4f0300 12690300 a56d0300           *
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types2-skiplist.d b/ld/testsuite/ld-pe/pdb-types2-skiplist.d
new file mode 100644
index 00000000000..52c10fa501d
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types2-skiplist.d
@@ -0,0 +1,5 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 00100000 00000000                    *
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types2-typelist.d b/ld/testsuite/ld-pe/pdb-types2-typelist.d
new file mode 100644
index 00000000000..d0fd26e0fa4
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types2-typelist.d
@@ -0,0 +1,20 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 0e000516 00000000 74657374 00f3f2f1  ........test....
+ 0010 0a000516 00000000 666f6f00 0a000516  ........foo.....
+ 0020 00000000 62617200 0e000416 02000000  ....bar.........
+ 0030 01100000 02100000 0a000516 03100000  ................
+ 0040 62617a00 0e000516 00000000 2f746d70  baz........./tmp
+ 0050 00f3f2f1 0a000516 00000000 67636300  ............gcc.
+ 0060 0e000516 00000000 746d702e 6300f2f1  ........tmp.c...
+ 0070 0e000516 00000000 746d702e 70646200  ........tmp.pdb.
+ 0080 12000516 00000000 2d67636f 64657669  ........-gcodevi
+ 0090 657700f1 1a000316 05000510 00000610  ew..............
+ 00a0 00000710 00000810 00000910 0000f2f1  ................
+ 00b0 12000516 00000000 6e616d65 73706163  ........namespac
+ 00c0 6500f2f1 12000116 00000000 01100000  e...............
+ 00d0 66756e63 3100f2f1 12000116 0b100000  func1...........
+ 00e0 01100000 66756e63 3200f2f1 12000216  ....func2.......
+ 00f0 02100000 04100000 6d657468 6f6400f1  ........method..
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types2a.s b/ld/testsuite/ld-pe/pdb-types2a.s
new file mode 100644
index 00000000000..e11843ae575
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types2a.s
@@ -0,0 +1,42 @@
+.equ CV_SIGNATURE_C13, 4
+
+.equ T_VOID, 0x0003
+.equ T_INT4, 0x0074
+
+.equ LF_PROCEDURE, 0x1008
+.equ LF_MFUNCTION, 0x1009
+.equ LF_POINTER, 0x1002
+.equ LF_ARGLIST, 0x1201
+.equ LF_FIELDLIST, 0x1203
+.equ LF_CLASS, 0x1504
+.equ LF_ONEMETHOD, 0x1511
+.equ LF_FUNC_ID, 0x1601
+.equ LF_MFUNC_ID, 0x1602
+.equ LF_BUILDINFO, 0x1603
+.equ LF_SUBSTR_LIST, 0x1604
+.equ LF_STRING_ID, 0x1605
+
+.equ CV_PTR_64, 0xc
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, string "test"
+.string1:
+.short .string2 - .string1 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "test"
+.byte 0xf3
+.byte 0xf2
+.byte 0xf1
+
+# Type 1001, string "foo"
+.string2:
+.short .types_end - .string2 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "foo"
+
+.types_end:
diff --git a/ld/testsuite/ld-pe/pdb-types2b.s b/ld/testsuite/ld-pe/pdb-types2b.s
new file mode 100644
index 00000000000..33541729f63
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types2b.s
@@ -0,0 +1,221 @@
+.equ CV_SIGNATURE_C13, 4
+
+.equ T_VOID, 0x0003
+.equ T_INT4, 0x0074
+
+.equ LF_PROCEDURE, 0x1008
+.equ LF_MFUNCTION, 0x1009
+.equ LF_POINTER, 0x1002
+.equ LF_ARGLIST, 0x1201
+.equ LF_FIELDLIST, 0x1203
+.equ LF_CLASS, 0x1504
+.equ LF_ONEMETHOD, 0x1511
+.equ LF_FUNC_ID, 0x1601
+.equ LF_MFUNC_ID, 0x1602
+.equ LF_BUILDINFO, 0x1603
+.equ LF_SUBSTR_LIST, 0x1604
+.equ LF_STRING_ID, 0x1605
+
+.equ CV_PTR_64, 0xc
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, string "foo"
+.string1:
+.short .string2 - .string1 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "foo"
+
+# Type 1001, string "bar"
+.string2:
+.short .substrlist1 - .string2 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "bar"
+
+# Type 1002, substr list of "foo" and "bar"
+.substrlist1:
+.short .string3 - .substrlist1 - 2
+.short LF_SUBSTR_LIST
+.long 2 # count
+.long 0x1000
+.long 0x1001
+
+# Type 1003, string "baz" referencing substr list 1002
+.string3:
+.short .string4 - .string3 - 2
+.short LF_STRING_ID
+.long 0x1002
+.asciz "baz"
+
+# Type 1004, string "/tmp" (build directory)
+.string4:
+.short .string5 - .string4 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "/tmp"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1005, string "gcc" (compiler)
+.string5:
+.short .string6 - .string5 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "gcc"
+
+# Type 1006, string "tmp.c" (source file)
+.string6:
+.short .string7 - .string6 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "tmp.c"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1007, string "tmp.pdb" (PDB file)
+.string7:
+.short .string8 - .string7 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "tmp.pdb"
+
+# Type 1008, string "-gcodeview" (command arguments)
+.string8:
+.short .buildinfo1 - .string8 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "-gcodeview"
+.byte 0xf1 # padding
+
+# The 1009, build info
+.buildinfo1:
+.short .string9 - .buildinfo1 - 2
+.short LF_BUILDINFO
+.short 5 # count
+.long 0x1004 # build directory
+.long 0x1005 # compiler
+.long 0x1006 # source file
+.long 0x1007 # PDB file
+.long 0x1008 # command arguments
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 100a, string "namespace"
+.string9:
+.short .arglist1 - .string9 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "namespace"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 100b, arg list of type T_INT4
+.arglist1:
+.short .proc1 - .arglist1 - 2
+.short LF_ARGLIST
+.long 1 # no. entries
+.long T_INT4
+
+# Type 100c, procedure, return type T_VOID, arg list 100b
+.proc1:
+.short .func1 - .proc1 - 2
+.short LF_PROCEDURE
+.long T_VOID
+.byte 0 # calling convention
+.byte 0 # attributes
+.short 1 # no. parameters
+.long 0x100b
+
+# Type 100d, function "func1"
+.func1:
+.short .func2 - .func1 - 2
+.short LF_FUNC_ID
+.long 0 # parent scope
+.long 0x100c # type
+.asciz "func1"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 100e, function "func2" within scope "namespace"
+.func2:
+.short .class1 - .func2 - 2
+.short LF_FUNC_ID
+.long 0x100a # parent scope
+.long 0x100c # type
+.asciz "func2"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 100f, forward declaration of class foo
+.class1:
+.short .ptr1 - .class1 - 2
+.short LF_CLASS
+.short 0 # no. members
+.short 0x80 # property (has unique name, forward declaration)
+.long 0 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 0 # size
+.asciz "foo" # name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1010, pointer to 100f
+.ptr1:
+.short .mfunction1 - .ptr1 - 2
+.short LF_POINTER
+.long 0x100f
+.long (8 << 13) | CV_PTR_64
+
+# Type 1011, member function of 100f, return type void, arg list 100b
+.mfunction1:
+.short .fieldlist1 - .mfunction1 - 2
+.short LF_MFUNCTION
+.long T_VOID
+.long 0x100f
+.long 0x1010 # type of "this" pointer
+.byte 0 # calling convention
+.byte 0 # attributes
+.short 1 # no. parameters
+.long 0x100b # arg list
+.long 0 # "this" adjustment
+
+# Type 1012, field list for class foo
+.fieldlist1:
+.short .class2 - .fieldlist1 - 2
+.short LF_FIELDLIST
+.short LF_ONEMETHOD
+.short 0 # method attribute
+.long 0x1010 # method type
+.asciz "method"
+.byte 0xf1
+
+# Type 1013, actual declaration of class foo
+.class2:
+.short .mfunc1 - .class2 - 2
+.short LF_CLASS
+.short 0 # no. members
+.short 0 # property
+.long 0x1012 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 0 # size
+.asciz "foo" # name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1014, function "method" within class "foo"
+.mfunc1:
+.short .types_end - .mfunc1 - 2
+.short LF_MFUNC_ID
+.long 0x100f # parent class
+.long 0x1011 # function type
+.asciz "method"
+.byte 0xf1 # padding
+
+.types_end:
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index 5661438c9e9..9753216f544 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -1148,8 +1148,180 @@ proc test5 { } {
     }
 }
 
+proc test6 { } {
+    global as
+    global ar
+    global ld
+    global objdump
+    global srcdir
+    global subdir
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-types2a.s tmpdir/pdb-types2a.o] {
+	unsupported "Build pdb-types2a.o"
+	return
+    }
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-types2b.s tmpdir/pdb-types2b.o] {
+	unsupported "Build pdb-types2b.o"
+	return
+    }
+
+    if ![ld_link $ld "tmpdir/pdb-types2.exe" "--pdb=tmpdir/pdb-types2.pdb tmpdir/pdb-types2a.o tmpdir/pdb-types2b.o"] {
+	unsupported "Create PE image with PDB file"
+	return
+    }
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types2.pdb 0004"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract IPI stream"
+	return
+    } else {
+	pass "Extracted IPI stream"
+    }
+
+    # check values in IPI header, and save anything interesting
+
+    set fi [open tmpdir/0004]
+    fconfigure $fi -translation binary
+
+    seek $fi 8 current
+
+    set data [read $fi 4]
+    binary scan $data i first_type
+
+    if { $first_type != 0x1000 } {
+	fail "Incorrect first type value in IPI stream."
+    } else {
+	pass "Correct first type value in IPI stream."
+    }
+
+    set data [read $fi 4]
+    binary scan $data i end_type
+
+    # end_type is one greater than the last type in the stream
+    if { $end_type != 0x100f } {
+	fail "Incorrect end type value in IPI stream."
+    } else {
+	pass "Correct end type value in IPI stream."
+    }
+
+    set data [read $fi 4]
+    binary scan $data i type_list_size
+
+    set data [read $fi 2]
+    binary scan $data s hash_stream_index
+
+    seek $fi 2 current
+
+    set data [read $fi 4]
+    binary scan $data i hash_size
+
+    if { $hash_size != 4 } {
+	fail "Incorrect hash size in IPI stream."
+    } else {
+	pass "Correct hash size in IPI stream."
+    }
+
+    set data [read $fi 4]
+    binary scan $data i num_buckets
+
+    if { $num_buckets != 0x3ffff } {
+	fail "Incorrect number of buckets in IPI stream."
+    } else {
+	pass "Correct number of buckets in IPI stream."
+    }
+
+    set data [read $fi 4]
+    binary scan $data i hash_list_offset
+
+    set data [read $fi 4]
+    binary scan $data i hash_list_size
+
+    set data [read $fi 4]
+    binary scan $data i skip_list_offset
+
+    set data [read $fi 4]
+    binary scan $data i skip_list_size
+
+    seek $fi 8 current
+
+    set type_list [read $fi $type_list_size]
+
+    close $fi
+
+    set fi [open tmpdir/pdb-types2-typelist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $type_list
+    close $fi
+
+    # check type list
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types2-typelist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types2-typelist"]
+    if ![string match $exp $got] {
+	fail "Incorrect type list in IPI stream."
+    } else {
+	pass "Correct type list in IPI stream."
+    }
+
+    # extract hash list and skip list
+
+    set index_str [format "%04x" $hash_stream_index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types2.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract IPI hash stream."
+    } else {
+	pass "Extracted IPI hash stream."
+    }
+
+    set fi [open tmpdir/$index_str]
+    fconfigure $fi -translation binary
+
+    seek $fi $hash_list_offset
+    set hash_list [read $fi $hash_list_size]
+
+    seek $fi $skip_list_offset
+    set skip_list [read $fi $skip_list_size]
+
+    close $fi
+
+    # check hash list
+
+    set fi [open tmpdir/pdb-types2-hashlist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $hash_list
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types2-hashlist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types2-hashlist"]
+    if ![string match $exp $got] {
+	fail "Incorrect hash list in IPI stream."
+    } else {
+	pass "Correct hash list in IPI stream."
+    }
+
+    # check skip list
+
+    set fi [open tmpdir/pdb-types2-skiplist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $skip_list
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types2-skiplist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types2-skiplist"]
+    if ![string match $exp $got] {
+	fail "Incorrect skip list in IPI stream."
+    } else {
+	pass "Correct skip list in IPI stream."
+    }
+}
+
 test1
 test2
 test3
 test4
 test5
+test6
-- 
2.37.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH] ld: Parse LF_UDT_SRC_LINE records when creating PDB file
  2022-11-29  0:10       ` [PATCH] ld: Write types into TPI stream of PDB Mark Harmstone
  2022-11-29  0:10         ` [PATCH] ld: Write types into IPI " Mark Harmstone
@ 2022-11-29  0:10         ` Mark Harmstone
  2022-12-05  1:53           ` [PATCH] ld: Write globals stream in PDB Mark Harmstone
  1 sibling, 1 reply; 19+ messages in thread
From: Mark Harmstone @ 2022-11-29  0:10 UTC (permalink / raw)
  To: binutils; +Cc: Mark Harmstone

This covers the special case of the LF_UDT_SRC_LINE records, which get
rewritten as LF_UDT_MOD_SRC_LINE records, adding the module number. So
if a struct is defined in multiple translation units, the PDB will
record the source file, the line number, and the first module in which
the struct is defined.

---
 ld/pdb.c                                 | 174 +++++++++++++++++++++--
 ld/pdb.h                                 |  23 +++
 ld/testsuite/ld-pe/pdb-types3-hashlist.d |   5 +
 ld/testsuite/ld-pe/pdb-types3-skiplist.d |   5 +
 ld/testsuite/ld-pe/pdb-types3-typelist.d |   7 +
 ld/testsuite/ld-pe/pdb-types3a.s         |  57 ++++++++
 ld/testsuite/ld-pe/pdb-types3b.s         |  68 +++++++++
 ld/testsuite/ld-pe/pdb.exp               | 133 +++++++++++++++++
 8 files changed, 461 insertions(+), 11 deletions(-)
 create mode 100644 ld/testsuite/ld-pe/pdb-types3-hashlist.d
 create mode 100644 ld/testsuite/ld-pe/pdb-types3-skiplist.d
 create mode 100644 ld/testsuite/ld-pe/pdb-types3-typelist.d
 create mode 100644 ld/testsuite/ld-pe/pdb-types3a.s
 create mode 100644 ld/testsuite/ld-pe/pdb-types3b.s

diff --git a/ld/pdb.c b/ld/pdb.c
index 6584ccd9911..6326e018666 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -76,6 +76,7 @@ struct type_entry
   struct type_entry *next;
   uint32_t index;
   uint32_t cv_hash;
+  bool has_udt_src_line;
   uint8_t data[];
 };
 
@@ -708,19 +709,19 @@ copy_filechksms (uint8_t *data, uint32_t size, char *string_table,
   return true;
 }
 
-/* Add a string to the strings table, if it's not already there.  */
-static void
+/* Add a string to the strings table, if it's not already there.  Returns its
+   offset within the string table.  */
+static uint32_t
 add_string (char *str, size_t len, struct string_table *strings)
 {
   uint32_t hash = calc_hash (str, len);
+  struct string *s;
   void **slot;
 
   slot = htab_find_slot_with_hash (strings->hashmap, str, hash, INSERT);
 
-  if (slot && !*slot)
+  if (!*slot)
     {
-      struct string *s;
-
       *slot = xmalloc (offsetof (struct string, s) + len);
 
       s = (struct string *) *slot;
@@ -741,6 +742,12 @@ add_string (char *str, size_t len, struct string_table *strings)
 
       strings->strings_len += len + 1;
     }
+  else
+    {
+      s = (struct string *) *slot;
+    }
+
+  return s->offset;
 }
 
 /* Return the hash of an entry in the string table.  */
@@ -1092,13 +1099,150 @@ is_name_anonymous (char *name, size_t len)
   return false;
 }
 
+/* Handle LF_UDT_SRC_LINE type entries, which are a special case.  These
+   give the source file and line number for each user-defined type that is
+   declared.  We parse these and emit instead an LF_UDT_MOD_SRC_LINE entry,
+   which also includes the module number.  */
+static bool
+handle_udt_src_line (uint8_t *data, uint16_t size, struct type_entry **map,
+		     uint32_t type_num, uint32_t num_types,
+		     struct types *ids, uint16_t mod_num,
+		     struct string_table *strings)
+{
+  struct lf_udt_src_line *usl = (struct lf_udt_src_line *) data;
+  uint32_t orig_type, source_file_type;
+  void **slot;
+  hashval_t hash;
+  struct type_entry *e, *type_e, *str_e;
+  struct lf_udt_mod_src_line *umsl;
+  struct lf_string_id *str;
+  uint32_t source_file_offset;
+
+  if (size < sizeof (struct lf_udt_src_line))
+    {
+      einfo (_("%P: warning: truncated CodeView type record"
+		" LF_UDT_SRC_LINE\n"));
+      return false;
+    }
+
+  /* Check if LF_UDT_MOD_SRC_LINE already present for type, and return.  */
+
+  orig_type = bfd_getl32 (&usl->type);
+
+  if (orig_type < TPI_FIRST_INDEX ||
+      orig_type >= TPI_FIRST_INDEX + num_types ||
+      !map[orig_type - TPI_FIRST_INDEX])
+    {
+      einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
+	       " referred to unknown type %v\n"), orig_type);
+      return false;
+    }
+
+  type_e = map[orig_type - TPI_FIRST_INDEX];
+
+  /* Skip if type already declared in other module.  */
+  if (type_e->has_udt_src_line)
+    return true;
+
+  if (!remap_type (&usl->type, map, type_num, num_types))
+    return false;
+
+  /* Extract string from source_file_type.  */
+
+  source_file_type = bfd_getl32 (&usl->source_file_type);
+
+  if (source_file_type < TPI_FIRST_INDEX ||
+      source_file_type >= TPI_FIRST_INDEX + num_types ||
+      !map[source_file_type - TPI_FIRST_INDEX])
+    {
+      einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
+	       " referred to unknown string %v\n"), source_file_type);
+      return false;
+    }
+
+  str_e = map[source_file_type - TPI_FIRST_INDEX];
+
+  if (bfd_getl16 (str_e->data + sizeof (uint16_t)) != LF_STRING_ID)
+    {
+      einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
+	       " pointed to unexpected record type\n"));
+      return false;
+    }
+
+  str = (struct lf_string_id *) str_e->data;
+
+  /* Add string to string table.  */
+
+  source_file_offset = add_string (str->string, strlen (str->string),
+				   strings);
+
+  /* Add LF_UDT_MOD_SRC_LINE entry.  */
+
+  size = sizeof (struct lf_udt_mod_src_line);
+
+  e = xmalloc (offsetof (struct type_entry, data) + size);
+
+  e->next = NULL;
+  e->index = ids->num_types;
+  e->has_udt_src_line = false;
+
+  /* LF_UDT_MOD_SRC_LINE use calc_hash on the type number, rather than
+     the crc32 used for type hashes elsewhere.  */
+  e->cv_hash = calc_hash ((char *) &usl->type, sizeof (uint32_t));
+
+  type_e->has_udt_src_line = true;
+
+  umsl = (struct lf_udt_mod_src_line *) e->data;
+
+  bfd_putl16 (size - sizeof (uint16_t), &umsl->size);
+  bfd_putl16 (LF_UDT_MOD_SRC_LINE, &umsl->kind);
+  memcpy (&umsl->type, &usl->type, sizeof (uint32_t));
+  bfd_putl32 (source_file_offset,
+	      &umsl->source_file_string);
+  memcpy (&umsl->line_no, &usl->line_no, sizeof (uint16_t));
+  bfd_putl16 (mod_num + 1, &umsl->module_no);
+
+  hash = iterative_hash (e->data, size, 0);
+
+  slot = htab_find_slot_with_hash (ids->hashmap, data, hash, INSERT);
+  if (!slot)
+    {
+      free (e);
+      return false;
+    }
+
+  if (*slot)
+    {
+      free (e);
+      einfo (_("%P: warning: duplicate CodeView type record "
+	       "LF_UDT_MOD_SRC_LINE\n"));
+      return false;
+    }
+
+  *slot = e;
+
+  if (ids->last)
+    ids->last->next = e;
+  else
+    ids->first = e;
+
+  ids->last = e;
+
+  map[type_num] = e;
+
+  ids->num_types++;
+
+  return true;
+}
+
 /* Parse a type definition in the .debug$T section.  We remap the numbers
    of any referenced types, and if the type is not a duplicate of one
    already seen add it to types (for TPI types) or ids (for IPI types).  */
 static bool
 handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
 	     uint32_t num_types, struct types *types,
-	     struct types *ids)
+	     struct types *ids, uint16_t mod_num,
+	     struct string_table *strings)
 {
   uint16_t size, type;
   void **slot;
@@ -2069,6 +2213,10 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
 	break;
       }
 
+    case LF_UDT_SRC_LINE:
+      return handle_udt_src_line (data, size, map, type_num, num_types,
+				  ids, mod_num, strings);
+
     default:
       einfo (_("%P: warning: unrecognized CodeView type %v\n"), type);
       return false;
@@ -2098,6 +2246,8 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
       else
 	e->cv_hash = crc32 (data, size);
 
+      e->has_udt_src_line = false;
+
       memcpy (e->data, data, size);
 
       if (t->last)
@@ -2123,7 +2273,8 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
    found to handle_type.  */
 static bool
 handle_debugt_section (asection *s, bfd *mod, struct types *types,
-		       struct types *ids)
+		       struct types *ids, uint16_t mod_num,
+		       struct string_table *strings)
 {
   bfd_byte *data = NULL;
   size_t off;
@@ -2180,7 +2331,8 @@ handle_debugt_section (asection *s, bfd *mod, struct types *types,
 
       size = bfd_getl16 (data + off);
 
-      if (!handle_type (data + off, map, type_num, num_types, types, ids))
+      if (!handle_type (data + off, map, type_num, num_types, types, ids,
+			mod_num, strings))
 	{
 	  free (data);
 	  free (map);
@@ -2206,7 +2358,7 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 			uint32_t *c13_info_size,
 			struct mod_source_files *mod_source,
 			bfd *abfd, struct types *types,
-			struct types *ids)
+			struct types *ids, uint16_t mod_num)
 {
   uint8_t int_buf[sizeof (uint32_t)];
   uint8_t *c13_info = NULL;
@@ -2230,7 +2382,7 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 	}
       else if (!strcmp (s->name, ".debug$T") && s->size >= sizeof (uint32_t))
 	{
-	  if (!handle_debugt_section (s, mod, types, ids))
+	  if (!handle_debugt_section (s, mod, types, ids, mod_num, strings))
 	    {
 	      free (c13_info);
 	      free (mod_source->files);
@@ -2365,7 +2517,7 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
       if (!populate_module_stream (stream, in, &sym_byte_size,
 				   strings, &c13_info_size,
 				   &source->mods[mod_num], abfd,
-				   types, ids))
+				   types, ids, mod_num))
 	{
 	  for (unsigned int i = 0; i < source->mod_count; i++)
 	    {
diff --git a/ld/pdb.h b/ld/pdb.h
index 7d87a3fef07..668f3e168fd 100644
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -59,6 +59,8 @@
 #define LF_BUILDINFO			0x1603
 #define LF_SUBSTR_LIST			0x1604
 #define LF_STRING_ID			0x1605
+#define LF_UDT_SRC_LINE			0x1606
+#define LF_UDT_MOD_SRC_LINE		0x1607
 
 #define LF_CHAR				0x8000
 #define LF_SHORT			0x8001
@@ -517,6 +519,27 @@ struct lf_mfunc_id
   char name[];
 } ATTRIBUTE_PACKED;
 
+/* lfUdtSrcLine in cvinfo.h */
+struct lf_udt_src_line
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t type;
+  uint32_t source_file_type;
+  uint32_t line_no;
+} ATTRIBUTE_PACKED;
+
+/* lfUdtModSrcLine in cvinfo.h */
+struct lf_udt_mod_src_line
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t type;
+  uint32_t source_file_string;
+  uint32_t line_no;
+  uint16_t module_no;
+} ATTRIBUTE_PACKED;
+
 extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
 
 #endif
diff --git a/ld/testsuite/ld-pe/pdb-types3-hashlist.d b/ld/testsuite/ld-pe/pdb-types3-hashlist.d
new file mode 100644
index 00000000000..4a3775be42a
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types3-hashlist.d
@@ -0,0 +1,5 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 d4d90000 0c1c0000                    *
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types3-skiplist.d b/ld/testsuite/ld-pe/pdb-types3-skiplist.d
new file mode 100644
index 00000000000..52c10fa501d
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types3-skiplist.d
@@ -0,0 +1,5 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 00100000 00000000                    *
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types3-typelist.d b/ld/testsuite/ld-pe/pdb-types3-typelist.d
new file mode 100644
index 00000000000..d6ffaadb246
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types3-typelist.d
@@ -0,0 +1,7 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 0e000516 00000000 666f6f2e 6800f2f1  ........foo.h...
+ 0010 10000716 01100000 01000000 2a000000  ............*...
+ 0020 0100                                 ..              
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-types3a.s b/ld/testsuite/ld-pe/pdb-types3a.s
new file mode 100644
index 00000000000..def001e52f7
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types3a.s
@@ -0,0 +1,57 @@
+.equ CV_SIGNATURE_C13, 4
+.equ T_INT4, 0x0074
+
+.equ LF_FIELDLIST, 0x1203
+.equ LF_STRUCTURE, 0x1505
+.equ LF_MEMBER, 0x150d
+.equ LF_STRING_ID, 0x1605
+.equ LF_UDT_SRC_LINE, 0x1606
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, fieldlist for struct foo
+.fieldlist1:
+.short .struct1 - .fieldlist1 - 2
+.short LF_FIELDLIST
+.short LF_MEMBER
+.short 3 # public
+.long T_INT4
+.short 0 # offset
+.asciz "num"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1001, struct foo
+.struct1:
+.short .string1 - .struct1 - 2
+.short LF_STRUCTURE
+.short 1 # no. members
+.short 0 # property
+.long 0x1000 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 4 # size
+.asciz "foo" # name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1002, string "foo"
+.string1:
+.short .udtsrcline1 - .string1 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "foo.h"
+.byte 0xf2
+.byte 0xf1
+
+# Type 1003, UDT source line for type 1001
+.udtsrcline1:
+.short .types_end - .udtsrcline1 - 2
+.short LF_UDT_SRC_LINE
+.long 0x1001
+.long 0x1002 # source file string
+.long 42 # line no.
+
+.types_end:
diff --git a/ld/testsuite/ld-pe/pdb-types3b.s b/ld/testsuite/ld-pe/pdb-types3b.s
new file mode 100644
index 00000000000..a22234e221f
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-types3b.s
@@ -0,0 +1,68 @@
+.equ CV_SIGNATURE_C13, 4
+
+.equ T_LONG, 0x0012
+.equ T_INT4, 0x0074
+
+.equ LF_MODIFIER, 0x1001
+.equ LF_FIELDLIST, 0x1203
+.equ LF_STRUCTURE, 0x1505
+.equ LF_MEMBER, 0x150d
+.equ LF_STRING_ID, 0x1605
+.equ LF_UDT_SRC_LINE, 0x1606
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, const long
+.mod1:
+.short .fieldlist1 - .mod1 - 2
+.short LF_MODIFIER
+.long T_LONG
+.short 1 # const
+.short 0 # padding
+
+# Type 1001, fieldlist for struct foo
+.fieldlist1:
+.short .struct1 - .fieldlist1 - 2
+.short LF_FIELDLIST
+.short LF_MEMBER
+.short 3 # public
+.long T_INT4
+.short 0 # offset
+.asciz "num"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1002, struct foo
+.struct1:
+.short .string1 - .struct1 - 2
+.short LF_STRUCTURE
+.short 1 # no. members
+.short 0 # property
+.long 0x1001 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 4 # size
+.asciz "foo" # name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1003, string "foo"
+.string1:
+.short .udtsrcline1 - .string1 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "foo.h"
+.byte 0xf2
+.byte 0xf1
+
+# Type 1004, UDT source line for type 1002
+.udtsrcline1:
+.short .types_end - .udtsrcline1 - 2
+.short LF_UDT_SRC_LINE
+.long 0x1002
+.long 0x1003 # source file string
+.long 42 # line no.
+
+.types_end:
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index 9753216f544..1a5416890d1 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -1319,9 +1319,142 @@ proc test6 { } {
     }
 }
 
+proc test7 { } {
+    global as
+    global ar
+    global ld
+    global objdump
+    global srcdir
+    global subdir
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-types3a.s tmpdir/pdb-types3a.o] {
+	unsupported "Build pdb-types3a.o"
+	return
+    }
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-types3b.s tmpdir/pdb-types3b.o] {
+	unsupported "Build pdb-types3b.o"
+	return
+    }
+
+    if ![ld_link $ld "tmpdir/pdb-types3.exe" "--pdb=tmpdir/pdb-types3.pdb tmpdir/pdb-types3a.o tmpdir/pdb-types3b.o"] {
+	unsupported "Create PE image with PDB file"
+	return
+    }
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types3.pdb 0004"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract IPI stream"
+	return
+    } else {
+	pass "Extracted IPI stream"
+    }
+
+    set fi [open tmpdir/0004]
+    fconfigure $fi -translation binary
+
+    seek $fi 16 current
+
+    set data [read $fi 4]
+    binary scan $data i type_list_size
+
+    set data [read $fi 2]
+    binary scan $data s hash_stream_index
+
+    seek $fi 10 current
+
+    set data [read $fi 4]
+    binary scan $data i hash_list_offset
+
+    set data [read $fi 4]
+    binary scan $data i hash_list_size
+
+    set data [read $fi 4]
+    binary scan $data i skip_list_offset
+
+    set data [read $fi 4]
+    binary scan $data i skip_list_size
+
+    seek $fi 8 current
+
+    set type_list [read $fi $type_list_size]
+
+    close $fi
+
+    set fi [open tmpdir/pdb-types3-typelist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $type_list
+    close $fi
+
+    # check type list
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types3-typelist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types3-typelist"]
+    if ![string match $exp $got] {
+	fail "Incorrect type list in IPI stream."
+    } else {
+	pass "Correct type list in IPI stream."
+    }
+
+    # extract hash list and skip list
+
+    set index_str [format "%04x" $hash_stream_index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-types3.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract IPI hash stream."
+    } else {
+	pass "Extracted IPI hash stream."
+    }
+
+    set fi [open tmpdir/$index_str]
+    fconfigure $fi -translation binary
+
+    seek $fi $hash_list_offset
+    set hash_list [read $fi $hash_list_size]
+
+    seek $fi $skip_list_offset
+    set skip_list [read $fi $skip_list_size]
+
+    close $fi
+
+    # check hash list
+
+    set fi [open tmpdir/pdb-types3-hashlist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $hash_list
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types3-hashlist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types3-hashlist"]
+    if ![string match $exp $got] {
+	fail "Incorrect hash list in IPI stream."
+    } else {
+	pass "Correct hash list in IPI stream."
+    }
+
+    # check skip list
+
+    set fi [open tmpdir/pdb-types3-skiplist w]
+    fconfigure $fi -translation binary
+    puts -nonewline $fi $skip_list
+    close $fi
+
+    set exp [file_contents "$srcdir/$subdir/pdb-types3-skiplist.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/pdb-types3-skiplist"]
+    if ![string match $exp $got] {
+	fail "Incorrect skip list in IPI stream."
+    } else {
+	pass "Correct skip list in IPI stream."
+    }
+}
+
 test1
 test2
 test3
 test4
 test5
 test6
+test7
-- 
2.37.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] ld: Fix segfault in populate_publics_stream
  2022-11-28 17:53       ` Mark Harmstone
@ 2022-11-29  9:00         ` Jan Beulich
  2022-11-29 17:47           ` Mark Harmstone
  0 siblings, 1 reply; 19+ messages in thread
From: Jan Beulich @ 2022-11-29  9:00 UTC (permalink / raw)
  To: Mark Harmstone; +Cc: binutils

On 28.11.2022 18:53, Mark Harmstone wrote:
>  > Out of curiosity - which tree was this diff generated against? The
>  > line number here looks to be off by several hundred from what I
>  > see in the repo right now.
> 
> This is inteded to be applied with the other patches in the series, the ones
> beginning with "[PATCH v2] ld: Generate PDB string table" at
> https://sourceware.org/pipermail/binutils/2022-November/thread.html.
> 
> Sorry, this probably wasn't obvious from a mail client. I've not numbered
> them as I'm not sure how many more there'll be, and if I wait for the previous
> patches to be accepted before submitting the next, I'll almost certainly miss
> the cut-off for the code freeze.

Such dependencies, if not otherwise obvious (like in a properly threaded
and numbered series) need calling out in a post-commit-message remark.

>  > Why / when would in->outsymbols be NULL but in->symcount be non-zero?
> 
> Try running the test in the DEBUG_S_LINES patch without this one - it'll fail
> because ld segfaults. outsymbols doesn't get set from within generate_reloc
> for the second object file, as it only has one non-loadable section. The
> "symbols" come from the .equs I'm using like #defines.

And then why would such symbols not need emitting debug info for? What
you say above and ...

>  > And if that was possible, why would it not also be possible that the
>  > array is smaller than in->symcount?
> 
> bfd_generic_link_read_symbols is called for each loadable section, and
> allocates the outsymbols array once. It was my mistake when I submitted my
> original patch for populate_publics_stream, in not realizing that it would
> break for object files without any loadable sections.

... here makes me think that assuming it is the right thing to do, it
wants not only properly describing in the patch, but should actually be
accompanied by a code comment.

Jan

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] ld: Fix segfault in populate_publics_stream
  2022-11-29  9:00         ` Jan Beulich
@ 2022-11-29 17:47           ` Mark Harmstone
  2022-11-30  7:00             ` Jan Beulich
  0 siblings, 1 reply; 19+ messages in thread
From: Mark Harmstone @ 2022-11-29 17:47 UTC (permalink / raw)
  To: Jan Beulich; +Cc: binutils

 > it ... should actually be accompanied by a code comment.

Not really, as it was a straightforward mistake in my original patch. If I'd
included a NULL check to begin with, I doubt anyone would have batted an
eyelid.

I could have easily worked around this in my test, either by adding in a dummy
section or by using the preprocessor rather than .equ, but figured that any
way of causing a segfault is a bug that needs to be fixed.

Mark

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] ld: Fix segfault in populate_publics_stream
  2022-11-29 17:47           ` Mark Harmstone
@ 2022-11-30  7:00             ` Jan Beulich
  0 siblings, 0 replies; 19+ messages in thread
From: Jan Beulich @ 2022-11-30  7:00 UTC (permalink / raw)
  To: Mark Harmstone; +Cc: binutils

On 29.11.2022 18:47, Mark Harmstone wrote:
>  > it ... should actually be accompanied by a code comment.
> 
> Not really, as it was a straightforward mistake in my original patch. If I'd
> included a NULL check to begin with, I doubt anyone would have batted an
> eyelid.

Maybe. Depends on how close a review would have been done.

> I could have easily worked around this in my test, either by adding in a dummy
> section or by using the preprocessor rather than .equ, but figured that any
> way of causing a segfault is a bug that needs to be fixed.

Of course. 

Jan

^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH] ld: Write globals stream in PDB
  2022-11-29  0:10         ` [PATCH] ld: Parse LF_UDT_SRC_LINE records when creating PDB file Mark Harmstone
@ 2022-12-05  1:53           ` Mark Harmstone
  2022-12-05  1:53             ` [PATCH] ld: Copy other symbols into PDB file Mark Harmstone
                               ` (2 more replies)
  0 siblings, 3 replies; 19+ messages in thread
From: Mark Harmstone @ 2022-12-05  1:53 UTC (permalink / raw)
  To: binutils; +Cc: Mark Harmstone

Parses the .debug$S sections of object files for DEBUG_S_SYMBOLS
subsections, creating a globals stream based on the records we find.
These also get copied into the module stream for each object file.

The globals stream lists the addresses and types of procedures and
global variables - similar to the publics stream, but using the unmangled
names.

---
 ld/pdb.c                                | 1133 +++++++++++++++++++++--
 ld/pdb.h                                |   77 ++
 ld/testsuite/ld-pe/pdb-syms1-globals.d  |   57 ++
 ld/testsuite/ld-pe/pdb-syms1-records.d  |   61 ++
 ld/testsuite/ld-pe/pdb-syms1-symbols1.d |    8 +
 ld/testsuite/ld-pe/pdb-syms1-symbols2.d |   56 ++
 ld/testsuite/ld-pe/pdb-syms1a.s         |  110 +++
 ld/testsuite/ld-pe/pdb-syms1b.s         |  737 +++++++++++++++
 ld/testsuite/ld-pe/pdb.exp              |  164 ++++
 9 files changed, 2306 insertions(+), 97 deletions(-)
 create mode 100644 ld/testsuite/ld-pe/pdb-syms1-globals.d
 create mode 100644 ld/testsuite/ld-pe/pdb-syms1-records.d
 create mode 100644 ld/testsuite/ld-pe/pdb-syms1-symbols1.d
 create mode 100644 ld/testsuite/ld-pe/pdb-syms1-symbols2.d
 create mode 100644 ld/testsuite/ld-pe/pdb-syms1a.s
 create mode 100644 ld/testsuite/ld-pe/pdb-syms1b.s

diff --git a/ld/pdb.c b/ld/pdb.c
index 6326e018666..268e2521b03 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -88,6 +88,24 @@ struct types
   struct type_entry *last;
 };
 
+struct global
+{
+  struct global *next;
+  uint32_t offset;
+  uint32_t hash;
+  uint32_t refcount;
+  unsigned int index;
+  uint8_t data[];
+};
+
+struct globals
+{
+  uint32_t num_entries;
+  struct global *first;
+  struct global *last;
+  htab_t hashmap;
+};
+
 static const uint32_t crc_table[] =
 {
   0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
@@ -747,50 +765,697 @@ add_string (char *str, size_t len, struct string_table *strings)
       s = (struct string *) *slot;
     }
 
-  return s->offset;
-}
+  return s->offset;
+}
+
+/* Return the hash of an entry in the string table.  */
+static hashval_t
+hash_string_table_entry (const void *p)
+{
+  const struct string *s = (const struct string *) p;
+
+  return s->hash;
+}
+
+/* Compare an entry in the string table with a string.  */
+static int
+eq_string_table_entry (const void *a, const void *b)
+{
+  const struct string *s1 = (const struct string *) a;
+  const char *s2 = (const char *) b;
+  size_t s2_len = strlen (s2);
+
+  if (s2_len != s1->len)
+    return 0;
+
+  return memcmp (s1->s, s2, s2_len) == 0;
+}
+
+/* Parse the string table within the .debug$S section.  */
+static void
+parse_string_table (bfd_byte *data, size_t size,
+		    struct string_table *strings)
+{
+  while (true)
+    {
+      size_t len = strnlen ((char *) data, size);
+
+      add_string ((char *) data, len, strings);
+
+      data += len + 1;
+
+      if (size <= len + 1)
+	break;
+
+      size -= len + 1;
+    }
+}
+
+/* Remap a type reference within a CodeView symbol.  */
+static bool remap_symbol_type (void *data, struct type_entry **map,
+			       uint32_t num_types)
+{
+  uint32_t type = bfd_getl32 (data);
+
+  /* Ignore builtin types (those with IDs below 0x1000).  */
+  if (type < TPI_FIRST_INDEX)
+    return true;
+
+  if (type >= TPI_FIRST_INDEX + num_types)
+    {
+      einfo (_("%P: CodeView symbol references out of range type %v\n"),
+	       type);
+      return false;
+    }
+
+  type = TPI_FIRST_INDEX + map[type - TPI_FIRST_INDEX]->index;
+  bfd_putl32 (type, data);
+
+  return true;
+}
+
+/* Add an entry into the globals stream.  If it already exists, increase
+   the refcount.  */
+static bool
+add_globals_ref (struct globals *glob, bfd *sym_rec_stream, const char *name,
+		 size_t name_len, uint8_t *data, size_t len)
+{
+  void **slot;
+  uint32_t hash;
+  struct global *g;
+
+  slot = htab_find_slot_with_hash (glob->hashmap, data,
+				   iterative_hash (data, len, 0), INSERT);
+
+  if (*slot)
+    {
+      g = *slot;
+      g->refcount++;
+      return true;
+    }
+
+  *slot = xmalloc (offsetof (struct global, data) + len);
+
+  hash = crc32 ((const uint8_t *) name, name_len);
+  hash %= NUM_GLOBALS_HASH_BUCKETS;
+
+  g = *slot;
+  g->next = NULL;
+  g->offset = bfd_tell (sym_rec_stream);
+  g->hash = hash;
+  g->refcount = 1;
+  memcpy (g->data, data, len + 1);
+
+  glob->num_entries++;
+
+  if (glob->last)
+    glob->last->next = g;
+  else
+    glob->first = g;
+
+  glob->last = g;
+
+  return bfd_bwrite (data, len, sym_rec_stream) == len;
+}
+
+/* Find the end of the current scope within symbols data.  */
+static uint8_t *
+find_end_of_scope (uint8_t *data, uint32_t size)
+{
+  unsigned int scope_level = 1;
+  uint16_t len;
+
+  len = bfd_getl16 (data) + sizeof (uint16_t);
+
+  data += len;
+  size -= len;
+
+  while (true)
+    {
+      uint16_t type;
+
+      if (size < sizeof (uint32_t))
+	return NULL;
+
+      len = bfd_getl16 (data) + sizeof (uint16_t);
+      type = bfd_getl16 (data + sizeof (uint16_t));
+
+      if (size < len)
+	return NULL;
+
+      switch (type)
+	{
+	case S_GPROC32:
+	case S_LPROC32:
+	  scope_level++;
+	  break;
+
+	case S_END:
+	case S_PROC_ID_END:
+	  scope_level--;
+
+	  if (scope_level == 0)
+	    return data;
+
+	  break;
+	}
+
+      data += len;
+      size -= len;
+    }
+}
+
+/* Return the size of an extended value parameter, as used in
+   LF_ENUMERATE etc.  */
+static unsigned int
+extended_value_len (uint16_t type)
+{
+  switch (type)
+    {
+    case LF_CHAR:
+      return 1;
+
+    case LF_SHORT:
+    case LF_USHORT:
+      return 2;
+
+    case LF_LONG:
+    case LF_ULONG:
+      return 4;
+
+    case LF_QUADWORD:
+    case LF_UQUADWORD:
+      return 8;
+    }
+
+  return 0;
+}
+
+/* Parse the symbols in a .debug$S section, and copy them to the module's
+   symbol stream.  */
+static bool
+parse_symbols (uint8_t *data, uint32_t size, uint8_t **buf,
+	       struct type_entry **map, uint32_t num_types,
+	       bfd *sym_rec_stream, struct globals *glob, uint16_t mod_num)
+{
+  uint8_t *orig_buf = *buf;
+  unsigned int scope_level = 0;
+
+  while (size >= sizeof (uint16_t))
+    {
+      uint16_t len, type;
+
+      len = bfd_getl16 (data) + sizeof (uint16_t);
+
+      if (len > size)
+	{
+	  bfd_set_error (bfd_error_bad_value);
+	  return false;
+	}
+
+      type = bfd_getl16 (data + sizeof (uint16_t));
+
+      switch (type)
+	{
+	case S_LDATA32:
+	case S_GDATA32:
+	case S_LTHREAD32:
+	case S_GTHREAD32:
+	  {
+	    struct datasym *d = (struct datasym *) data;
+	    size_t name_len;
+
+	    if (len < offsetof (struct datasym, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			" S_LDATA32/S_GDATA32/S_LTHREAD32/S_GTHREAD32\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (scope_level == 0)
+	      {
+		uint16_t section = bfd_getl16 (&d->section);
+
+		if (section == 0) /* GC'd, ignore */
+		  break;
+	      }
+
+	    name_len =
+	      strnlen (d->name, len - offsetof (struct datasym, name));
+
+	    if (name_len == len - offsetof (struct datasym, name))
+	      {
+		einfo (_("%P: warning: name for S_LDATA32/S_GDATA32/"
+			 "S_LTHREAD32/S_GTHREAD32 has no terminating"
+			 " zero\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (!remap_symbol_type (&d->type, map, num_types))
+	      {
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    /* If S_LDATA32 or S_LTHREAD32, copy into module symbols.  */
+
+	    if (type == S_LDATA32 || type == S_LTHREAD32)
+	      {
+		memcpy (*buf, d, len);
+		*buf += len;
+	      }
+
+	    /* S_LDATA32 and S_LTHREAD32 only go in globals if
+	       not in function scope.  */
+	    if (type == S_GDATA32 || type == S_GTHREAD32 || scope_level == 0)
+	      {
+		if (!add_globals_ref (glob, sym_rec_stream, d->name,
+				      name_len, data, len))
+		  return false;
+	      }
+
+	    break;
+	  }
+
+	case S_GPROC32:
+	case S_LPROC32:
+	case S_GPROC32_ID:
+	case S_LPROC32_ID:
+	  {
+	    struct procsym *proc = (struct procsym *) data;
+	    size_t name_len;
+	    uint16_t section;
+	    uint32_t end;
+	    uint8_t *endptr;
+	    size_t ref_size, padding;
+	    struct refsym *ref;
+
+	    if (len < offsetof (struct procsym, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			" S_GPROC32/S_LPROC32\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    section = bfd_getl16 (&proc->section);
+
+	    endptr = find_end_of_scope (data, size);
+
+	    if (!endptr)
+	      {
+		einfo (_("%P: warning: could not find end of"
+			" S_GPROC32/S_LPROC32 record\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (section == 0) /* skip if GC'd */
+	      {
+		/* Skip to after S_END.  */
+
+		size -= endptr - data;
+		data = endptr;
+
+		len = bfd_getl16 (data) + sizeof (uint16_t);
+
+		data += len;
+		size -= len;
+
+		continue;
+	      }
+
+	    name_len =
+	      strnlen (proc->name, len - offsetof (struct procsym, name));
+
+	    if (name_len == len - offsetof (struct procsym, name))
+	      {
+		einfo (_("%P: warning: name for S_GPROC32/S_LPROC32 has no"
+			  " terminating zero\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (type == S_GPROC32_ID || type == S_LPROC32_ID)
+	      {
+		/* Transform into S_GPROC32 / S_LPROC32.  */
+
+		uint32_t t_idx = bfd_getl32 (&proc->type);
+		struct type_entry *t;
+		uint16_t t_type;
+
+		if (t_idx < TPI_FIRST_INDEX
+		    || t_idx >= TPI_FIRST_INDEX + num_types)
+		  {
+		    einfo (_("%P: CodeView symbol references out of range"
+			     " type %v\n"), type);
+		    bfd_set_error (bfd_error_bad_value);
+		    return false;
+		  }
+
+		t = map[t_idx - TPI_FIRST_INDEX];
+
+		t_type = bfd_getl16 (t->data + sizeof (uint16_t));
+
+		switch (t_type)
+		  {
+		  case LF_FUNC_ID:
+		    {
+		      struct lf_func_id *t_data =
+			(struct lf_func_id *) t->data;
+
+		      /* Replace proc->type with function type.  */
+
+		      memcpy (&proc->type, &t_data->function_type,
+			      sizeof (uint32_t));
+
+		      break;
+		    }
+
+		  case LF_MFUNC_ID:
+		    {
+		      struct lf_mfunc_id *t_data =
+			(struct lf_mfunc_id *) t->data;
+
+		      /* Replace proc->type with function type.  */
+
+		      memcpy (&proc->type, &t_data->function_type,
+			      sizeof (uint32_t));
+
+		      break;
+		    }
+
+		  default:
+		    einfo (_("%P: CodeView S_GPROC32_ID/S_LPROC32_ID symbol"
+			     " referenced unknown type as ID\n"));
+		    bfd_set_error (bfd_error_bad_value);
+		    return false;
+		  }
+
+		/* Change record type.  */
+
+		if (type == S_GPROC32_ID)
+		  bfd_putl32 (S_GPROC32, &proc->kind);
+		else
+		  bfd_putl32 (S_LPROC32, &proc->kind);
+	      }
+	    else
+	      {
+		if (!remap_symbol_type (&proc->type, map, num_types))
+		  {
+		    bfd_set_error (bfd_error_bad_value);
+		    return false;
+		  }
+	      }
+
+	    end = *buf - orig_buf + sizeof (uint32_t) + endptr - data;
+	    bfd_putl32 (end, &proc->end);
+
+	    /* Add S_PROCREF / S_LPROCREF to globals stream.  */
+
+	    ref_size = offsetof (struct refsym, name) + name_len + 1;
+
+	    if (ref_size % sizeof (uint32_t))
+	      padding = sizeof (uint32_t) - (ref_size % sizeof (uint32_t));
+	    else
+	      padding = 0;
+
+	    ref = xmalloc (ref_size + padding);
+
+	    bfd_putl16 (ref_size + padding - sizeof (uint16_t), &ref->size);
+	    bfd_putl16 (type == S_GPROC32 || type == S_GPROC32_ID ?
+			S_PROCREF : S_LPROCREF, &ref->kind);
+	    bfd_putl32 (0, &ref->sum_name);
+	    bfd_putl32 (*buf - orig_buf + sizeof (uint32_t),
+			&ref->symbol_offset);
+	    bfd_putl16 (mod_num + 1, &ref->mod);
+
+	    memcpy (ref->name, proc->name, name_len + 1);
+
+	    memset (ref->name + name_len + 1, 0, padding);
+
+	    if (!add_globals_ref (glob, sym_rec_stream, proc->name, name_len,
+				  (uint8_t *) ref, ref_size + padding))
+	      {
+		free (ref);
+		return false;
+	      }
+
+	    free (ref);
+
+	    memcpy (*buf, proc, len);
+	    *buf += len;
+
+	    scope_level++;
+
+	    break;
+	  }
+
+	case S_UDT:
+	  {
+	    struct udtsym *udt = (struct udtsym *) data;
+	    size_t name_len;
+
+	    if (len < offsetof (struct udtsym, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			" S_UDT\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    name_len =
+	      strnlen (udt->name, len - offsetof (struct udtsym, name));
+
+	    if (name_len == len - offsetof (struct udtsym, name))
+	      {
+		einfo (_("%P: warning: name for S_UDT has no"
+			 " terminating zero\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (!remap_symbol_type (&udt->type, map, num_types))
+	      {
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    /* S_UDT goes in the symbols stream if within a procedure,
+	       otherwise it goes in the globals stream.  */
+	    if (scope_level == 0)
+	      {
+		if (!add_globals_ref (glob, sym_rec_stream, udt->name,
+				      name_len, data, len))
+		  return false;
+	      }
+	    else
+	      {
+		memcpy (*buf, udt, len);
+		*buf += len;
+	      }
+
+	    break;
+	  }
+
+	case S_CONSTANT:
+	  {
+	    struct constsym *c = (struct constsym *) data;
+	    size_t name_len, rec_size;
+	    uint16_t val;
+
+	    if (len < offsetof (struct constsym, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			 " S_CONSTANT\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    rec_size = offsetof (struct constsym, name);
+
+	    val = bfd_getl16 (&c->value);
+
+	    /* If val >= 0x8000, actual value follows.  */
+	    if (val >= 0x8000)
+	      {
+		unsigned int param_len = extended_value_len (val);
+
+		if (param_len == 0)
+		  {
+		    einfo (_("%P: warning: unhandled type %v within"
+			     " S_CONSTANT\n"), val);
+		    bfd_set_error (bfd_error_bad_value);
+		    return false;
+		  }
+
+		rec_size += param_len;
+	      }
+
+	    name_len =
+	      strnlen ((const char *) data + rec_size, len - rec_size);
+
+	    if (name_len == len - rec_size)
+	      {
+		einfo (_("%P: warning: name for S_CONSTANT has no"
+			 " terminating zero\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (!remap_symbol_type (&c->type, map, num_types))
+	      {
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (!add_globals_ref (glob, sym_rec_stream,
+				  (const char *) data + rec_size, name_len,
+				  data, len))
+	      return false;
+
+	    break;
+	  }
+
+	case S_END:
+	case S_PROC_ID_END:
+	  memcpy (*buf, data, len);
+
+	  if (type == S_PROC_ID_END) /* transform to S_END */
+	    bfd_putl16 (S_END, *buf + sizeof (uint16_t));
+
+	  *buf += len;
+	  scope_level--;
+	  break;
+
+	default:
+	  einfo (_("%P: warning: unrecognized CodeView record %v\n"), type);
+	  bfd_set_error (bfd_error_bad_value);
+	  return false;
+	}
+
+      data += len;
+      size -= len;
+    }
+
+  return true;
+}
+
+/* For a given symbol subsection, work out how much space to allocate in the
+   result module stream.  This is different because we don't copy certain
+   symbols, such as S_CONSTANT, and we skip over any procedures or data that
+   have been GC'd out.  */
+static bool
+calculate_symbols_size (uint8_t *data, uint32_t size, uint32_t *sym_size)
+{
+  unsigned int scope_level = 0;
+
+  while (size >= sizeof (uint32_t))
+    {
+      uint16_t len = bfd_getl16 (data) + sizeof (uint16_t);
+      uint16_t type = bfd_getl16 (data + sizeof (uint16_t));
+
+      switch (type)
+	{
+	case S_LDATA32:
+	case S_LTHREAD32:
+	  {
+	    struct datasym *d = (struct datasym *) data;
+	    uint16_t section;
+
+	    if (len < offsetof (struct datasym, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			" S_LDATA32/S_LTHREAD32\n"));
+		return false;
+	      }
+
+	    section = bfd_getl16 (&d->section);
+
+	    /* copy if not GC'd or within function */
+	    if (scope_level != 0 || section != 0)
+	      *sym_size += len;
+	  }
+
+	case S_GDATA32:
+	case S_GTHREAD32:
+	case S_CONSTANT:
+	  /* Not copied into symbols stream.  */
+	  break;
+
+	case S_GPROC32:
+	case S_LPROC32:
+	case S_GPROC32_ID:
+	case S_LPROC32_ID:
+	  {
+	    struct procsym *proc = (struct procsym *) data;
+	    uint16_t section;
+
+	    if (len < offsetof (struct procsym, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			" S_GPROC32/S_LPROC32\n"));
+		return false;
+	      }
+
+	    section = bfd_getl16 (&proc->section);
+
+	    if (section != 0)
+	      {
+		*sym_size += len;
+	      }
+	    else
+	      {
+		uint8_t *endptr = find_end_of_scope (data, size);
+
+		if (!endptr)
+		  {
+		    einfo (_("%P: warning: could not find end of"
+			    " S_GPROC32/S_LPROC32 record\n"));
+		    return false;
+		  }
 
-/* Return the hash of an entry in the string table.  */
-static hashval_t
-hash_string_table_entry (const void *p)
-{
-  const struct string *s = (const struct string *) p;
+		/* Skip to after S_END.  */
 
-  return s->hash;
-}
+		size -= endptr - data;
+		data = endptr;
 
-/* Compare an entry in the string table with a string.  */
-static int
-eq_string_table_entry (const void *a, const void *b)
-{
-  const struct string *s1 = (const struct string *) a;
-  const char *s2 = (const char *) b;
-  size_t s2_len = strlen (s2);
+		len = bfd_getl16 (data) + sizeof (uint16_t);
 
-  if (s2_len != s1->len)
-    return 0;
+		data += len;
+		size -= len;
 
-  return memcmp (s1->s, s2, s2_len) == 0;
-}
+		continue;
+	      }
 
-/* Parse the string table within the .debug$S section.  */
-static void
-parse_string_table (bfd_byte *data, size_t size,
-		    struct string_table *strings)
-{
-  while (true)
-    {
-      size_t len = strnlen ((char *) data, size);
+	    scope_level++;
 
-      add_string ((char *) data, len, strings);
+	    break;
+	  }
 
-      data += len + 1;
+	case S_UDT:
+	  if (scope_level != 0) /* only goes in symbols if local */
+	    *sym_size += len;
+	  break;
 
-      if (size <= len + 1)
-	break;
+	case S_END: /* always copied */
+	case S_PROC_ID_END:
+	  *sym_size += len;
+	  scope_level--;
+	  break;
 
-      size -= len + 1;
+	default:
+	  einfo (_("%P: warning: unrecognized CodeView record %v\n"), type);
+	  return false;
+	}
+
+      data += len;
+      size -= len;
     }
+
+  return true;
 }
 
 /* Parse the .debug$S section within an object file.  */
@@ -798,13 +1463,17 @@ static bool
 handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 		       uint8_t **dataptr, uint32_t *sizeptr,
 		       struct mod_source_files *mod_source,
-		       bfd *abfd)
+		       bfd *abfd, uint8_t **syms, uint32_t *sym_byte_size,
+		       struct type_entry **map, uint32_t num_types,
+		       bfd *sym_rec_stream, struct globals *glob,
+		       uint16_t mod_num)
 {
   bfd_byte *data = NULL;
   size_t off;
   uint32_t c13_size = 0;
   char *string_table = NULL;
-  uint8_t *buf, *bufptr;
+  uint8_t *buf, *bufptr, *symbuf, *symbufptr;
+  uint32_t sym_size = 0;
 
   if (!bfd_get_full_section_contents (mod, s, &data))
     return false;
@@ -943,6 +1612,16 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 
 	    break;
 	  }
+
+	case DEBUG_S_SYMBOLS:
+	  if (!calculate_symbols_size (data + off, size, &sym_size))
+	    {
+	      free (data);
+	      bfd_set_error (bfd_error_bad_value);
+	      return false;
+	    }
+
+	  break;
 	}
 
       off += size;
@@ -951,7 +1630,10 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 	off += sizeof (uint32_t) - (off % sizeof (uint32_t));
     }
 
-  if (c13_size == 0)
+  if (sym_size % sizeof (uint32_t))
+    sym_size += sizeof (uint32_t) - (sym_size % sizeof (uint32_t));
+
+  if (c13_size == 0 && sym_size == 0)
     {
       free (data);
       return true;
@@ -959,8 +1641,25 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 
   /* copy data */
 
-  buf = xmalloc (c13_size);
-  bufptr = buf;
+  if (c13_size != 0)
+    {
+      buf = xmalloc (c13_size);
+      bufptr = buf;
+    }
+  else
+    {
+      buf = NULL;
+    }
+
+  if (sym_size != 0)
+    {
+      symbuf = xmalloc (sym_size);
+      symbufptr = symbuf;
+    }
+  else
+    {
+      symbuf = NULL;
+    }
 
   off = sizeof (uint32_t);
 
@@ -982,6 +1681,7 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 				mod_source))
 	    {
 	      free (data);
+	      free (symbuf);
 	      return false;
 	    }
 
@@ -1010,6 +1710,17 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 
 	    break;
 	  }
+
+	case DEBUG_S_SYMBOLS:
+	  if (!parse_symbols (data + off, size, &symbufptr, map, num_types,
+			      sym_rec_stream, glob, mod_num))
+	    {
+	      free (data);
+	      free (symbuf);
+	      return false;
+	    }
+
+	  break;
 	}
 
       off += size;
@@ -1020,22 +1731,42 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 
   free (data);
 
-  if (*dataptr)
+  if (buf)
     {
-      /* Append the C13 info to what's already there, if the module has
-	 multiple .debug$S sections.  */
+      if (*dataptr)
+	{
+	  /* Append the C13 info to what's already there, if the module has
+	    multiple .debug$S sections.  */
 
-      *dataptr = xrealloc (*dataptr, *sizeptr + c13_size);
-      memcpy (*dataptr + *sizeptr, buf, c13_size);
+	  *dataptr = xrealloc (*dataptr, *sizeptr + c13_size);
+	  memcpy (*dataptr + *sizeptr, buf, c13_size);
 
-      free (buf);
+	  free (buf);
+	}
+      else
+	{
+	  *dataptr = buf;
+	}
+
+      *sizeptr += c13_size;
     }
-  else
+
+  if (symbuf)
     {
-      *dataptr = buf;
-    }
+      if (*syms)
+	{
+	  *syms = xrealloc (*syms, *sym_byte_size + sym_size);
+	  memcpy (*syms + *sym_byte_size, symbuf, sym_size);
+
+	  free (symbuf);
+	}
+      else
+	{
+	  *syms = symbuf;
+	}
 
-  *sizeptr += c13_size;
+      *sym_byte_size += sym_size;
+    }
 
   return true;
 }
@@ -1455,33 +2186,14 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
 		  /* If val >= 0x8000, the actual value immediate follows.  */
 		  if (val >= 0x8000)
 		    {
-		      size_t param_len;
-
-		      switch (val) {
-			case LF_CHAR:
-			  param_len = 1;
-			  break;
+		      unsigned int param_len = extended_value_len (val);
 
-			case LF_SHORT:
-			case LF_USHORT:
-			  param_len = 2;
-			  break;
-
-			case LF_LONG:
-			case LF_ULONG:
-			  param_len = 4;
-			  break;
-
-			case LF_QUADWORD:
-			case LF_UQUADWORD:
-			  param_len = 8;
-			  break;
-
-			default:
+		      if (param_len == 0)
+			{
 			  einfo (_("%P: warning: unhandled type %v within"
 				   " LF_ENUMERATE\n"), val);
 			  return false;
-		      }
+			}
 
 		      if (left < subtype_len + param_len)
 			{
@@ -2274,12 +2986,11 @@ handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
 static bool
 handle_debugt_section (asection *s, bfd *mod, struct types *types,
 		       struct types *ids, uint16_t mod_num,
-		       struct string_table *strings)
+		       struct string_table *strings,
+		       struct type_entry ***map, uint32_t *num_types)
 {
   bfd_byte *data = NULL;
   size_t off;
-  unsigned int num_types = 0;
-  struct type_entry **map;
   uint32_t type_num;
 
   if (!bfd_get_full_section_contents (mod, s, &data))
@@ -2310,17 +3021,17 @@ handle_debugt_section (asection *s, bfd *mod, struct types *types,
 	  return false;
 	}
 
-      num_types++;
+      (*num_types)++;
       off += size;
     }
 
-  if (num_types == 0)
+  if (*num_types == 0)
     {
       free (data);
       return true;
     }
 
-  map = xcalloc (num_types, sizeof (struct type_entry *));
+  *map = xcalloc (*num_types, sizeof (struct type_entry *));
 
   off = sizeof (uint32_t);
   type_num = 0;
@@ -2331,11 +3042,11 @@ handle_debugt_section (asection *s, bfd *mod, struct types *types,
 
       size = bfd_getl16 (data + off);
 
-      if (!handle_type (data + off, map, type_num, num_types, types, ids,
+      if (!handle_type (data + off, *map, type_num, *num_types, types, ids,
 			mod_num, strings))
 	{
 	  free (data);
-	  free (map);
+	  free (*map);
 	  bfd_set_error (bfd_error_bad_value);
 	  return false;
 	}
@@ -2345,7 +3056,6 @@ handle_debugt_section (asection *s, bfd *mod, struct types *types,
     }
 
   free (data);
-  free (map);
 
   return true;
 }
@@ -2358,39 +3068,57 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 			uint32_t *c13_info_size,
 			struct mod_source_files *mod_source,
 			bfd *abfd, struct types *types,
-			struct types *ids, uint16_t mod_num)
+			struct types *ids, uint16_t mod_num,
+			bfd *sym_rec_stream, struct globals *glob)
 {
   uint8_t int_buf[sizeof (uint32_t)];
   uint8_t *c13_info = NULL;
+  uint8_t *syms = NULL;
+  struct type_entry **map = NULL;
+  uint32_t num_types = 0;
 
-  *sym_byte_size = sizeof (uint32_t);
+  *sym_byte_size = 0;
   *c13_info_size = 0;
 
-  /* Process .debug$S section(s).  */
+  /* Process .debug$T section.  */
 
   for (asection *s = mod->sections; s; s = s->next)
     {
-      if (!strcmp (s->name, ".debug$S") && s->size >= sizeof (uint32_t))
+      if (!strcmp (s->name, ".debug$T") && s->size >= sizeof (uint32_t))
 	{
-	  if (!handle_debugs_section (s, mod, strings, &c13_info,
-				      c13_info_size, mod_source, abfd))
+	  if (!handle_debugt_section (s, mod, types, ids, mod_num, strings,
+				      &map, &num_types))
 	    {
-	      free (c13_info);
 	      free (mod_source->files);
 	      return false;
 	    }
+
+	  break;
 	}
-      else if (!strcmp (s->name, ".debug$T") && s->size >= sizeof (uint32_t))
+    }
+
+  /* Process .debug$S section(s).  */
+
+  for (asection *s = mod->sections; s; s = s->next)
+    {
+      if (!strcmp (s->name, ".debug$S") && s->size >= sizeof (uint32_t))
 	{
-	  if (!handle_debugt_section (s, mod, types, ids, mod_num, strings))
+	  if (!handle_debugs_section (s, mod, strings, &c13_info,
+				      c13_info_size, mod_source, abfd,
+				      &syms, sym_byte_size, map, num_types,
+				      sym_rec_stream, glob, mod_num))
 	    {
 	      free (c13_info);
+	      free (syms);
 	      free (mod_source->files);
+	      free (map);
 	      return false;
 	    }
 	}
     }
 
+  free (map);
+
   /* Write the signature.  */
 
   bfd_putl32 (CV_SIGNATURE_C13, int_buf);
@@ -2398,9 +3126,23 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
   if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
     {
       free (c13_info);
+      free (syms);
       return false;
     }
 
+  if (syms)
+    {
+      if (bfd_bwrite (syms, *sym_byte_size, stream)
+	  != *sym_byte_size)
+	{
+	  free (c13_info);
+	  free (syms);
+	  return false;
+	}
+
+      free (syms);
+    }
+
   if (c13_info)
     {
       if (bfd_bwrite (c13_info, *c13_info_size, stream)
@@ -2428,7 +3170,8 @@ static bool
 create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 			      uint32_t *size, struct string_table *strings,
 			      struct source_files_info *source,
-			      struct types *types, struct types *ids)
+			      struct types *types, struct types *ids,
+			      bfd *sym_rec_stream, struct globals *glob)
 {
   uint8_t *ptr;
   unsigned int mod_num;
@@ -2517,7 +3260,8 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
       if (!populate_module_stream (stream, in, &sym_byte_size,
 				   strings, &c13_info_size,
 				   &source->mods[mod_num], abfd,
-				   types, ids, mod_num))
+				   types, ids, mod_num,
+				   sym_rec_stream, glob))
 	{
 	  for (unsigned int i = 0; i < source->mod_count; i++)
 	    {
@@ -2545,7 +3289,7 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 
       bfd_putl16 (0, &mod->flags);
       bfd_putl16 (stream_num, &mod->module_sym_stream);
-      bfd_putl32 (sym_byte_size, &mod->sym_byte_size);
+      bfd_putl32 (sizeof (uint32_t) + sym_byte_size, &mod->sym_byte_size);
       bfd_putl32 (0, &mod->c11_byte_size);
       bfd_putl32 (c13_info_size, &mod->c13_byte_size);
       bfd_putl16 (0, &mod->source_file_count);
@@ -2833,6 +3577,170 @@ create_source_info_substream (void **data, uint32_t *size,
     }
 }
 
+/* Used as parameter to qsort, to sort globals by hash.  */
+static int
+global_compare_hash (const void *s1, const void *s2)
+{
+  const struct global *g1 = *(const struct global **) s1;
+  const struct global *g2 = *(const struct global **) s2;
+
+  if (g1->hash < g2->hash)
+    return -1;
+  if (g1->hash > g2->hash)
+    return 1;
+
+  return 0;
+}
+
+/* Create the globals stream, which contains the unmangled symbol names.  */
+static bool create_globals_stream (bfd *pdb, struct globals *glob,
+				   uint16_t *stream_num)
+{
+  bfd *stream;
+  struct globals_hash_header h;
+  uint32_t buckets_size, filled_buckets = 0;
+  struct global **sorted = NULL;
+  bool ret = false;
+  struct global *buckets[NUM_GLOBALS_HASH_BUCKETS];
+  char int_buf[sizeof (uint32_t)];
+
+  stream = add_stream (pdb, NULL, stream_num);
+  if (!stream)
+    return false;
+
+  memset (buckets, 0, sizeof (buckets));
+
+  if (glob->num_entries > 0)
+    {
+      struct global *g;
+
+      /* Create an array of pointers, sorted by hash value.  */
+
+      sorted = xmalloc (sizeof (struct global *) * glob->num_entries);
+
+      g = glob->first;
+      for (unsigned int i = 0; i < glob->num_entries; i++)
+	{
+	  sorted[i] = g;
+	  g = g->next;
+	}
+
+      qsort (sorted, glob->num_entries, sizeof (struct global *),
+	     global_compare_hash);
+
+      /* Populate the buckets.  */
+
+      for (unsigned int i = 0; i < glob->num_entries; i++)
+	{
+	  if (!buckets[sorted[i]->hash])
+	    {
+	      buckets[sorted[i]->hash] = sorted[i];
+	      filled_buckets++;
+	    }
+
+	  sorted[i]->index = i;
+	}
+    }
+
+  buckets_size = NUM_GLOBALS_HASH_BUCKETS / 8;
+  buckets_size += sizeof (uint32_t);
+  buckets_size += filled_buckets * sizeof (uint32_t);
+
+  bfd_putl32 (GLOBALS_HASH_SIGNATURE, &h.signature);
+  bfd_putl32 (GLOBALS_HASH_VERSION_70, &h.version);
+  bfd_putl32 (glob->num_entries * sizeof (struct hash_record),
+	      &h.entries_size);
+  bfd_putl32 (buckets_size, &h.buckets_size);
+
+  if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h))
+    return false;
+
+  /* Write hash entries, sorted by hash.  */
+
+  for (unsigned int i = 0; i < glob->num_entries; i++)
+    {
+      struct hash_record hr;
+
+      bfd_putl32 (sorted[i]->offset + 1, &hr.offset);
+      bfd_putl32 (sorted[i]->refcount, &hr.reference);
+
+      if (bfd_bwrite (&hr, sizeof (hr), stream) != sizeof (hr))
+	goto end;
+    }
+
+  /* Write the bitmap for filled and unfilled buckets.  */
+
+  for (unsigned int i = 0; i < NUM_GLOBALS_HASH_BUCKETS; i += 8)
+    {
+      uint8_t v = 0;
+
+      for (unsigned int j = 0; j < 8; j++)
+	{
+	  if (buckets[i + j])
+	    v |= 1 << j;
+	}
+
+      if (bfd_bwrite (&v, sizeof (v), stream) != sizeof (v))
+	goto end;
+    }
+
+  /* Add a 4-byte gap.  */
+
+  bfd_putl32 (0, int_buf);
+
+  if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
+    goto end;
+
+  /* Write the bucket offsets.  */
+
+  for (unsigned int i = 0; i < NUM_GLOBALS_HASH_BUCKETS; i++)
+    {
+      if (buckets[i])
+	{
+	  /* 0xc is size of internal hash_record structure in
+	     Microsoft's parser.  */
+	  bfd_putl32 (buckets[i]->index * 0xc, int_buf);
+
+	  if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) !=
+	      sizeof (uint32_t))
+	    goto end;
+	}
+    }
+
+  ret = true;
+
+end:
+  free (sorted);
+
+  return ret;
+}
+
+/* Hash an entry in the globals list.  */
+static hashval_t
+hash_global_entry (const void *p)
+{
+  const struct global *g = (const struct global *) p;
+  uint16_t len = bfd_getl16 (g->data);
+
+  return iterative_hash (g->data, len, 0);
+}
+
+/* Compare an entry in the globals list with a symbol.  */
+static int
+eq_global_entry (const void *a, const void *b)
+{
+  const struct global *g = (const struct global *) a;
+  uint16_t len1, len2;
+
+  len1 = bfd_getl16 (g->data) + sizeof (uint16_t);
+  len2 = bfd_getl16 (b) + sizeof (uint16_t);
+
+  if (len1 != len2)
+    return 0;
+
+  return !memcmp (g->data, b, len1);
+}
+
 /* Stream 4 is the debug information (DBI) stream.  */
 static bool
 populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
@@ -2841,20 +3749,51 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
 		     uint16_t publics_stream_num,
 		     struct string_table *strings,
 		     struct types *types,
-		     struct types *ids)
+		     struct types *ids,
+		     bfd *sym_rec_stream)
 {
   struct pdb_dbi_stream_header h;
   struct optional_dbg_header opt;
   void *mod_info, *sc, *source_info;
   uint32_t mod_info_size, sc_size, source_info_size;
   struct source_files_info source;
+  struct globals glob;
+  uint16_t globals_stream_num;
 
   source.mod_count = 0;
   source.mods = NULL;
 
+  glob.num_entries = 0;
+  glob.first = NULL;
+  glob.last = NULL;
+
+  glob.hashmap = htab_create_alloc (0, hash_global_entry,
+				    eq_global_entry, free,
+				    xcalloc, free);
+
   if (!create_module_info_substream (abfd, pdb, &mod_info, &mod_info_size,
-				     strings, &source, types, ids))
-    return false;
+				     strings, &source, types, ids,
+				     sym_rec_stream, &glob))
+    {
+      htab_delete (glob.hashmap);
+      return false;
+    }
+
+  if (!create_globals_stream (pdb, &glob, &globals_stream_num))
+    {
+      htab_delete (glob.hashmap);
+
+      for (unsigned int i = 0; i < source.mod_count; i++)
+	{
+	  free (source.mods[i].files);
+	}
+      free (source.mods);
+
+      free (mod_info);
+      return false;
+    }
+
+  htab_delete (glob.hashmap);
 
   if (!create_section_contrib_substream (abfd, &sc, &sc_size))
     {
@@ -2880,7 +3819,7 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
   bfd_putl32 (0xffffffff, &h.version_signature);
   bfd_putl32 (DBI_STREAM_VERSION_70, &h.version_header);
   bfd_putl32 (1, &h.age);
-  bfd_putl16 (0xffff, &h.global_stream_index);
+  bfd_putl16 (globals_stream_num, &h.global_stream_index);
   bfd_putl16 (0x8e1d, &h.build_number); // MSVC 14.29
   bfd_putl16 (publics_stream_num, &h.public_stream_index);
   bfd_putl16 (0, &h.pdb_dll_version);
@@ -3528,7 +4467,7 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
 
   if (!populate_dbi_stream (dbi_stream, abfd, pdb, section_header_stream_num,
 			    sym_rec_stream_num, publics_stream_num,
-			    &strings, &types, &ids))
+			    &strings, &types, &ids, sym_rec_stream))
     {
       einfo (_("%P: warning: cannot populate DBI stream "
 	       "in PDB file: %E\n"));
diff --git a/ld/pdb.h b/ld/pdb.h
index 668f3e168fd..3f516f8a5f8 100644
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -70,7 +70,21 @@
 #define LF_QUADWORD			0x8009
 #define LF_UQUADWORD			0x800a
 
+#define S_END				0x0006
+#define S_CONSTANT			0x1107
+#define S_UDT				0x1108
+#define S_LDATA32			0x110c
+#define S_GDATA32 			0x110d
 #define S_PUB32				0x110e
+#define S_LPROC32			0x110f
+#define S_GPROC32			0x1110
+#define S_LTHREAD32			0x1112
+#define S_GTHREAD32			0x1113
+#define S_PROCREF			0x1125
+#define S_LPROCREF			0x1127
+#define S_LPROC32_ID			0x1146
+#define S_GPROC32_ID			0x1147
+#define S_PROC_ID_END			0x114f
 
 /* PDBStream70 in pdb1.h */
 struct pdb_stream_70
@@ -109,6 +123,8 @@ struct pdb_tpi_stream_header
 #define TPI_FIRST_INDEX			0x1000
 #define NUM_TPI_HASH_BUCKETS		0x3ffff
 
+#define NUM_GLOBALS_HASH_BUCKETS	4096
+
 /* NewDBIHdr in dbi.h */
 struct pdb_dbi_stream_header
 {
@@ -198,6 +214,7 @@ struct optional_dbg_header
 
 #define CV_SIGNATURE_C13		4
 
+#define DEBUG_S_SYMBOLS			0xf1
 #define DEBUG_S_LINES			0xf2
 #define DEBUG_S_STRINGTABLE		0xf3
 #define DEBUG_S_FILECHKSMS		0xf4
@@ -540,6 +557,66 @@ struct lf_udt_mod_src_line
   uint16_t module_no;
 } ATTRIBUTE_PACKED;
 
+/* DATASYM32 in cvinfo.h */
+struct datasym
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t type;
+  uint32_t offset;
+  uint16_t section;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* PROCSYM32 in cvinfo.h */
+struct procsym
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t parent;
+  uint32_t end;
+  uint32_t next;
+  uint32_t proc_len;
+  uint32_t debug_start;
+  uint32_t debug_end;
+  uint32_t type;
+  uint32_t offset;
+  uint16_t section;
+  uint8_t flags;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* REFSYM2 in cvinfo.h */
+struct refsym
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t sum_name;
+  uint32_t symbol_offset;
+  uint16_t mod;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* UDTSYM in cvinfo.h */
+struct udtsym
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t type;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* CONSTSYM in cvinfo.h */
+struct constsym
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t type;
+  uint16_t value;
+  /* then actual value if value >= 0x8000 */
+  char name[];
+} ATTRIBUTE_PACKED;
+
 extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
 
 #endif
diff --git a/ld/testsuite/ld-pe/pdb-syms1-globals.d b/ld/testsuite/ld-pe/pdb-syms1-globals.d
new file mode 100644
index 00000000000..356c5de22a3
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-syms1-globals.d
@@ -0,0 +1,57 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 ffffffff 1a092ff1 d0000000 64020000  ....../.....d...
+ 0010 fd000000 01000000 35010000 01000000  ........5.......
+ 0020 15000000 01000000 19010000 01000000  ................
+ 0030 c1000000 01000000 b9010000 01000000  ................
+ 0040 d5000000 01000000 61010000 01000000  ........a.......
+ 0050 e9000000 01000000 11020000 01000000  ................
+ 0060 e9010000 01000000 d5010000 01000000  ................
+ 0070 49010000 01000000 a5010000 01000000  I...............
+ 0080 99010000 01000000 85000000 01000000  ................
+ 0090 01000000 01000000 99000000 01000000  ................
+ 00a0 7d010000 01000000 ad000000 01000000  }...............
+ 00b0 5d000000 01000000 49000000 01000000  ].......I.......
+ 00c0 21000000 02000000 35000000 01000000  !.......5.......
+ 00d0 fd010000 01000000 71000000 01000000  ........q.......
+ 00e0 00000000 00000000 00000000 00000000  ................
+ 00f0 00000000 00000000 00000000 00000000  ................
+ 0100 00000000 00000000 00000000 00000000  ................
+ 0110 00000000 00000000 00000000 00000000  ................
+ 0120 00000000 00000000 00000000 00000000  ................
+ 0130 00000000 00000000 00000000 00000000  ................
+ 0140 00000000 00000000 00000000 00000000  ................
+ 0150 00000002 00000000 00000000 00000000  ................
+ 0160 00000000 00000000 00000000 00000000  ................
+ 0170 00000000 00020000 00000000 00000000  ................
+ 0180 00000000 00000000 00000000 00000000  ................
+ 0190 00000000 00000001 00000000 00000000  ................
+ 01a0 00000000 00000000 00000000 00000000  ................
+ 01b0 00000000 00000000 00000000 00000000  ................
+ 01c0 00000000 00000000 00000000 00000000  ................
+ 01d0 00000000 00000000 00000000 00000000  ................
+ 01e0 08001000 00000000 00000000 00000040  ...............@
+ 01f0 04002000 00000000 00000000 00000000  .. .............
+ 0200 00000000 00000001 00000000 00000000  ................
+ 0210 00000000 00000000 00000000 00000000  ................
+ 0220 00000000 00000000 00000000 00000000  ................
+ 0230 00000000 00000000 00080000 00004000  ..............@.
+ 0240 00000000 00000000 00100010 00000000  ................
+ 0250 40000000 00000000 00000000 00000000  @...............
+ 0260 00000000 00000800 00000000 00000000  ................
+ 0270 00000008 00000000 00000000 00000000  ................
+ 0280 00000000 02004000 00000000 00000000  ......@.........
+ 0290 00000000 00008000 00000000 00000000  ................
+ 02a0 00000000 00000000 10000000 00000000  ................
+ 02b0 00000000 00000000 00000000 00800000  ................
+ 02c0 00000000 00000000 00002000 00010000  .......... .....
+ 02d0 00000000 00000000 00000000 00000040  ...............@
+ 02e0 00000000 00000000 0c000000 18000000  ................
+ 02f0 24000000 30000000 3c000000 48000000  $...0...<...H...
+ 0300 54000000 60000000 6c000000 78000000  T...`...l...x...
+ 0310 84000000 90000000 9c000000 a8000000  ................
+ 0320 b4000000 c0000000 d8000000 e4000000  ................
+ 0330 f0000000 fc000000 08010000 20010000  ............ ...
+ 0340 2c010000                             ,...            
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-syms1-records.d b/ld/testsuite/ld-pe/pdb-syms1-records.d
new file mode 100644
index 00000000000..bbf6d7f158e
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-syms1-records.d
@@ -0,0 +1,61 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 12002511 00000000 04000000 01007072  ..%...........pr
+ 0010 6f633200 0a000811 04100000 62617200  oc2.........bar.
+ 0020 12000c11 00100000 08000000 02006c76  ..............lv
+ 0030 61723100 12000c11 00100000 0c000000  ar1.............
+ 0040 02006c76 61723100 12000c11 00100000  ..lvar1.........
+ 0050 00000000 03006c76 61723200 12000d11  ......lvar2.....
+ 0060 00100000 18000000 02006776 61723100  ..........gvar1.
+ 0070 12000d11 00100000 04000000 03006776  ..............gv
+ 0080 61723200 12002511 00000000 54000000  ar2...%.....T...
+ 0090 02007072 6f633100 12002511 00000000  ..proc1...%.....
+ 00a0 88000000 02007072 6f633200 12002711  ......proc2...'.
+ 00b0 00000000 f0000000 02007072 6f633300  ..........proc3.
+ 00c0 12002711 00000000 24010000 02007072  ..'.....$.....pr
+ 00d0 6f633400 12002511 00000000 58010000  oc4...%.....X...
+ 00e0 02007072 6f633500 12002511 00000000  ..proc5...%.....
+ 00f0 8c010000 02007072 6f633600 1a002511  ......proc6...%.
+ 0100 00000000 c0010000 0200666f 6f3a3a6d  ..........foo::m
+ 0110 6574686f 64000000 1a002511 00000000  ethod.....%.....
+ 0120 f8010000 0200666f 6f3a3a6d 6574686f  ......foo::metho
+ 0130 64320000 12002711 00000000 30020000  d2....'.....0...
+ 0140 02007072 6f633900 16002511 00000000  ..proc9...%.....
+ 0150 64020000 02007072 6f633130 00000000  d.....proc10....
+ 0160 1a002711 00000000 98020000 0200666f  ..'...........fo
+ 0170 6f3a3a6d 6574686f 64330000 1a002711  o::method3....'.
+ 0180 00000000 d0020000 0200666f 6f3a3a6d  ..........foo::m
+ 0190 6574686f 64340000 0a000811 0a100000  ethod4..........
+ 01a0 666f6f00 12000711 75000000 2a00616e  foo.....u...*.an
+ 01b0 73776572 00f3f2f1 1a000711 23000000  swer........#...
+ 01c0 0a80efcd ab896745 2301616e 73776572  ......gE#.answer
+ 01d0 3200f2f1 12001211 00100000 20000000  2........... ...
+ 01e0 02006c76 61723500 12001211 00100000  ..lvar5.........
+ 01f0 12000000 03006c76 61723600 12001311  ......lvar6.....
+ 0200 00100000 1c000000 02006776 61723300  ..........gvar3.
+ 0210 12001311 00100000 08000000 03006776  ..............gv
+ 0220 61723400 12000e11 00000000 18000000  ar4.............
+ 0230 02006776 61723100 12000e11 00000000  ..gvar1.........
+ 0240 04000000 03006776 61723200 12000e11  ......gvar2.....
+ 0250 00000000 0c000000 03007072 6f633100  ..........proc1.
+ 0260 12000e11 02000000 06000000 01007072  ..............pr
+ 0270 6f633200 12000e11 00000000 0d000000  oc2.............
+ 0280 03007072 6f633300 12000e11 02000000  ..proc3.........
+ 0290 07000000 01007072 6f633400 12000e11  ......proc4.....
+ 02a0 00000000 0e000000 03007072 6f633500  ..........proc5.
+ 02b0 12000e11 02000000 08000000 01007072  ..............pr
+ 02c0 6f633600 12000e11 00000000 0f000000  oc6.............
+ 02d0 03007072 6f633700 12000e11 02000000  ..proc7.........
+ 02e0 09000000 01007072 6f633800 12000e11  ......proc8.....
+ 02f0 00000000 10000000 03007072 6f633900  ..........proc9.
+ 0300 16000e11 02000000 0a000000 01007072  ..............pr
+ 0310 6f633130 00000000 16000e11 00000000  oc10............
+ 0320 11000000 03007072 6f633131 00000000  ......proc11....
+ 0330 16000e11 02000000 0b000000 01007072  ..............pr
+ 0340 6f633132 00000000 12000e11 00000000  oc12............
+ 0350 1c000000 02006776 61723300 12000e11  ......gvar3.....
+ 0360 00000000 08000000 03006776 61723400  ..........gvar4.
+ 0370 12000e11 02000000 00000000 01006d61  ..............ma
+ 0380 696e0000                             in..            
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-syms1-symbols1.d b/ld/testsuite/ld-pe/pdb-syms1-symbols1.d
new file mode 100644
index 00000000000..4de22acbd08
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-syms1-symbols1.d
@@ -0,0 +1,8 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 04000000 2e001011 00000000 34000000  ............4...
+ 0010 00000000 01000000 00000000 00000000  ................
+ 0020 02100000 06000000 01000070 726f6332  ...........proc2
+ 0030 00f3f2f1 02000600 00000000           ............    
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-syms1-symbols2.d b/ld/testsuite/ld-pe/pdb-syms1-symbols2.d
new file mode 100644
index 00000000000..f134637a744
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-syms1-symbols2.d
@@ -0,0 +1,56 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 04000000 12000c11 00100000 08000000  ................
+ 0010 02006c76 61723100 12000c11 00100000  ..lvar1.........
+ 0020 08000000 02006c76 61723100 12000c11  ......lvar1.....
+ 0030 00100000 0c000000 02006c76 61723100  ..........lvar1.
+ 0040 12000c11 00100000 00000000 03006c76  ..............lv
+ 0050 61723200 2e001011 00000000 84000000  ar2.............
+ 0060 00000000 01000000 00000000 00000000  ................
+ 0070 05100000 0c000000 03000070 726f6331  ...........proc1
+ 0080 00f3f2f1 02000600 2e001011 00000000  ................
+ 0090 ec000000 00000000 01000000 00000000  ................
+ 00a0 00000000 05100000 06000000 01000070  ...............p
+ 00b0 726f6332 00f3f2f1 0a000811 04100000  roc2............
+ 00c0 62617200 12000c11 00100000 10000000  bar.............
+ 00d0 02006c76 61723300 12001211 00100000  ..lvar3.........
+ 00e0 14000000 02006c76 61723400 02000600  ......lvar4.....
+ 00f0 2e000f11 00000000 20010000 00000000  ........ .......
+ 0100 01000000 00000000 00000000 05100000  ................
+ 0110 0d000000 03000070 726f6333 00f3f2f1  .......proc3....
+ 0120 02000600 2e000f11 00000000 54010000  ............T...
+ 0130 00000000 01000000 00000000 00000000  ................
+ 0140 05100000 07000000 01000070 726f6334  ...........proc4
+ 0150 00f3f2f1 02000600 2e001011 00000000  ................
+ 0160 88010000 00000000 01000000 00000000  ................
+ 0170 00000000 05100000 0e000000 03000070  ...............p
+ 0180 726f6335 00f3f2f1 02000600 2e001011  roc5............
+ 0190 00000000 bc010000 00000000 01000000  ................
+ 01a0 00000000 00000000 05100000 08000000  ................
+ 01b0 01000070 726f6336 00f3f2f1 02000600  ...proc6........
+ 01c0 32001011 00000000 f4010000 00000000  2...............
+ 01d0 01000000 00000000 00000000 05100000  ................
+ 01e0 0f000000 03000066 6f6f3a3a 6d657468  .......foo::meth
+ 01f0 6f6400f1 02000600 32001011 00000000  od......2.......
+ 0200 2c020000 00000000 01000000 00000000  ,...............
+ 0210 00000000 05100000 09000000 01000066  ...............f
+ 0220 6f6f3a3a 6d657468 6f643200 02000600  oo::method2.....
+ 0230 2e000f11 00000000 60020000 00000000  ........`.......
+ 0240 01000000 00000000 00000000 05100000  ................
+ 0250 10000000 03000070 726f6339 00f3f2f1  .......proc9....
+ 0260 02000600 2e001011 00000000 94020000  ................
+ 0270 00000000 01000000 00000000 00000000  ................
+ 0280 05100000 0a000000 01000070 726f6331  ...........proc1
+ 0290 3000f2f1 02000600 32000f11 00000000  0.......2.......
+ 02a0 cc020000 00000000 01000000 00000000  ................
+ 02b0 00000000 05100000 11000000 03000066  ...............f
+ 02c0 6f6f3a3a 6d657468 6f643300 02000600  oo::method3.....
+ 02d0 32000f11 00000000 04030000 00000000  2...............
+ 02e0 01000000 00000000 00000000 05100000  ................
+ 02f0 0b000000 01000066 6f6f3a3a 6d657468  .......foo::meth
+ 0300 6f643400 02000600 12001211 00100000  od4.............
+ 0310 20000000 02006c76 61723500 12001211   .....lvar5.....
+ 0320 00100000 12000000 03006c76 61723600  ..........lvar6.
+ 0330 00000000                             ....            
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-syms1a.s b/ld/testsuite/ld-pe/pdb-syms1a.s
new file mode 100644
index 00000000000..c1929c3ec85
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-syms1a.s
@@ -0,0 +1,110 @@
+.equ CV_SIGNATURE_C13, 4
+.equ DEBUG_S_SYMBOLS, 0xf1
+
+.equ T_UINT4, 0x0075
+
+.equ LF_MODIFIER, 0x1001
+.equ LF_PROCEDURE, 0x1008
+.equ LF_ARGLIST, 0x1201
+.equ LF_FIELDLIST, 0x1203
+.equ LF_STRUCTURE, 0x1505
+.equ LF_MEMBER, 0x150d
+
+.equ S_END, 0x0006
+.equ S_UDT, 0x1108
+.equ S_GPROC32, 0x1110
+
+.section ".debug$S", "rn"
+
+.long CV_SIGNATURE_C13
+
+.long DEBUG_S_SYMBOLS
+.long .syms_end - .syms_start
+
+.syms_start:
+
+.gproc2:
+.short .gproc2_end - .gproc2 - 2
+.short S_GPROC32
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long 1 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x1002 # type
+.secrel32 proc2
+.secidx proc2
+.byte 0 # flags
+.asciz "proc2"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.gproc2_end:
+.short .udt1 - .gproc2_end - 2
+.short S_END
+
+.udt1:
+.short .syms_end - .udt1 - 2
+.short S_UDT
+.long 0x1004 # struct bar
+.asciz "bar"
+
+.syms_end:
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, const uint32_t
+.mod1:
+.short .arglist1 - .mod1 - 2
+.short LF_MODIFIER
+.long T_UINT4
+.short 1 # const
+.p2align 2
+
+# Type 1001, arglist (uint32_t)
+.arglist1:
+.short .proctype1 - .arglist1 - 2
+.short LF_ARGLIST
+.long 1 # no. entries
+.long T_UINT4
+
+# Type 1002, procedure (return type T_VOID, arglist 1001)
+.proctype1:
+.short .fieldlist1 - .proctype1 - 2
+.short LF_PROCEDURE
+.long T_VOID
+.byte 0 # calling convention
+.byte 0 # attributes
+.short 1 # no. parameters
+.long 0x1001
+
+# Type 1003, field list for struct bar
+.fieldlist1:
+.short .struct1 - .fieldlist1 - 2
+.short LF_FIELDLIST
+.short LF_MEMBER
+.short 3 # public
+.long T_UINT4
+.short 0 # offset
+.asciz "num1"
+.byte 0xf1 # padding
+
+# Type 1004, declaration of struct bar
+.struct1:
+.short .types_end - .struct1 - 2
+.short LF_STRUCTURE
+.short 1 # no. members
+.short 0 # property
+.long 0x1003 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 4 # size
+.asciz "bar" # name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.types_end:
diff --git a/ld/testsuite/ld-pe/pdb-syms1b.s b/ld/testsuite/ld-pe/pdb-syms1b.s
new file mode 100644
index 00000000000..ddc471104b5
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-syms1b.s
@@ -0,0 +1,737 @@
+.equ CV_SIGNATURE_C13, 4
+.equ DEBUG_S_SYMBOLS, 0xf1
+
+.equ T_VOID, 0x0003
+.equ T_UQUAD, 0x0023
+.equ T_UINT4, 0x0075
+
+.equ LF_MODIFIER, 0x1001
+.equ LF_POINTER, 0x1002
+.equ LF_PROCEDURE, 0x1008
+.equ LF_MFUNCTION, 0x1009
+.equ LF_ARGLIST, 0x1201
+.equ LF_FIELDLIST, 0x1203
+.equ LF_CLASS, 0x1504
+.equ LF_STRUCTURE, 0x1505
+.equ LF_MEMBER, 0x150d
+.equ LF_ONEMETHOD, 0x1511
+.equ LF_FUNC_ID, 0x1601
+.equ LF_MFUNC_ID, 0x1602
+
+.equ LF_UQUADWORD, 0x800a
+
+.equ S_END, 0x0006
+.equ S_CONSTANT, 0x1107
+.equ S_UDT, 0x1108
+.equ S_LDATA32, 0x110c
+.equ S_GDATA32, 0x110d
+.equ S_LPROC32, 0x110f
+.equ S_GPROC32, 0x1110
+.equ S_LTHREAD32, 0x1112
+.equ S_GTHREAD32, 0x1113
+.equ S_LPROC32_ID, 0x1146
+.equ S_GPROC32_ID, 0x1147
+.equ S_PROC_ID_END, 0x114f
+
+.equ CV_PTR_64, 0xc
+
+.section ".debug$S", "rn"
+
+.long CV_SIGNATURE_C13
+
+.long DEBUG_S_SYMBOLS
+.long .syms_end - .syms_start
+
+.syms_start:
+
+.ldata1:
+.short .ldata1a - .ldata1 - 2
+.short S_LDATA32
+.long 0x1000 # const uint32_t
+.secrel32 lvar1
+.secidx lvar1
+.asciz "lvar1"
+
+.ldata1a: # duplicate with same address
+.short .ldata1b - .ldata1a - 2
+.short S_LDATA32
+.long 0x1000 # const uint32_t
+.secrel32 lvar1
+.secidx lvar1
+.asciz "lvar1"
+
+.ldata1b: # duplicate with different address
+.short .ldata2 - .ldata1b - 2
+.short S_LDATA32
+.long 0x1000 # const uint32_t
+.secrel32 lvar1a
+.secidx lvar1a
+.asciz "lvar1"
+
+.ldata2:
+.short .gdata1 - .ldata2 - 2
+.short S_LDATA32
+.long 0x1000 # const uint32_t
+.secrel32 lvar2
+.secidx lvar2
+.asciz "lvar2"
+
+.gdata1:
+.short .gdata2 - .gdata1 - 2
+.short S_GDATA32
+.long 0x1000 # const uint32_t
+.secrel32 gvar1
+.secidx gvar1
+.asciz "gvar1"
+
+.gdata2:
+.short .gproc1 - .gdata2 - 2
+.short S_GDATA32
+.long 0x1000 # const uint32_t
+.secrel32 gvar2
+.secidx gvar2
+.asciz "gvar2"
+
+.gproc1:
+.short .gproc1_end - .gproc1 - 2
+.short S_GPROC32
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc1_end - proc1 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x1002 # type
+.secrel32 proc1
+.secidx proc1
+.byte 0 # flags
+.asciz "proc1"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.gproc1_end:
+.short .gproc2 - .gproc1_end - 2
+.short S_END
+
+.gproc2:
+.short .udt1 - .gproc2 - 2
+.short S_GPROC32
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc2_end - proc2 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x1002 # type
+.secrel32 proc2
+.secidx proc2
+.byte 0 # flags
+.asciz "proc2"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.udt1:
+.short .ldata3 - .udt1 - 2
+.short S_UDT
+.long 0x1011 # struct bar
+.asciz "bar"
+
+.ldata3:
+.short .lthread1 - .ldata3 - 2
+.short S_LDATA32
+.long 0x1000 # const uint32_t
+.secrel32 lvar3
+.secidx lvar3
+.asciz "lvar3"
+
+.lthread1:
+.short .gproc2_end - .lthread1 - 2
+.short S_LTHREAD32
+.long 0x1000 # const uint32_t
+.secrel32 lvar4
+.secidx lvar4
+.asciz "lvar4"
+
+.gproc2_end:
+.short .gproc3 - .gproc2_end - 2
+.short S_END
+
+.gproc3:
+.short .gproc3_end - .gproc3 - 2
+.short S_LPROC32
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc3_end - proc3 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x1002 # type
+.secrel32 proc3
+.secidx proc3
+.byte 0 # flags
+.asciz "proc3"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.gproc3_end:
+.short .gproc4 - .gproc3_end - 2
+.short S_END
+
+.gproc4:
+.short .gproc4_end - .gproc4 - 2
+.short S_LPROC32
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc4_end - proc4 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x1002 # type
+.secrel32 proc4
+.secidx proc4
+.byte 0 # flags
+.asciz "proc4"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.gproc4_end:
+.short .gproc5 - .gproc4_end - 2
+.short S_END
+
+.gproc5:
+.short .gproc5_end - .gproc5 - 2
+.short S_GPROC32_ID
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc5_end - proc5 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x1003 # func ID
+.secrel32 proc5
+.secidx proc5
+.byte 0 # flags
+.asciz "proc5"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.gproc5_end:
+.short .gproc6 - .gproc5_end - 2
+.short S_PROC_ID_END
+
+.gproc6:
+.short .gproc6_end - .gproc6 - 2
+.short S_GPROC32_ID
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc6_end - proc6 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x1004 # func ID
+.secrel32 proc6
+.secidx proc6
+.byte 0 # flags
+.asciz "proc6"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.gproc6_end:
+.short .gproc7 - .gproc6_end - 2
+.short S_PROC_ID_END
+
+.gproc7:
+.short .gproc7_end - .gproc7 - 2
+.short S_GPROC32_ID
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc7_end - proc7 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x100a # func ID
+.secrel32 proc7
+.secidx proc7
+.byte 0 # flags
+.asciz "foo::method"
+.byte 0xf1 # padding
+
+.gproc7_end:
+.short .gproc8 - .gproc7_end - 2
+.short S_PROC_ID_END
+
+.gproc8:
+.short .gproc8_end - .gproc8 - 2
+.short S_GPROC32_ID
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc8_end - proc8 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x100b # func ID
+.secrel32 proc8
+.secidx proc8
+.byte 0 # flags
+.asciz "foo::method2"
+
+.gproc8_end:
+.short .gproc9 - .gproc8_end - 2
+.short S_PROC_ID_END
+
+.gproc9:
+.short .gproc9_end - .gproc9 - 2
+.short S_LPROC32_ID
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc9_end - proc9 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x100c # func ID
+.secrel32 proc9
+.secidx proc9
+.byte 0 # flags
+.asciz "proc9"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.gproc9_end:
+.short .gproc10 - .gproc9_end - 2
+.short S_PROC_ID_END
+
+.gproc10:
+.short .gproc10_end - .gproc10 - 2
+.short S_GPROC32_ID
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc10_end - proc10 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x100d # func ID
+.secrel32 proc10
+.secidx proc10
+.byte 0 # flags
+.asciz "proc10"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.gproc10_end:
+.short .gproc11 - .gproc10_end - 2
+.short S_PROC_ID_END
+
+.gproc11:
+.short .gproc11_end - .gproc11 - 2
+.short S_LPROC32_ID
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc11_end - proc11 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x100e # func ID
+.secrel32 proc11
+.secidx proc11
+.byte 0 # flags
+.asciz "foo::method3"
+
+.gproc11_end:
+.short .gproc12 - .gproc11_end - 2
+.short S_PROC_ID_END
+
+.gproc12:
+.short .gproc12_end - .gproc12 - 2
+.short S_LPROC32_ID
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc12_end - proc12 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x100f # func ID
+.secrel32 proc12
+.secidx proc12
+.byte 0 # flags
+.asciz "foo::method4"
+
+.gproc12_end:
+.short .udt2 - .gproc12_end - 2
+.short S_PROC_ID_END
+
+.udt2:
+.short .constant1 - .udt2 - 2
+.short S_UDT
+.long 0x1009 # class foo
+.asciz "foo"
+
+.constant1:
+.short .constant2 - .constant1 - 2
+.short S_CONSTANT
+.long T_UINT4
+.short 42
+.asciz "answer"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.constant2:
+.short .lthread2 - .constant2 - 2
+.short S_CONSTANT
+.long T_UQUAD
+.short LF_UQUADWORD
+.quad 0x0123456789abcdef
+.asciz "answer2"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.lthread2:
+.short .lthread3 - .lthread2 - 2
+.short S_LTHREAD32
+.long 0x1000 # const uint32_t
+.secrel32 lvar5
+.secidx lvar5
+.asciz "lvar5"
+
+.lthread3:
+.short .gthread1 - .lthread3 - 2
+.short S_LTHREAD32
+.long 0x1000 # const uint32_t
+.secrel32 lvar6
+.secidx lvar6
+.asciz "lvar6"
+
+.gthread1:
+.short .gthread2 - .gthread1 - 2
+.short S_GTHREAD32
+.long 0x1000 # const uint32_t
+.secrel32 gvar3
+.secidx gvar3
+.asciz "gvar3"
+
+.gthread2:
+.short .syms_end - .gthread2 - 2
+.short S_GTHREAD32
+.long 0x1000 # const uint32_t
+.secrel32 gvar4
+.secidx gvar4
+.asciz "gvar4"
+
+.p2align 2
+.syms_end:
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, const uint32_t
+.mod1:
+.short .arglist1 - .mod1 - 2
+.short LF_MODIFIER
+.long T_UINT4
+.short 1 # const
+.p2align 2
+
+# Type 1001, arglist (uint32_t)
+.arglist1:
+.short .proctype1 - .arglist1 - 2
+.short LF_ARGLIST
+.long 1 # no. entries
+.long T_UINT4
+
+# Type 1002, procedure (return type T_VOID, arglist 1001)
+.proctype1:
+.short .funcid1 - .proctype1 - 2
+.short LF_PROCEDURE
+.long T_VOID
+.byte 0 # calling convention
+.byte 0 # attributes
+.short 1 # no. parameters
+.long 0x1001
+
+# Type 1003, func ID for proc5
+.funcid1:
+.short .funcid2 - .funcid1 - 2
+.short LF_FUNC_ID
+.long 0 # parent scope
+.long 0x1002 # type
+.asciz "proc5"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1004, func ID for proc6
+.funcid2:
+.short .class1 - .funcid2 - 2
+.short LF_FUNC_ID
+.long 0 # parent scope
+.long 0x1002 # type
+.asciz "proc6"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1005, forward declaration of class foo
+.class1:
+.short .ptr1 - .class1 - 2
+.short LF_CLASS
+.short 0 # no. members
+.short 0x80 # property (forward declaration)
+.long 0 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 0 # size
+.asciz "foo" # name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1006, pointer to 1005
+.ptr1:
+.short .mfunction1 - .ptr1 - 2
+.short LF_POINTER
+.long 0x1005
+.long (8 << 13) | CV_PTR_64
+
+# Type 1007, member function of 1005, return type void, arg list 1001
+.mfunction1:
+.short .fieldlist1 - .mfunction1 - 2
+.short LF_MFUNCTION
+.long T_VOID
+.long 0x1005
+.long 0x1006 # type of "this" pointer
+.byte 0 # calling convention
+.byte 0 # attributes
+.short 1 # no. parameters
+.long 0x1001 # arg list
+.long 0 # "this" adjustment
+
+# Type 1008, field list for class foo
+.fieldlist1:
+.short .class2 - .fieldlist1 - 2
+.short LF_FIELDLIST
+.short LF_ONEMETHOD
+.short 0 # method attribute
+.long 0x1007 # method type
+.asciz "method"
+.byte 0xf1 # padding
+.short LF_ONEMETHOD
+.short 0 # method attribute
+.long 0x1007 # method type
+.asciz "method2"
+.short LF_ONEMETHOD
+.short 0 # method attribute
+.long 0x1007 # method type
+.asciz "method3"
+.short LF_ONEMETHOD
+.short 0 # method attribute
+.long 0x1007 # method type
+.asciz "method4"
+
+# Type 1009, actual declaration of class foo
+.class2:
+.short .mfunc1 - .class2 - 2
+.short LF_CLASS
+.short 0 # no. members
+.short 0 # property
+.long 0x1008 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 0 # size
+.asciz "foo" # name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 100a, function "method" within class "foo"
+.mfunc1:
+.short .mfunc2 - .mfunc1 - 2
+.short LF_MFUNC_ID
+.long 0x1009 # parent class
+.long 0x1002 # function type
+.asciz "method"
+.byte 0xf1 # padding
+
+# Type 100b, function "method2" within class "foo"
+.mfunc2:
+.short .funcid3 - .mfunc2 - 2
+.short LF_MFUNC_ID
+.long 0x1009 # parent class
+.long 0x1002 # function type
+.asciz "method2"
+
+# Type 100c, func ID for proc9
+.funcid3:
+.short .funcid4 - .funcid3 - 2
+.short LF_FUNC_ID
+.long 0 # parent scope
+.long 0x1002 # type
+.asciz "proc9"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 100d, func ID for proc10
+.funcid4:
+.short .mfunc3 - .funcid4 - 2
+.short LF_FUNC_ID
+.long 0 # parent scope
+.long 0x1002 # type
+.asciz "proc10"
+.byte 0xf1 # padding
+
+# Type 100e, function "method3" within class "foo"
+.mfunc3:
+.short .mfunc4 - .mfunc3 - 2
+.short LF_MFUNC_ID
+.long 0x1009 # parent class
+.long 0x1002 # function type
+.asciz "method3"
+
+# Type 100f, function "method4" within class "foo"
+.mfunc4:
+.short .fieldlist2 - .mfunc4 - 2
+.short LF_MFUNC_ID
+.long 0x1009 # parent class
+.long 0x1002 # function type
+.asciz "method4"
+
+# Type 1010, field list for struct bar
+.fieldlist2:
+.short .struct1 - .fieldlist2 - 2
+.short LF_FIELDLIST
+.short LF_MEMBER
+.short 3 # public
+.long T_UINT4
+.short 0 # offset
+.asciz "num1"
+.byte 0xf1 # padding
+
+# Type 1011, declaration of struct bar
+.struct1:
+.short .types_end - .struct1 - 2
+.short LF_STRUCTURE
+.short 1 # no. members
+.short 0 # property
+.long 0x1010 # field list
+.long 0 # type derived from
+.long 0 # type of vshape table
+.short 4 # size
+.asciz "bar" # name
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.types_end:
+
+.data
+
+.long 0x12345678
+.long 0x12345678
+
+lvar1:
+.long 42
+
+lvar1a:
+.long 0x12345678
+
+lvar3:
+.long 84
+
+lvar4:
+.long 85
+
+.global gvar1
+gvar1:
+.long 43
+
+.global gvar3
+gvar3:
+.long 41
+
+lvar5:
+.long 86
+
+.text
+
+.global main
+main:
+    jmp main
+    .secrel32 .data
+
+.global proc2
+proc2:
+    nop
+.proc2_end:
+
+.global proc4
+proc4:
+    nop
+.proc4_end:
+
+.global proc6
+proc6:
+    nop
+.proc6_end:
+
+.global proc8
+proc8:
+    nop
+.proc8_end:
+
+.global proc10
+proc10:
+    nop
+.proc10_end:
+
+.global proc12
+proc12:
+    nop
+.proc12_end:
+
+.section "gcsect"
+
+lvar2:
+.long 84
+
+.global gvar2
+gvar2:
+.long 85
+
+.global gvar4
+gvar4:
+.long 86
+
+.global proc1
+proc1:
+    nop
+.proc1_end:
+
+.global proc3
+proc3:
+    nop
+.proc3_end:
+
+.global proc5
+proc5:
+    nop
+.proc5_end:
+
+.global proc7
+proc7:
+    nop
+.proc7_end:
+
+.global proc9
+proc9:
+    nop
+.proc9_end:
+
+.global proc11
+proc11:
+    nop
+.proc11_end:
+
+lvar6:
+.long 86
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index 1a5416890d1..34eafc142a7 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -1451,6 +1451,169 @@ proc test7 { } {
     }
 }
 
+proc test8 { } {
+    global as
+    global ar
+    global ld
+    global objdump
+    global srcdir
+    global subdir
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-syms1a.s tmpdir/pdb-syms1a.o] {
+	unsupported "Build pdb-syms1a.o"
+	return
+    }
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-syms1b.s tmpdir/pdb-syms1b.o] {
+	unsupported "Build pdb-syms1b.o"
+	return
+    }
+
+    if ![ld_link $ld "tmpdir/pdb-syms1.exe" "--pdb=tmpdir/pdb-syms1.pdb tmpdir/pdb-syms1a.o tmpdir/pdb-syms1b.o"] {
+	unsupported "Create PE image with PDB file"
+	return
+    }
+
+    # get index of globals stream and records stream
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms1.pdb 0003"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract DBI stream"
+	return
+    } else {
+	pass "Extracted DBI stream"
+    }
+
+    set fi [open tmpdir/0003]
+    fconfigure $fi -translation binary
+
+    seek $fi 12
+    set data [read $fi 2]
+    binary scan $data s globals_index
+
+    seek $fi 6 current
+    set data [read $fi 2]
+    binary scan $data s records_index
+
+    seek $fi 2 current
+    set data [read $fi 4]
+    binary scan $data i mod_info_size
+
+    seek $fi 36 current
+    set mod_info [read $fi $mod_info_size]
+
+    close $fi
+
+    # get index of first and second module streams
+
+    binary scan [string range $mod_info 34 35] s mod1_index
+
+    set off 64
+
+    set obj1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
+    incr off [expr [string length $obj1] + 1]
+
+    set ar1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
+    incr off [expr [string length $ar1] + 1]
+
+    if { [expr $off % 4] != 0 } {
+	set off [expr $off + 4 - ($off % 4)]
+    }
+
+    incr off 34
+
+    binary scan [string range $mod_info $off [expr $off + 1]] s mod2_index
+
+    # check globals stream
+
+    set index_str [format "%04x" $globals_index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms1.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract globals stream"
+	return
+    } else {
+	pass "Extracted globals stream"
+    }
+
+    set exp [file_contents "$srcdir/$subdir/pdb-syms1-globals.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]
+
+    if [string match $exp $got] {
+	pass "Correct globals stream"
+    } else {
+	fail "Incorrect globals stream"
+    }
+
+    # check records stream
+
+    set index_str [format "%04x" $records_index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms1.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract records stream"
+	return
+    } else {
+	pass "Extracted records stream"
+    }
+
+    set exp [file_contents "$srcdir/$subdir/pdb-syms1-records.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]
+
+    if [string match $exp $got] {
+	pass "Correct records stream"
+    } else {
+	fail "Incorrect records stream"
+    }
+
+    # check symbols in first module
+
+    set index_str [format "%04x" $mod1_index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms1.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract first module's symbols"
+	return
+    } else {
+	pass "Extracted first module's symbols"
+    }
+
+    set exp [file_contents "$srcdir/$subdir/pdb-syms1-symbols1.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]
+
+    if [string match $exp $got] {
+	pass "Correct symbols in first module's stream"
+    } else {
+	fail "Incorrect symbols in first module's stream"
+    }
+
+    # check symbols in second module
+
+    set index_str [format "%04x" $mod2_index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms1.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract second module's symbols"
+	return
+    } else {
+	pass "Extracted second module's symbols"
+    }
+
+    set exp [file_contents "$srcdir/$subdir/pdb-syms1-symbols2.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]
+
+    if [string match $exp $got] {
+	pass "Correct symbols in second module's stream"
+    } else {
+	fail "Incorrect symbols in second module's stream"
+    }
+}
+
 test1
 test2
 test3
@@ -1458,3 +1621,4 @@ test4
 test5
 test6
 test7
+test8
-- 
2.37.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH] ld: Copy other symbols into PDB file
  2022-12-05  1:53           ` [PATCH] ld: Write globals stream in PDB Mark Harmstone
@ 2022-12-05  1:53             ` Mark Harmstone
  2022-12-05  1:53             ` [PATCH] ld: Write linker symbols in PDB Mark Harmstone
  2022-12-06 17:07             ` [PATCH] ld: Write globals stream " Nick Clifton
  2 siblings, 0 replies; 19+ messages in thread
From: Mark Harmstone @ 2022-12-05  1:53 UTC (permalink / raw)
  To: binutils; +Cc: Mark Harmstone

Copies the remaining symbols in the DEBUG_S_SYMBOLS subsection, the
local variables etc. that don't make up the globals stream.

I think that this is all, or nearly all, of the records that Clang emits
for C or C++.

---
 ld/pdb.c                                | 362 ++++++++++++++++++++
 ld/pdb.h                                | 179 ++++++++++
 ld/testsuite/ld-pe/pdb-syms2-symbols1.d |  38 +++
 ld/testsuite/ld-pe/pdb-syms2.s          | 430 ++++++++++++++++++++++++
 ld/testsuite/ld-pe/pdb.exp              |  67 ++++
 5 files changed, 1076 insertions(+)
 create mode 100644 ld/testsuite/ld-pe/pdb-syms2-symbols1.d
 create mode 100644 ld/testsuite/ld-pe/pdb-syms2.s

diff --git a/ld/pdb.c b/ld/pdb.c
index 268e2521b03..e16e45c23b1 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -907,11 +907,15 @@ find_end_of_scope (uint8_t *data, uint32_t size)
 	{
 	case S_GPROC32:
 	case S_LPROC32:
+	case S_BLOCK32:
+	case S_INLINESITE:
+	case S_THUNK32:
 	  scope_level++;
 	  break;
 
 	case S_END:
 	case S_PROC_ID_END:
+	case S_INLINESITE_END:
 	  scope_level--;
 
 	  if (scope_level == 0)
@@ -960,6 +964,7 @@ parse_symbols (uint8_t *data, uint32_t size, uint8_t **buf,
 {
   uint8_t *orig_buf = *buf;
   unsigned int scope_level = 0;
+  uint8_t *scope = NULL;
 
   while (size >= sizeof (uint16_t))
     {
@@ -1205,6 +1210,8 @@ parse_symbols (uint8_t *data, uint32_t size, uint8_t **buf,
 
 	    free (ref);
 
+	    scope = *buf;
+
 	    memcpy (*buf, proc, len);
 	    *buf += len;
 
@@ -1320,16 +1327,343 @@ parse_symbols (uint8_t *data, uint32_t size, uint8_t **buf,
 	  }
 
 	case S_END:
+	case S_INLINESITE_END:
 	case S_PROC_ID_END:
 	  memcpy (*buf, data, len);
 
 	  if (type == S_PROC_ID_END) /* transform to S_END */
 	    bfd_putl16 (S_END, *buf + sizeof (uint16_t));
 
+	  /* Reset scope variable back to the address of the previous
+	     scope start.  */
+	  if (scope)
+	    {
+	      uint32_t parent;
+	      uint16_t scope_start_type =
+		bfd_getl16 (scope + sizeof (uint16_t));
+
+	      switch (scope_start_type)
+		{
+		case S_GPROC32:
+		case S_LPROC32:
+		  parent = bfd_getl32 (scope + offsetof (struct procsym,
+							  parent));
+		  break;
+
+		case S_BLOCK32:
+		  parent = bfd_getl32 (scope + offsetof (struct blocksym,
+							 parent));
+		  break;
+
+		case S_INLINESITE:
+		  parent = bfd_getl32 (scope + offsetof (struct inline_site,
+							 parent));
+		  break;
+
+		case S_THUNK32:
+		  parent = bfd_getl32 (scope + offsetof (struct thunk,
+							 parent));
+		  break;
+
+		default:
+		  einfo (_("%P: warning: unexpected CodeView scope start"
+			   " record %v\n"), scope_start_type);
+		  bfd_set_error (bfd_error_bad_value);
+		  return false;
+		}
+
+	      if (parent == 0)
+		scope = NULL;
+	      else
+		scope = orig_buf + parent - sizeof (uint32_t);
+	    }
+
 	  *buf += len;
 	  scope_level--;
 	  break;
 
+	case S_BUILDINFO:
+	  {
+	    struct buildinfosym *bi = (struct buildinfosym *) data;
+
+	    if (len < sizeof (struct buildinfosym))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			 " S_BUILDINFO\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (!remap_symbol_type (&bi->type, map, num_types))
+	      {
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    memcpy (*buf, data, len);
+	    *buf += len;
+
+	    break;
+	  }
+
+	case S_BLOCK32:
+	  {
+	    struct blocksym *bl = (struct blocksym *) data;
+	    uint8_t *endptr;
+	    uint32_t end;
+
+	    if (len < offsetof (struct blocksym, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			 " S_BLOCK32\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    bfd_putl32 (scope - orig_buf + sizeof (uint32_t), &bl->parent);
+
+	    endptr = find_end_of_scope (data, size);
+
+	    if (!endptr)
+	      {
+		einfo (_("%P: warning: could not find end of"
+			" S_BLOCK32 record\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    end = *buf - orig_buf + sizeof (uint32_t) + endptr - data;
+	    bfd_putl32 (end, &bl->end);
+
+	    scope = *buf;
+
+	    memcpy (*buf, data, len);
+	    *buf += len;
+
+	    scope_level++;
+
+	    break;
+	  }
+
+	case S_BPREL32:
+	  {
+	    struct bprelsym *bp = (struct bprelsym *) data;
+
+	    if (len < offsetof (struct bprelsym, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			 " S_BPREL32\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (!remap_symbol_type (&bp->type, map, num_types))
+	      {
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    memcpy (*buf, data, len);
+	    *buf += len;
+
+	    break;
+	  }
+
+	case S_REGISTER:
+	  {
+	    struct regsym *reg = (struct regsym *) data;
+
+	    if (len < offsetof (struct regsym, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			 " S_REGISTER\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (!remap_symbol_type (&reg->type, map, num_types))
+	      {
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    memcpy (*buf, data, len);
+	    *buf += len;
+
+	    break;
+	  }
+
+	case S_REGREL32:
+	  {
+	    struct regrel *rr = (struct regrel *) data;
+
+	    if (len < offsetof (struct regrel, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			 " S_REGREL32\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (!remap_symbol_type (&rr->type, map, num_types))
+	      {
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    memcpy (*buf, data, len);
+	    *buf += len;
+
+	    break;
+	  }
+
+	case S_LOCAL:
+	  {
+	    struct localsym *l = (struct localsym *) data;
+
+	    if (len < offsetof (struct localsym, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			 " S_LOCAL\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (!remap_symbol_type (&l->type, map, num_types))
+	      {
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    memcpy (*buf, data, len);
+	    *buf += len;
+
+	    break;
+	  }
+
+	case S_INLINESITE:
+	  {
+	    struct inline_site *is = (struct inline_site *) data;
+	    uint8_t *endptr;
+	    uint32_t end;
+
+	    if (len < offsetof (struct inline_site, binary_annotations))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			 " S_INLINESITE\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    bfd_putl32 (scope - orig_buf + sizeof (uint32_t), &is->parent);
+
+	    endptr = find_end_of_scope (data, size);
+
+	    if (!endptr)
+	      {
+		einfo (_("%P: warning: could not find end of"
+			" S_INLINESITE record\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    end = *buf - orig_buf + sizeof (uint32_t) + endptr - data;
+	    bfd_putl32 (end, &is->end);
+
+	    if (!remap_symbol_type (&is->inlinee, map, num_types))
+	      {
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    scope = *buf;
+
+	    memcpy (*buf, data, len);
+	    *buf += len;
+
+	    scope_level++;
+
+	    break;
+	  }
+
+	case S_THUNK32:
+	  {
+	    struct thunk *th = (struct thunk *) data;
+	    uint8_t *endptr;
+	    uint32_t end;
+
+	    if (len < offsetof (struct thunk, name))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			 " S_THUNK32\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    bfd_putl32 (scope - orig_buf + sizeof (uint32_t), &th->parent);
+
+	    endptr = find_end_of_scope (data, size);
+
+	    if (!endptr)
+	      {
+		einfo (_("%P: warning: could not find end of"
+			" S_THUNK32 record\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    end = *buf - orig_buf + sizeof (uint32_t) + endptr - data;
+	    bfd_putl32 (end, &th->end);
+
+	    scope = *buf;
+
+	    memcpy (*buf, data, len);
+	    *buf += len;
+
+	    scope_level++;
+
+	    break;
+	  }
+
+	case S_HEAPALLOCSITE:
+	  {
+	    struct heap_alloc_site *has = (struct heap_alloc_site *) data;
+
+	    if (len < sizeof (struct heap_alloc_site))
+	      {
+		einfo (_("%P: warning: truncated CodeView record"
+			 " S_HEAPALLOCSITE\n"));
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    if (!remap_symbol_type (&has->type, map, num_types))
+	      {
+		bfd_set_error (bfd_error_bad_value);
+		return false;
+	      }
+
+	    memcpy (*buf, data, len);
+	    *buf += len;
+
+	    break;
+	  }
+
+	case S_OBJNAME: /* just copy */
+	case S_COMPILE3:
+	case S_UNAMESPACE:
+	case S_FRAMEPROC:
+	case S_FRAMECOOKIE:
+	case S_LABEL32:
+	case S_DEFRANGE_REGISTER_REL:
+	case S_DEFRANGE_FRAMEPOINTER_REL:
+	case S_DEFRANGE_SUBFIELD_REGISTER:
+	case S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE:
+	case S_DEFRANGE_REGISTER:
+	  memcpy (*buf, data, len);
+	  *buf += len;
+	  break;
+
 	default:
 	  einfo (_("%P: warning: unrecognized CodeView record %v\n"), type);
 	  bfd_set_error (bfd_error_bad_value);
@@ -1440,12 +1774,40 @@ calculate_symbols_size (uint8_t *data, uint32_t size, uint32_t *sym_size)
 	    *sym_size += len;
 	  break;
 
+	case S_BLOCK32: /* always copied */
+	case S_INLINESITE:
+	case S_THUNK32:
+	  *sym_size += len;
+	  scope_level++;
+	  break;
+
 	case S_END: /* always copied */
 	case S_PROC_ID_END:
+	case S_INLINESITE_END:
 	  *sym_size += len;
 	  scope_level--;
 	  break;
 
+	case S_OBJNAME: /* always copied */
+	case S_COMPILE3:
+	case S_UNAMESPACE:
+	case S_FRAMEPROC:
+	case S_FRAMECOOKIE:
+	case S_LABEL32:
+	case S_BUILDINFO:
+	case S_BPREL32:
+	case S_REGISTER:
+	case S_REGREL32:
+	case S_LOCAL:
+	case S_DEFRANGE_REGISTER_REL:
+	case S_DEFRANGE_FRAMEPOINTER_REL:
+	case S_DEFRANGE_SUBFIELD_REGISTER:
+	case S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE:
+	case S_DEFRANGE_REGISTER:
+	case S_HEAPALLOCSITE:
+	  *sym_size += len;
+	  break;
+
 	default:
 	  einfo (_("%P: warning: unrecognized CodeView record %v\n"), type);
 	  return false;
diff --git a/ld/pdb.h b/ld/pdb.h
index 3f516f8a5f8..2ac4701645e 100644
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -71,20 +71,41 @@
 #define LF_UQUADWORD			0x800a
 
 #define S_END				0x0006
+#define S_FRAMEPROC			0x1012
+#define S_OBJNAME			0x1101
+#define S_THUNK32			0x1102
+#define S_BLOCK32			0x1103
+#define S_LABEL32			0x1105
+#define S_REGISTER			0x1106
 #define S_CONSTANT			0x1107
 #define S_UDT				0x1108
+#define S_BPREL32			0x110b
 #define S_LDATA32			0x110c
 #define S_GDATA32 			0x110d
 #define S_PUB32				0x110e
 #define S_LPROC32			0x110f
 #define S_GPROC32			0x1110
+#define S_REGREL32			0x1111
 #define S_LTHREAD32			0x1112
 #define S_GTHREAD32			0x1113
+#define S_UNAMESPACE			0x1124
 #define S_PROCREF			0x1125
 #define S_LPROCREF			0x1127
+#define S_FRAMECOOKIE			0x113a
+#define S_COMPILE3			0x113c
+#define S_LOCAL				0x113e
+#define S_DEFRANGE_REGISTER		0x1141
+#define S_DEFRANGE_FRAMEPOINTER_REL	0x1142
+#define S_DEFRANGE_SUBFIELD_REGISTER	0x1143
+#define S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE	0x1144
+#define S_DEFRANGE_REGISTER_REL		0x1145
 #define S_LPROC32_ID			0x1146
 #define S_GPROC32_ID			0x1147
+#define S_BUILDINFO			0x114c
+#define S_INLINESITE			0x114d
+#define S_INLINESITE_END		0x114e
 #define S_PROC_ID_END			0x114f
+#define S_HEAPALLOCSITE			0x115e
 
 /* PDBStream70 in pdb1.h */
 struct pdb_stream_70
@@ -617,6 +638,164 @@ struct constsym
   char name[];
 } ATTRIBUTE_PACKED;
 
+/* BUILDINFOSYM in cvinfo.h */
+struct buildinfosym
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t type;
+} ATTRIBUTE_PACKED;
+
+/* BLOCKSYM32 in cvinfo.h */
+struct blocksym
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t parent;
+  uint32_t end;
+  uint32_t len;
+  uint32_t offset;
+  uint16_t section;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* BPRELSYM32 in cvinfo.h */
+struct bprelsym
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t bp_offset;
+  uint32_t type;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* REGSYM in cvinfo.h */
+struct regsym
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t type;
+  uint16_t reg;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* REGREL32 in cvinfo.h */
+struct regrel
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t offset;
+  uint32_t type;
+  uint16_t reg;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* LOCALSYM in cvinfo.h */
+struct localsym
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t type;
+  uint16_t flags;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* CV_LVAR_ADDR_RANGE in cvinfo.h */
+struct lvar_addr_range
+{
+  uint32_t offset;
+  uint16_t section;
+  uint16_t length;
+} ATTRIBUTE_PACKED;
+
+/* CV_LVAR_ADDR_GAP in cvinfo.h */
+struct lvar_addr_gap {
+  uint16_t offset;
+  uint16_t length;
+} ATTRIBUTE_PACKED;
+
+/* DEFRANGESYMREGISTERREL in cvinfo.h */
+struct defrange_register_rel
+{
+  uint16_t size;
+  uint16_t kind;
+  uint16_t reg;
+  uint16_t offset_parent;
+  uint32_t offset_register;
+  struct lvar_addr_range range;
+  struct lvar_addr_gap gaps[];
+} ATTRIBUTE_PACKED;
+
+/* DEFRANGESYMFRAMEPOINTERREL in cvinfo.h */
+struct defrange_framepointer_rel
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t offset;
+  struct lvar_addr_range range;
+  struct lvar_addr_gap gaps[];
+} ATTRIBUTE_PACKED;
+
+/* DEFRANGESYMSUBFIELDREGISTER in cvinfo.h */
+struct defrange_subfield_register
+{
+  uint16_t size;
+  uint16_t kind;
+  uint16_t reg;
+  uint16_t attributes;
+  uint32_t offset_parent;
+  struct lvar_addr_range range;
+  struct lvar_addr_gap gaps[];
+} ATTRIBUTE_PACKED;
+
+/* DEFRANGESYMREGISTER in cvinfo.h */
+struct defrange_register
+{
+  uint16_t size;
+  uint16_t kind;
+  uint16_t reg;
+  uint16_t attributes;
+  struct lvar_addr_range range;
+  struct lvar_addr_gap gaps[];
+} ATTRIBUTE_PACKED;
+
+/* INLINESITESYM in cvinfo.h */
+struct inline_site
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t parent;
+  uint32_t end;
+  uint32_t inlinee;
+  uint8_t binary_annotations[];
+} ATTRIBUTE_PACKED;
+
+/* THUNKSYM32 in cvinfo.h */
+struct thunk
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t parent;
+  uint32_t end;
+  uint32_t next;
+  uint32_t offset;
+  uint16_t section;
+  uint16_t length;
+  uint8_t thunk_type;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+/* HEAPALLOCSITE in cvinfo.h */
+struct heap_alloc_site
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t offset;
+  uint16_t section;
+  uint16_t length;
+  uint32_t type;
+} ATTRIBUTE_PACKED;
+
 extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
 
 #endif
diff --git a/ld/testsuite/ld-pe/pdb-syms2-symbols1.d b/ld/testsuite/ld-pe/pdb-syms2-symbols1.d
new file mode 100644
index 00000000000..34132d1264e
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-syms2-symbols1.d
@@ -0,0 +1,38 @@
+
+*:     file format binary
+
+Contents of section .data:
+ 0000 04000000 0e000111 00000000 73796d73  ............syms
+ 0010 332e6f00 22003c11 00000000 d0000000  3.o.".<.........
+ 0020 00000000 00000000 00000000 0000474e  ..............GN
+ 0030 55204153 00f3f2f1 06002411 73746400  U AS......$.std.
+ 0040 06004c11 05100000 2e001011 00000000  ..L.............
+ 0050 18020000 00000000 06000000 00000000  ................
+ 0060 00000000 01100000 00000000 01000070  ...............p
+ 0070 726f6331 00f3f2f1 1e001210 00000000  roc1............
+ 0080 00000000 00000000 00000000 00000000  ................
+ 0090 00000000 0000f2f1 0e003a11 08000000  ..........:.....
+ 00a0 48010000 000000f1 0e000b11 04000000  H...............
+ 00b0 02100000 666f6f00 0e000611 02100000  ....foo.........
+ 00c0 48016261 7200f2f1 12001111 04000000  H.bar...........
+ 00d0 02100000 48016261 7a00f2f1 12003e11  ....H.baz.....>.
+ 00e0 02100000 00006c6f 63616c31 00f3f2f1  ......local1....
+ 00f0 16004511 48010000 00000000 01000000  ..E.H...........
+ 0100 01000400 02000100 12003e11 02100000  ..........>.....
+ 0110 00006c6f 63616c32 00f3f2f1 12004211  ..local2......B.
+ 0120 04000000 01000000 01000400 02000100  ................
+ 0130 12003e11 02100000 00006c6f 63616c33  ..>.......local3
+ 0140 00f3f2f1 16004311 48010000 04000000  ......C.H.......
+ 0150 01000000 01000400 02000100 12003e11  ..............>.
+ 0160 02100000 00006c6f 63616c34 00f3f2f1  ......local4....
+ 0170 06004411 04000000 12003e11 02100000  ..D.......>.....
+ 0180 00006c6f 63616c35 00f3f2f1 12004111  ..local5......A.
+ 0190 48010000 01000000 01000400 02000100  H...............
+ 01a0 0e004d11 48000000 b0010000 06100000  ..M.H...........
+ 01b0 02004e11 16000311 48000000 e0010000  ..N.....H.......
+ 01c0 04000000 01000000 010000f1 12000511  ................
+ 01d0 02000000 0100006c 6162656c 00f3f2f1  .......label....
+ 01e0 02000600 1e000211 48000000 04020000  ........H.......
+ 01f0 00000000 06000000 01000100 00746875  .............thu
+ 0200 6e6b00f1 02000600 0e005e11 04000000  nk........^.....
+ 0210 01000100 02100000 02000600 00000000  ................
\ No newline at end of file
diff --git a/ld/testsuite/ld-pe/pdb-syms2.s b/ld/testsuite/ld-pe/pdb-syms2.s
new file mode 100644
index 00000000000..ec677eaee43
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb-syms2.s
@@ -0,0 +1,430 @@
+.equ CV_SIGNATURE_C13, 4
+.equ DEBUG_S_SYMBOLS, 0xf1
+
+.equ T_VOID, 0x0003
+.equ T_UINT4, 0x0075
+
+.equ LF_MODIFIER, 0x1001
+.equ LF_PROCEDURE, 0x1008
+.equ LF_ARGLIST, 0x1201
+.equ LF_FUNC_ID, 0x1601
+.equ LF_BUILDINFO, 0x1603
+.equ LF_STRING_ID, 0x1605
+
+.equ S_END, 0x0006
+.equ S_FRAMEPROC, 0x1012
+.equ S_OBJNAME, 0x1101
+.equ S_THUNK32, 0x1102
+.equ S_BLOCK32, 0x1103
+.equ S_LABEL32, 0x1105
+.equ S_REGISTER, 0x1106
+.equ S_BPREL32, 0x110b
+.equ S_GPROC32, 0x1110
+.equ S_REGREL32, 0x1111
+.equ S_UNAMESPACE, 0x1124
+.equ S_FRAMECOOKIE, 0x113a
+.equ S_COMPILE3, 0x113c
+.equ S_LOCAL, 0x113e
+.equ S_DEFRANGE_REGISTER, 0x1141
+.equ S_DEFRANGE_FRAMEPOINTER_REL, 0x1142
+.equ S_DEFRANGE_SUBFIELD_REGISTER, 0x1143
+.equ S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE, 0x1144
+.equ S_DEFRANGE_REGISTER_REL, 0x1145
+.equ S_BUILDINFO, 0x114c
+.equ S_INLINESITE, 0x114d
+.equ S_INLINESITE_END, 0x114e
+.equ S_HEAPALLOCSITE, 0x115e
+
+.equ CV_AMD64_RAX, 328
+.equ CV_CFL_AMD64, 0xd0
+
+.section ".debug$S", "rn"
+
+.long CV_SIGNATURE_C13
+
+.long DEBUG_S_SYMBOLS
+.long .syms_end - .syms_start
+
+.syms_start:
+
+.objname1:
+.short .compile1 - .objname1 - 2
+.short S_OBJNAME
+.long 0 # signature
+.asciz "syms3.o"
+
+.compile1:
+.short .unamespace1 - .compile1 - 2
+.short S_COMPILE3
+.long 0 # flags
+.short CV_CFL_AMD64 # target processor
+.short 0 # frontend major
+.short 0 # frontend minor
+.short 0 # frontend build
+.short 0 # frontend qfe
+.short 0 # backend major
+.short 0 # backend minor
+.short 0 # backend build
+.short 0 # backend qfe
+.asciz "GNU AS"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.unamespace1:
+.short .sbuildinfo1 - .unamespace1 - 2
+.short S_UNAMESPACE
+.asciz "std"
+
+.sbuildinfo1:
+.short .gproc1 - .sbuildinfo1 - 2
+.short S_BUILDINFO
+.long 0x1007 # type
+
+.gproc1:
+.short .frameproc1 - .gproc1 - 2
+.short S_GPROC32
+.long 0 # parent
+.long 0 # end
+.long 0 # next symbol
+.long .proc1_end - proc1 # length
+.long 0 # debug start offset
+.long 0 # debug end offset
+.long 0x1001 # type
+.secrel32 proc1
+.secidx proc1
+.byte 0 # flags
+.asciz "proc1"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.frameproc1:
+.short .framecookie1 - .frameproc1 - 2
+.short S_FRAMEPROC
+.long 0 # frame size
+.long 0 # frame padding
+.long 0 # padding offset
+.long 0 # size of callee-save registers
+.long 0 # offset of exception handler
+.short 0 # section of exception handler
+.long 0 # flags
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.framecookie1:
+.short .bprel1 - .framecookie1 - 2
+.short S_FRAMECOOKIE
+.long 8 # frame-relative offset
+.short CV_AMD64_RAX # register
+.long 0 # cookie type (CV_COOKIETYPE_COPY)
+.byte 0 # flags
+.byte 0xf1 # padding
+
+.bprel1:
+.short .reg1 - .bprel1 - 2
+.short S_BPREL32
+.long 4 # BP-relative offset
+.long 0x1008 # type
+.asciz "foo"
+
+.reg1:
+.short .regrel1 - .reg1 - 2
+.short S_REGISTER
+.long 0x1008 # type
+.short CV_AMD64_RAX
+.asciz "bar"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.regrel1:
+.short .local1 - .regrel1 - 2
+.short S_REGREL32
+.long 4 # offset
+.long 0x1008 # type
+.short CV_AMD64_RAX
+.asciz "baz"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.local1:
+.short .defrange1 - .local1 - 2
+.short S_LOCAL
+.long 0x1008 # type
+.short 0 # flags
+.asciz "local1"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.defrange1:
+.short .local2 - .defrange1 - 2
+.short S_DEFRANGE_REGISTER_REL
+.short CV_AMD64_RAX
+.short 0 # offset parent
+.long 0 # offset register
+.secrel32 .block1 # offset
+.secidx .block1 # section
+.short .block1_end - .block1 # length
+.short .gap1 - .block1 # gap 1 offset
+.short .gap1_end - .gap1 # gap 1 length
+
+.local2:
+.short .defrange2 - .local2 - 2
+.short S_LOCAL
+.long 0x1008 # type
+.short 0 # flags
+.asciz "local2"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.defrange2:
+.short .local3 - .defrange2 - 2
+.short S_DEFRANGE_FRAMEPOINTER_REL
+.long 4 # frame pointer offset
+.secrel32 .block1 # offset
+.secidx .block1 # section
+.short .block1_end - .block1 # length
+.short .gap1 - .block1 # gap 1 offset
+.short .gap1_end - .gap1 # gap 1 length
+
+.local3:
+.short .defrange3 - .local3 - 2
+.short S_LOCAL
+.long 0x1008 # type
+.short 0 # flags
+.asciz "local3"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.defrange3:
+.short .local4 - .defrange3 - 2
+.short S_DEFRANGE_SUBFIELD_REGISTER
+.short CV_AMD64_RAX
+.short 0 # attributes
+.long 4 # offset in parent variable
+.secrel32 .block1 # offset
+.secidx .block1 # section
+.short .block1_end - .block1 # length
+.short .gap1 - .block1 # gap 1 offset
+.short .gap1_end - .gap1 # gap 1 length
+
+.local4:
+.short .defrange4 - .local4 - 2
+.short S_LOCAL
+.long 0x1008 # type
+.short 0 # flags
+.asciz "local4"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.defrange4:
+.short .local5 - .defrange4 - 2
+.short S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE
+.long 4 # frame pointer offset
+
+.local5:
+.short .defrange5 - .local5 - 2
+.short S_LOCAL
+.long 0x1008 # type
+.short 0 # flags
+.asciz "local5"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.defrange5:
+.short .inlinesite1 - .defrange5 - 2
+.short S_DEFRANGE_REGISTER
+.short CV_AMD64_RAX
+.short 0 # attributes
+.secrel32 .block1 # offset
+.secidx .block1 # section
+.short .block1_end - .block1 # length
+.short .gap1 - .block1 # gap 1 offset
+.short .gap1_end - .gap1 # gap 1 length
+
+.inlinesite1:
+.short .inlinesite1end - .inlinesite1 - 2
+.short S_INLINESITE
+.long 0 # parent
+.long 0 # end
+.long 0x1009 # inlinee (inline_func)
+
+.inlinesite1end:
+.short .sblock1 - .inlinesite1end - 2
+.short S_INLINESITE_END
+
+.sblock1:
+.short .label1 - .sblock1 - 2
+.short S_BLOCK32
+.long 0 # parent (filled in by linker)
+.long 0 # end (filled in by linker)
+.long .block1_end - .block1 # length
+.secrel32 .block1
+.secidx .block1
+.byte 0 # name
+.byte 0xf1 # padding
+
+.label1:
+.short .sblock1_end - .label1 - 2
+.short S_LABEL32
+.secrel32 label
+.secidx label
+.byte 0 # flags
+.asciz "label"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+.sblock1_end:
+.short .thunk1 - .sblock1_end - 2
+.short S_END
+
+.thunk1:
+.short .thunk1_end - .thunk1 - 2
+.short S_THUNK32
+.long 0 # parent
+.long 0 # end
+.long 0 # next
+.secrel32 thunk
+.secidx thunk
+.short .thunk_end - thunk
+.byte 0 # THUNK_ORDINAL value
+.asciz "thunk"
+.byte 0xf1 # padding
+
+.thunk1_end:
+.short .heapallocsite1 - .thunk1_end - 2
+.short S_END
+
+.heapallocsite1:
+.short .gproc1_end - .heapallocsite1 - 2
+.short S_HEAPALLOCSITE
+.secrel32 .gap1_end
+.secidx .gap1_end
+.short .block1_end - .gap1_end
+.long 0x1008 # type
+
+.gproc1_end:
+.short .syms_end - .gproc1_end - 2
+.short S_END
+
+.syms_end:
+
+.section ".debug$T", "rn"
+
+.long CV_SIGNATURE_C13
+
+# Type 1000, arglist (uint32_t)
+.arglist1:
+.short .proctype1 - .arglist1 - 2
+.short LF_ARGLIST
+.long 1 # no. entries
+.long T_UINT4
+
+# Type 1001, procedure (return type T_VOID, arglist 1000)
+.proctype1:
+.short .string1 - .proctype1 - 2
+.short LF_PROCEDURE
+.long T_VOID
+.byte 0 # calling convention
+.byte 0 # attributes
+.short 1 # no. parameters
+.long 0x1000
+
+# Type 1002, string "/tmp" (build directory)
+.string1:
+.short .string2 - .string1 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "/tmp"
+.byte 0xf3 # padding
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1003, string "gcc" (compiler)
+.string2:
+.short .string3 - .string2 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "gcc"
+
+# Type 1004, string "tmp.c" (source file)
+.string3:
+.short .string4 - .string3 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "tmp.c"
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1005, string "tmp.pdb" (PDB file)
+.string4:
+.short .string5 - .string4 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "tmp.pdb"
+
+# Type 1006, string "-gcodeview" (command arguments)
+.string5:
+.short .buildinfo1 - .string5 - 2
+.short LF_STRING_ID
+.long 0 # sub-string
+.asciz "-gcodeview"
+.byte 0xf1 # padding
+
+# Type 1007, build info
+.buildinfo1:
+.short .mod1 - .buildinfo1 - 2
+.short LF_BUILDINFO
+.short 5 # count
+.long 0x1002 # build directory
+.long 0x1003 # compiler
+.long 0x1004 # source file
+.long 0x1005 # PDB file
+.long 0x1006 # command arguments
+.byte 0xf2 # padding
+.byte 0xf1 # padding
+
+# Type 1008, const uint32_t
+.mod1:
+.short .funcid1 - .mod1 - 2
+.short LF_MODIFIER
+.long T_UINT4
+.short 1 # const
+.p2align 2
+
+# Type 1009, func ID for inline_func
+.funcid1:
+.short .types_end - .funcid1 - 2
+.short LF_FUNC_ID
+.long 0 # parent scope
+.long 0x1001 # type
+.asciz "inline_func"
+
+.types_end:
+
+.text
+
+.global proc1
+proc1:
+  nop
+.block1:
+  nop
+label:
+  nop
+.gap1:
+  nop
+.gap1_end:
+  nop
+.block1_end:
+  nop
+.proc1_end:
+
+thunk:
+  nop
+.thunk_end:
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index 34eafc142a7..5df1583c247 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -1614,6 +1614,72 @@ proc test8 { } {
     }
 }
 
+proc test9 { } {
+    global as
+    global ar
+    global ld
+    global objdump
+    global srcdir
+    global subdir
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb-syms2.s tmpdir/pdb-syms2.o] {
+	unsupported "Build pdb-syms2.o"
+	return
+    }
+
+    if ![ld_link $ld "tmpdir/pdb-syms2.exe" "--pdb=tmpdir/pdb-syms2.pdb tmpdir/pdb-syms2.o"] {
+	unsupported "Create PE image with PDB file"
+	return
+    }
+
+    # get index of module stream
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms2.pdb 0003"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract DBI stream"
+	return
+    } else {
+	pass "Extracted DBI stream"
+    }
+
+    set fi [open tmpdir/0003]
+    fconfigure $fi -translation binary
+
+    seek $fi 24
+    set data [read $fi 4]
+    binary scan $data i mod_info_size
+
+    seek $fi 36 current
+    set mod_info [read $fi $mod_info_size]
+
+    close $fi
+
+    binary scan [string range $mod_info 34 35] s module_index
+
+    # check module records
+
+    set index_str [format "%04x" $module_index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms2.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract module symbols"
+	return
+    } else {
+	pass "Extracted module symbols"
+    }
+
+    set exp [file_contents "$srcdir/$subdir/pdb-syms2-symbols1.d"]
+    set got [run_host_cmd "$objdump" "-s --target=binary tmpdir/$index_str"]
+
+    if [string match $exp $got] {
+	pass "Correct symbols in module stream"
+    } else {
+	fail "Incorrect symbols in module stream"
+    }
+}
+
 test1
 test2
 test3
@@ -1622,3 +1688,4 @@ test5
 test6
 test7
 test8
+test9
-- 
2.37.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* [PATCH] ld: Write linker symbols in PDB
  2022-12-05  1:53           ` [PATCH] ld: Write globals stream in PDB Mark Harmstone
  2022-12-05  1:53             ` [PATCH] ld: Copy other symbols into PDB file Mark Harmstone
@ 2022-12-05  1:53             ` Mark Harmstone
  2022-12-06 17:07             ` [PATCH] ld: Write globals stream " Nick Clifton
  2 siblings, 0 replies; 19+ messages in thread
From: Mark Harmstone @ 2022-12-05  1:53 UTC (permalink / raw)
  To: binutils; +Cc: Mark Harmstone

Adds a few symbols that Microsoft's LINK generates itself, with the
version number etc.

---
 ld/pdb.c                   | 246 ++++++++++++++++++++++++++++++++-----
 ld/pdb.h                   |  42 +++++++
 ld/testsuite/ld-pe/pdb.exp |  77 ++++++++++++
 3 files changed, 331 insertions(+), 34 deletions(-)

diff --git a/ld/pdb.c b/ld/pdb.c
index e16e45c23b1..86c71cfdfbb 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -21,6 +21,7 @@
 #include "pdb.h"
 #include "bfdlink.h"
 #include "ld.h"
+#include "ldmain.h"
 #include "ldmisc.h"
 #include "libbfd.h"
 #include "libiberty.h"
@@ -3422,6 +3423,171 @@ handle_debugt_section (asection *s, bfd *mod, struct types *types,
   return true;
 }
 
+/* Return the CodeView constant for the selected architecture.  */
+static uint16_t
+target_processor (bfd *abfd)
+{
+  if (abfd->arch_info->arch != bfd_arch_i386)
+    return 0;
+
+  if (abfd->arch_info->mach & bfd_mach_x86_64)
+    return CV_CFL_X64;
+  else
+    return CV_CFL_80386;
+}
+
+/* Create the symbols that go in "* Linker *", the dummy module created
+   for the linker itself.  */
+static bool
+create_linker_symbols (bfd *abfd, uint8_t **syms, uint32_t *sym_byte_size,
+		       const char *pdb_name)
+{
+  uint8_t *ptr;
+  struct objname *name;
+  struct compile3 *comp;
+  struct envblock *env;
+  size_t padding1, padding2, env_size;
+  char *cwdval, *exeval, *pdbval;
+
+  /* extra NUL for padding */
+  static const char linker_fn[] = "* Linker *\0";
+  static const char linker_name[] = "GNU LD " VERSION;
+
+  static const char cwd[] = "cwd";
+  static const char exe[] = "exe";
+  static const char pdb[] = "pdb";
+
+  cwdval = getcwd (NULL, 0);
+  if (!cwdval)
+    {
+      einfo (_("%P: warning: unable to get working directory\n"));
+      return false;
+    }
+
+  exeval = lrealpath (program_name);
+
+  if (!exeval)
+    {
+      einfo (_("%P: warning: unable to get program name\n"));
+      free (cwdval);
+      return false;
+    }
+
+  pdbval = lrealpath (pdb_name);
+
+  if (!pdbval)
+    {
+      einfo (_("%P: warning: unable to get full path to PDB\n"));
+      free (exeval);
+      free (cwdval);
+      return false;
+    }
+
+  *sym_byte_size += offsetof (struct objname, name) + sizeof (linker_fn);
+  *sym_byte_size += offsetof (struct compile3, compiler) + sizeof (linker_name);
+
+  if (*sym_byte_size % 4)
+    padding1 = 4 - (*sym_byte_size % 4);
+  else
+    padding1 = 0;
+
+  *sym_byte_size += padding1;
+
+  env_size = offsetof (struct envblock, strings);
+  env_size += sizeof (cwd);
+  env_size += strlen (cwdval) + 1;
+  env_size += sizeof (exe);
+  env_size += strlen (exeval) + 1;
+  env_size += sizeof (pdb);
+  env_size += strlen (pdbval) + 1;
+
+  if (env_size % 4)
+    padding2 = 4 - (env_size % 4);
+  else
+    padding2 = 0;
+
+  env_size += padding2;
+
+  *sym_byte_size += env_size;
+
+  *syms = xmalloc (*sym_byte_size);
+  ptr = *syms;
+
+  /* Write S_OBJNAME */
+
+  name = (struct objname *) ptr;
+  bfd_putl16 (offsetof (struct objname, name)
+	      + sizeof (linker_fn) - sizeof (uint16_t),
+	      &name->size);
+  bfd_putl16 (S_OBJNAME, &name->kind);
+  bfd_putl32 (0, &name->signature);
+  memcpy (name->name, linker_fn, sizeof (linker_fn));
+
+  ptr += offsetof (struct objname, name) + sizeof (linker_fn);
+
+  /* Write S_COMPILE3 */
+
+  comp = (struct compile3 *) ptr;
+
+  bfd_putl16 (offsetof (struct compile3, compiler) + sizeof (linker_name)
+	      + padding1 - sizeof (uint16_t),
+	      &comp->size);
+  bfd_putl16 (S_COMPILE3, &comp->kind);
+  bfd_putl32 (CV_CFL_LINK, &comp->flags);
+  bfd_putl16 (target_processor (abfd), &comp->machine);
+  bfd_putl16 (0, &comp->frontend_major);
+  bfd_putl16 (0, &comp->frontend_minor);
+  bfd_putl16 (0, &comp->frontend_build);
+  bfd_putl16 (0, &comp->frontend_qfe);
+  bfd_putl16 (0, &comp->backend_major);
+  bfd_putl16 (0, &comp->backend_minor);
+  bfd_putl16 (0, &comp->backend_build);
+  bfd_putl16 (0, &comp->backend_qfe);
+  memcpy (comp->compiler, linker_name, sizeof (linker_name));
+
+  memset (comp->compiler + sizeof (linker_name), 0, padding1);
+
+  ptr += offsetof (struct compile3, compiler)
+	 + sizeof (linker_name) + padding1;
+
+  /* Write S_ENVBLOCK */
+
+  env = (struct envblock *) ptr;
+
+  bfd_putl16 (env_size - sizeof (uint16_t), &env->size);
+  bfd_putl16 (S_ENVBLOCK, &env->kind);
+  env->flags = 0;
+
+  ptr += offsetof (struct envblock, strings);
+
+  memcpy (ptr, cwd, sizeof (cwd));
+  ptr += sizeof (cwd);
+  memcpy (ptr, cwdval, strlen (cwdval) + 1);
+  ptr += strlen (cwdval) + 1;
+
+  memcpy (ptr, exe, sizeof (exe));
+  ptr += sizeof (exe);
+  memcpy (ptr, exeval, strlen (exeval) + 1);
+  ptr += strlen (exeval) + 1;
+
+  memcpy (ptr, pdb, sizeof (pdb));
+  ptr += sizeof (pdb);
+  memcpy (ptr, pdbval, strlen (pdbval) + 1);
+  ptr += strlen (pdbval) + 1;
+
+  /* Microsoft's LINK also includes "cmd", the command-line options passed
+     to the linker, but unfortunately we don't have access to argc and argv
+     at this stage.  */
+
+  memset (ptr, 0, padding2);
+
+  free (pdbval);
+  free (exeval);
+  free (cwdval);
+
+  return true;
+}
+
 /* Populate the module stream, which consists of the transformed .debug$S
    data for each object file.  */
 static bool
@@ -3431,55 +3597,65 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
 			struct mod_source_files *mod_source,
 			bfd *abfd, struct types *types,
 			struct types *ids, uint16_t mod_num,
-			bfd *sym_rec_stream, struct globals *glob)
+			bfd *sym_rec_stream, struct globals *glob,
+			const char *pdb_name)
 {
   uint8_t int_buf[sizeof (uint32_t)];
   uint8_t *c13_info = NULL;
   uint8_t *syms = NULL;
-  struct type_entry **map = NULL;
-  uint32_t num_types = 0;
 
   *sym_byte_size = 0;
   *c13_info_size = 0;
 
-  /* Process .debug$T section.  */
-
-  for (asection *s = mod->sections; s; s = s->next)
+  if (!strcmp (bfd_get_filename (mod), "dll stuff"))
     {
-      if (!strcmp (s->name, ".debug$T") && s->size >= sizeof (uint32_t))
+      if (!create_linker_symbols (mod, &syms, sym_byte_size, pdb_name))
+	return false;
+    }
+  else
+    {
+      struct type_entry **map = NULL;
+      uint32_t num_types = 0;
+
+      /* Process .debug$T section.  */
+
+      for (asection *s = mod->sections; s; s = s->next)
 	{
-	  if (!handle_debugt_section (s, mod, types, ids, mod_num, strings,
-				      &map, &num_types))
+	  if (!strcmp (s->name, ".debug$T") && s->size >= sizeof (uint32_t))
 	    {
-	      free (mod_source->files);
-	      return false;
-	    }
+	      if (!handle_debugt_section (s, mod, types, ids, mod_num, strings,
+					  &map, &num_types))
+		{
+		  free (mod_source->files);
+		  return false;
+		}
 
-	  break;
+	      break;
+	    }
 	}
-    }
 
-  /* Process .debug$S section(s).  */
+      /* Process .debug$S section(s).  */
 
-  for (asection *s = mod->sections; s; s = s->next)
-    {
-      if (!strcmp (s->name, ".debug$S") && s->size >= sizeof (uint32_t))
+      for (asection *s = mod->sections; s; s = s->next)
 	{
-	  if (!handle_debugs_section (s, mod, strings, &c13_info,
-				      c13_info_size, mod_source, abfd,
-				      &syms, sym_byte_size, map, num_types,
-				      sym_rec_stream, glob, mod_num))
+	  if (!strcmp (s->name, ".debug$S") && s->size >= sizeof (uint32_t))
 	    {
-	      free (c13_info);
-	      free (syms);
-	      free (mod_source->files);
-	      free (map);
-	      return false;
+	      if (!handle_debugs_section (s, mod, strings, &c13_info,
+					  c13_info_size, mod_source, abfd,
+					  &syms, sym_byte_size, map, num_types,
+					  sym_rec_stream, glob, mod_num))
+		{
+		  free (c13_info);
+		  free (syms);
+		  free (mod_source->files);
+		  free (map);
+		  return false;
+		}
 	    }
 	}
-    }
 
-  free (map);
+      free (map);
+    }
 
   /* Write the signature.  */
 
@@ -3533,7 +3709,8 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 			      uint32_t *size, struct string_table *strings,
 			      struct source_files_info *source,
 			      struct types *types, struct types *ids,
-			      bfd *sym_rec_stream, struct globals *glob)
+			      bfd *sym_rec_stream, struct globals *glob,
+			      const char *pdb_name)
 {
   uint8_t *ptr;
   unsigned int mod_num;
@@ -3623,7 +3800,7 @@ create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
 				   strings, &c13_info_size,
 				   &source->mods[mod_num], abfd,
 				   types, ids, mod_num,
-				   sym_rec_stream, glob))
+				   sym_rec_stream, glob, pdb_name))
 	{
 	  for (unsigned int i = 0; i < source->mod_count; i++)
 	    {
@@ -4112,7 +4289,7 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
 		     struct string_table *strings,
 		     struct types *types,
 		     struct types *ids,
-		     bfd *sym_rec_stream)
+		     bfd *sym_rec_stream, const char *pdb_name)
 {
   struct pdb_dbi_stream_header h;
   struct optional_dbg_header opt;
@@ -4135,7 +4312,7 @@ populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
 
   if (!create_module_info_substream (abfd, pdb, &mod_info, &mod_info_size,
 				     strings, &source, types, ids,
-				     sym_rec_stream, &glob))
+				     sym_rec_stream, &glob, pdb_name))
     {
       htab_delete (glob.hashmap);
       return false;
@@ -4829,7 +5006,8 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
 
   if (!populate_dbi_stream (dbi_stream, abfd, pdb, section_header_stream_num,
 			    sym_rec_stream_num, publics_stream_num,
-			    &strings, &types, &ids, sym_rec_stream))
+			    &strings, &types, &ids, sym_rec_stream,
+			    pdb_name))
     {
       einfo (_("%P: warning: cannot populate DBI stream "
 	       "in PDB file: %E\n"));
diff --git a/ld/pdb.h b/ld/pdb.h
index 2ac4701645e..43daf7ca748 100644
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -93,6 +93,7 @@
 #define S_LPROCREF			0x1127
 #define S_FRAMECOOKIE			0x113a
 #define S_COMPILE3			0x113c
+#define S_ENVBLOCK			0x113d
 #define S_LOCAL				0x113e
 #define S_DEFRANGE_REGISTER		0x1141
 #define S_DEFRANGE_FRAMEPOINTER_REL	0x1142
@@ -796,6 +797,47 @@ struct heap_alloc_site
   uint32_t type;
 } ATTRIBUTE_PACKED;
 
+/* OBJNAMESYM in cvinfo.h */
+struct objname
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t signature;
+  char name[];
+} ATTRIBUTE_PACKED;
+
+#define CV_CFL_80386			0x03
+#define CV_CFL_X64			0xD0
+
+#define CV_CFL_LINK			0x07
+
+/* COMPILESYM3 in cvinfo.h */
+struct compile3
+{
+  uint16_t size;
+  uint16_t kind;
+  uint32_t flags;
+  uint16_t machine;
+  uint16_t frontend_major;
+  uint16_t frontend_minor;
+  uint16_t frontend_build;
+  uint16_t frontend_qfe;
+  uint16_t backend_major;
+  uint16_t backend_minor;
+  uint16_t backend_build;
+  uint16_t backend_qfe;
+  char compiler[];
+} ATTRIBUTE_PACKED;
+
+/* ENVBLOCKSYM in cvinfo.h */
+struct envblock
+{
+  uint16_t size;
+  uint16_t kind;
+  uint8_t flags;
+  char strings[];
+} ATTRIBUTE_PACKED;
+
 extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
 
 #endif
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index 5df1583c247..bd50b2fb076 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -1678,6 +1678,83 @@ proc test9 { } {
     } else {
 	fail "Incorrect symbols in module stream"
     }
+
+    # check linker symbols
+
+    set off 64
+
+    set obj1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
+    incr off [expr [string length $obj1] + 1]
+
+    set ar1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
+    incr off [expr [string length $ar1] + 1]
+
+    if { [expr $off % 4] != 0 } {
+	set off [expr $off + 4 - ($off % 4)]
+    }
+
+    incr off 34
+
+    binary scan [string range $mod_info $off [expr $off + 1]] s linker_syms_index
+
+    set index_str [format "%04x" $linker_syms_index]
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb-syms2.pdb $index_str"]
+
+    if ![string match "" $exec_output] {
+	fail "Could not extract linker symbols"
+	return
+    } else {
+	pass "Extracted linker symbols"
+    }
+
+    set syms [file_contents "tmpdir/$index_str"]
+
+    # check S_OBJNAME
+
+    set off 4
+    binary scan [string range $syms $off [expr $off + 1]] s sym_len
+    binary scan [string range $syms [expr $off + 2] [expr $off + 3]] s sym_type
+
+    if { $sym_type != 0x1101 } {
+	fail "First linker symbol was not S_OBJNAME"
+    } else {
+	pass "First linker symbol was S_OBJNAME"
+
+	set linker_fn [string range $syms [expr $off + 8] [expr [string first \000 $syms [expr $off + 8]] - 1]]
+
+	if ![string equal $linker_fn "* Linker *"] {
+	    fail "Incorrect linker object name"
+	} else {
+	    pass "Correct linker object name"
+	}
+    }
+
+    incr off [expr $sym_len + 2]
+
+    # check S_COMPILE3
+
+    binary scan [string range $syms $off [expr $off + 1]] s sym_len
+    binary scan [string range $syms [expr $off + 2] [expr $off + 3]] s sym_type
+
+    if { $sym_type != 0x113c } {
+	fail "Second linker symbol was not S_COMPILE3"
+    } else {
+	pass "Second linker symbol was S_COMPILE3"
+    }
+
+    incr off [expr $sym_len + 2]
+
+    # check S_ENVBLOCK
+
+    binary scan [string range $syms $off [expr $off + 1]] s sym_len
+    binary scan [string range $syms [expr $off + 2] [expr $off + 3]] s sym_type
+
+    if { $sym_type != 0x113d } {
+	fail "Third linker symbol was not S_ENVBLOCK"
+    } else {
+	pass "Third linker symbol was S_ENVBLOCK"
+    }
 }
 
 test1
-- 
2.37.4


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] ld: Write globals stream in PDB
  2022-12-05  1:53           ` [PATCH] ld: Write globals stream in PDB Mark Harmstone
  2022-12-05  1:53             ` [PATCH] ld: Copy other symbols into PDB file Mark Harmstone
  2022-12-05  1:53             ` [PATCH] ld: Write linker symbols in PDB Mark Harmstone
@ 2022-12-06 17:07             ` Nick Clifton
  2022-12-06 17:52               ` Mark Harmstone
  2022-12-09  1:11               ` Mark Harmstone
  2 siblings, 2 replies; 19+ messages in thread
From: Nick Clifton @ 2022-12-06 17:07 UTC (permalink / raw)
  To: Mark Harmstone, binutils

Hi Mark,

> Parses the .debug$S sections of object files for DEBUG_S_SYMBOLS
> subsections, creating a globals stream based on the records we find.
> These also get copied into the module stream for each object file.

A few comments on the patch ...

> +struct global
> +{
> +  struct global *next;
> +  uint32_t offset;
> +  uint32_t hash;
> +  uint32_t refcount;
> +  unsigned int index;
> +  uint8_t data[];
> +};

I dislike variable length arrays, espcially ones that do not have an
associated length field for bounds checking.  I understand the need,
but I would definitely advise being more paranoid...


> +/* Compare an entry in the string table with a string.  */
> +static int
> +eq_string_table_entry (const void *a, const void *b)

Is this really an "int" function ?  It looks like it returns a bool
to me.


> +/* Remap a type reference within a CodeView symbol.  */
> +static bool remap_symbol_type (void *data, struct type_entry **map,
> +			       uint32_t num_types)

Formatting - please put the function name at the start of the next line.


> +      switch (type)
> +	{
> +	case S_GPROC32:
> +	case S_LPROC32:
> +	  scope_level++;
> +	  break;
> +
> +	case S_END:
> +	case S_PROC_ID_END:
> +	  scope_level--;
> +
> +	  if (scope_level == 0)
> +	    return data;
> +
> +	  break;
> +	}

Please can you add a default case for this switch.  Probably with
an error result of some kind, unless other types are expected and
can be ignored.  (There are several other switch statements like
this too).


I also found that the patch no longer applies to today's sources, so if you
could rebase it that would be great.

Cheers
   Nick


^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] ld: Write globals stream in PDB
  2022-12-06 17:07             ` [PATCH] ld: Write globals stream " Nick Clifton
@ 2022-12-06 17:52               ` Mark Harmstone
  2022-12-08 11:00                 ` Nick Clifton
  2022-12-09  1:11               ` Mark Harmstone
  1 sibling, 1 reply; 19+ messages in thread
From: Mark Harmstone @ 2022-12-06 17:52 UTC (permalink / raw)
  To: Nick Clifton, binutils

Thanks Nick.

On 6/12/22 17:07, Nick Clifton wrote:
 >
 >> +struct global
 >> +{
 >> +  struct global *next;
 >> +  uint32_t offset;
 >> +  uint32_t hash;
 >> +  uint32_t refcount;
 >> +  unsigned int index;
 >> +  uint8_t data[];
 >> +};
 >
 > I dislike variable length arrays, espcially ones that do not have an
 > associated length field for bounds checking.  I understand the need,
 > but I would definitely advise being more paranoid...

There is bounds-checking, it's just not immediately obvious. It ends
with a CodeView record, so the first two bytes of "data" are a little-
endian uint16_t of how many bytes follow.

 >> +/* Compare an entry in the string table with a string.  */
 >> +static int
 >> +eq_string_table_entry (const void *a, const void *b)
 >
 > Is this really an "int" function ?  It looks like it returns a bool
 > to me.

It's a htab_eq, as seen in include/hashtab.h. It's old, it looks like it was
written before _Bool was standardized.

 > I also found that the patch no longer applies to today's sources, so if you
 > could rebase it that would be great.

Sorry, that's because it's part of a larger set of patches, starting with
"[PATCH v2] ld: Generate PDB string table" at
https://sourceware.org/pipermail/binutils/2022-November/thread.html ... I seem
to have confused Jan Beulich with this too.

I think the easiest way is probably if you let me know which (if any) patches
you're happy with, and I'll resubmit the rest as a job lot. I think this patch
series is complete now.

Thanks

Mark

^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] ld: Write globals stream in PDB
  2022-12-06 17:52               ` Mark Harmstone
@ 2022-12-08 11:00                 ` Nick Clifton
  0 siblings, 0 replies; 19+ messages in thread
From: Nick Clifton @ 2022-12-08 11:00 UTC (permalink / raw)
  To: Mark Harmstone, binutils

Hi Mark,


>> I also found that the patch no longer applies to today's sources, so if you
>> could rebase it that would be great.
> 
> Sorry, that's because it's part of a larger set of patches, starting with
> "[PATCH v2] ld: Generate PDB string table" at
> https://sourceware.org/pipermail/binutils/2022-November/thread.html ... I seem
> to have confused Jan Beulich with this too.
> 
> I think the easiest way is probably if you let me know which (if any) patches
> you're happy with, and I'll resubmit the rest as a job lot. I think this patch
> series is complete now.

Please just resubmit the entire series, maybe as a patch set.  I will make
it a priority to review them so that they can get in before the 2.40 branch
is cut.

Cheers
   Nick



^ permalink raw reply	[flat|nested] 19+ messages in thread

* Re: [PATCH] ld: Write globals stream in PDB
  2022-12-06 17:07             ` [PATCH] ld: Write globals stream " Nick Clifton
  2022-12-06 17:52               ` Mark Harmstone
@ 2022-12-09  1:11               ` Mark Harmstone
  1 sibling, 0 replies; 19+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:11 UTC (permalink / raw)
  To: Nick Clifton, binutils

Hi Nick,

>> +      switch (type)
>> +    {
>> +    case S_GPROC32:
>> +    case S_LPROC32:
>> +      scope_level++;
>> +      break;
>> +
>> +    case S_END:
>> +    case S_PROC_ID_END:
>> +      scope_level--;
>> +
>> +      if (scope_level == 0)
>> +        return data;
>> +
>> +      break;
>> +    }
>
> Please can you add a default case for this switch.  Probably with
> an error result of some kind, unless other types are expected and
> can be ignored.  (There are several other switch statements like
> this too). 

Sorry, I missed replying to this comment. Yes, that's what happens: S_GPROC32 etc. increases scope, S_END etc. decreases it, and the majority of records don't affect it.

Mark


^ permalink raw reply	[flat|nested] 19+ messages in thread

end of thread, other threads:[~2022-12-09  1:11 UTC | newest]

Thread overview: 19+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-11-25  2:53 [PATCH v2] ld: Generate PDB string table Mark Harmstone
2022-11-25  2:54 ` [PATCH] ld: Write DEBUG_S_FILECHKSMS entries in PDBs Mark Harmstone
2022-11-27  2:38   ` [PATCH] ld: Fix segfault in populate_publics_stream Mark Harmstone
2022-11-27  2:38     ` [PATCH] ld: Write DEBUG_S_LINES entries in PDB file Mark Harmstone
2022-11-29  0:10       ` [PATCH] ld: Write types into TPI stream of PDB Mark Harmstone
2022-11-29  0:10         ` [PATCH] ld: Write types into IPI " Mark Harmstone
2022-11-29  0:10         ` [PATCH] ld: Parse LF_UDT_SRC_LINE records when creating PDB file Mark Harmstone
2022-12-05  1:53           ` [PATCH] ld: Write globals stream in PDB Mark Harmstone
2022-12-05  1:53             ` [PATCH] ld: Copy other symbols into PDB file Mark Harmstone
2022-12-05  1:53             ` [PATCH] ld: Write linker symbols in PDB Mark Harmstone
2022-12-06 17:07             ` [PATCH] ld: Write globals stream " Nick Clifton
2022-12-06 17:52               ` Mark Harmstone
2022-12-08 11:00                 ` Nick Clifton
2022-12-09  1:11               ` Mark Harmstone
2022-11-28 14:54     ` [PATCH] ld: Fix segfault in populate_publics_stream Jan Beulich
2022-11-28 17:53       ` Mark Harmstone
2022-11-29  9:00         ` Jan Beulich
2022-11-29 17:47           ` Mark Harmstone
2022-11-30  7:00             ` Jan Beulich

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).