aboutsummaryrefslogtreecommitdiff
path: root/ld/pdb.c
diff options
context:
space:
mode:
Diffstat (limited to 'ld/pdb.c')
-rw-r--r--ld/pdb.c347
1 files changed, 341 insertions, 6 deletions
diff --git a/ld/pdb.c b/ld/pdb.c
index 1190dcf..8e151da 100644
--- a/ld/pdb.c
+++ b/ld/pdb.c
@@ -383,10 +383,31 @@ get_arch_number (bfd *abfd)
return IMAGE_FILE_MACHINE_I386;
}
+/* Return the index of a given output section. */
+static uint16_t
+find_section_number (bfd *abfd, asection *sect)
+{
+ uint16_t i = 1;
+
+ for (asection *s = abfd->sections; s; s = s->next)
+ {
+ if (s == sect)
+ return i;
+
+ /* Empty sections aren't output. */
+ if (s->size != 0)
+ i++;
+ }
+
+ return 0;
+}
+
/* Stream 4 is the debug information (DBI) stream. */
static bool
populate_dbi_stream (bfd *stream, bfd *abfd,
- uint16_t section_header_stream_num)
+ 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;
@@ -396,9 +417,9 @@ populate_dbi_stream (bfd *stream, bfd *abfd,
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 (publics_stream_num, &h.public_stream_index);
bfd_putl16 (0, &h.pdb_dll_version);
- bfd_putl16 (0xffff, &h.sym_record_stream);
+ 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 (0, &h.section_contribution_size);
@@ -433,6 +454,293 @@ populate_dbi_stream (bfd *stream, bfd *abfd,
return true;
}
+/* Used as parameter to qsort, to sort publics by hash. */
+static int
+public_compare_hash (const void *s1, const void *s2)
+{
+ const struct public *p1 = *(const struct public **) s1;
+ const struct public *p2 = *(const struct public **) s2;
+
+ if (p1->hash < p2->hash)
+ return -1;
+ if (p1->hash > p2->hash)
+ return 1;
+
+ return 0;
+}
+
+/* Used as parameter to qsort, to sort publics by address. */
+static int
+public_compare_addr (const void *s1, const void *s2)
+{
+ const struct public *p1 = *(const struct public **) s1;
+ const struct public *p2 = *(const struct public **) s2;
+
+ if (p1->section < p2->section)
+ return -1;
+ if (p1->section > p2->section)
+ return 1;
+
+ if (p1->address < p2->address)
+ return -1;
+ if (p1->address > p2->address)
+ return 1;
+
+ return 0;
+}
+
+/* The publics stream is a hash map of S_PUB32 records, which are stored
+ in the symbol record stream. Each S_PUB32 entry represents a symbol
+ from the point of view of the linker: a section index, an offset within
+ the section, and a mangled name. Compare with S_GDATA32 and S_GPROC32,
+ which are the same thing but generated by the compiler. */
+static bool
+populate_publics_stream (bfd *stream, bfd *abfd, bfd *sym_rec_stream)
+{
+ struct publics_header header;
+ struct globals_hash_header hash_header;
+ const unsigned int num_buckets = 4096;
+ unsigned int num_entries = 0, filled_buckets = 0;
+ unsigned int buckets_size, sym_hash_size;
+ char int_buf[sizeof (uint32_t)];
+ struct public *publics_head = NULL, *publics_tail = NULL;
+ struct public **buckets;
+ struct public **sorted = NULL;
+ bool ret = false;
+
+ buckets = xmalloc (sizeof (struct public *) * num_buckets);
+ memset (buckets, 0, sizeof (struct public *) * num_buckets);
+
+ /* Loop through the global symbols in our input files, and write S_PUB32
+ records in the symbol record stream for those that make it into the
+ final image. */
+ for (bfd *in = coff_data (abfd)->link_info->input_bfds; in;
+ in = in->link.next)
+ {
+ for (unsigned int i = 0; i < in->symcount; i++)
+ {
+ struct bfd_symbol *sym = in->outsymbols[i];
+
+ if (sym->flags & BSF_GLOBAL)
+ {
+ struct pubsym ps;
+ uint16_t record_length;
+ const char *name = sym->name;
+ size_t name_len = strlen (name);
+ struct public *p = xmalloc (sizeof (struct public));
+ unsigned int padding = 0;
+ uint16_t section;
+ uint32_t flags = 0;
+
+ section =
+ find_section_number (abfd, sym->section->output_section);
+
+ if (section == 0)
+ continue;
+
+ p->next = NULL;
+ p->offset = bfd_tell (sym_rec_stream);
+ p->hash = calc_hash (name, name_len) % num_buckets;
+ p->section = section;
+ p->address = sym->section->output_offset + sym->value;
+
+ record_length = sizeof (struct pubsym) + name_len + 1;
+
+ if (record_length % 4)
+ padding = 4 - (record_length % 4);
+
+ /* Assume that all global symbols in executable sections
+ are functions. */
+ if (sym->section->flags & SEC_CODE)
+ flags = PUBSYM_FUNCTION;
+
+ bfd_putl16 (record_length + padding - sizeof (uint16_t),
+ &ps.record_length);
+ bfd_putl16 (S_PUB32, &ps.record_type);
+ bfd_putl32 (flags, &ps.flags);
+ bfd_putl32 (p->address, &ps.offset);
+ bfd_putl16 (p->section, &ps.section);
+
+ if (bfd_bwrite (&ps, sizeof (struct pubsym), sym_rec_stream) !=
+ sizeof (struct pubsym))
+ goto end;
+
+ if (bfd_bwrite (name, name_len + 1, sym_rec_stream) !=
+ name_len + 1)
+ goto end;
+
+ for (unsigned int j = 0; j < padding; j++)
+ {
+ uint8_t b = 0;
+
+ if (bfd_bwrite (&b, sizeof (uint8_t), sym_rec_stream) !=
+ sizeof (uint8_t))
+ goto end;
+ }
+
+ if (!publics_head)
+ publics_head = p;
+ else
+ publics_tail->next = p;
+
+ publics_tail = p;
+ num_entries++;
+ }
+ }
+ }
+
+
+ if (num_entries > 0)
+ {
+ /* Create an array of pointers, sorted by hash value. */
+
+ sorted = xmalloc (sizeof (struct public *) * num_entries);
+
+ struct public *p = publics_head;
+ for (unsigned int i = 0; i < num_entries; i++)
+ {
+ sorted[i] = p;
+ p = p->next;
+ }
+
+ qsort (sorted, num_entries, sizeof (struct public *),
+ public_compare_hash);
+
+ /* Populate the buckets. */
+
+ for (unsigned int i = 0; i < num_entries; i++)
+ {
+ if (!buckets[sorted[i]->hash])
+ {
+ buckets[sorted[i]->hash] = sorted[i];
+ filled_buckets++;
+ }
+
+ sorted[i]->index = i;
+ }
+ }
+
+ buckets_size = num_buckets / 8;
+ buckets_size += sizeof (uint32_t);
+ buckets_size += filled_buckets * sizeof (uint32_t);
+
+ sym_hash_size = sizeof (hash_header);
+ sym_hash_size += num_entries * sizeof (struct hash_record);
+ sym_hash_size += buckets_size;
+
+ /* Output the publics header. */
+
+ bfd_putl32 (sym_hash_size, &header.sym_hash_size);
+ bfd_putl32 (num_entries * sizeof (uint32_t), &header.addr_map_size);
+ bfd_putl32 (0, &header.num_thunks);
+ bfd_putl32 (0, &header.thunks_size);
+ bfd_putl32 (0, &header.thunk_table);
+ bfd_putl32 (0, &header.thunk_table_offset);
+ bfd_putl32 (0, &header.num_sects);
+
+ if (bfd_bwrite (&header, sizeof (header), stream) != sizeof (header))
+ goto end;
+
+ /* Output the global hash header. */
+
+ bfd_putl32 (GLOBALS_HASH_SIGNATURE, &hash_header.signature);
+ bfd_putl32 (GLOBALS_HASH_VERSION_70, &hash_header.version);
+ bfd_putl32 (num_entries * sizeof (struct hash_record),
+ &hash_header.entries_size);
+ bfd_putl32 (buckets_size, &hash_header.buckets_size);
+
+ if (bfd_bwrite (&hash_header, sizeof (hash_header), stream) !=
+ sizeof (hash_header))
+ goto end;
+
+ /* Write the entries in hash order. */
+
+ for (unsigned int i = 0; i < num_entries; i++)
+ {
+ struct hash_record hr;
+
+ bfd_putl32 (sorted[i]->offset + 1, &hr.offset);
+ bfd_putl32 (1, &hr.reference);
+
+ if (bfd_bwrite (&hr, sizeof (hr), stream) != sizeof (hr))
+ goto end;
+ }
+
+ /* Write the bitmap for filled and unfilled buckets. */
+
+ for (unsigned int i = 0; i < num_buckets; i += 8)
+ {
+ uint8_t v = 0;
+
+ for (unsigned int j = 0; j < 8; j++)
+ {
+ if (buckets[i + j])
+ v |= 1 << j;
+ }
+
+ if (bfd_bwrite (&v, sizeof (v), stream) != sizeof (v))
+ goto end;
+ }
+
+ /* Add a 4-byte gap. */
+
+ bfd_putl32 (0, int_buf);
+
+ if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
+ goto end;
+
+ /* Write the bucket offsets. */
+
+ for (unsigned int i = 0; i < num_buckets; i++)
+ {
+ if (buckets[i])
+ {
+ /* 0xc is size of internal hash_record structure in
+ Microsoft's parser. */
+ bfd_putl32 (buckets[i]->index * 0xc, int_buf);
+
+ if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) !=
+ sizeof (uint32_t))
+ goto end;
+ }
+ }
+
+ /* Write the address map: offsets into the symbol record stream of
+ S_PUB32 records, ordered by address. */
+
+ if (num_entries > 0)
+ {
+ qsort (sorted, num_entries, sizeof (struct public *),
+ public_compare_addr);
+
+ for (unsigned int i = 0; i < num_entries; i++)
+ {
+ bfd_putl32 (sorted[i]->offset, int_buf);
+
+ if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) !=
+ sizeof (uint32_t))
+ goto end;
+ }
+ }
+
+ ret = true;
+
+end:
+ free (buckets);
+
+ while (publics_head)
+ {
+ struct public *p = publics_head->next;
+
+ free (publics_head);
+ publics_head = p;
+ }
+
+ free (sorted);
+
+ return ret;
+}
+
/* The section header stream contains a copy of the section headers
from the PE file, in the same format. */
static bool
@@ -494,8 +802,9 @@ 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;
+ bfd *info_stream, *dbi_stream, *names_stream, *sym_rec_stream,
+ *publics_stream;
+ uint16_t section_header_stream_num, sym_rec_stream_num, publics_stream_num;
pdb = bfd_openw (pdb_name, "pdb");
if (!pdb)
@@ -554,6 +863,24 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
goto end;
}
+ sym_rec_stream = add_stream (pdb, NULL, &sym_rec_stream_num);
+
+ if (!sym_rec_stream)
+ {
+ einfo (_("%P: warning: cannot create symbol record stream "
+ "in PDB file: %E\n"));
+ goto end;
+ }
+
+ publics_stream = add_stream (pdb, NULL, &publics_stream_num);
+
+ if (!publics_stream)
+ {
+ einfo (_("%P: warning: cannot create publics stream "
+ "in PDB file: %E\n"));
+ goto end;
+ }
+
if (!create_section_header_stream (pdb, abfd, &section_header_stream_num))
{
einfo (_("%P: warning: cannot create section header stream "
@@ -561,13 +888,21 @@ 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, section_header_stream_num,
+ sym_rec_stream_num, publics_stream_num))
{
einfo (_("%P: warning: cannot populate DBI stream "
"in PDB file: %E\n"));
goto end;
}
+ if (!populate_publics_stream (publics_stream, abfd, sym_rec_stream))
+ {
+ einfo (_("%P: warning: cannot populate publics stream "
+ "in PDB file: %E\n"));
+ goto end;
+ }
+
if (!populate_info_stream (pdb, info_stream, guid))
{
einfo (_("%P: warning: cannot populate info stream "