From 5967ca921c88a09d7cec9d9864ae23799d88ffbb Mon Sep 17 00:00:00 2001 From: Mark Harmstone Date: Thu, 3 Nov 2022 02:46:04 +0000 Subject: [PATCH] ld: Add module information substream to PDB files --- 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 diff --git a/ld/pdb.c b/ld/pdb.c index 8e151dad10f..910589b4d09 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 -- 2.30.2