[v2] ld: Add module information substream to PDB files

Message ID 20221103024604.614-1-mark@harmstone.com
State Accepted
Headers
Series [v2] ld: Add module information substream to PDB files |

Checks

Context Check Description
snail/binutils-gdb-check success Github commit url

Commit Message

Mark Harmstone Nov. 3, 2022, 2:46 a.m. UTC
  Resubmission taking into account
https://sourceware.org/pipermail/binutils/2022-November/124128.html.

---
 ld/pdb.c                   | 214 ++++++++++++++++++++++++++++++++++++-
 ld/pdb.h                   |  33 ++++++
 ld/testsuite/ld-pe/pdb.exp | 214 ++++++++++++++++++++++++++++++-------
 ld/testsuite/ld-pe/pdb2a.s |   5 +
 ld/testsuite/ld-pe/pdb2b.s |   5 +
 5 files changed, 427 insertions(+), 44 deletions(-)
 create mode 100644 ld/testsuite/ld-pe/pdb2a.s
 create mode 100644 ld/testsuite/ld-pe/pdb2b.s
  

Comments

Nick Clifton Nov. 7, 2022, 1:33 p.m. UTC | #1
Hi Mark,

> Resubmission taking into account
> https://sourceware.org/pipermail/binutils/2022-November/124128.html.
> 
> ---
>   ld/pdb.c                   | 214 ++++++++++++++++++++++++++++++++++++-
>   ld/pdb.h                   |  33 ++++++
>   ld/testsuite/ld-pe/pdb.exp | 214 ++++++++++++++++++++++++++++++-------
>   ld/testsuite/ld-pe/pdb2a.s |   5 +
>   ld/testsuite/ld-pe/pdb2b.s |   5 +

Approved - please apply.

Cheers
   Nick
  

Patch

diff --git a/ld/pdb.c b/ld/pdb.c
index a2cdb84e271..b560c13198a 100644
--- 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 "
diff --git a/ld/pdb.h b/ld/pdb.h
index 1a80101d288..a44618578b7 100644
--- 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
diff --git a/ld/testsuite/ld-pe/pdb.exp b/ld/testsuite/ld-pe/pdb.exp
index ee314c41f9b..ab22506d0f2 100644
--- a/ld/testsuite/ld-pe/pdb.exp
+++ b/ld/testsuite/ld-pe/pdb.exp
@@ -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
index 00000000000..414edeb2eec
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb2a.s
@@ -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
index 00000000000..64010237975
--- /dev/null
+++ b/ld/testsuite/ld-pe/pdb2b.s
@@ -0,0 +1,5 @@ 
+.text
+
+.global bar
+bar:
+	.long 0x12345678