public inbox for binutils@sourceware.org
 help / color / mirror / Atom feed
* [PATCH 01/10] ld: Generate PDB string table
@ 2022-12-09  1:52 Mark Harmstone
  2022-12-09  1:52 ` [PATCH 02/10] ld: Write DEBUG_S_FILECHKSMS entries in PDBs Mark Harmstone
                   ` (9 more replies)
  0 siblings, 10 replies; 14+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:52 UTC (permalink / raw)
  To: binutils, nickc; +Cc: Mark Harmstone

Resubmitted patch set as per Nick's request.

---
 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..98663a1f9ae 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)
+    {
+      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] 14+ messages in thread

* [PATCH 02/10] ld: Write DEBUG_S_FILECHKSMS entries in PDBs
  2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
@ 2022-12-09  1:52 ` Mark Harmstone
  2022-12-09  1:52 ` [PATCH 03/10] ld: Fix segfault in populate_publics_stream Mark Harmstone
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:52 UTC (permalink / raw)
  To: binutils, nickc; +Cc: Mark Harmstone

---
 ld/pdb.c                              | 430 +++++++++++++++++++++++++-
 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, 708 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 98663a1f9ae..b37a5a0ebfd 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,63 @@ 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 +743,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 +767,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 +783,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 +798,21 @@ 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 +827,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 +876,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 +946,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 +1008,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 +1124,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 +1242,37 @@ 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 +1285,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 +1296,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 +1304,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 +1314,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] 14+ messages in thread

* [PATCH 03/10] ld: Fix segfault in populate_publics_stream
  2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
  2022-12-09  1:52 ` [PATCH 02/10] ld: Write DEBUG_S_FILECHKSMS entries in PDBs Mark Harmstone
@ 2022-12-09  1:52 ` Mark Harmstone
  2022-12-09  1:52 ` [PATCH 04/10] ld: Write DEBUG_S_LINES entries in PDB file Mark Harmstone
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:52 UTC (permalink / raw)
  To: binutils, nickc; +Cc: Mark Harmstone

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

diff --git a/ld/pdb.c b/ld/pdb.c
index b37a5a0ebfd..3edeab25332 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -1410,6 +1410,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] 14+ messages in thread

* [PATCH 04/10] ld: Write DEBUG_S_LINES entries in PDB file
  2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
  2022-12-09  1:52 ` [PATCH 02/10] ld: Write DEBUG_S_FILECHKSMS entries in PDBs Mark Harmstone
  2022-12-09  1:52 ` [PATCH 03/10] ld: Fix segfault in populate_publics_stream Mark Harmstone
@ 2022-12-09  1:52 ` Mark Harmstone
  2022-12-09  1:52 ` [PATCH 05/10] ld: Write types into TPI stream of PDB Mark Harmstone
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:52 UTC (permalink / raw)
  To: binutils, nickc; +Cc: Mark Harmstone

---
 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 3edeab25332..a2f78c4c348 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;
@@ -733,6 +813,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;
@@ -769,7 +871,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;
@@ -784,7 +887,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);
@@ -916,7 +1019,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] 14+ messages in thread

* [PATCH 05/10] ld: Write types into TPI stream of PDB
  2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
                   ` (2 preceding siblings ...)
  2022-12-09  1:52 ` [PATCH 04/10] ld: Write DEBUG_S_LINES entries in PDB file Mark Harmstone
@ 2022-12-09  1:52 ` Mark Harmstone
  2022-12-09  1:52 ` [PATCH 06/10] ld: Write types into IPI " Mark Harmstone
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:52 UTC (permalink / raw)
  To: binutils, nickc; +Cc: Mark Harmstone

---
 ld/pdb.c                                 | 1290 +++++++++++++++++++++-
 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, 2270 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 a2f78c4c348..4bb19420f95 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;
 }
 
@@ -619,6 +786,32 @@ parse_string_table (bfd_byte *data, size_t size,
     }
 }
 
+/* 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 .debug$S section within an object file.  */
 static bool
 handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
@@ -865,6 +1058,984 @@ 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 immediately follows.  */
+		  if (val >= 0x8000)
+		    {
+		      unsigned int param_len = extended_value_len (val);
+
+		      if (param_len == 0)
+			{
+			  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
@@ -872,7 +2043,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;
@@ -894,6 +2065,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.  */
@@ -931,7 +2111,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;
@@ -1019,7 +2200,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++)
 	    {
@@ -1341,7 +2523,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;
@@ -1353,7 +2536,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))
@@ -1883,6 +3066,31 @@ 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
@@ -1891,9 +3099,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)
@@ -1927,7 +3136,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"));
@@ -1943,7 +3154,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"));
@@ -1984,15 +3197,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] 14+ messages in thread

* [PATCH 06/10] ld: Write types into IPI stream of PDB
  2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
                   ` (3 preceding siblings ...)
  2022-12-09  1:52 ` [PATCH 05/10] ld: Write types into TPI stream of PDB Mark Harmstone
@ 2022-12-09  1:52 ` Mark Harmstone
  2022-12-09  1:52 ` [PATCH 07/10] ld: Parse LF_UDT_SRC_LINE records when creating PDB file Mark Harmstone
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:52 UTC (permalink / raw)
  To: binutils, nickc; +Cc: Mark Harmstone

---
 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 4bb19420f95..69fade50814 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -1123,13 +1123,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));
@@ -1910,6 +1913,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;
@@ -1917,7 +2082,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;
 
@@ -1930,7 +2097,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;
@@ -1939,16 +2106,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 */
     {
@@ -1961,7 +2128,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;
@@ -2018,7 +2186,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);
@@ -2043,7 +2211,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;
@@ -2067,7 +2236,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);
@@ -2112,7 +2281,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;
@@ -2201,7 +2370,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++)
 	    {
@@ -2524,7 +2693,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;
@@ -2536,7 +2706,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))
@@ -3209,7 +3379,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] 14+ messages in thread

* [PATCH 07/10] ld: Parse LF_UDT_SRC_LINE records when creating PDB file
  2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
                   ` (4 preceding siblings ...)
  2022-12-09  1:52 ` [PATCH 06/10] ld: Write types into IPI " Mark Harmstone
@ 2022-12-09  1:52 ` Mark Harmstone
  2022-12-09  1:52 ` [PATCH 08/10] ld: Write globals stream in PDB Mark Harmstone
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:52 UTC (permalink / raw)
  To: binutils, nickc; +Cc: Mark Harmstone

---
 ld/pdb.c                                 | 171 +++++++++++++++++++++--
 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, 459 insertions(+), 10 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 69fade50814..53a83fb8d6b 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)
     {
-      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.  */
@@ -1118,13 +1125,149 @@ 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;
@@ -2075,6 +2218,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;
@@ -2104,6 +2251,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)
@@ -2129,7 +2278,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;
@@ -2186,7 +2336,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);
@@ -2212,7 +2363,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;
@@ -2236,7 +2387,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);
@@ -2370,7 +2521,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] 14+ messages in thread

* [PATCH 08/10] ld: Write globals stream in PDB
  2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
                   ` (5 preceding siblings ...)
  2022-12-09  1:52 ` [PATCH 07/10] ld: Parse LF_UDT_SRC_LINE records when creating PDB file Mark Harmstone
@ 2022-12-09  1:52 ` Mark Harmstone
  2022-12-09  1:52 ` [PATCH 09/10] ld: Copy other symbols into PDB file Mark Harmstone
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:52 UTC (permalink / raw)
  To: binutils, nickc; +Cc: Mark Harmstone

---
 ld/pdb.c                                | 1088 +++++++++++++++++++++--
 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, 2279 insertions(+), 79 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 53a83fb8d6b..5257cda2e25 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,
@@ -767,56 +785,677 @@ eq_string_table_entry (const void *a, const void *b)
   const char *s2 = (const char *) b;
   size_t s2_len = strlen (s2);
 
-  if (s2_len != s1->len)
-    return 0;
+  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 memcmp (s1->s, s2, s2_len) == 0;
-}
+		/* Skip to after S_END.  */
 
-/* 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);
+		size -= endptr - data;
+		data = endptr;
 
-      add_string ((char *) data, len, strings);
+		len = bfd_getl16 (data) + sizeof (uint16_t);
 
-      data += len + 1;
+		data += len;
+		size -= len;
 
-      if (size <= len + 1)
-	break;
+		continue;
+	      }
 
-      size -= len + 1;
-    }
-}
+	    scope_level++;
 
-/* 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;
+	    break;
+	  }
 
-    case LF_SHORT:
-    case LF_USHORT:
-      return 2;
+	case S_UDT:
+	  if (scope_level != 0) /* only goes in symbols if local */
+	    *sym_size += len;
+	  break;
 
-    case LF_LONG:
-    case LF_ULONG:
-      return 4;
+	case S_END: /* always copied */
+	case S_PROC_ID_END:
+	  *sym_size += len;
+	  scope_level--;
+	  break;
 
-    case LF_QUADWORD:
-    case LF_UQUADWORD:
-      return 8;
+	default:
+	  einfo (_("%P: warning: unrecognized CodeView record %v\n"), type);
+	  return false;
+	}
+
+      data += len;
+      size -= len;
     }
 
-  return 0;
+  return true;
 }
 
 /* Parse the .debug$S section within an object file.  */
@@ -824,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;
@@ -969,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;
@@ -977,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;
@@ -985,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);
 
@@ -1007,6 +1680,7 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 				strings, bufptr, mod_source))
 	    {
 	      free (data);
+	      free (symbuf);
 	      return false;
 	    }
 
@@ -1035,6 +1709,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;
@@ -1045,22 +1730,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;
 }
@@ -2279,12 +2984,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))
@@ -2315,17 +3019,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;
@@ -2336,11 +3040,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;
 	}
@@ -2350,7 +3054,6 @@ handle_debugt_section (asection *s, bfd *mod, struct types *types,
     }
 
   free (data);
-  free (map);
 
   return true;
 }
@@ -2363,39 +3066,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);
@@ -2403,9 +3124,22 @@ 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) != *c13_info_size)
@@ -2432,7 +3166,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;
@@ -2521,7 +3256,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++)
 	    {
@@ -2549,7 +3285,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);
@@ -2837,6 +3573,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,
@@ -2845,20 +3745,50 @@ 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))
     {
@@ -2883,7 +3813,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);
@@ -3530,7 +4460,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..9048b5fa37c 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] 14+ messages in thread

* [PATCH 09/10] ld: Copy other symbols into PDB file
  2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
                   ` (6 preceding siblings ...)
  2022-12-09  1:52 ` [PATCH 08/10] ld: Write globals stream in PDB Mark Harmstone
@ 2022-12-09  1:52 ` Mark Harmstone
  2022-12-09  1:52 ` [PATCH 10/10] ld: Write linker symbols in PDB Mark Harmstone
  2022-12-22  0:05 ` [PATCH 01/10] ld: Generate PDB string table Alan Modra
  9 siblings, 0 replies; 14+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:52 UTC (permalink / raw)
  To: binutils, nickc; +Cc: Mark Harmstone

---
 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 5257cda2e25..09377a02e52 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 9048b5fa37c..ddb9b86ce85 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] 14+ messages in thread

* [PATCH 10/10] ld: Write linker symbols in PDB
  2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
                   ` (7 preceding siblings ...)
  2022-12-09  1:52 ` [PATCH 09/10] ld: Copy other symbols into PDB file Mark Harmstone
@ 2022-12-09  1:52 ` Mark Harmstone
  2022-12-22  0:05 ` [PATCH 01/10] ld: Generate PDB string table Alan Modra
  9 siblings, 0 replies; 14+ messages in thread
From: Mark Harmstone @ 2022-12-09  1:52 UTC (permalink / raw)
  To: binutils, nickc; +Cc: Mark Harmstone

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

diff --git a/ld/pdb.c b/ld/pdb.c
index 09377a02e52..0346ccb388c 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"
@@ -3420,6 +3421,168 @@ 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
@@ -3429,55 +3592,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.  */
 
@@ -3529,7 +3702,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;
@@ -3619,7 +3793,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++)
 	    {
@@ -4108,7 +4282,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;
@@ -4130,7 +4304,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;
@@ -4822,7 +4996,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, 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 ddb9b86ce85..749a60249df 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] 14+ messages in thread

* Re: [PATCH 01/10] ld: Generate PDB string table
  2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
                   ` (8 preceding siblings ...)
  2022-12-09  1:52 ` [PATCH 10/10] ld: Write linker symbols in PDB Mark Harmstone
@ 2022-12-22  0:05 ` Alan Modra
  2022-12-22  1:25   ` Alan Modra
  9 siblings, 1 reply; 14+ messages in thread
From: Alan Modra @ 2022-12-22  0:05 UTC (permalink / raw)
  To: Mark Harmstone; +Cc: binutils, nickc

On Fri, Dec 09, 2022 at 01:52:31AM +0000, Mark Harmstone wrote:
> Resubmitted patch set as per Nick's request.

The following is a show-stopper.  Any target that doesn't have COFF
support enabled fails to build.

alpha-dec-vms ********make
arm-nacl ********make
csky-elf ********make
bfin-elf ********make
bpf-none ********make
am33_2.0-linux-gnu ********make
arm-vxworks ********make
cris-elf ********make
armeb-linux-gnueabi ********make
arm-netbsdelf ********make
arm-elf ********make
cr16-elf ********make
d10v-elf ********make
arm-nto ********make
arm-linux-gnueabi ********make
crx-elf ********make
arc-elf ********make
arc-linux-uclibc ********make
crisv32-linux-gnu ********make
avr-elf ********make
bfin-linux-uclibc ********make
csky-linux-gnu ********make
cris-linux-gnu ********make
aarch64_be-linux-gnu_ilp32 ********make
alpha-linux-gnuecoff check
alpha-unknown-freebsd4.7 OK
alpha-netbsd OK
arm-pe check
arm-wince-pe check
aarch64-elf OK
alpha-linux-gnu check
aarch64-linux-gnu OK
i386-msdos ********make
d30v-elf ********make
h8300-linux-gnu ********make
ip2k-elf ********make
fr30-elf ********make
hppa64-linux-gnu ********make
ft32-elf ********make
hppa-linux-gnu ********make
h8300-elf ********make
iq2000-elf ********make
i386-bsd ********make
i686-vxworks ********make
frv-linux-gnu ********make
hppa64-hp-hpux11.23 ********make
epiphany-elf ********make
dlx-elf ********make
frv-elf ********make
ia64-vms check
ia64-hpux check
hppa-hp-hpux10 OK
ia64-netbsd check
ia64-freebsd5 check
ia64-elf check
i386-go32 check
i686-pe OK
i386-darwin check
i686-pc-beos OK
i686-nto OK
i386-lynxos OK
i686-pc-elf OK
i586-linux-gnu OK
ia64-linux-gnu check
loongarch32-elf ********make
microblaze-linux-gnu ********make
mcore-elf ********make
microblaze-elf ********make
m32r-linux-gnu ********make
m68hc12-elf ********make
mep-elf ********make
msp430-elf ********make
m32r-elf ********make
m68k-linux-gnu ********make
m68hc11-elf ********make
mn10200-elf ********make
m32c-elf ********make
mn10300-elf ********make
moxie-elf ********make
metag-linux-gnu ********make
m68k-elf ********make
lm32-elf ********make
lm32-linux-gnu ********make
mmix ********make
mcore-pe check
mips-vxworks check
loongarch64-linux-gnu OK
mips64el-openbsd check
mips64-openbsd check
mipstx39-elf check
mipsisa32r2el-elf check
mips-sgi-irix6 check
mipsel-linux-gnu OK
mipsisa32el-linux-gnu OK
mips-linux-gnu check
mips64-linux-gnuabi64 check
pdp11-dec-aout ********make
nios2-linux-gnu ********make
ns32k-pc532-mach ********make
s12z-elf ********make
pru-elf ********make
ns32k-openbsd ********make
rx-elf ********make
nds32le-linux-gnu ********make
mt-elf ********make
riscv32-elf ********make
or1k-elf ********make
pj-elf ********make
rl78-elf ********make
nds32be-elf ********make
or1k-linux-gnu ********make
s390-linux-gnu ********make
riscv64-linux-gnu ********make
rs6000-aix5.1 check
rs6000-aix4.3.3 check
powerpc-aix5.1 check
rs6000-aix7.2 check
powerpc-aix7.2 check
powerpc-wrs-vxworks OK
powerpc-nto OK
powerpc-freebsd OK
powerpc-eabisim OK
powerpcle-elf OK
powerpc-eabivle OK
powerpc64-freebsd OK
powerpc-linux-gnu OK
powerpc64-linux-gnu OK
powerpc64le-linux-gnu OK
x86_64-rdos ********make
v850-elf ********make
x86_64-cloudabi ********make
sparc-elf ********make
xgate-elf ********make
visium-elf ********make
tilepro-linux-gnu ********make
xstormy16-elf ********make
spu-elf ********make
s390x-linux-gnu ********make
sparc-vxworks ********make
vax-netbsdelf ********make
sparc-linux-gnu ********make
score-elf ********make
sparc64-linux-gnu ********make
tic6x-elf ********make
sparc-sun-solaris2 ********make
tilegx-linux-gnu ********make
wasm32 OK
sh-pe check
tic30-unknown-coff OK
tic4x-coff OK
sh-coff check
sh-vxworks check
tic54x-coff OK
sh-rtems check
sh-nto check
shle-unknown-netbsdelf check
x86_64-w64-mingw32 check
sh4-linux-gnu check
x86_64-pc-linux-gnux32 OK
x86_64-linux-gnu OK
z80-elf ********make
xtensa-lx106-elf ********make
z80-coff OK
z8k-coff OK

The errors are all like this:
/usr/local/bin/ld: pdb.o: in function `handle_debugs_section':
/home/alan/src/binutils-gdb/ld/pdb.c:1863: undefined reference to `_bfd_coff_read_internal_relocs'
collect2: error: ld returned 1 exit status

There are also (false positive I think) compiler warnings that should
be fixed.

In function ‘handle_debugs_section’,
    inlined from ‘populate_module_stream’ at /home/alan/src/binutils-gdb/ld/pdb.c:3638:13,
    inlined from ‘create_module_info_substream’ at /home/alan/src/binutils-gdb/ld/pdb.c:3792:12,
    inlined from ‘populate_dbi_stream’ at /home/alan/src/binutils-gdb/ld/pdb.c:4305:8,
    inlined from ‘create_pdb_file’ at /home/alan/src/binutils-gdb/ld/pdb.c:4997:8:
/home/alan/src/binutils-gdb/ld/pdb.c:2067:24: error: ‘bufptr’ may be used uninitialized [-Werror=maybe-uninitialized]
 2067 |                 bufptr += sizeof (uint32_t);
      |                 ~~~~~~~^~~~~~~~~~~~~~~~~~~~
/home/alan/src/binutils-gdb/ld/pdb.c: In function ‘create_pdb_file’:
/home/alan/src/binutils-gdb/ld/pdb.c:1838:18: note: ‘bufptr’ was declared here
 1838 |   uint8_t *buf, *bufptr, *symbuf, *symbufptr;
      |                  ^~~~~~
In function ‘copy_filechksms’,
    inlined from ‘handle_debugs_section’ at /home/alan/src/binutils-gdb/ld/pdb.c:2042:9,
    inlined from ‘populate_module_stream’ at /home/alan/src/binutils-gdb/ld/pdb.c:3638:13,
    inlined from ‘create_module_info_substream’ at /home/alan/src/binutils-gdb/ld/pdb.c:3792:12,
    inlined from ‘populate_dbi_stream’ at /home/alan/src/binutils-gdb/ld/pdb.c:4305:8,
    inlined from ‘create_pdb_file’ at /home/alan/src/binutils-gdb/ld/pdb.c:4997:8:
/home/alan/src/binutils-gdb/ld/pdb.c:717:13: error: ‘strptr’ may be used uninitialized [-Werror=maybe-uninitialized]
  717 |       strptr++;
      |       ~~~~~~^~
/home/alan/src/binutils-gdb/ld/pdb.c: In function ‘create_pdb_file’:
/home/alan/src/binutils-gdb/ld/pdb.c:613:19: note: ‘strptr’ was declared here
  613 |   struct string **strptr;
      |                   ^~~~~~

-- 
Alan Modra
Australia Development Lab, IBM

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

* Re: [PATCH 01/10] ld: Generate PDB string table
  2022-12-22  0:05 ` [PATCH 01/10] ld: Generate PDB string table Alan Modra
@ 2022-12-22  1:25   ` Alan Modra
  2022-12-22 10:22     ` Alan Modra
  0 siblings, 1 reply; 14+ messages in thread
From: Alan Modra @ 2022-12-22  1:25 UTC (permalink / raw)
  To: Mark Harmstone; +Cc: binutils, nickc

On Thu, Dec 22, 2022 at 10:35:48AM +1030, Alan Modra wrote:
> On Fri, Dec 09, 2022 at 01:52:31AM +0000, Mark Harmstone wrote:
> > Resubmitted patch set as per Nick's request.
> 
> The following is a show-stopper.  Any target that doesn't have COFF
> support enabled fails to build.

This fixes the builds for me.  However, it doesn't match the pdb
support you have in bfd/config.bfd so I'm guessing you'll want to
change it to only compile ld/pdb.c for x86 and make the calls in
pe*.em to pdb functions conditional on x86.  Or make bfd/pdb.c
compile for all pe targets.

diff --git a/ld/Makefile.am b/ld/Makefile.am
index 65fef4e1690..005c38c9c16 100644
--- a/ld/Makefile.am
+++ b/ld/Makefile.am
@@ -466,6 +466,7 @@ ALL_64_EMULATIONS = $(ALL_64_EMULATION_SOURCES:.c=.@OBJEXT@)
 
 ALL_EMUL_EXTRA_OFILES = \
 	deffilep.@OBJEXT@ \
+	pdb.@OBJEXT@ \
 	pe-dll.@OBJEXT@ \
 	ldelf.@OBJEXT@ \
 	ldelfgen.@OBJEXT@
@@ -496,7 +497,7 @@ OFILES = ldgram.@OBJEXT@ ldlex-wrapper.@OBJEXT@ lexsup.@OBJEXT@ ldlang.@OBJEXT@
 	mri.@OBJEXT@ ldctor.@OBJEXT@ ldmain.@OBJEXT@ plugin.@OBJEXT@ \
 	ldwrite.@OBJEXT@ ldexp.@OBJEXT@  ldemul.@OBJEXT@ ldver.@OBJEXT@ ldmisc.@OBJEXT@ \
 	ldfile.@OBJEXT@ ldcref.@OBJEXT@ ${EMULATION_OFILES} ${EMUL_EXTRA_OFILES} \
-	ldbuildid.@OBJEXT@ pdb.@OBJEXT@
+	ldbuildid.@OBJEXT@
 
 STAGESTUFF = *.@OBJEXT@ ldscripts/* e*.c
 
@@ -960,7 +961,7 @@ EXTRA_ld_new_SOURCES += pep-dll.c pe-dll.c ldelf.c ldelfgen.c
 
 ld_new_SOURCES = ldgram.y ldlex-wrapper.c lexsup.c ldlang.c mri.c ldctor.c ldmain.c \
 	ldwrite.c ldexp.c ldemul.c ldver.c ldmisc.c ldfile.c ldcref.c plugin.c \
-	ldbuildid.c pdb.c
+	ldbuildid.c
 ld_new_DEPENDENCIES = $(EMULATION_OFILES) $(EMUL_EXTRA_OFILES) \
 		      $(BFDLIB) $(LIBCTF) $(LIBIBERTY) $(LIBINTL_DEP) $(JANSSON_LIBS)
 ld_new_LDADD = $(EMULATION_OFILES) $(EMUL_EXTRA_OFILES) $(BFDLIB) $(LIBCTF) \
diff --git a/ld/Makefile.in b/ld/Makefile.in
index ff4c916c27b..f3d23c996d1 100644
--- a/ld/Makefile.in
+++ b/ld/Makefile.in
@@ -212,7 +212,7 @@ am_ld_new_OBJECTS = ldgram.$(OBJEXT) ldlex-wrapper.$(OBJEXT) \
 	ldctor.$(OBJEXT) ldmain.$(OBJEXT) ldwrite.$(OBJEXT) \
 	ldexp.$(OBJEXT) ldemul.$(OBJEXT) ldver.$(OBJEXT) \
 	ldmisc.$(OBJEXT) ldfile.$(OBJEXT) ldcref.$(OBJEXT) \
-	plugin.$(OBJEXT) ldbuildid.$(OBJEXT) pdb.$(OBJEXT)
+	plugin.$(OBJEXT) ldbuildid.$(OBJEXT)
 ld_new_OBJECTS = $(am_ld_new_OBJECTS)
 am__DEPENDENCIES_1 =
 @ENABLE_LIBCTF_TRUE@am__DEPENDENCIES_2 = ../libctf/libctf.la
@@ -965,6 +965,7 @@ ALL_64_EMULATION_SOURCES = \
 ALL_64_EMULATIONS = $(ALL_64_EMULATION_SOURCES:.c=.@OBJEXT@)
 ALL_EMUL_EXTRA_OFILES = \
 	deffilep.@OBJEXT@ \
+	pdb.@OBJEXT@ \
 	pe-dll.@OBJEXT@ \
 	ldelf.@OBJEXT@ \
 	ldelfgen.@OBJEXT@
@@ -994,7 +995,7 @@ OFILES = ldgram.@OBJEXT@ ldlex-wrapper.@OBJEXT@ lexsup.@OBJEXT@ ldlang.@OBJEXT@
 	mri.@OBJEXT@ ldctor.@OBJEXT@ ldmain.@OBJEXT@ plugin.@OBJEXT@ \
 	ldwrite.@OBJEXT@ ldexp.@OBJEXT@  ldemul.@OBJEXT@ ldver.@OBJEXT@ ldmisc.@OBJEXT@ \
 	ldfile.@OBJEXT@ ldcref.@OBJEXT@ ${EMULATION_OFILES} ${EMUL_EXTRA_OFILES} \
-	ldbuildid.@OBJEXT@ pdb.@OBJEXT@
+	ldbuildid.@OBJEXT@
 
 STAGESTUFF = *.@OBJEXT@ ldscripts/* e*.c
 SRC_POTFILES = $(CFILES) $(HFILES)
@@ -1013,7 +1014,7 @@ EXTRA_ld_new_SOURCES = deffilep.y ldlex.l pep-dll.c pe-dll.c ldelf.c \
 	$(ALL_64_EMULATION_SOURCES)
 ld_new_SOURCES = ldgram.y ldlex-wrapper.c lexsup.c ldlang.c mri.c ldctor.c ldmain.c \
 	ldwrite.c ldexp.c ldemul.c ldver.c ldmisc.c ldfile.c ldcref.c plugin.c \
-	ldbuildid.c pdb.c
+	ldbuildid.c
 
 ld_new_DEPENDENCIES = $(EMULATION_OFILES) $(EMUL_EXTRA_OFILES) \
 		      $(BFDLIB) $(LIBCTF) $(LIBIBERTY) $(LIBINTL_DEP) $(JANSSON_LIBS)
@@ -1579,7 +1580,6 @@ distclean-compile:
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libldtestplug4_la-testplug4.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libldtestplug_la-testplug.Plo@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mri.Po@am__quote@
-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pdb.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pe-dll.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/pep-dll.Po@am__quote@
 @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/plugin.Po@am__quote@
diff --git a/ld/configure.tgt b/ld/configure.tgt
index 741b246f67e..2d7c14b6308 100644
--- a/ld/configure.tgt
+++ b/ld/configure.tgt
@@ -120,7 +120,7 @@ aarch64-*-haiku*)	targ_emul=aarch64haiku
 			;;
 aarch64-*-pe*)
 			targ_emul=aarch64pe
-			targ_extra_ofiles="deffilep.o pep-dll-aarch64.o"
+			targ_extra_ofiles="deffilep.o pdb.o pep-dll-aarch64.o"
 			;;
 alpha*-*-freebsd* | alpha*-*-kfreebsd*-gnu)
 			targ_emul=elf64alpha_fbsd
@@ -163,15 +163,15 @@ arc*-*-linux*)		case "${with_cpu}" in
 			targ_extra_emuls="${targ_extra_emuls} arcelf arcv2elf arcv2elfx"
 			;;
 arm*-*-cegcc*)		targ_emul=arm_wince_pe
-			targ_extra_ofiles="deffilep.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pe-dll.o"
 			LIB_PATH='${tooldir}/lib/w32api'
 			;;
 arm-wince-pe | arm-*-wince | arm*-*-mingw32ce*)
 			targ_emul=arm_wince_pe
-			targ_extra_ofiles="deffilep.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pe-dll.o"
 			;;
 arm-*-pe)		targ_emul=armpe
-			targ_extra_ofiles="deffilep.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pe-dll.o"
 			;;
 arm*b-*-freebsd*)	targ_emul=armelfb_fbsd
 			targ_extra_emuls="armelf_fbsd armelf"
@@ -389,7 +389,7 @@ i[3-7]86-*-solaris*)	targ_emul=elf_i386_ldso
 			targ_extra_libpath=$targ_extra_emuls
 			;;
 i[3-7]86-*-netbsdpe*)	targ_emul=i386pe
-			targ_extra_ofiles="deffilep.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pe-dll.o"
 			;;
 i[3-7]86-*-netbsd* | \
 i[3-7]86-*-netbsd*-gnu* | \
@@ -423,17 +423,17 @@ i[3-7]86-*-moss*)	targ_emul=i386moss
 			targ_extra_emuls=i386msdos
 			;;
 i[3-7]86-*-winnt*)	targ_emul=i386pe ;
-			targ_extra_ofiles="deffilep.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pe-dll.o"
 			;;
 i[3-7]86-*-pe)		targ_emul=i386pe ;
-			targ_extra_ofiles="deffilep.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pe-dll.o"
 			;;
 i[3-7]86-*-cygwin*)	targ_emul=i386pe ;
-			targ_extra_ofiles="deffilep.o pe-dll.o" ;
+			targ_extra_ofiles="deffilep.o pdb.o pe-dll.o" ;
 			test "$targ" != "$host" && LIB_PATH='${tooldir}/lib/w32api'
 			;;
 i[3-7]86-*-mingw32*)	targ_emul=i386pe ;
-			targ_extra_ofiles="deffilep.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pe-dll.o"
 			;;
 i[3-7]86-*-interix*)	targ_emul=i386pe_posix;
 			targ_extra_ofiles="deffilep.o pe-dll.o"
@@ -503,7 +503,7 @@ m68*-*-haiku*)		targ_emul=m68kelf
 m68*-*-*)		targ_emul=m68kelf
 			;;
 mcore-*-pe)		targ_emul=mcorepe ;
-			targ_extra_ofiles="deffilep.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pe-dll.o"
 			;;
 mcore-*-elf)		targ_emul=elf32mcore
 			;;
@@ -900,7 +900,7 @@ sh-*-nto*)		targ_emul=shelf_nto
 			targ_extra_emuls=shlelf_nto
 			;;
 sh-*-pe)		targ_emul=shpe ;
-			targ_extra_ofiles="deffilep.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pe-dll.o"
 			;;
 sh-*-*)			targ_emul=sh;
 			targ_extra_emuls=shl
@@ -1047,16 +1047,16 @@ x86_64-*-freebsd* | x86_64-*-kfreebsd*-gnu)
 			;;
 x86_64-*-pe | x86_64-*-pep) targ_emul=i386pep ;
 			targ_extra_emuls=i386pe ;
-			targ_extra_ofiles="deffilep.o pep-dll-x86_64.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pep-dll-x86_64.o pe-dll.o"
 			;;
 x86_64-*-cygwin)	targ_emul=i386pep ;
 			targ_extra_emuls=i386pe
-			targ_extra_ofiles="deffilep.o pep-dll.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pep-dll.o pe-dll.o"
 			test "$targ" != "$host" && LIB_PATH='${tooldir}/lib/w32api'
 			;;
 x86_64-*-mingw*)	targ_emul=i386pep ;
 			targ_extra_emuls=i386pe
-			targ_extra_ofiles="deffilep.o pep-dll.o pe-dll.o"
+			targ_extra_ofiles="deffilep.o pdb.o pep-dll.o pe-dll.o"
 			;;
 xgate-*-*)		targ_emul=xgateelf
 			targ_extra_ofiles=ldelfgen.o
diff --git a/ld/pdb.c b/ld/pdb.c
index 0346ccb388c..2df75e8f842 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -663,6 +663,7 @@ copy_filechksms (uint8_t *data, uint32_t size, char *string_table,
   /* Add the files to mod_source, so that they'll appear in the source
      info substream.  */
 
+  strptr = NULL;
   if (num_files > 0)
     {
       uint16_t new_count = num_files + mod_source->files_count;
@@ -2004,25 +2005,15 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
 
   /* copy data */
 
+  buf = NULL;
   if (c13_size != 0)
-    {
-      buf = xmalloc (c13_size);
-      bufptr = buf;
-    }
-  else
-    {
-      buf = NULL;
-    }
+    buf = xmalloc (c13_size);
+  bufptr = buf;
 
+  symbuf = NULL;
   if (sym_size != 0)
-    {
-      symbuf = xmalloc (sym_size);
-      symbufptr = symbuf;
-    }
-  else
-    {
-      symbuf = NULL;
-    }
+    symbuf = xmalloc (sym_size);
+  symbufptr = symbuf;
 
   off = sizeof (uint32_t);
 

-- 
Alan Modra
Australia Development Lab, IBM

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

* Re: [PATCH 01/10] ld: Generate PDB string table
  2022-12-22  1:25   ` Alan Modra
@ 2022-12-22 10:22     ` Alan Modra
  2022-12-23 10:54       ` Alan Modra
  0 siblings, 1 reply; 14+ messages in thread
From: Alan Modra @ 2022-12-22 10:22 UTC (permalink / raw)
  To: Mark Harmstone; +Cc: binutils, nickc

There is also this:

ld/pdb.h:571
/* 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;

ld/pdb.c:2286
  memcpy (&umsl->line_no, &usl->line_no, sizeof (uint16_t));

which results in uninitialised data.

-- 
Alan Modra
Australia Development Lab, IBM

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

* Re: [PATCH 01/10] ld: Generate PDB string table
  2022-12-22 10:22     ` Alan Modra
@ 2022-12-23 10:54       ` Alan Modra
  0 siblings, 0 replies; 14+ messages in thread
From: Alan Modra @ 2022-12-23 10:54 UTC (permalink / raw)
  To: Mark Harmstone; +Cc: binutils, nickc

Mark, I've now pushed your patches, with fixes for the compiler
warnings plus the wrong-size memcpy.

-- 
Alan Modra
Australia Development Lab, IBM

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

end of thread, other threads:[~2022-12-23 10:54 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-12-09  1:52 [PATCH 01/10] ld: Generate PDB string table Mark Harmstone
2022-12-09  1:52 ` [PATCH 02/10] ld: Write DEBUG_S_FILECHKSMS entries in PDBs Mark Harmstone
2022-12-09  1:52 ` [PATCH 03/10] ld: Fix segfault in populate_publics_stream Mark Harmstone
2022-12-09  1:52 ` [PATCH 04/10] ld: Write DEBUG_S_LINES entries in PDB file Mark Harmstone
2022-12-09  1:52 ` [PATCH 05/10] ld: Write types into TPI stream of PDB Mark Harmstone
2022-12-09  1:52 ` [PATCH 06/10] ld: Write types into IPI " Mark Harmstone
2022-12-09  1:52 ` [PATCH 07/10] ld: Parse LF_UDT_SRC_LINE records when creating PDB file Mark Harmstone
2022-12-09  1:52 ` [PATCH 08/10] ld: Write globals stream in PDB Mark Harmstone
2022-12-09  1:52 ` [PATCH 09/10] ld: Copy other symbols into PDB file Mark Harmstone
2022-12-09  1:52 ` [PATCH 10/10] ld: Write linker symbols in PDB Mark Harmstone
2022-12-22  0:05 ` [PATCH 01/10] ld: Generate PDB string table Alan Modra
2022-12-22  1:25   ` Alan Modra
2022-12-22 10:22     ` Alan Modra
2022-12-23 10:54       ` Alan Modra

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).