public inbox for binutils@sourceware.org
 help / color / mirror / Atom feed
From: Mark Harmstone <mark@harmstone.com>
To: binutils@sourceware.org, nickc@redhat.com
Cc: Mark Harmstone <mark@harmstone.com>
Subject: [PATCH 08/10] ld: Write globals stream in PDB
Date: Fri,  9 Dec 2022 01:52:38 +0000	[thread overview]
Message-ID: <20221209015240.6348-8-mark@harmstone.com> (raw)
In-Reply-To: <20221209015240.6348-1-mark@harmstone.com>

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


  parent reply	other threads:[~2022-12-09  1:52 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Mark Harmstone [this message]
2022-12-09  1:52 ` [PATCH 09/10] ld: Copy other symbols into " 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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20221209015240.6348-8-mark@harmstone.com \
    --to=mark@harmstone.com \
    --cc=binutils@sourceware.org \
    --cc=nickc@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).