summaryrefslogtreecommitdiff
path: root/ld
diff options
context:
space:
mode:
authorMark Harmstone <mark@harmstone.com>2022-12-09 01:52:32 +0000
committerAlan Modra <amodra@gmail.com>2022-12-23 19:37:57 +1030
commit803561cb74922892a0c22ea4ff0564f448796c1c (patch)
treedb918dc778815918fc7cd530d3ff7234fd45c339 /ld
parentf559276dc5f1ac25ca5c310135287dc20c2925e8 (diff)
downloadbinutils-gdb-803561cb74922892a0c22ea4ff0564f448796c1c.tar.gz
ld: Write DEBUG_S_FILECHKSMS entries in PDBs
Diffstat (limited to 'ld')
-rw-r--r--ld/pdb.c431
-rw-r--r--ld/pdb.h9
-rw-r--r--ld/testsuite/ld-pe/pdb.exp154
-rw-r--r--ld/testsuite/ld-pe/pdb3-c13-info1.d8
-rw-r--r--ld/testsuite/ld-pe/pdb3-c13-info2.d8
-rw-r--r--ld/testsuite/ld-pe/pdb3-source-info.d7
-rw-r--r--ld/testsuite/ld-pe/pdb3a.s52
-rw-r--r--ld/testsuite/ld-pe/pdb3b.s52
8 files changed, 709 insertions, 12 deletions
diff --git a/ld/pdb.c b/ld/pdb.c
index 98663a1f9ae..44ba21f2b46 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,135 @@ 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. */
+
+ strptr = NULL;
+ 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 +562,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 +622,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 +646,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 +676,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 +744,23 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings)
free (data);
+ if (*dataptr)
+ {
+ /* Append the C13 info to what's already there, if the module has
+ multiple .debug$S sections. */
+
+ *dataptr = xrealloc (*dataptr, *sizeptr + c13_size);
+ memcpy (*dataptr + *sizeptr, buf, c13_size);
+
+ free (buf);
+ }
+ else
+ {
+ *dataptr = buf;
+ }
+
+ *sizeptr += c13_size;
+
return true;
}
@@ -547,11 +768,15 @@ handle_debugs_section (asection *s, bfd *mod, struct string_table *strings)
data for each object file. */
static bool
populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
- struct string_table *strings)
+ struct string_table *strings,
+ uint32_t *c13_info_size,
+ struct mod_source_files *mod_source)
{
uint8_t int_buf[sizeof (uint32_t)];
+ uint8_t *c13_info = NULL;
*sym_byte_size = sizeof (uint32_t);
+ *c13_info_size = 0;
/* Process .debug$S section(s). */
@@ -559,8 +784,13 @@ populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
{
if (!strcmp (s->name, ".debug$S") && s->size >= sizeof (uint32_t))
{
- if (!handle_debugs_section (s, mod, strings))
+ if (!handle_debugs_section (s, mod, strings, &c13_info,
+ c13_info_size, mod_source))
+ {
+ free (c13_info);
+ free (mod_source->files);
return false;
+ }
}
}
@@ -569,7 +799,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 +828,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 +877,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 +947,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 +1009,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 +1125,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 +1243,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 +1286,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 +1297,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 +1305,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 +1315,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