/* Support for generating PDB CodeView debugging files. Copyright (C) 2022 Free Software Foundation, Inc. This file is part of the GNU Binutils. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA. */ #include "pdb.h" #include "bfdlink.h" #include "ld.h" #include "ldmisc.h" #include "libbfd.h" #include "libiberty.h" #include "coff/i386.h" #include "coff/external.h" #include "coff/internal.h" #include "coff/pe.h" #include "libcoff.h" #include struct public { struct public *next; uint32_t offset; uint32_t hash; unsigned int index; uint16_t section; uint32_t address; }; /* 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) { bfd *stream; uint16_t num; stream = bfd_create (name ? name : "", pdb); if (!stream) return NULL; if (!bfd_make_writable (stream)) { bfd_close (stream); return false; } if (!pdb->archive_head) { bfd_set_archive_head (pdb, stream); num = 0; } else { bfd *b = pdb->archive_head; num = 1; while (b->archive_next) { num++; b = b->archive_next; } b->archive_next = stream; } if (stream_num) *stream_num = num; return stream; } /* Stream 0 ought to be a copy of the MSF directory from the last time the PDB file was written. Because we don't do incremental writes this isn't applicable to us, but we fill it with a dummy value so as not to confuse radare. */ static bool create_old_directory_stream (bfd *pdb) { bfd *stream; char buf[sizeof (uint32_t)]; stream = add_stream (pdb, NULL, NULL); if (!stream) return false; bfd_putl32 (0, buf); return bfd_bwrite (buf, sizeof (uint32_t), stream) == sizeof (uint32_t); } /* Calculate the hash of a given string. */ static uint32_t calc_hash (const char *data, size_t len) { uint32_t hash = 0; while (len >= 4) { hash ^= data[0]; hash ^= data[1] << 8; hash ^= data[2] << 16; hash ^= data[3] << 24; data += 4; len -= 4; } if (len >= 2) { hash ^= data[0]; hash ^= data[1] << 8; data += 2; len -= 2; } if (len != 0) hash ^= *data; hash |= 0x20202020; hash ^= (hash >> 11); return hash ^ (hash >> 16); } /* Stream 1 is the PDB info stream - see https://llvm.org/docs/PDB/PdbStream.html. */ static bool populate_info_stream (bfd *pdb, bfd *info_stream, const unsigned char *guid) { bool ret = false; struct pdb_stream_70 h; uint32_t num_entries, num_buckets; uint32_t names_length, stream_num; char int_buf[sizeof (uint32_t)]; struct hash_entry { uint32_t offset; uint32_t value; }; struct hash_entry **buckets = NULL; /* Write header. */ bfd_putl32 (PDB_STREAM_VERSION_VC70, &h.version); bfd_putl32 (time (NULL), &h.signature); bfd_putl32 (1, &h.age); bfd_putl32 (bfd_getb32 (guid), h.guid); bfd_putl16 (bfd_getb16 (&guid[4]), &h.guid[4]); bfd_putl16 (bfd_getb16 (&guid[6]), &h.guid[6]); memcpy (&h.guid[8], &guid[8], 8); if (bfd_bwrite (&h, sizeof (h), info_stream) != sizeof (h)) return false; /* Write hash list of named streams. This is a "rollover" hash, i.e. if a bucket is filled an entry gets placed in the next free slot. */ num_entries = 0; for (bfd *b = pdb->archive_head; b; b = b->archive_next) { if (strcmp (b->filename, "")) num_entries++; } num_buckets = num_entries * 2; names_length = 0; stream_num = 0; if (num_buckets > 0) { buckets = xmalloc (sizeof (struct hash_entry *) * num_buckets); memset (buckets, 0, sizeof (struct hash_entry *) * num_buckets); for (bfd *b = pdb->archive_head; b; b = b->archive_next) { if (strcmp (b->filename, "")) { size_t len = strlen (b->filename); uint32_t hash = (uint16_t) calc_hash (b->filename, len); uint32_t bucket_num = hash % num_buckets; while (buckets[bucket_num]) { bucket_num++; if (bucket_num == num_buckets) bucket_num = 0; } buckets[bucket_num] = xmalloc (sizeof (struct hash_entry)); buckets[bucket_num]->offset = names_length; buckets[bucket_num]->value = stream_num; names_length += len + 1; } stream_num++; } } /* Write the strings list - the hash keys are indexes into this. */ bfd_putl32 (names_length, int_buf); if (bfd_bwrite (int_buf, sizeof (uint32_t), info_stream) != sizeof (uint32_t)) goto end; for (bfd *b = pdb->archive_head; b; b = b->archive_next) { if (!strcmp (b->filename, "")) continue; size_t len = strlen (b->filename) + 1; if (bfd_bwrite (b->filename, len, info_stream) != len) goto end; } /* Write the number of entries and buckets. */ bfd_putl32 (num_entries, int_buf); if (bfd_bwrite (int_buf, sizeof (uint32_t), info_stream) != sizeof (uint32_t)) goto end; bfd_putl32 (num_buckets, int_buf); if (bfd_bwrite (int_buf, sizeof (uint32_t), info_stream) != sizeof (uint32_t)) goto end; /* Write the present bitmap. */ bfd_putl32 ((num_buckets + 31) / 32, int_buf); if (bfd_bwrite (int_buf, sizeof (uint32_t), info_stream) != sizeof (uint32_t)) goto end; for (unsigned int i = 0; i < num_buckets; i += 32) { uint32_t v = 0; for (unsigned int j = 0; j < 32; j++) { if (i + j >= num_buckets) break; if (buckets[i + j]) v |= 1 << j; } bfd_putl32 (v, int_buf); if (bfd_bwrite (int_buf, sizeof (uint32_t), info_stream) != sizeof (uint32_t)) goto end; } /* Write the (empty) deleted bitmap. */ bfd_putl32 (0, int_buf); if (bfd_bwrite (int_buf, sizeof (uint32_t), info_stream) != sizeof (uint32_t)) goto end; /* Write the buckets. */ for (unsigned int i = 0; i < num_buckets; i++) { if (buckets[i]) { bfd_putl32 (buckets[i]->offset, int_buf); if (bfd_bwrite (int_buf, sizeof (uint32_t), info_stream) != sizeof (uint32_t)) goto end; bfd_putl32 (buckets[i]->value, int_buf); if (bfd_bwrite (int_buf, sizeof (uint32_t), info_stream) != sizeof (uint32_t)) goto end; } } bfd_putl32 (0, int_buf); if (bfd_bwrite (int_buf, sizeof (uint32_t), info_stream) != sizeof (uint32_t)) goto end; bfd_putl32 (PDB_STREAM_VERSION_VC140, int_buf); if (bfd_bwrite (int_buf, sizeof (uint32_t), info_stream) != sizeof (uint32_t)) goto end; ret = true; end: for (unsigned int i = 0; i < num_buckets; i++) { if (buckets[i]) free (buckets[i]); } free (buckets); return ret; } /* Stream 2 is the type information (TPI) stream, and stream 4 is the ID information (IPI) stream. They differ only in which records go in which stream. */ static bool create_type_stream (bfd *pdb) { bfd *stream; struct pdb_tpi_stream_header h; stream = add_stream (pdb, NULL, NULL); if (!stream) return false; bfd_putl32 (TPI_STREAM_VERSION_80, &h.version); bfd_putl32 (sizeof (h), &h.header_size); bfd_putl32 (TPI_FIRST_INDEX, &h.type_index_begin); bfd_putl32 (TPI_FIRST_INDEX, &h.type_index_end); bfd_putl32 (0, &h.type_record_bytes); bfd_putl16 (0xffff, &h.hash_stream_index); bfd_putl16 (0xffff, &h.hash_aux_stream_index); bfd_putl32 (4, &h.hash_key_size); bfd_putl32 (0x3ffff, &h.num_hash_buckets); bfd_putl32 (0, &h.hash_value_buffer_offset); bfd_putl32 (0, &h.hash_value_buffer_length); bfd_putl32 (0, &h.index_offset_buffer_offset); bfd_putl32 (0, &h.index_offset_buffer_length); bfd_putl32 (0, &h.hash_adj_buffer_offset); bfd_putl32 (0, &h.hash_adj_buffer_length); if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h)) return false; return true; } /* Return the PE architecture number for the image. */ static uint16_t get_arch_number (bfd *abfd) { if (abfd->arch_info->arch != bfd_arch_i386) return 0; if (abfd->arch_info->mach & bfd_mach_x86_64) return IMAGE_FILE_MACHINE_AMD64; return IMAGE_FILE_MACHINE_I386; } /* Stream 4 is the debug information (DBI) stream. */ static bool populate_dbi_stream (bfd *stream, bfd *abfd, uint16_t section_header_stream_num) { struct pdb_dbi_stream_header h; struct optional_dbg_header opt; 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 (0x8e1d, &h.build_number); // MSVC 14.29 bfd_putl16 (0xffff, &h.public_stream_index); bfd_putl16 (0, &h.pdb_dll_version); bfd_putl16 (0xffff, &h.sym_record_stream); bfd_putl16 (0, &h.pdb_dll_rbld); bfd_putl32 (0, &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); 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); bfd_putl32 (0, &h.ec_substream_size); bfd_putl16 (0, &h.flags); bfd_putl16 (get_arch_number (abfd), &h.machine); bfd_putl32 (0, &h.padding); if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h)) return false; bfd_putl16 (0xffff, &opt.fpo_stream); bfd_putl16 (0xffff, &opt.exception_stream); bfd_putl16 (0xffff, &opt.fixup_stream); bfd_putl16 (0xffff, &opt.omap_to_src_stream); bfd_putl16 (0xffff, &opt.omap_from_src_stream); bfd_putl16 (section_header_stream_num, &opt.section_header_stream); bfd_putl16 (0xffff, &opt.token_map_stream); bfd_putl16 (0xffff, &opt.xdata_stream); bfd_putl16 (0xffff, &opt.pdata_stream); bfd_putl16 (0xffff, &opt.new_fpo_stream); bfd_putl16 (0xffff, &opt.orig_section_header_stream); if (bfd_bwrite (&opt, sizeof (opt), stream) != sizeof (opt)) return false; return true; } /* The section header stream contains a copy of the section headers from the PE file, in the same format. */ static bool create_section_header_stream (bfd *pdb, bfd *abfd, uint16_t *num) { bfd *stream; unsigned int section_count; file_ptr scn_base; size_t len; char *buf; stream = add_stream (pdb, NULL, num); if (!stream) return false; section_count = abfd->section_count; /* Empty sections aren't output. */ for (asection *sect = abfd->sections; sect; sect = sect->next) { if (sect->size == 0) section_count--; } if (section_count == 0) return true; /* Copy section table from output - it's already been written at this point. */ scn_base = bfd_coff_filhsz (abfd) + bfd_coff_aoutsz (abfd); bfd_seek (abfd, scn_base, SEEK_SET); len = section_count * sizeof (struct external_scnhdr); buf = xmalloc (len); if (bfd_bread (buf, len, abfd) != len) { free (buf); return false; } if (bfd_bwrite (buf, len, stream) != len) { free (buf); return false; } free (buf); return true; } /* Create a PDB debugging file for the PE image file abfd with the build ID guid, stored at pdb_name. */ bool create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid) { bfd *pdb; bool ret = false; bfd *info_stream, *dbi_stream, *names_stream; uint16_t section_header_stream_num; pdb = bfd_openw (pdb_name, "pdb"); if (!pdb) { einfo (_("%P: warning: cannot create PDB file: %E\n")); return false; } bfd_set_format (pdb, bfd_archive); if (!create_old_directory_stream (pdb)) { einfo (_("%P: warning: cannot create old directory stream " "in PDB file: %E\n")); goto end; } info_stream = add_stream (pdb, NULL, NULL); if (!info_stream) { einfo (_("%P: warning: cannot create info stream " "in PDB file: %E\n")); goto end; } if (!create_type_stream (pdb)) { einfo (_("%P: warning: cannot create TPI stream " "in PDB file: %E\n")); goto end; } dbi_stream = add_stream (pdb, NULL, NULL); if (!dbi_stream) { einfo (_("%P: warning: cannot create DBI stream " "in PDB file: %E\n")); goto end; } if (!create_type_stream (pdb)) { einfo (_("%P: warning: cannot create IPI stream " "in PDB file: %E\n")); goto end; } names_stream = add_stream (pdb, "/names", NULL); if (!names_stream) { einfo (_("%P: warning: cannot create /names stream " "in PDB file: %E\n")); goto end; } if (!create_section_header_stream (pdb, abfd, §ion_header_stream_num)) { einfo (_("%P: warning: cannot create section header stream " "in PDB file: %E\n")); goto end; } if (!populate_dbi_stream (dbi_stream, abfd, section_header_stream_num)) { einfo (_("%P: warning: cannot populate DBI stream " "in PDB file: %E\n")); goto end; } if (!populate_info_stream (pdb, info_stream, guid)) { einfo (_("%P: warning: cannot populate info stream " "in PDB file: %E\n")); goto end; } ret = true; end: bfd_close (pdb); return ret; }