ld: Add module information substream to PDB files
authorMark Harmstone <mark@harmstone.com>
Thu, 3 Nov 2022 02:46:04 +0000 (02:46 +0000)
committerMark Harmstone <mark@harmstone.com>
Thu, 10 Nov 2022 04:50:43 +0000 (04:50 +0000)
ld/pdb.c
ld/pdb.h
ld/testsuite/ld-pe/pdb.exp
ld/testsuite/ld-pe/pdb2a.s [new file with mode: 0644]
ld/testsuite/ld-pe/pdb2b.s [new file with mode: 0644]

index 8e151dad10ff6b8eeb0666a2c3df4846dd18952d..910589b4d09d452e8bf1b4576940e1acfdf0c8a6 100644 (file)
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -383,6 +383,196 @@ get_arch_number (bfd *abfd)
   return IMAGE_FILE_MACHINE_I386;
 }
 
+/* Populate the module stream, which consists of the transformed .debug$S
+   data for each object file.  */
+static bool
+populate_module_stream (bfd *stream, uint32_t *sym_byte_size)
+{
+  uint8_t int_buf[sizeof (uint32_t)];
+
+  *sym_byte_size = sizeof (uint32_t);
+
+  /* Write the signature.  */
+
+  bfd_putl32 (CV_SIGNATURE_C13, int_buf);
+
+  if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
+    return false;
+
+  /* Write the global refs size.  */
+
+  bfd_putl32 (0, int_buf);
+
+  if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
+    return false;
+
+  return true;
+}
+
+/* Create the module info substream within the DBI.  */
+static bool
+create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
+                             uint32_t *size)
+{
+  uint8_t *ptr;
+
+  static const char linker_fn[] = "* Linker *";
+
+  *size = 0;
+
+  for (bfd *in = coff_data (abfd)->link_info->input_bfds; in;
+       in = in->link.next)
+    {
+      size_t len = sizeof (struct module_info);
+
+      if (!strcmp (bfd_get_filename (in), "dll stuff"))
+       {
+         len += sizeof (linker_fn); /* Object name.  */
+         len++; /* Empty module name.  */
+       }
+      else if (in->my_archive)
+       {
+         char *name = lrealpath (bfd_get_filename (in));
+
+         len += strlen (name) + 1; /* Object name.  */
+
+         free (name);
+
+         name = lrealpath (bfd_get_filename (in->my_archive));
+
+         len += strlen (name) + 1; /* Archive name.  */
+
+         free (name);
+       }
+      else
+       {
+         char *name = lrealpath (bfd_get_filename (in));
+         size_t name_len = strlen (name) + 1;
+
+         len += name_len; /* Object name.  */
+         len += name_len; /* And again as the archive name.  */
+
+         free (name);
+       }
+
+      if (len % 4)
+       len += 4 - (len % 4);
+
+      *size += len;
+    }
+
+  *data = xmalloc (*size);
+
+  ptr = *data;
+
+  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;
+      uint8_t *start = ptr;
+
+      stream = add_stream (pdb, NULL, &stream_num);
+
+      if (!stream)
+       {
+         free (*data);
+         return false;
+       }
+
+      if (!populate_module_stream (stream, &sym_byte_size))
+       {
+         free (*data);
+         return false;
+       }
+
+      bfd_putl32 (0, &mod->unused1);
+
+      /* These are dummy values - MSVC copies the first section contribution
+        entry here, but doesn't seem to use it for anything.  */
+      bfd_putl16 (0xffff, &mod->sc.section);
+      bfd_putl16 (0, &mod->sc.padding1);
+      bfd_putl32 (0, &mod->sc.offset);
+      bfd_putl32 (0xffffffff, &mod->sc.size);
+      bfd_putl32 (0, &mod->sc.characteristics);
+      bfd_putl16 (0xffff, &mod->sc.module_index);
+      bfd_putl16 (0, &mod->sc.padding2);
+      bfd_putl32 (0, &mod->sc.data_crc);
+      bfd_putl32 (0, &mod->sc.reloc_crc);
+
+      bfd_putl16 (0, &mod->flags);
+      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_putl16 (0, &mod->source_file_count);
+      bfd_putl16 (0, &mod->padding);
+      bfd_putl32 (0, &mod->unused2);
+      bfd_putl32 (0, &mod->source_file_name_index);
+      bfd_putl32 (0, &mod->pdb_file_path_name_index);
+
+      ptr += sizeof (struct module_info);
+
+      if (!strcmp (bfd_get_filename (in), "dll stuff"))
+       {
+         /* Object name.  */
+         memcpy (ptr, linker_fn, sizeof (linker_fn));
+         ptr += sizeof (linker_fn);
+
+         /* Empty module name.  */
+         *ptr = 0;
+         ptr++;
+       }
+      else if (in->my_archive)
+       {
+         char *name = lrealpath (bfd_get_filename (in));
+         size_t name_len = strlen (name) + 1;
+
+         /* Object name.  */
+         memcpy (ptr, name, name_len);
+         ptr += name_len;
+
+         free (name);
+
+         name = lrealpath (bfd_get_filename (in->my_archive));
+         name_len = strlen (name) + 1;
+
+         /* Archive name.  */
+         memcpy (ptr, name, name_len);
+         ptr += name_len;
+
+         free (name);
+       }
+      else
+       {
+         char *name = lrealpath (bfd_get_filename (in));
+         size_t name_len = strlen (name) + 1;
+
+         /* Object name.  */
+         memcpy (ptr, name, name_len);
+         ptr += name_len;
+
+         /* Object name again as archive name.  */
+         memcpy (ptr, name, name_len);
+         ptr += name_len;
+
+         free (name);
+       }
+
+      /* Pad to next four-byte boundary.  */
+
+      if ((ptr - start) % 4)
+       {
+         memset (ptr, 0, 4 - ((ptr - start) % 4));
+         ptr += 4 - ((ptr - start) % 4);
+       }
+    }
+
+  return true;
+}
+
 /* Return the index of a given output section.  */
 static uint16_t
 find_section_number (bfd *abfd, asection *sect)
@@ -404,13 +594,18 @@ find_section_number (bfd *abfd, asection *sect)
 
 /* Stream 4 is the debug information (DBI) stream.  */
 static bool
-populate_dbi_stream (bfd *stream, bfd *abfd,
+populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
                     uint16_t section_header_stream_num,
                     uint16_t sym_rec_stream_num,
                     uint16_t publics_stream_num)
 {
   struct pdb_dbi_stream_header h;
   struct optional_dbg_header opt;
+  void *mod_info;
+  uint32_t mod_info_size;
+
+  if (!create_module_info_substream (abfd, pdb, &mod_info, &mod_info_size))
+    return false;
 
   bfd_putl32 (0xffffffff, &h.version_signature);
   bfd_putl32 (DBI_STREAM_VERSION_70, &h.version_header);
@@ -421,7 +616,7 @@ populate_dbi_stream (bfd *stream, bfd *abfd,
   bfd_putl16 (0, &h.pdb_dll_version);
   bfd_putl16 (sym_rec_stream_num, &h.sym_record_stream);
   bfd_putl16 (0, &h.pdb_dll_rbld);
-  bfd_putl32 (0, &h.mod_info_size);
+  bfd_putl32 (mod_info_size, &h.mod_info_size);
   bfd_putl32 (0, &h.section_contribution_size);
   bfd_putl32 (0, &h.section_map_size);
   bfd_putl32 (0, &h.source_info_size);
@@ -434,7 +629,18 @@ populate_dbi_stream (bfd *stream, bfd *abfd,
   bfd_putl32 (0, &h.padding);
 
   if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h))
-    return false;
+    {
+      free (mod_info);
+      return false;
+    }
+
+  if (bfd_bwrite (mod_info, mod_info_size, stream) != mod_info_size)
+    {
+      free (mod_info);
+      return false;
+    }
+
+  free (mod_info);
 
   bfd_putl16 (0xffff, &opt.fpo_stream);
   bfd_putl16 (0xffff, &opt.exception_stream);
@@ -888,7 +1094,7 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
       goto end;
     }
 
-  if (!populate_dbi_stream (dbi_stream, abfd, section_header_stream_num,
+  if (!populate_dbi_stream (dbi_stream, abfd, pdb, section_header_stream_num,
                            sym_rec_stream_num, publics_stream_num))
     {
       einfo (_("%P: warning: cannot populate DBI stream "
index 1a80101d2888a2068e146ba7b72b27c8dc9dd91a..a44618578b764868c71660fd7e40350ed787f72c 100644 (file)
--- a/ld/pdb.h
+++ b/ld/pdb.h
@@ -153,6 +153,39 @@ struct optional_dbg_header
   uint16_t orig_section_header_stream;
 };
 
+#define CV_SIGNATURE_C13               4
+
+/* SC in dbicommon.h */
+struct section_contribution
+{
+  uint16_t section;
+  uint16_t padding1;
+  uint32_t offset;
+  uint32_t size;
+  uint32_t characteristics;
+  uint16_t module_index;
+  uint16_t padding2;
+  uint32_t data_crc;
+  uint32_t reloc_crc;
+};
+
+/* MODI_60_Persist in dbi.h */
+struct module_info
+{
+  uint32_t unused1;
+  struct section_contribution sc;
+  uint16_t flags;
+  uint16_t module_sym_stream;
+  uint32_t sym_byte_size;
+  uint32_t c11_byte_size;
+  uint32_t c13_byte_size;
+  uint16_t source_file_count;
+  uint16_t padding;
+  uint32_t unused2;
+  uint32_t source_file_name_index;
+  uint32_t pdb_file_path_name_index;
+};
+
 extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
 
 #endif
index ee314c41f9b6d98207505adfec27f357d9454be5..ab22506d0f235f194da6fbf0208fcec99eb294b3 100644 (file)
@@ -494,55 +494,189 @@ proc check_publics_stream { pdb } {
     return 1
 }
 
-if ![ld_assemble $as $srcdir/$subdir/pdb1.s tmpdir/pdb1.o] {
-    unsupported "Build pdb1.o"
-    return
-}
+proc test1 { } {
+    global as
+    global ld
+    global srcdir
+    global subdir
 
-if ![ld_link $ld "tmpdir/pdb1.exe" "--pdb=tmpdir/pdb1.pdb --gc-sections -e foo tmpdir/pdb1.o"] {
-    fail "Could not create a PE image with a PDB file"
-    return
-}
+    if ![ld_assemble $as $srcdir/$subdir/pdb1.s tmpdir/pdb1.o] {
+       unsupported "Build pdb1.o"
+       return
+    }
 
-if ![string equal [get_pdb_name "tmpdir/pdb1.exe"] "pdb1.pdb"] {
-    fail "PDB filename not found in CodeView debug info"
-    return
-}
+    if ![ld_link $ld "tmpdir/pdb1.exe" "--pdb=tmpdir/pdb1.pdb --gc-sections -e foo tmpdir/pdb1.o"] {
+       fail "Could not create a PE image with a PDB file"
+       return
+    }
 
-pass "PDB filename present in CodeView debug info"
+    if ![string equal [get_pdb_name "tmpdir/pdb1.exe"] "pdb1.pdb"] {
+       fail "PDB filename not found in CodeView debug info"
+       return
+    }
 
-if [check_pdb_info_stream tmpdir/pdb1.pdb [get_pdb_guid "tmpdir/pdb1.exe"]] {
-    pass "Valid PDB info stream"
-} else {
-    fail "Invalid PDB info stream"
-}
+    pass "PDB filename present in CodeView debug info"
 
-if [check_type_stream tmpdir/pdb1.pdb "0002"] {
-    pass "Valid TPI stream"
-} else {
-    fail "Invalid TPI stream"
-}
+    if [check_pdb_info_stream tmpdir/pdb1.pdb [get_pdb_guid "tmpdir/pdb1.exe"]] {
+       pass "Valid PDB info stream"
+    } else {
+       fail "Invalid PDB info stream"
+    }
 
-if [check_type_stream tmpdir/pdb1.pdb "0004"] {
-    pass "Valid IPI stream"
-} else {
-    fail "Invalid IPI stream"
-}
+    if [check_type_stream tmpdir/pdb1.pdb "0002"] {
+       pass "Valid TPI stream"
+    } else {
+       fail "Invalid TPI stream"
+    }
+
+    if [check_type_stream tmpdir/pdb1.pdb "0004"] {
+       pass "Valid IPI stream"
+    } else {
+       fail "Invalid IPI stream"
+    }
+
+    if [check_dbi_stream tmpdir/pdb1.pdb] {
+       pass "Valid DBI stream"
+    } else {
+       fail "Invalid DBI stream"
+    }
 
-if [check_dbi_stream tmpdir/pdb1.pdb] {
-    pass "Valid DBI stream"
-} else {
-    fail "Invalid DBI stream"
+    if [check_section_stream tmpdir/pdb1.exe tmpdir/pdb1.pdb] {
+       pass "Valid section stream"
+    } else {
+       fail "Invalid section stream"
+    }
+
+    if [check_publics_stream tmpdir/pdb1.pdb] {
+       pass "Valid publics stream"
+    } else {
+       fail "Invalid publics stream"
+    }
 }
 
-if [check_section_stream tmpdir/pdb1.exe tmpdir/pdb1.pdb] {
-    pass "Valid section stream"
-} else {
-    fail "Invalid section stream"
+proc test_mod_info { mod_info } {
+    # check filenames in mod_info
+
+    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 [string match "*pdb2a.o" $obj1] {
+       pass "Correct name for first object file"
+    } else {
+       fail "Incorrect name for first object file"
+    }
+
+    if [string equal $obj1 $ar1] {
+       pass "Correct archive name for first object file"
+    } else {
+       fail "Incorrect archive name for first object file"
+    }
+
+    if { [expr $off % 4] != 0 } {
+       set off [expr $off + 4 - ($off % 4)]
+    }
+
+    incr off 64
+
+    set obj2 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
+    incr off [expr [string length $obj2] + 1]
+
+    set ar2 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
+    incr off [expr [string length $ar2] + 1]
+
+    if [string match "*pdb2b.o" $obj2] {
+       pass "Correct name for second object file"
+    } else {
+       fail "Incorrect name for second object file"
+    }
+
+    if [string match "*pdb2b.a" $ar2] {
+       pass "Correct archive name for second object file"
+    } else {
+       fail "Incorrect archive name for second object file"
+    }
+
+    if { [expr $off % 4] != 0 } {
+       set off [expr $off + 4 - ($off % 4)]
+    }
+
+    incr off 64
+
+    set obj3 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
+    incr off [expr [string length $obj3] + 1]
+
+    set ar3 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
+    incr off [expr [string length $ar3] + 1]
+
+    if [string equal $obj3 "* Linker *"] {
+       pass "Correct name for dummy object file"
+    } else {
+       fail "Incorrect name for dummy object file"
+    }
+
+    if [string equal $ar3 ""] {
+       pass "Correct archive name for dummy object file"
+    } else {
+       fail "Incorrect archive name for dummy object file"
+    }
 }
 
-if [check_publics_stream tmpdir/pdb1.pdb] {
-    pass "Valid publics stream"
-} else {
-    fail "Invalid publics stream"
+proc test2 { } {
+    global as
+    global ar
+    global ld
+    global srcdir
+    global subdir
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb2a.s tmpdir/pdb2a.o] {
+       unsupported "Build pdb2a.o"
+       return
+    }
+
+    if ![ld_assemble $as $srcdir/$subdir/pdb2b.s tmpdir/pdb2b.o] {
+       unsupported "Build pdb2b.o"
+       return
+    }
+
+    set exec_output [run_host_cmd "$ar" "cr tmpdir/pdb2b.a tmpdir/pdb2b.o"]
+
+    if ![string match "" $exec_output] {
+       unsupported "Create pdb2b.a"
+       return
+    }
+
+    if ![ld_link $ld "tmpdir/pdb2.exe" "--pdb=tmpdir/pdb2.pdb -e foo tmpdir/pdb2a.o tmpdir/pdb2b.a"] {
+       unsupported "Create PE image with PDB file"
+       return
+    }
+
+    set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb2.pdb 0003"]
+
+    if ![string match "" $exec_output] {
+       return 0
+    }
+
+    set fi [open tmpdir/0003]
+    fconfigure $fi -translation binary
+
+    seek $fi 24
+
+    set data [read $fi 4]
+    binary scan $data i mod_info_size
+
+    seek $fi 36 current
+
+    set mod_info [read $fi $mod_info_size]
+
+    close $fi
+
+    test_mod_info $mod_info
 }
+
+test1
+test2
diff --git a/ld/testsuite/ld-pe/pdb2a.s b/ld/testsuite/ld-pe/pdb2a.s
new file mode 100644 (file)
index 0000000..414edeb
--- /dev/null
@@ -0,0 +1,5 @@
+.text
+
+.global foo
+foo:
+       .secrel32 bar
diff --git a/ld/testsuite/ld-pe/pdb2b.s b/ld/testsuite/ld-pe/pdb2b.s
new file mode 100644 (file)
index 0000000..6401023
--- /dev/null
@@ -0,0 +1,5 @@
+.text
+
+.global bar
+bar:
+       .long 0x12345678