binutils-gdb/ld/pdb.c
Mark Harmstone 38395c77d7 ld: Sort section contributions in PDB files
Microsoft's DIA library, and thus also MSVC and WinDbg, expects section
contributions to be ordered by section number and offset, otherwise it's
unable to resolve line numbers.
2023-02-28 00:28:26 +00:00

5312 lines
121 KiB
C

/* Support for generating PDB CodeView debugging files.
Copyright (C) 2022-2023 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 "ldmain.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 <time.h>
struct public
{
struct public *next;
uint32_t offset;
uint32_t hash;
unsigned int index;
uint16_t section;
uint32_t address;
};
struct string
{
struct string *next;
uint32_t hash;
uint32_t offset;
uint32_t source_file_offset;
size_t len;
char s[];
};
struct string_table
{
struct string *strings_head;
struct string *strings_tail;
uint32_t strings_len;
htab_t hashmap;
};
struct mod_source_files
{
uint16_t files_count;
struct string **files;
};
struct source_files_info
{
uint16_t mod_count;
struct mod_source_files *mods;
};
struct type_entry
{
struct type_entry *next;
uint32_t index;
uint32_t cv_hash;
bool has_udt_src_line;
uint8_t data[];
};
struct types
{
htab_t hashmap;
uint32_t num_types;
struct type_entry *first;
struct type_entry *last;
};
struct global
{
struct global *next;
uint32_t offset;
uint32_t hash;
uint32_t refcount;
unsigned int index;
uint8_t data[];
};
struct globals
{
uint32_t num_entries;
struct global *first;
struct global *last;
htab_t hashmap;
};
struct in_sc
{
asection *s;
uint16_t sect_num;
uint16_t mod_index;
};
static const uint32_t crc_table[] =
{
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
/* 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;
}
/* Calculate the CRC32 used for type hashes. */
static uint32_t
crc32 (const uint8_t *data, size_t len)
{
uint32_t crc = 0;
while (len > 0)
{
crc = (crc >> 8) ^ crc_table[(crc & 0xff) ^ *data];
data++;
len--;
}
return crc;
}
/* 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
populate_type_stream (bfd *pdb, bfd *stream, struct types *types)
{
struct pdb_tpi_stream_header h;
struct type_entry *e;
uint32_t len = 0, index_offset_len, off;
struct bfd *hash_stream = NULL;
uint16_t hash_stream_index;
static const uint32_t index_skip = 0x2000;
e = types->first;
index_offset_len = 0;
while (e)
{
uint32_t old_len = len;
len += sizeof (uint16_t) + bfd_getl16 (e->data);
if (old_len == 0 || old_len / index_skip != len / index_skip)
index_offset_len += sizeof (uint32_t) * 2;
e = e->next;
}
/* Each type stream also has a stream which holds the hash value for each
type, along with a skip list to speed up searching. */
hash_stream = add_stream (pdb, "", &hash_stream_index);
if (!hash_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 + types->num_types, &h.type_index_end);
bfd_putl32 (len, &h.type_record_bytes);
bfd_putl16 (hash_stream_index, &h.hash_stream_index);
bfd_putl16 (0xffff, &h.hash_aux_stream_index);
bfd_putl32 (sizeof (uint32_t), &h.hash_key_size);
bfd_putl32 (NUM_TPI_HASH_BUCKETS, &h.num_hash_buckets);
bfd_putl32 (0, &h.hash_value_buffer_offset);
bfd_putl32 (types->num_types * sizeof (uint32_t),
&h.hash_value_buffer_length);
bfd_putl32 (types->num_types * sizeof (uint32_t),
&h.index_offset_buffer_offset);
bfd_putl32 (index_offset_len, &h.index_offset_buffer_length);
bfd_putl32 ((types->num_types * sizeof (uint32_t)) + index_offset_len,
&h.hash_adj_buffer_offset);
bfd_putl32 (0, &h.hash_adj_buffer_length);
if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h))
return false;
/* Write the type definitions into the main stream, and the hashes
into the hash stream. The hashes have already been calculated
in handle_type. */
e = types->first;
while (e)
{
uint8_t buf[sizeof (uint32_t)];
uint16_t size;
size = bfd_getl16 (e->data);
if (bfd_bwrite (e->data, size + sizeof (uint16_t), stream)
!= size + sizeof (uint16_t))
return false;
bfd_putl32 (e->cv_hash % NUM_TPI_HASH_BUCKETS, buf);
if (bfd_bwrite (buf, sizeof (uint32_t), hash_stream)
!= sizeof (uint32_t))
return false;
e = e->next;
}
/* Write the index offsets, i.e. the skip list, into the hash stream. We
copy MSVC here by writing a new entry for every 8192 bytes. */
e = types->first;
off = 0;
while (e)
{
uint32_t old_off = off;
uint16_t size = bfd_getl16 (e->data);
off += size + sizeof (uint16_t);
if (old_off == 0 || old_off / index_skip != len / index_skip)
{
uint8_t buf[sizeof (uint32_t)];
bfd_putl32 (TPI_FIRST_INDEX + e->index, buf);
if (bfd_bwrite (buf, sizeof (uint32_t), hash_stream)
!= sizeof (uint32_t))
return false;
bfd_putl32 (old_off, buf);
if (bfd_bwrite (buf, sizeof (uint32_t), hash_stream)
!= sizeof (uint32_t))
return false;
}
e = e->next;
}
return true;
}
/* Return the PE architecture number for the image. */
static uint16_t
get_arch_number (bfd *abfd)
{
switch (abfd->arch_info->arch)
{
case bfd_arch_i386:
if (abfd->arch_info->mach & bfd_mach_x86_64)
return IMAGE_FILE_MACHINE_AMD64;
else
return IMAGE_FILE_MACHINE_I386;
case bfd_arch_aarch64:
return IMAGE_FILE_MACHINE_ARM64;
default:
return 0;
}
}
/* Validate the DEBUG_S_FILECHKSMS entry within a module's .debug$S
section, and copy it to the module's symbol stream. */
static bool
copy_filechksms (uint8_t *data, uint32_t size, char *string_table,
struct string_table *strings, uint8_t *out,
struct mod_source_files *mod_source)
{
uint8_t *orig_data = data;
uint32_t orig_size = size;
uint16_t num_files = 0;
struct string **strptr;
bfd_putl32 (DEBUG_S_FILECHKSMS, out);
out += sizeof (uint32_t);
bfd_putl32 (size, out);
out += sizeof (uint32_t);
/* Calculate the number of files, and check for any overflows. */
while (size > 0)
{
struct file_checksum *fc = (struct file_checksum *) data;
uint8_t padding;
size_t len;
if (size < sizeof (struct file_checksum))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
len = sizeof (struct file_checksum) + fc->checksum_length;
if (size < len)
{
bfd_set_error (bfd_error_bad_value);
return false;
}
data += len;
size -= len;
if (len % sizeof (uint32_t))
padding = sizeof (uint32_t) - (len % sizeof (uint32_t));
else
padding = 0;
if (size < padding)
{
bfd_set_error (bfd_error_bad_value);
return false;
}
num_files++;
data += padding;
size -= padding;
}
/* Add the files to mod_source, so that they'll appear in the source
info substream. */
strptr = NULL;
if (num_files > 0)
{
uint16_t new_count = num_files + mod_source->files_count;
mod_source->files = xrealloc (mod_source->files,
sizeof (struct string *) * new_count);
strptr = mod_source->files + mod_source->files_count;
mod_source->files_count += num_files;
}
/* Actually copy the data. */
data = orig_data;
size = orig_size;
while (size > 0)
{
struct file_checksum *fc = (struct file_checksum *) data;
uint32_t string_off;
uint8_t padding;
size_t len;
struct string *str = NULL;
string_off = bfd_getl32 (&fc->file_id);
len = sizeof (struct file_checksum) + fc->checksum_length;
if (len % sizeof (uint32_t))
padding = sizeof (uint32_t) - (len % sizeof (uint32_t));
else
padding = 0;
/* Remap the "file ID", i.e. the offset in the module's string table,
so it points to the right place in the main string table. */
if (string_table)
{
char *fn = string_table + string_off;
size_t fn_len = strlen (fn);
uint32_t hash = calc_hash (fn, fn_len);
void **slot;
slot = htab_find_slot_with_hash (strings->hashmap, fn, hash,
NO_INSERT);
if (slot)
str = (struct string *) *slot;
}
*strptr = str;
strptr++;
bfd_putl32 (str ? str->offset : 0, &fc->file_id);
memcpy (out, data, len + padding);
data += len + padding;
size -= len + padding;
out += len + padding;
}
return true;
}
/* Add a string to the strings table, if it's not already there. Returns its
offset within the string table. */
static uint32_t
add_string (char *str, size_t len, struct string_table *strings)
{
uint32_t hash = calc_hash (str, len);
struct string *s;
void **slot;
slot = htab_find_slot_with_hash (strings->hashmap, str, hash, INSERT);
if (!*slot)
{
*slot = xmalloc (offsetof (struct string, s) + len);
s = (struct string *) *slot;
s->next = NULL;
s->hash = hash;
s->offset = strings->strings_len;
s->source_file_offset = 0xffffffff;
s->len = len;
memcpy (s->s, str, len);
if (strings->strings_tail)
strings->strings_tail->next = s;
else
strings->strings_head = s;
strings->strings_tail = s;
strings->strings_len += len + 1;
}
else
{
s = (struct string *) *slot;
}
return s->offset;
}
/* Return the hash of an entry in the string table. */
static hashval_t
hash_string_table_entry (const void *p)
{
const struct string *s = (const struct string *) p;
return s->hash;
}
/* Compare an entry in the string table with a string. */
static int
eq_string_table_entry (const void *a, const void *b)
{
const struct string *s1 = (const struct string *) a;
const char *s2 = (const char *) b;
size_t s2_len = strlen (s2);
if (s2_len != s1->len)
return 0;
return memcmp (s1->s, s2, s2_len) == 0;
}
/* Parse the string table within the .debug$S section. */
static void
parse_string_table (bfd_byte *data, size_t size,
struct string_table *strings)
{
while (true)
{
size_t len = strnlen ((char *) data, size);
add_string ((char *) data, len, strings);
data += len + 1;
if (size <= len + 1)
break;
size -= len + 1;
}
}
/* Remap a type reference within a CodeView symbol. */
static bool
remap_symbol_type (void *data, struct type_entry **map, uint32_t num_types)
{
uint32_t type = bfd_getl32 (data);
/* Ignore builtin types (those with IDs below 0x1000). */
if (type < TPI_FIRST_INDEX)
return true;
if (type >= TPI_FIRST_INDEX + num_types)
{
einfo (_("%P: CodeView symbol references out of range type %v\n"),
type);
return false;
}
type = TPI_FIRST_INDEX + map[type - TPI_FIRST_INDEX]->index;
bfd_putl32 (type, data);
return true;
}
/* Add an entry into the globals stream. If it already exists, increase
the refcount. */
static bool
add_globals_ref (struct globals *glob, bfd *sym_rec_stream, const char *name,
size_t name_len, uint8_t *data, size_t len)
{
void **slot;
uint32_t hash;
struct global *g;
slot = htab_find_slot_with_hash (glob->hashmap, data,
iterative_hash (data, len, 0), INSERT);
if (*slot)
{
g = *slot;
g->refcount++;
return true;
}
*slot = xmalloc (offsetof (struct global, data) + len);
hash = crc32 ((const uint8_t *) name, name_len);
hash %= NUM_GLOBALS_HASH_BUCKETS;
g = *slot;
g->next = NULL;
g->offset = bfd_tell (sym_rec_stream);
g->hash = hash;
g->refcount = 1;
memcpy (g->data, data, len + 1);
glob->num_entries++;
if (glob->last)
glob->last->next = g;
else
glob->first = g;
glob->last = g;
return bfd_bwrite (data, len, sym_rec_stream) == len;
}
/* Find the end of the current scope within symbols data. */
static uint8_t *
find_end_of_scope (uint8_t *data, uint32_t size)
{
unsigned int scope_level = 1;
uint16_t len;
len = bfd_getl16 (data) + sizeof (uint16_t);
data += len;
size -= len;
while (true)
{
uint16_t type;
if (size < sizeof (uint32_t))
return NULL;
len = bfd_getl16 (data) + sizeof (uint16_t);
type = bfd_getl16 (data + sizeof (uint16_t));
if (size < len)
return NULL;
switch (type)
{
case S_GPROC32:
case S_LPROC32:
case S_BLOCK32:
case S_INLINESITE:
case S_THUNK32:
scope_level++;
break;
case S_END:
case S_PROC_ID_END:
case S_INLINESITE_END:
scope_level--;
if (scope_level == 0)
return data;
break;
}
data += len;
size -= len;
}
}
/* Return the size of an extended value parameter, as used in
LF_ENUMERATE etc. */
static unsigned int
extended_value_len (uint16_t type)
{
switch (type)
{
case LF_CHAR:
return 1;
case LF_SHORT:
case LF_USHORT:
return 2;
case LF_LONG:
case LF_ULONG:
return 4;
case LF_QUADWORD:
case LF_UQUADWORD:
return 8;
}
return 0;
}
/* Parse the symbols in a .debug$S section, and copy them to the module's
symbol stream. */
static bool
parse_symbols (uint8_t *data, uint32_t size, uint8_t **buf,
struct type_entry **map, uint32_t num_types,
bfd *sym_rec_stream, struct globals *glob, uint16_t mod_num)
{
uint8_t *orig_buf = *buf;
unsigned int scope_level = 0;
uint8_t *scope = NULL;
while (size >= sizeof (uint16_t))
{
uint16_t len, type;
len = bfd_getl16 (data) + sizeof (uint16_t);
if (len > size)
{
bfd_set_error (bfd_error_bad_value);
return false;
}
type = bfd_getl16 (data + sizeof (uint16_t));
switch (type)
{
case S_LDATA32:
case S_GDATA32:
case S_LTHREAD32:
case S_GTHREAD32:
{
struct datasym *d = (struct datasym *) data;
size_t name_len;
if (len < offsetof (struct datasym, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_LDATA32/S_GDATA32/S_LTHREAD32/S_GTHREAD32\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (scope_level == 0)
{
uint16_t section = bfd_getl16 (&d->section);
if (section == 0) /* GC'd, ignore */
break;
}
name_len =
strnlen (d->name, len - offsetof (struct datasym, name));
if (name_len == len - offsetof (struct datasym, name))
{
einfo (_("%P: warning: name for S_LDATA32/S_GDATA32/"
"S_LTHREAD32/S_GTHREAD32 has no terminating"
" zero\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (!remap_symbol_type (&d->type, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
/* If S_LDATA32 or S_LTHREAD32, copy into module symbols. */
if (type == S_LDATA32 || type == S_LTHREAD32)
{
memcpy (*buf, d, len);
*buf += len;
}
/* S_LDATA32 and S_LTHREAD32 only go in globals if
not in function scope. */
if (type == S_GDATA32 || type == S_GTHREAD32 || scope_level == 0)
{
if (!add_globals_ref (glob, sym_rec_stream, d->name,
name_len, data, len))
return false;
}
break;
}
case S_GPROC32:
case S_LPROC32:
case S_GPROC32_ID:
case S_LPROC32_ID:
{
struct procsym *proc = (struct procsym *) data;
size_t name_len;
uint16_t section;
uint32_t end;
uint8_t *endptr;
size_t ref_size, padding;
struct refsym *ref;
if (len < offsetof (struct procsym, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_GPROC32/S_LPROC32\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
section = bfd_getl16 (&proc->section);
endptr = find_end_of_scope (data, size);
if (!endptr)
{
einfo (_("%P: warning: could not find end of"
" S_GPROC32/S_LPROC32 record\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (section == 0) /* skip if GC'd */
{
/* Skip to after S_END. */
size -= endptr - data;
data = endptr;
len = bfd_getl16 (data) + sizeof (uint16_t);
data += len;
size -= len;
continue;
}
name_len =
strnlen (proc->name, len - offsetof (struct procsym, name));
if (name_len == len - offsetof (struct procsym, name))
{
einfo (_("%P: warning: name for S_GPROC32/S_LPROC32 has no"
" terminating zero\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (type == S_GPROC32_ID || type == S_LPROC32_ID)
{
/* Transform into S_GPROC32 / S_LPROC32. */
uint32_t t_idx = bfd_getl32 (&proc->type);
struct type_entry *t;
uint16_t t_type;
if (t_idx < TPI_FIRST_INDEX
|| t_idx >= TPI_FIRST_INDEX + num_types)
{
einfo (_("%P: CodeView symbol references out of range"
" type %v\n"), type);
bfd_set_error (bfd_error_bad_value);
return false;
}
t = map[t_idx - TPI_FIRST_INDEX];
t_type = bfd_getl16 (t->data + sizeof (uint16_t));
switch (t_type)
{
case LF_FUNC_ID:
{
struct lf_func_id *t_data =
(struct lf_func_id *) t->data;
/* Replace proc->type with function type. */
memcpy (&proc->type, &t_data->function_type,
sizeof (uint32_t));
break;
}
case LF_MFUNC_ID:
{
struct lf_mfunc_id *t_data =
(struct lf_mfunc_id *) t->data;
/* Replace proc->type with function type. */
memcpy (&proc->type, &t_data->function_type,
sizeof (uint32_t));
break;
}
default:
einfo (_("%P: CodeView S_GPROC32_ID/S_LPROC32_ID symbol"
" referenced unknown type as ID\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
/* Change record type. */
if (type == S_GPROC32_ID)
bfd_putl32 (S_GPROC32, &proc->kind);
else
bfd_putl32 (S_LPROC32, &proc->kind);
}
else
{
if (!remap_symbol_type (&proc->type, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
}
end = *buf - orig_buf + sizeof (uint32_t) + endptr - data;
bfd_putl32 (end, &proc->end);
/* Add S_PROCREF / S_LPROCREF to globals stream. */
ref_size = offsetof (struct refsym, name) + name_len + 1;
if (ref_size % sizeof (uint32_t))
padding = sizeof (uint32_t) - (ref_size % sizeof (uint32_t));
else
padding = 0;
ref = xmalloc (ref_size + padding);
bfd_putl16 (ref_size + padding - sizeof (uint16_t), &ref->size);
bfd_putl16 (type == S_GPROC32 || type == S_GPROC32_ID ?
S_PROCREF : S_LPROCREF, &ref->kind);
bfd_putl32 (0, &ref->sum_name);
bfd_putl32 (*buf - orig_buf + sizeof (uint32_t),
&ref->symbol_offset);
bfd_putl16 (mod_num + 1, &ref->mod);
memcpy (ref->name, proc->name, name_len + 1);
memset (ref->name + name_len + 1, 0, padding);
if (!add_globals_ref (glob, sym_rec_stream, proc->name, name_len,
(uint8_t *) ref, ref_size + padding))
{
free (ref);
return false;
}
free (ref);
scope = *buf;
memcpy (*buf, proc, len);
*buf += len;
scope_level++;
break;
}
case S_UDT:
{
struct udtsym *udt = (struct udtsym *) data;
size_t name_len;
if (len < offsetof (struct udtsym, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_UDT\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
name_len =
strnlen (udt->name, len - offsetof (struct udtsym, name));
if (name_len == len - offsetof (struct udtsym, name))
{
einfo (_("%P: warning: name for S_UDT has no"
" terminating zero\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (!remap_symbol_type (&udt->type, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
/* S_UDT goes in the symbols stream if within a procedure,
otherwise it goes in the globals stream. */
if (scope_level == 0)
{
if (!add_globals_ref (glob, sym_rec_stream, udt->name,
name_len, data, len))
return false;
}
else
{
memcpy (*buf, udt, len);
*buf += len;
}
break;
}
case S_CONSTANT:
{
struct constsym *c = (struct constsym *) data;
size_t name_len, rec_size;
uint16_t val;
if (len < offsetof (struct constsym, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_CONSTANT\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
rec_size = offsetof (struct constsym, name);
val = bfd_getl16 (&c->value);
/* If val >= 0x8000, actual value follows. */
if (val >= 0x8000)
{
unsigned int param_len = extended_value_len (val);
if (param_len == 0)
{
einfo (_("%P: warning: unhandled type %v within"
" S_CONSTANT\n"), val);
bfd_set_error (bfd_error_bad_value);
return false;
}
rec_size += param_len;
}
name_len =
strnlen ((const char *) data + rec_size, len - rec_size);
if (name_len == len - rec_size)
{
einfo (_("%P: warning: name for S_CONSTANT has no"
" terminating zero\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (!remap_symbol_type (&c->type, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
if (!add_globals_ref (glob, sym_rec_stream,
(const char *) data + rec_size, name_len,
data, len))
return false;
break;
}
case S_END:
case S_INLINESITE_END:
case S_PROC_ID_END:
memcpy (*buf, data, len);
if (type == S_PROC_ID_END) /* transform to S_END */
bfd_putl16 (S_END, *buf + sizeof (uint16_t));
/* Reset scope variable back to the address of the previous
scope start. */
if (scope)
{
uint32_t parent;
uint16_t scope_start_type =
bfd_getl16 (scope + sizeof (uint16_t));
switch (scope_start_type)
{
case S_GPROC32:
case S_LPROC32:
parent = bfd_getl32 (scope + offsetof (struct procsym,
parent));
break;
case S_BLOCK32:
parent = bfd_getl32 (scope + offsetof (struct blocksym,
parent));
break;
case S_INLINESITE:
parent = bfd_getl32 (scope + offsetof (struct inline_site,
parent));
break;
case S_THUNK32:
parent = bfd_getl32 (scope + offsetof (struct thunk,
parent));
break;
default:
einfo (_("%P: warning: unexpected CodeView scope start"
" record %v\n"), scope_start_type);
bfd_set_error (bfd_error_bad_value);
return false;
}
if (parent == 0)
scope = NULL;
else
scope = orig_buf + parent - sizeof (uint32_t);
}
*buf += len;
scope_level--;
break;
case S_BUILDINFO:
{
struct buildinfosym *bi = (struct buildinfosym *) data;
if (len < sizeof (struct buildinfosym))
{
einfo (_("%P: warning: truncated CodeView record"
" S_BUILDINFO\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (!remap_symbol_type (&bi->type, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
memcpy (*buf, data, len);
*buf += len;
break;
}
case S_BLOCK32:
{
struct blocksym *bl = (struct blocksym *) data;
uint8_t *endptr;
uint32_t end;
if (len < offsetof (struct blocksym, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_BLOCK32\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
bfd_putl32 (scope - orig_buf + sizeof (uint32_t), &bl->parent);
endptr = find_end_of_scope (data, size);
if (!endptr)
{
einfo (_("%P: warning: could not find end of"
" S_BLOCK32 record\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
end = *buf - orig_buf + sizeof (uint32_t) + endptr - data;
bfd_putl32 (end, &bl->end);
scope = *buf;
memcpy (*buf, data, len);
*buf += len;
scope_level++;
break;
}
case S_BPREL32:
{
struct bprelsym *bp = (struct bprelsym *) data;
if (len < offsetof (struct bprelsym, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_BPREL32\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (!remap_symbol_type (&bp->type, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
memcpy (*buf, data, len);
*buf += len;
break;
}
case S_REGISTER:
{
struct regsym *reg = (struct regsym *) data;
if (len < offsetof (struct regsym, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_REGISTER\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (!remap_symbol_type (&reg->type, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
memcpy (*buf, data, len);
*buf += len;
break;
}
case S_REGREL32:
{
struct regrel *rr = (struct regrel *) data;
if (len < offsetof (struct regrel, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_REGREL32\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (!remap_symbol_type (&rr->type, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
memcpy (*buf, data, len);
*buf += len;
break;
}
case S_LOCAL:
{
struct localsym *l = (struct localsym *) data;
if (len < offsetof (struct localsym, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_LOCAL\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (!remap_symbol_type (&l->type, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
memcpy (*buf, data, len);
*buf += len;
break;
}
case S_INLINESITE:
{
struct inline_site *is = (struct inline_site *) data;
uint8_t *endptr;
uint32_t end;
if (len < offsetof (struct inline_site, binary_annotations))
{
einfo (_("%P: warning: truncated CodeView record"
" S_INLINESITE\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
bfd_putl32 (scope - orig_buf + sizeof (uint32_t), &is->parent);
endptr = find_end_of_scope (data, size);
if (!endptr)
{
einfo (_("%P: warning: could not find end of"
" S_INLINESITE record\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
end = *buf - orig_buf + sizeof (uint32_t) + endptr - data;
bfd_putl32 (end, &is->end);
if (!remap_symbol_type (&is->inlinee, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
scope = *buf;
memcpy (*buf, data, len);
*buf += len;
scope_level++;
break;
}
case S_THUNK32:
{
struct thunk *th = (struct thunk *) data;
uint8_t *endptr;
uint32_t end;
if (len < offsetof (struct thunk, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_THUNK32\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
bfd_putl32 (scope - orig_buf + sizeof (uint32_t), &th->parent);
endptr = find_end_of_scope (data, size);
if (!endptr)
{
einfo (_("%P: warning: could not find end of"
" S_THUNK32 record\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
end = *buf - orig_buf + sizeof (uint32_t) + endptr - data;
bfd_putl32 (end, &th->end);
scope = *buf;
memcpy (*buf, data, len);
*buf += len;
scope_level++;
break;
}
case S_HEAPALLOCSITE:
{
struct heap_alloc_site *has = (struct heap_alloc_site *) data;
if (len < sizeof (struct heap_alloc_site))
{
einfo (_("%P: warning: truncated CodeView record"
" S_HEAPALLOCSITE\n"));
bfd_set_error (bfd_error_bad_value);
return false;
}
if (!remap_symbol_type (&has->type, map, num_types))
{
bfd_set_error (bfd_error_bad_value);
return false;
}
memcpy (*buf, data, len);
*buf += len;
break;
}
case S_OBJNAME: /* just copy */
case S_COMPILE3:
case S_UNAMESPACE:
case S_FRAMEPROC:
case S_FRAMECOOKIE:
case S_LABEL32:
case S_DEFRANGE_REGISTER_REL:
case S_DEFRANGE_FRAMEPOINTER_REL:
case S_DEFRANGE_SUBFIELD_REGISTER:
case S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE:
case S_DEFRANGE_REGISTER:
memcpy (*buf, data, len);
*buf += len;
break;
default:
einfo (_("%P: warning: unrecognized CodeView record %v\n"), type);
bfd_set_error (bfd_error_bad_value);
return false;
}
data += len;
size -= len;
}
return true;
}
/* For a given symbol subsection, work out how much space to allocate in the
result module stream. This is different because we don't copy certain
symbols, such as S_CONSTANT, and we skip over any procedures or data that
have been GC'd out. */
static bool
calculate_symbols_size (uint8_t *data, uint32_t size, uint32_t *sym_size)
{
unsigned int scope_level = 0;
while (size >= sizeof (uint32_t))
{
uint16_t len = bfd_getl16 (data) + sizeof (uint16_t);
uint16_t type = bfd_getl16 (data + sizeof (uint16_t));
switch (type)
{
case S_LDATA32:
case S_LTHREAD32:
{
struct datasym *d = (struct datasym *) data;
uint16_t section;
if (len < offsetof (struct datasym, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_LDATA32/S_LTHREAD32\n"));
return false;
}
section = bfd_getl16 (&d->section);
/* copy if not GC'd or within function */
if (scope_level != 0 || section != 0)
*sym_size += len;
}
case S_GDATA32:
case S_GTHREAD32:
case S_CONSTANT:
/* Not copied into symbols stream. */
break;
case S_GPROC32:
case S_LPROC32:
case S_GPROC32_ID:
case S_LPROC32_ID:
{
struct procsym *proc = (struct procsym *) data;
uint16_t section;
if (len < offsetof (struct procsym, name))
{
einfo (_("%P: warning: truncated CodeView record"
" S_GPROC32/S_LPROC32\n"));
return false;
}
section = bfd_getl16 (&proc->section);
if (section != 0)
{
*sym_size += len;
}
else
{
uint8_t *endptr = find_end_of_scope (data, size);
if (!endptr)
{
einfo (_("%P: warning: could not find end of"
" S_GPROC32/S_LPROC32 record\n"));
return false;
}
/* Skip to after S_END. */
size -= endptr - data;
data = endptr;
len = bfd_getl16 (data) + sizeof (uint16_t);
data += len;
size -= len;
continue;
}
scope_level++;
break;
}
case S_UDT:
if (scope_level != 0) /* only goes in symbols if local */
*sym_size += len;
break;
case S_BLOCK32: /* always copied */
case S_INLINESITE:
case S_THUNK32:
*sym_size += len;
scope_level++;
break;
case S_END: /* always copied */
case S_PROC_ID_END:
case S_INLINESITE_END:
*sym_size += len;
scope_level--;
break;
case S_OBJNAME: /* always copied */
case S_COMPILE3:
case S_UNAMESPACE:
case S_FRAMEPROC:
case S_FRAMECOOKIE:
case S_LABEL32:
case S_BUILDINFO:
case S_BPREL32:
case S_REGISTER:
case S_REGREL32:
case S_LOCAL:
case S_DEFRANGE_REGISTER_REL:
case S_DEFRANGE_FRAMEPOINTER_REL:
case S_DEFRANGE_SUBFIELD_REGISTER:
case S_DEFRANGE_FRAMEPOINTER_REL_FULL_SCOPE:
case S_DEFRANGE_REGISTER:
case S_HEAPALLOCSITE:
*sym_size += len;
break;
default:
einfo (_("%P: warning: unrecognized CodeView record %v\n"), type);
return false;
}
data += len;
size -= len;
}
return true;
}
/* Parse the .debug$S section within an object file. */
static bool
handle_debugs_section (asection *s, bfd *mod, struct string_table *strings,
uint8_t **dataptr, uint32_t *sizeptr,
struct mod_source_files *mod_source,
bfd *abfd, uint8_t **syms, uint32_t *sym_byte_size,
struct type_entry **map, uint32_t num_types,
bfd *sym_rec_stream, struct globals *glob,
uint16_t mod_num)
{
bfd_byte *data = NULL;
size_t off;
uint32_t c13_size = 0;
char *string_table = NULL;
uint8_t *buf, *bufptr, *symbuf, *symbufptr;
uint32_t sym_size = 0;
if (!bfd_get_full_section_contents (mod, s, &data))
return false;
if (!data)
return false;
/* Resolve relocations. Addresses are stored within the .debug$S section as
a .secidx, .secrel32 pair. */
if (s->flags & SEC_RELOC)
{
struct internal_reloc *relocs;
struct internal_syment *symbols;
asection **sectlist;
unsigned int syment_count;
int sect_num;
struct external_syment *ext;
syment_count = obj_raw_syment_count (mod);
relocs =
_bfd_coff_read_internal_relocs (mod, s, false, NULL, true, NULL);
symbols = xmalloc (sizeof (struct internal_syment) * syment_count);
sectlist = xmalloc (sizeof (asection *) * syment_count);
ext = (struct external_syment *) (coff_data (mod)->external_syms);
for (unsigned int i = 0; i < syment_count; i++)
{
bfd_coff_swap_sym_in (mod, &ext[i], &symbols[i]);
}
sect_num = 1;
for (asection *sect = mod->sections; sect; sect = sect->next)
{
for (unsigned int i = 0; i < syment_count; i++)
{
if (symbols[i].n_scnum == sect_num)
sectlist[i] = sect;
}
sect_num++;
}
if (!bfd_coff_relocate_section (abfd, coff_data (abfd)->link_info, mod,
s, data, relocs, symbols, sectlist))
{
free (sectlist);
free (symbols);
free (data);
return false;
}
free (sectlist);
free (symbols);
}
if (bfd_getl32 (data) != CV_SIGNATURE_C13)
{
free (data);
return true;
}
off = sizeof (uint32_t);
/* calculate size */
while (off + sizeof (uint32_t) <= s->size)
{
uint32_t type, size;
type = bfd_getl32 (data + off);
off += sizeof (uint32_t);
if (off + sizeof (uint32_t) > s->size)
{
free (data);
bfd_set_error (bfd_error_bad_value);
return false;
}
size = bfd_getl32 (data + off);
off += sizeof (uint32_t);
if (off + size > s->size)
{
free (data);
bfd_set_error (bfd_error_bad_value);
return false;
}
switch (type)
{
case DEBUG_S_FILECHKSMS:
c13_size += sizeof (uint32_t) + sizeof (uint32_t) + size;
if (c13_size % sizeof (uint32_t))
c13_size += sizeof (uint32_t) - (c13_size % sizeof (uint32_t));
break;
case DEBUG_S_STRINGTABLE:
parse_string_table (data + off, size, strings);
string_table = (char *) data + off;
break;
case DEBUG_S_LINES:
{
uint16_t sect;
if (size < sizeof (uint32_t) + sizeof (uint16_t))
{
free (data);
bfd_set_error (bfd_error_bad_value);
return false;
}
sect = bfd_getl16 (data + off + sizeof (uint32_t));
/* Skip GC'd symbols. */
if (sect != 0)
{
c13_size += sizeof (uint32_t) + sizeof (uint32_t) + size;
if (c13_size % sizeof (uint32_t))
c13_size +=
sizeof (uint32_t) - (c13_size % sizeof (uint32_t));
}
break;
}
case DEBUG_S_SYMBOLS:
if (!calculate_symbols_size (data + off, size, &sym_size))
{
free (data);
bfd_set_error (bfd_error_bad_value);
return false;
}
break;
}
off += size;
if (off % sizeof (uint32_t))
off += sizeof (uint32_t) - (off % sizeof (uint32_t));
}
if (sym_size % sizeof (uint32_t))
sym_size += sizeof (uint32_t) - (sym_size % sizeof (uint32_t));
if (c13_size == 0 && sym_size == 0)
{
free (data);
return true;
}
/* copy data */
buf = NULL;
if (c13_size != 0)
buf = xmalloc (c13_size);
bufptr = buf;
symbuf = NULL;
if (sym_size != 0)
symbuf = xmalloc (sym_size);
symbufptr = symbuf;
off = sizeof (uint32_t);
while (off + sizeof (uint32_t) <= s->size)
{
uint32_t type, size;
type = bfd_getl32 (data + off);
off += sizeof (uint32_t);
size = bfd_getl32 (data + off);
off += sizeof (uint32_t);
switch (type)
{
case DEBUG_S_FILECHKSMS:
if (!copy_filechksms (data + off, size, string_table,
strings, bufptr, mod_source))
{
free (data);
free (symbuf);
return false;
}
bufptr += sizeof (uint32_t) + sizeof (uint32_t) + size;
break;
case DEBUG_S_LINES:
{
uint16_t sect;
sect = bfd_getl16 (data + off + sizeof (uint32_t));
/* Skip if GC'd. */
if (sect != 0)
{
bfd_putl32 (type, bufptr);
bufptr += sizeof (uint32_t);
bfd_putl32 (size, bufptr);
bufptr += sizeof (uint32_t);
memcpy (bufptr, data + off, size);
bufptr += size;
}
break;
}
case DEBUG_S_SYMBOLS:
if (!parse_symbols (data + off, size, &symbufptr, map, num_types,
sym_rec_stream, glob, mod_num))
{
free (data);
free (symbuf);
return false;
}
break;
}
off += size;
if (off % sizeof (uint32_t))
off += sizeof (uint32_t) - (off % sizeof (uint32_t));
}
free (data);
if (buf)
{
if (*dataptr)
{
/* Append the C13 info to what's already there, if the module has
multiple .debug$S sections. */
*dataptr = xrealloc (*dataptr, *sizeptr + c13_size);
memcpy (*dataptr + *sizeptr, buf, c13_size);
free (buf);
}
else
{
*dataptr = buf;
}
*sizeptr += c13_size;
}
if (symbuf)
{
if (*syms)
{
*syms = xrealloc (*syms, *sym_byte_size + sym_size);
memcpy (*syms + *sym_byte_size, symbuf, sym_size);
free (symbuf);
}
else
{
*syms = symbuf;
}
*sym_byte_size += sym_size;
}
return true;
}
/* Remap the type number stored in data from the per-module numbering to
that of the deduplicated output list. */
static bool
remap_type (void *data, struct type_entry **map,
uint32_t type_num, uint32_t num_types)
{
uint32_t type = bfd_getl32 (data);
/* Ignore builtin types (those with IDs below 0x1000). */
if (type < TPI_FIRST_INDEX)
return true;
if (type >= TPI_FIRST_INDEX + type_num)
{
einfo (_("%P: CodeView type %v references other type %v not yet "
"declared\n"), TPI_FIRST_INDEX + type_num, type);
return false;
}
if (type >= TPI_FIRST_INDEX + num_types)
{
einfo (_("%P: CodeView type %v references out of range type %v\n"),
TPI_FIRST_INDEX + type_num, type);
return false;
}
type = TPI_FIRST_INDEX + map[type - TPI_FIRST_INDEX]->index;
bfd_putl32 (type, data);
return true;
}
/* Determines whether the name of a struct, class, or union counts as
"anonymous". Non-anonymous types have a hash based on just the name,
rather than the whole structure. */
static bool
is_name_anonymous (char *name, size_t len)
{
static const char tag1[] = "<unnamed-tag>";
static const char tag2[] = "__unnamed";
static const char tag3[] = "::<unnamed-tag>";
static const char tag4[] = "::__unnamed";
if (len == sizeof (tag1) - 1 && !memcmp (name, tag1, sizeof (tag1) - 1))
return true;
if (len == sizeof (tag2) - 1 && !memcmp (name, tag2, sizeof (tag2) - 1))
return true;
if (len >= sizeof (tag3) - 1
&& !memcmp (name + len - sizeof (tag3) + 1, tag3, sizeof (tag3) - 1))
return true;
if (len >= sizeof (tag4) - 1
&& !memcmp (name + len - sizeof (tag4) + 1, tag4, sizeof (tag4) - 1))
return true;
return false;
}
/* Handle LF_UDT_SRC_LINE type entries, which are a special case. These
give the source file and line number for each user-defined type that is
declared. We parse these and emit instead an LF_UDT_MOD_SRC_LINE entry,
which also includes the module number. */
static bool
handle_udt_src_line (uint8_t *data, uint16_t size, struct type_entry **map,
uint32_t type_num, uint32_t num_types,
struct types *ids, uint16_t mod_num,
struct string_table *strings)
{
struct lf_udt_src_line *usl = (struct lf_udt_src_line *) data;
uint32_t orig_type, source_file_type;
void **slot;
hashval_t hash;
struct type_entry *e, *type_e, *str_e;
struct lf_udt_mod_src_line *umsl;
struct lf_string_id *str;
uint32_t source_file_offset;
if (size < sizeof (struct lf_udt_src_line))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_UDT_SRC_LINE\n"));
return false;
}
/* Check if LF_UDT_MOD_SRC_LINE already present for type, and return. */
orig_type = bfd_getl32 (&usl->type);
if (orig_type < TPI_FIRST_INDEX ||
orig_type >= TPI_FIRST_INDEX + num_types ||
!map[orig_type - TPI_FIRST_INDEX])
{
einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
" referred to unknown type %v\n"), orig_type);
return false;
}
type_e = map[orig_type - TPI_FIRST_INDEX];
/* Skip if type already declared in other module. */
if (type_e->has_udt_src_line)
return true;
if (!remap_type (&usl->type, map, type_num, num_types))
return false;
/* Extract string from source_file_type. */
source_file_type = bfd_getl32 (&usl->source_file_type);
if (source_file_type < TPI_FIRST_INDEX ||
source_file_type >= TPI_FIRST_INDEX + num_types ||
!map[source_file_type - TPI_FIRST_INDEX])
{
einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
" referred to unknown string %v\n"), source_file_type);
return false;
}
str_e = map[source_file_type - TPI_FIRST_INDEX];
if (bfd_getl16 (str_e->data + sizeof (uint16_t)) != LF_STRING_ID)
{
einfo (_("%P: warning: CodeView type record LF_UDT_SRC_LINE"
" pointed to unexpected record type\n"));
return false;
}
str = (struct lf_string_id *) str_e->data;
/* Add string to string table. */
source_file_offset = add_string (str->string, strlen (str->string),
strings);
/* Add LF_UDT_MOD_SRC_LINE entry. */
size = sizeof (struct lf_udt_mod_src_line);
e = xmalloc (offsetof (struct type_entry, data) + size);
e->next = NULL;
e->index = ids->num_types;
e->has_udt_src_line = false;
/* LF_UDT_MOD_SRC_LINE use calc_hash on the type number, rather than
the crc32 used for type hashes elsewhere. */
e->cv_hash = calc_hash ((char *) &usl->type, sizeof (uint32_t));
type_e->has_udt_src_line = true;
umsl = (struct lf_udt_mod_src_line *) e->data;
bfd_putl16 (size - sizeof (uint16_t), &umsl->size);
bfd_putl16 (LF_UDT_MOD_SRC_LINE, &umsl->kind);
memcpy (&umsl->type, &usl->type, sizeof (uint32_t));
bfd_putl32 (source_file_offset, &umsl->source_file_string);
memcpy (&umsl->line_no, &usl->line_no, sizeof (uint32_t));
bfd_putl16 (mod_num + 1, &umsl->module_no);
hash = iterative_hash (e->data, size, 0);
slot = htab_find_slot_with_hash (ids->hashmap, data, hash, INSERT);
if (!slot)
{
free (e);
return false;
}
if (*slot)
{
free (e);
einfo (_("%P: warning: duplicate CodeView type record "
"LF_UDT_MOD_SRC_LINE\n"));
return false;
}
*slot = e;
if (ids->last)
ids->last->next = e;
else
ids->first = e;
ids->last = e;
map[type_num] = e;
ids->num_types++;
return true;
}
/* Parse a type definition in the .debug$T section. We remap the numbers
of any referenced types, and if the type is not a duplicate of one
already seen add it to types (for TPI types) or ids (for IPI types). */
static bool
handle_type (uint8_t *data, struct type_entry **map, uint32_t type_num,
uint32_t num_types, struct types *types,
struct types *ids, uint16_t mod_num,
struct string_table *strings)
{
uint16_t size, type;
void **slot;
hashval_t hash;
bool other_hash = false;
uint32_t cv_hash;
struct types *t;
bool ipi = false;
size = bfd_getl16 (data) + sizeof (uint16_t);
type = bfd_getl16 (data + sizeof (uint16_t));
switch (type)
{
case LF_MODIFIER:
{
struct lf_modifier *mod = (struct lf_modifier *) data;
if (size < offsetof (struct lf_modifier, modifier))
{
einfo (_("%P: warning: truncated CodeView type record "
"LF_MODIFIER\n"));
return false;
}
if (!remap_type (&mod->base_type, map, type_num, num_types))
return false;
break;
}
case LF_POINTER:
{
struct lf_pointer *ptr = (struct lf_pointer *) data;
if (size < offsetof (struct lf_pointer, attributes))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_POINTER\n"));
return false;
}
if (!remap_type (&ptr->base_type, map, type_num, num_types))
return false;
break;
}
case LF_PROCEDURE:
{
struct lf_procedure *proc = (struct lf_procedure *) data;
if (size < sizeof (struct lf_procedure))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_PROCEDURE\n"));
return false;
}
if (!remap_type (&proc->return_type, map, type_num, num_types))
return false;
if (!remap_type (&proc->arglist, map, type_num, num_types))
return false;
break;
}
case LF_MFUNCTION:
{
struct lf_mfunction *func = (struct lf_mfunction *) data;
if (size < sizeof (struct lf_procedure))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_MFUNCTION\n"));
return false;
}
if (!remap_type (&func->return_type, map, type_num, num_types))
return false;
if (!remap_type (&func->containing_class_type, map, type_num,
num_types))
return false;
if (!remap_type (&func->this_type, map, type_num, num_types))
return false;
if (!remap_type (&func->arglist, map, type_num, num_types))
return false;
break;
}
case LF_ARGLIST:
{
uint32_t num_entries;
struct lf_arglist *al = (struct lf_arglist *) data;
if (size < offsetof (struct lf_arglist, args))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_ARGLIST\n"));
return false;
}
num_entries = bfd_getl32 (&al->num_entries);
if (size < offsetof (struct lf_arglist, args)
+ (num_entries * sizeof (uint32_t)))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_ARGLIST\n"));
return false;
}
for (uint32_t i = 0; i < num_entries; i++)
{
if (!remap_type (&al->args[i], map, type_num, num_types))
return false;
}
break;
}
case LF_FIELDLIST:
{
uint16_t left = size - sizeof (uint16_t) - sizeof (uint16_t);
uint8_t *ptr = data + sizeof (uint16_t) + sizeof (uint16_t);
while (left > 0)
{
uint16_t subtype;
if (left < sizeof (uint16_t))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_FIELDLIST\n"));
return false;
}
subtype = bfd_getl16 (ptr);
switch (subtype)
{
case LF_MEMBER:
{
struct lf_member *mem = (struct lf_member *) ptr;
uint16_t offset;
size_t name_len, subtype_len;
if (left < offsetof (struct lf_member, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_MEMBER\n"));
return false;
}
if (!remap_type (&mem->type, map, type_num, num_types))
return false;
subtype_len = offsetof (struct lf_member, name);
offset = bfd_getl16 (&mem->offset);
/* If offset >= 0x8000, actual value follows. */
if (offset >= 0x8000)
{
unsigned int param_len = extended_value_len (offset);
if (param_len == 0)
{
einfo (_("%P: warning: unhandled type %v within"
" LF_MEMBER\n"), offset);
return false;
}
subtype_len += param_len;
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_MEMBER\n"));
return false;
}
}
name_len =
strnlen ((char *) mem + subtype_len, left - subtype_len);
if (name_len == left - offsetof (struct lf_member, name))
{
einfo (_("%P: warning: name for LF_MEMBER has no"
" terminating zero\n"));
return false;
}
name_len++;
subtype_len += name_len;
if (subtype_len % 4 != 0)
subtype_len += 4 - (subtype_len % 4);
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_FIELDLIST\n"));
return false;
}
ptr += subtype_len;
left -= subtype_len;
break;
}
case LF_ENUMERATE:
{
struct lf_enumerate *en = (struct lf_enumerate *) ptr;
size_t name_len, subtype_len;
uint16_t val;
if (left < offsetof (struct lf_enumerate, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_ENUMERATE\n"));
return false;
}
subtype_len = offsetof (struct lf_enumerate, name);
val = bfd_getl16 (&en->value);
/* If val >= 0x8000, the actual value immediately follows. */
if (val >= 0x8000)
{
unsigned int param_len = extended_value_len (val);
if (param_len == 0)
{
einfo (_("%P: warning: unhandled type %v within"
" LF_ENUMERATE\n"), val);
return false;
}
if (left < subtype_len + param_len)
{
einfo (_("%P: warning: truncated CodeView type"
" record LF_ENUMERATE\n"));
return false;
}
subtype_len += param_len;
}
name_len = strnlen ((char *) ptr + subtype_len,
left - subtype_len);
if (name_len == left - offsetof (struct lf_enumerate, name))
{
einfo (_("%P: warning: name for LF_ENUMERATE has no"
" terminating zero\n"));
return false;
}
name_len++;
subtype_len += name_len;
if (subtype_len % 4 != 0)
subtype_len += 4 - (subtype_len % 4);
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_ENUMERATE\n"));
return false;
}
ptr += subtype_len;
left -= subtype_len;
break;
}
case LF_INDEX:
{
struct lf_index *ind = (struct lf_index *) ptr;
if (left < sizeof (struct lf_index))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_INDEX\n"));
return false;
}
if (!remap_type (&ind->index, map, type_num, num_types))
return false;
ptr += sizeof (struct lf_index);
left -= sizeof (struct lf_index);
break;
}
case LF_ONEMETHOD:
{
struct lf_onemethod *meth = (struct lf_onemethod *) ptr;
size_t name_len, subtype_len;
if (left < offsetof (struct lf_onemethod, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_ONEMETHOD\n"));
return false;
}
if (!remap_type (&meth->method_type, map, type_num,
num_types))
return false;
name_len =
strnlen (meth->name,
left - offsetof (struct lf_onemethod, name));
if (name_len == left - offsetof (struct lf_onemethod, name))
{
einfo (_("%P: warning: name for LF_ONEMETHOD has no"
" terminating zero\n"));
return false;
}
name_len++;
subtype_len = offsetof (struct lf_onemethod, name)
+ name_len;
if (subtype_len % 4 != 0)
subtype_len += 4 - (subtype_len % 4);
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_FIELDLIST\n"));
return false;
}
ptr += subtype_len;
left -= subtype_len;
break;
}
case LF_METHOD:
{
struct lf_method *meth = (struct lf_method *) ptr;
size_t name_len, subtype_len;
if (left < offsetof (struct lf_method, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_METHOD\n"));
return false;
}
if (!remap_type (&meth->method_list, map, type_num,
num_types))
return false;
name_len =
strnlen (meth->name,
left - offsetof (struct lf_method, name));
if (name_len == left - offsetof (struct lf_method, name))
{
einfo (_("%P: warning: name for LF_METHOD has no"
" terminating zero\n"));
return false;
}
name_len++;
subtype_len = offsetof (struct lf_method, name) + name_len;
if (subtype_len % 4 != 0)
subtype_len += 4 - (subtype_len % 4);
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_FIELDLIST\n"));
return false;
}
ptr += subtype_len;
left -= subtype_len;
break;
}
case LF_BCLASS:
{
struct lf_bclass *bc = (struct lf_bclass *) ptr;
size_t subtype_len;
uint16_t offset;
if (left < sizeof (struct lf_bclass))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_BCLASS\n"));
return false;
}
if (!remap_type (&bc->base_class_type, map, type_num,
num_types))
return false;
subtype_len = sizeof (struct lf_bclass);
offset = bfd_getl16 (&bc->offset);
/* If offset >= 0x8000, actual value follows. */
if (offset >= 0x8000)
{
unsigned int param_len = extended_value_len (offset);
if (param_len == 0)
{
einfo (_("%P: warning: unhandled type %v within"
" LF_BCLASS\n"), offset);
return false;
}
subtype_len += param_len;
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_BCLASS\n"));
return false;
}
}
if (subtype_len % 4 != 0)
subtype_len += 4 - (subtype_len % 4);
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_BCLASS\n"));
return false;
}
ptr += subtype_len;
left -= subtype_len;
break;
}
case LF_VFUNCTAB:
{
struct lf_vfunctab *vft = (struct lf_vfunctab *) ptr;
if (left < sizeof (struct lf_vfunctab))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_VFUNCTAB\n"));
return false;
}
if (!remap_type (&vft->type, map, type_num, num_types))
return false;
ptr += sizeof (struct lf_vfunctab);
left -= sizeof (struct lf_vfunctab);
break;
}
case LF_VBCLASS:
case LF_IVBCLASS:
{
struct lf_vbclass *vbc = (struct lf_vbclass *) ptr;
size_t subtype_len;
uint16_t offset;
if (left < sizeof (struct lf_vbclass))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_VBCLASS/LF_IVBCLASS\n"));
return false;
}
if (!remap_type (&vbc->base_class_type, map, type_num,
num_types))
return false;
if (!remap_type (&vbc->virtual_base_pointer_type, map,
type_num, num_types))
return false;
subtype_len = offsetof (struct lf_vbclass,
virtual_base_vbtable_offset);
offset = bfd_getl16 (&vbc->virtual_base_pointer_offset);
/* If offset >= 0x8000, actual value follows. */
if (offset >= 0x8000)
{
unsigned int param_len = extended_value_len (offset);
if (param_len == 0)
{
einfo (_("%P: warning: unhandled type %v within"
" LF_VBCLASS/LF_IVBCLASS\n"), offset);
return false;
}
subtype_len += param_len;
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_VBCLASS/LF_IVBCLASS\n"));
return false;
}
}
offset = bfd_getl16 ((char *)vbc + subtype_len);
subtype_len += sizeof (uint16_t);
/* If offset >= 0x8000, actual value follows. */
if (offset >= 0x8000)
{
unsigned int param_len = extended_value_len (offset);
if (param_len == 0)
{
einfo (_("%P: warning: unhandled type %v within"
" LF_VBCLASS/LF_IVBCLASS\n"), offset);
return false;
}
subtype_len += param_len;
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_VBCLASS/LF_IVBCLASS\n"));
return false;
}
}
if (subtype_len % 4 != 0)
subtype_len += 4 - (subtype_len % 4);
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_VBCLASS/LF_IVBCLASS\n"));
return false;
}
ptr += subtype_len;
left -= subtype_len;
break;
}
case LF_STMEMBER:
{
struct lf_static_member *st =
(struct lf_static_member *) ptr;
size_t name_len, subtype_len;
if (left < offsetof (struct lf_static_member, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_STMEMBER\n"));
return false;
}
if (!remap_type (&st->type, map, type_num, num_types))
return false;
name_len =
strnlen (st->name,
left - offsetof (struct lf_static_member, name));
if (name_len == left
- offsetof (struct lf_static_member, name))
{
einfo (_("%P: warning: name for LF_STMEMBER has no"
" terminating zero\n"));
return false;
}
name_len++;
subtype_len = offsetof (struct lf_static_member, name)
+ name_len;
if (subtype_len % 4 != 0)
subtype_len += 4 - (subtype_len % 4);
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_FIELDLIST\n"));
return false;
}
ptr += subtype_len;
left -= subtype_len;
break;
}
case LF_NESTTYPE:
{
struct lf_nest_type *nest = (struct lf_nest_type *) ptr;
size_t name_len, subtype_len;
if (left < offsetof (struct lf_nest_type, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_NESTTYPE\n"));
return false;
}
if (!remap_type (&nest->type, map, type_num, num_types))
return false;
name_len =
strnlen (nest->name,
left - offsetof (struct lf_nest_type, name));
if (name_len == left - offsetof (struct lf_nest_type, name))
{
einfo (_("%P: warning: name for LF_NESTTYPE has no"
" terminating zero\n"));
return false;
}
name_len++;
subtype_len = offsetof (struct lf_nest_type, name)
+ name_len;
if (subtype_len % 4 != 0)
subtype_len += 4 - (subtype_len % 4);
if (left < subtype_len)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_FIELDLIST\n"));
return false;
}
ptr += subtype_len;
left -= subtype_len;
break;
}
default:
einfo (_("%P: warning: unrecognized CodeView subtype %v\n"),
subtype);
return false;
}
}
break;
}
case LF_BITFIELD:
{
struct lf_bitfield *bf = (struct lf_bitfield *) data;
if (size < offsetof (struct lf_bitfield, length))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_BITFIELD\n"));
return false;
}
if (!remap_type (&bf->base_type, map, type_num, num_types))
return false;
break;
}
case LF_METHODLIST:
{
struct lf_methodlist *ml = (struct lf_methodlist *) data;
unsigned int num_entries;
if (size < offsetof (struct lf_methodlist, entries))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_METHODLIST\n"));
return false;
}
if ((size - offsetof (struct lf_methodlist, entries))
% sizeof (struct lf_methodlist_entry))
{
einfo (_("%P: warning: malformed CodeView type record"
" LF_METHODLIST\n"));
return false;
}
num_entries = (size - offsetof (struct lf_methodlist, entries))
/ sizeof (struct lf_methodlist_entry);
for (unsigned int i = 0; i < num_entries; i++)
{
if (!remap_type (&ml->entries[i].method_type, map,
type_num, num_types))
return false;
}
break;
}
case LF_ARRAY:
{
struct lf_array *arr = (struct lf_array *) data;
if (size < offsetof (struct lf_array, length_in_bytes))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_ARRAY\n"));
return false;
}
if (!remap_type (&arr->element_type, map, type_num, num_types))
return false;
if (!remap_type (&arr->index_type, map, type_num, num_types))
return false;
break;
}
case LF_CLASS:
case LF_STRUCTURE:
{
struct lf_class *cl = (struct lf_class *) data;
uint16_t prop, num_bytes;
size_t name_len, name_off;
if (size < offsetof (struct lf_class, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_CLASS/LF_STRUCTURE\n"));
return false;
}
if (!remap_type (&cl->field_list, map, type_num, num_types))
return false;
if (!remap_type (&cl->derived_from, map, type_num, num_types))
return false;
if (!remap_type (&cl->vshape, map, type_num, num_types))
return false;
name_off = offsetof (struct lf_class, name);
num_bytes = bfd_getl16 (&cl->length);
/* If num_bytes >= 0x8000, actual value follows. */
if (num_bytes >= 0x8000)
{
unsigned int param_len = extended_value_len (num_bytes);
if (param_len == 0)
{
einfo (_("%P: warning: unhandled type %v within"
" LF_CLASS/LF_STRUCTURE\n"), num_bytes);
return false;
}
name_off += param_len;
if (size < name_off)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_CLASS/LF_STRUCTURE\n"));
return false;
}
}
name_len = strnlen ((char *) cl + name_off, size - name_off);
if (name_len == size - name_off)
{
einfo (_("%P: warning: name for LF_CLASS/LF_STRUCTURE has no"
" terminating zero\n"));
return false;
}
prop = bfd_getl16 (&cl->properties);
if (prop & CV_PROP_HAS_UNIQUE_NAME)
{
/* Structure has another name following first one. */
size_t len = name_off + name_len + 1;
size_t unique_name_len;
unique_name_len = strnlen ((char *) cl + name_off + name_len + 1,
size - len);
if (unique_name_len == size - len)
{
einfo (_("%P: warning: unique name for LF_CLASS/LF_STRUCTURE"
" has no terminating zero\n"));
return false;
}
}
if (!(prop & (CV_PROP_FORWARD_REF | CV_PROP_SCOPED))
&& !is_name_anonymous ((char *) cl + name_off, name_len))
{
other_hash = true;
cv_hash = crc32 ((uint8_t *) cl + name_off, name_len);
}
break;
}
case LF_UNION:
{
struct lf_union *un = (struct lf_union *) data;
uint16_t prop, num_bytes;
size_t name_len, name_off;
if (size < offsetof (struct lf_union, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_UNION\n"));
return false;
}
if (!remap_type (&un->field_list, map, type_num, num_types))
return false;
name_off = offsetof (struct lf_union, name);
num_bytes = bfd_getl16 (&un->length);
/* If num_bytes >= 0x8000, actual value follows. */
if (num_bytes >= 0x8000)
{
unsigned int param_len = extended_value_len (num_bytes);
if (param_len == 0)
{
einfo (_("%P: warning: unhandled type %v within"
" LF_UNION\n"), num_bytes);
return false;
}
name_off += param_len;
if (size < name_off)
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_UNION\n"));
return false;
}
}
name_len = strnlen ((char *) un + name_off, size - name_off);
if (name_len == size - name_off)
{
einfo (_("%P: warning: name for LF_UNION has no"
" terminating zero\n"));
return false;
}
prop = bfd_getl16 (&un->properties);
if (prop & CV_PROP_HAS_UNIQUE_NAME)
{
/* Structure has another name following first one. */
size_t len = name_off + name_len + 1;
size_t unique_name_len;
unique_name_len = strnlen ((char *) un + name_off + name_len + 1,
size - len);
if (unique_name_len == size - len)
{
einfo (_("%P: warning: unique name for LF_UNION has"
" no terminating zero\n"));
return false;
}
}
if (!(prop & (CV_PROP_FORWARD_REF | CV_PROP_SCOPED))
&& !is_name_anonymous ((char *) un + name_off, name_len))
{
other_hash = true;
cv_hash = crc32 ((uint8_t *) un + name_off, name_len);
}
break;
}
case LF_ENUM:
{
struct lf_enum *en = (struct lf_enum *) data;
uint16_t prop;
size_t name_len;
if (size < offsetof (struct lf_enum, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_ENUM\n"));
return false;
}
if (!remap_type (&en->underlying_type, map, type_num, num_types))
return false;
if (!remap_type (&en->field_list, map, type_num, num_types))
return false;
name_len = strnlen (en->name, size - offsetof (struct lf_enum, name));
if (name_len == size - offsetof (struct lf_enum, name))
{
einfo (_("%P: warning: name for LF_ENUM has no"
" terminating zero\n"));
return false;
}
prop = bfd_getl16 (&en->properties);
if (prop & CV_PROP_HAS_UNIQUE_NAME)
{
/* Structure has another name following first one. */
size_t len = offsetof (struct lf_enum, name) + name_len + 1;
size_t unique_name_len;
unique_name_len = strnlen (en->name + name_len + 1, size - len);
if (unique_name_len == size - len)
{
einfo (_("%P: warning: unique name for LF_ENUM has"
" no terminating zero\n"));
return false;
}
}
break;
}
case LF_VTSHAPE:
/* Does not reference any types, nothing to be done. */
break;
case LF_VFTABLE:
{
struct lf_vftable *vft = (struct lf_vftable *) data;
if (size < offsetof (struct lf_vftable, names))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_VFTABLE\n"));
return false;
}
if (!remap_type (&vft->type, map, type_num, num_types))
return false;
if (!remap_type (&vft->base_vftable, map, type_num, num_types))
return false;
break;
}
case LF_STRING_ID:
{
struct lf_string_id *str = (struct lf_string_id *) data;
size_t string_len;
if (size < offsetof (struct lf_string_id, string))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_STRING_ID\n"));
return false;
}
if (!remap_type (&str->substring, map, type_num, num_types))
return false;
string_len = strnlen (str->string,
size - offsetof (struct lf_string_id, string));
if (string_len == size - offsetof (struct lf_string_id, string))
{
einfo (_("%P: warning: string for LF_STRING_ID has no"
" terminating zero\n"));
return false;
}
ipi = true;
break;
}
case LF_SUBSTR_LIST:
{
uint32_t num_entries;
struct lf_arglist *ssl = (struct lf_arglist *) data;
if (size < offsetof (struct lf_arglist, args))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_SUBSTR_LIST\n"));
return false;
}
num_entries = bfd_getl32 (&ssl->num_entries);
if (size < offsetof (struct lf_arglist, args)
+ (num_entries * sizeof (uint32_t)))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_SUBSTR_LIST\n"));
return false;
}
for (uint32_t i = 0; i < num_entries; i++)
{
if (!remap_type (&ssl->args[i], map, type_num, num_types))
return false;
}
ipi = true;
break;
}
case LF_BUILDINFO:
{
uint16_t num_entries;
struct lf_build_info *bi = (struct lf_build_info *) data;
if (size < offsetof (struct lf_build_info, strings))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_BUILDINFO\n"));
return false;
}
num_entries = bfd_getl16 (&bi->count);
if (size < offsetof (struct lf_build_info, strings)
+ (num_entries * sizeof (uint32_t)))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_BUILDINFO\n"));
return false;
}
for (uint32_t i = 0; i < num_entries; i++)
{
if (!remap_type (&bi->strings[i], map, type_num, num_types))
return false;
}
ipi = true;
break;
}
case LF_FUNC_ID:
{
struct lf_func_id *func = (struct lf_func_id *) data;
size_t name_len;
if (size < offsetof (struct lf_func_id, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_FUNC_ID\n"));
return false;
}
if (!remap_type (&func->parent_scope, map, type_num, num_types))
return false;
if (!remap_type (&func->function_type, map, type_num, num_types))
return false;
name_len = strnlen (func->name,
size - offsetof (struct lf_func_id, name));
if (name_len == size - offsetof (struct lf_func_id, name))
{
einfo (_("%P: warning: string for LF_FUNC_ID has no"
" terminating zero\n"));
return false;
}
ipi = true;
break;
}
case LF_MFUNC_ID:
{
struct lf_mfunc_id *mfunc = (struct lf_mfunc_id *) data;
size_t name_len;
if (size < offsetof (struct lf_mfunc_id, name))
{
einfo (_("%P: warning: truncated CodeView type record"
" LF_MFUNC_ID\n"));
return false;
}
if (!remap_type (&mfunc->parent_type, map, type_num, num_types))
return false;
if (!remap_type (&mfunc->function_type, map, type_num, num_types))
return false;
name_len = strnlen (mfunc->name,
size - offsetof (struct lf_mfunc_id, name));
if (name_len == size - offsetof (struct lf_mfunc_id, name))
{
einfo (_("%P: warning: string for LF_MFUNC_ID has no"
" terminating zero\n"));
return false;
}
ipi = true;
break;
}
case LF_UDT_SRC_LINE:
return handle_udt_src_line (data, size, map, type_num, num_types,
ids, mod_num, strings);
default:
einfo (_("%P: warning: unrecognized CodeView type %v\n"), type);
return false;
}
hash = iterative_hash (data, size, 0);
t = ipi ? ids : types;
slot = htab_find_slot_with_hash (t->hashmap, data, hash, INSERT);
if (!slot)
return false;
if (!*slot) /* new entry */
{
struct type_entry *e;
*slot = xmalloc (offsetof (struct type_entry, data) + size);
e = (struct type_entry *) *slot;
e->next = NULL;
e->index = t->num_types;
if (other_hash)
e->cv_hash = cv_hash;
else
e->cv_hash = crc32 (data, size);
e->has_udt_src_line = false;
memcpy (e->data, data, size);
if (t->last)
t->last->next = e;
else
t->first = e;
t->last = e;
map[type_num] = e;
t->num_types++;
}
else /* duplicate */
{
map[type_num] = (struct type_entry *) *slot;
}
return true;
}
/* Parse the .debug$T section of a module, and pass any type definitions
found to handle_type. */
static bool
handle_debugt_section (asection *s, bfd *mod, struct types *types,
struct types *ids, uint16_t mod_num,
struct string_table *strings,
struct type_entry ***map, uint32_t *num_types)
{
bfd_byte *data = NULL;
size_t off;
uint32_t type_num;
if (!bfd_get_full_section_contents (mod, s, &data))
return false;
if (!data)
return false;
if (bfd_getl32 (data) != CV_SIGNATURE_C13)
{
free (data);
return true;
}
off = sizeof (uint32_t);
while (off + sizeof (uint16_t) <= s->size)
{
uint16_t size;
size = bfd_getl16 (data + off);
off += sizeof (uint16_t);
if (size + off > s->size || size <= sizeof (uint16_t))
{
free (data);
bfd_set_error (bfd_error_bad_value);
return false;
}
(*num_types)++;
off += size;
}
if (*num_types == 0)
{
free (data);
return true;
}
*map = xcalloc (*num_types, sizeof (struct type_entry *));
off = sizeof (uint32_t);
type_num = 0;
while (off + sizeof (uint16_t) <= s->size)
{
uint16_t size;
size = bfd_getl16 (data + off);
if (!handle_type (data + off, *map, type_num, *num_types, types, ids,
mod_num, strings))
{
free (data);
free (*map);
bfd_set_error (bfd_error_bad_value);
return false;
}
off += sizeof (uint16_t) + size;
type_num++;
}
free (data);
return true;
}
/* Return the CodeView constant for the selected architecture. */
static uint16_t
target_processor (bfd *abfd)
{
switch (abfd->arch_info->arch)
{
case bfd_arch_i386:
if (abfd->arch_info->mach & bfd_mach_x86_64)
return CV_CFL_X64;
else
return CV_CFL_80386;
case bfd_arch_aarch64:
return CV_CFL_ARM64;
default:
return 0;
}
}
/* Create the symbols that go in "* Linker *", the dummy module created
for the linker itself. */
static bool
create_linker_symbols (bfd *abfd, uint8_t **syms, uint32_t *sym_byte_size,
const char *pdb_name)
{
uint8_t *ptr;
struct objname *name;
struct compile3 *comp;
struct envblock *env;
size_t padding1, padding2, env_size;
char *cwdval, *exeval, *pdbval;
/* extra NUL for padding */
static const char linker_fn[] = "* Linker *\0";
static const char linker_name[] = "GNU LD " VERSION;
static const char cwd[] = "cwd";
static const char exe[] = "exe";
static const char pdb[] = "pdb";
cwdval = getcwd (NULL, 0);
if (!cwdval)
{
einfo (_("%P: warning: unable to get working directory\n"));
return false;
}
exeval = lrealpath (program_name);
if (!exeval)
{
einfo (_("%P: warning: unable to get program name\n"));
free (cwdval);
return false;
}
pdbval = lrealpath (pdb_name);
if (!pdbval)
{
einfo (_("%P: warning: unable to get full path to PDB\n"));
free (exeval);
free (cwdval);
return false;
}
*sym_byte_size += offsetof (struct objname, name) + sizeof (linker_fn);
*sym_byte_size += offsetof (struct compile3, compiler) + sizeof (linker_name);
if (*sym_byte_size % 4)
padding1 = 4 - (*sym_byte_size % 4);
else
padding1 = 0;
*sym_byte_size += padding1;
env_size = offsetof (struct envblock, strings);
env_size += sizeof (cwd);
env_size += strlen (cwdval) + 1;
env_size += sizeof (exe);
env_size += strlen (exeval) + 1;
env_size += sizeof (pdb);
env_size += strlen (pdbval) + 1;
if (env_size % 4)
padding2 = 4 - (env_size % 4);
else
padding2 = 0;
env_size += padding2;
*sym_byte_size += env_size;
*syms = xmalloc (*sym_byte_size);
ptr = *syms;
/* Write S_OBJNAME */
name = (struct objname *) ptr;
bfd_putl16 (offsetof (struct objname, name)
+ sizeof (linker_fn) - sizeof (uint16_t), &name->size);
bfd_putl16 (S_OBJNAME, &name->kind);
bfd_putl32 (0, &name->signature);
memcpy (name->name, linker_fn, sizeof (linker_fn));
ptr += offsetof (struct objname, name) + sizeof (linker_fn);
/* Write S_COMPILE3 */
comp = (struct compile3 *) ptr;
bfd_putl16 (offsetof (struct compile3, compiler) + sizeof (linker_name)
+ padding1 - sizeof (uint16_t), &comp->size);
bfd_putl16 (S_COMPILE3, &comp->kind);
bfd_putl32 (CV_CFL_LINK, &comp->flags);
bfd_putl16 (target_processor (abfd), &comp->machine);
bfd_putl16 (0, &comp->frontend_major);
bfd_putl16 (0, &comp->frontend_minor);
bfd_putl16 (0, &comp->frontend_build);
bfd_putl16 (0, &comp->frontend_qfe);
bfd_putl16 (0, &comp->backend_major);
bfd_putl16 (0, &comp->backend_minor);
bfd_putl16 (0, &comp->backend_build);
bfd_putl16 (0, &comp->backend_qfe);
memcpy (comp->compiler, linker_name, sizeof (linker_name));
memset (comp->compiler + sizeof (linker_name), 0, padding1);
ptr += offsetof (struct compile3, compiler) + sizeof (linker_name) + padding1;
/* Write S_ENVBLOCK */
env = (struct envblock *) ptr;
bfd_putl16 (env_size - sizeof (uint16_t), &env->size);
bfd_putl16 (S_ENVBLOCK, &env->kind);
env->flags = 0;
ptr += offsetof (struct envblock, strings);
memcpy (ptr, cwd, sizeof (cwd));
ptr += sizeof (cwd);
memcpy (ptr, cwdval, strlen (cwdval) + 1);
ptr += strlen (cwdval) + 1;
memcpy (ptr, exe, sizeof (exe));
ptr += sizeof (exe);
memcpy (ptr, exeval, strlen (exeval) + 1);
ptr += strlen (exeval) + 1;
memcpy (ptr, pdb, sizeof (pdb));
ptr += sizeof (pdb);
memcpy (ptr, pdbval, strlen (pdbval) + 1);
ptr += strlen (pdbval) + 1;
/* Microsoft's LINK also includes "cmd", the command-line options passed
to the linker, but unfortunately we don't have access to argc and argv
at this stage. */
memset (ptr, 0, padding2);
free (pdbval);
free (exeval);
free (cwdval);
return true;
}
/* Populate the module stream, which consists of the transformed .debug$S
data for each object file. */
static bool
populate_module_stream (bfd *stream, bfd *mod, uint32_t *sym_byte_size,
struct string_table *strings,
uint32_t *c13_info_size,
struct mod_source_files *mod_source,
bfd *abfd, struct types *types,
struct types *ids, uint16_t mod_num,
bfd *sym_rec_stream, struct globals *glob,
const char *pdb_name)
{
uint8_t int_buf[sizeof (uint32_t)];
uint8_t *c13_info = NULL;
uint8_t *syms = NULL;
*sym_byte_size = 0;
*c13_info_size = 0;
if (!strcmp (bfd_get_filename (mod), "dll stuff"))
{
if (!create_linker_symbols (mod, &syms, sym_byte_size, pdb_name))
return false;
}
else
{
struct type_entry **map = NULL;
uint32_t num_types = 0;
/* Process .debug$T section. */
for (asection *s = mod->sections; s; s = s->next)
{
if (!strcmp (s->name, ".debug$T") && s->size >= sizeof (uint32_t))
{
if (!handle_debugt_section (s, mod, types, ids, mod_num, strings,
&map, &num_types))
{
free (mod_source->files);
return false;
}
break;
}
}
/* Process .debug$S section(s). */
for (asection *s = mod->sections; s; s = s->next)
{
if (!strcmp (s->name, ".debug$S") && s->size >= sizeof (uint32_t))
{
if (!handle_debugs_section (s, mod, strings, &c13_info,
c13_info_size, mod_source, abfd,
&syms, sym_byte_size, map, num_types,
sym_rec_stream, glob, mod_num))
{
free (c13_info);
free (syms);
free (mod_source->files);
free (map);
return false;
}
}
}
free (map);
}
/* Write the signature. */
bfd_putl32 (CV_SIGNATURE_C13, int_buf);
if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
{
free (c13_info);
free (syms);
return false;
}
if (syms)
{
if (bfd_bwrite (syms, *sym_byte_size, stream) != *sym_byte_size)
{
free (c13_info);
free (syms);
return false;
}
free (syms);
}
if (c13_info)
{
if (bfd_bwrite (c13_info, *c13_info_size, stream) != *c13_info_size)
{
free (c13_info);
return false;
}
free (c13_info);
}
/* 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, struct string_table *strings,
struct source_files_info *source,
struct types *types, struct types *ids,
bfd *sym_rec_stream, struct globals *glob,
const char *pdb_name)
{
uint8_t *ptr;
unsigned int mod_num;
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;
source->mod_count++;
}
*data = xmalloc (*size);
ptr = *data;
source->mods = xmalloc (source->mod_count
* sizeof (struct mod_source_files));
memset (source->mods, 0,
source->mod_count * sizeof (struct mod_source_files));
mod_num = 0;
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, c13_info_size;
uint8_t *start = ptr;
stream = add_stream (pdb, NULL, &stream_num);
if (!stream)
{
for (unsigned int i = 0; i < source->mod_count; i++)
{
free (source->mods[i].files);
}
free (source->mods);
free (*data);
return false;
}
if (!populate_module_stream (stream, in, &sym_byte_size,
strings, &c13_info_size,
&source->mods[mod_num], abfd,
types, ids, mod_num,
sym_rec_stream, glob, pdb_name))
{
for (unsigned int i = 0; i < source->mod_count; i++)
{
free (source->mods[i].files);
}
free (source->mods);
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 (sizeof (uint32_t) + sym_byte_size, &mod->sym_byte_size);
bfd_putl32 (0, &mod->c11_byte_size);
bfd_putl32 (c13_info_size, &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);
}
mod_num++;
}
return true;
}
/* 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;
}
/* Used as parameter to qsort, to sort section contributions by section and
offset. */
static int
section_contribs_compare (const void *p1, const void *p2)
{
const struct in_sc *sc1 = p1;
const struct in_sc *sc2 = p2;
if (sc1->sect_num < sc2->sect_num)
return -1;
if (sc1->sect_num > sc2->sect_num)
return 1;
if (sc1->s->output_offset < sc2->s->output_offset)
return -1;
if (sc1->s->output_offset > sc2->s->output_offset)
return 1;
return 0;
}
/* Create the substream which maps addresses in the image file to locations
in the original object files. */
static bool
create_section_contrib_substream (bfd *abfd, void **data, uint32_t *size)
{
unsigned int num_sc = 0;
struct section_contribution *sc;
uint16_t mod_index;
char *sect_flags;
file_ptr offset;
struct in_sc *sc_in, *sc2;
uint32_t *ptr;
for (bfd *in = coff_data (abfd)->link_info->input_bfds; in;
in = in->link.next)
{
for (asection *s = in->sections; s; s = s->next)
{
if (s->size == 0 || discarded_section (s))
continue;
num_sc++;
}
}
*size = sizeof (uint32_t) + (num_sc * sizeof (struct section_contribution));
*data = xmalloc (*size);
bfd_putl32 (SECTION_CONTRIB_VERSION_60, *data);
/* Read characteristics of outputted sections. */
sect_flags = xmalloc (sizeof (uint32_t) * abfd->section_count);
offset = bfd_coff_filhsz (abfd) + bfd_coff_aoutsz (abfd);
offset += offsetof (struct external_scnhdr, s_flags);
for (unsigned int i = 0; i < abfd->section_count; i++)
{
bfd_seek (abfd, offset, SEEK_SET);
if (bfd_bread (sect_flags + (i * sizeof (uint32_t)), sizeof (uint32_t),
abfd) != sizeof (uint32_t))
{
free (*data);
free (sect_flags);
return false;
}
offset += sizeof (struct external_scnhdr);
}
/* Microsoft's DIA expects section contributions to be sorted by section
number and offset, otherwise it will be unable to resolve line numbers. */
sc_in = xmalloc (num_sc * sizeof (* sc_in));
sc2 = sc_in;
mod_index = 0;
for (bfd *in = coff_data (abfd)->link_info->input_bfds; in;
in = in->link.next)
{
for (asection *s = in->sections; s; s = s->next)
{
if (s->size == 0 || discarded_section (s))
continue;
sc2->s = s;
sc2->sect_num = find_section_number (abfd, s->output_section);
sc2->mod_index = mod_index;
sc2++;
}
mod_index++;
}
qsort (sc_in, num_sc, sizeof (* sc_in), section_contribs_compare);
ptr = *data;
sc = (struct section_contribution *) (ptr + 1); /* Skip the version word. */
for (unsigned int i = 0; i < num_sc; i++)
{
memcpy (&sc->characteristics,
sect_flags + ((sc_in[i].sect_num - 1) * sizeof (uint32_t)),
sizeof (uint32_t));
bfd_putl16 (sc_in[i].sect_num, &sc->section);
bfd_putl16 (0, &sc->padding1);
bfd_putl32 (sc_in[i].s->output_offset, &sc->offset);
bfd_putl32 (sc_in[i].s->size, &sc->size);
bfd_putl16 (sc_in[i].mod_index, &sc->module_index);
bfd_putl16 (0, &sc->padding2);
bfd_putl32 (0, &sc->data_crc);
bfd_putl32 (0, &sc->reloc_crc);
sc++;
}
free (sc_in);
free (sect_flags);
return true;
}
/* The source info substream lives within the DBI stream, and lists the
source files for each object file (i.e. it's derived from the
DEBUG_S_FILECHKSMS parts of the .debug$S sections). This is a bit
superfluous, as the filenames are also available in the C13 parts of
the module streams, but MSVC relies on it to work properly. */
static void
create_source_info_substream (void **data, uint32_t *size,
struct source_files_info *source)
{
uint16_t dedupe_source_files_count = 0;
uint16_t source_files_count = 0;
uint32_t strings_len = 0;
uint8_t *ptr;
/* Loop through the source files, marking unique filenames. The pointers
here are for entries in the main string table, and so have already
been deduplicated. */
for (uint16_t i = 0; i < source->mod_count; i++)
{
for (uint16_t j = 0; j < source->mods[i].files_count; j++)
{
if (source->mods[i].files[j])
{
if (source->mods[i].files[j]->source_file_offset == 0xffffffff)
{
source->mods[i].files[j]->source_file_offset = strings_len;
strings_len += source->mods[i].files[j]->len + 1;
dedupe_source_files_count++;
}
source_files_count++;
}
}
}
*size = sizeof (uint16_t) + sizeof (uint16_t);
*size += (sizeof (uint16_t) + sizeof (uint16_t)) * source->mod_count;
*size += sizeof (uint32_t) * source_files_count;
*size += strings_len;
*data = xmalloc (*size);
ptr = (uint8_t *) *data;
/* Write header (module count and source file count). */
bfd_putl16 (source->mod_count, ptr);
ptr += sizeof (uint16_t);
bfd_putl16 (dedupe_source_files_count, ptr);
ptr += sizeof (uint16_t);
/* Write "ModIndices". As the LLVM documentation puts it, "this array is
present, but does not appear to be useful". */
for (uint16_t i = 0; i < source->mod_count; i++)
{
bfd_putl16 (i, ptr);
ptr += sizeof (uint16_t);
}
/* Write source file count for each module. */
for (uint16_t i = 0; i < source->mod_count; i++)
{
bfd_putl16 (source->mods[i].files_count, ptr);
ptr += sizeof (uint16_t);
}
/* For each module, write the offsets within the string table
for each source file. */
for (uint16_t i = 0; i < source->mod_count; i++)
{
for (uint16_t j = 0; j < source->mods[i].files_count; j++)
{
if (source->mods[i].files[j])
{
bfd_putl32 (source->mods[i].files[j]->source_file_offset, ptr);
ptr += sizeof (uint32_t);
}
}
}
/* Write the string table. We set source_file_offset to a dummy value for
each entry we write, so we don't write duplicate filenames. */
for (uint16_t i = 0; i < source->mod_count; i++)
{
for (uint16_t j = 0; j < source->mods[i].files_count; j++)
{
if (source->mods[i].files[j]
&& source->mods[i].files[j]->source_file_offset != 0xffffffff)
{
memcpy (ptr, source->mods[i].files[j]->s,
source->mods[i].files[j]->len);
ptr += source->mods[i].files[j]->len;
*ptr = 0;
ptr++;
source->mods[i].files[j]->source_file_offset = 0xffffffff;
}
}
}
}
/* Used as parameter to qsort, to sort globals by hash. */
static int
global_compare_hash (const void *s1, const void *s2)
{
const struct global *g1 = *(const struct global **) s1;
const struct global *g2 = *(const struct global **) s2;
if (g1->hash < g2->hash)
return -1;
if (g1->hash > g2->hash)
return 1;
return 0;
}
/* Create the globals stream, which contains the unmangled symbol names. */
static bool
create_globals_stream (bfd *pdb, struct globals *glob, uint16_t *stream_num)
{
bfd *stream;
struct globals_hash_header h;
uint32_t buckets_size, filled_buckets = 0;
struct global **sorted = NULL;
bool ret = false;
struct global *buckets[NUM_GLOBALS_HASH_BUCKETS];
char int_buf[sizeof (uint32_t)];
stream = add_stream (pdb, NULL, stream_num);
if (!stream)
return false;
memset (buckets, 0, sizeof (buckets));
if (glob->num_entries > 0)
{
struct global *g;
/* Create an array of pointers, sorted by hash value. */
sorted = xmalloc (sizeof (struct global *) * glob->num_entries);
g = glob->first;
for (unsigned int i = 0; i < glob->num_entries; i++)
{
sorted[i] = g;
g = g->next;
}
qsort (sorted, glob->num_entries, sizeof (struct global *),
global_compare_hash);
/* Populate the buckets. */
for (unsigned int i = 0; i < glob->num_entries; i++)
{
if (!buckets[sorted[i]->hash])
{
buckets[sorted[i]->hash] = sorted[i];
filled_buckets++;
}
sorted[i]->index = i;
}
}
buckets_size = NUM_GLOBALS_HASH_BUCKETS / 8;
buckets_size += sizeof (uint32_t);
buckets_size += filled_buckets * sizeof (uint32_t);
bfd_putl32 (GLOBALS_HASH_SIGNATURE, &h.signature);
bfd_putl32 (GLOBALS_HASH_VERSION_70, &h.version);
bfd_putl32 (glob->num_entries * sizeof (struct hash_record),
&h.entries_size);
bfd_putl32 (buckets_size, &h.buckets_size);
if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h))
return false;
/* Write hash entries, sorted by hash. */
for (unsigned int i = 0; i < glob->num_entries; i++)
{
struct hash_record hr;
bfd_putl32 (sorted[i]->offset + 1, &hr.offset);
bfd_putl32 (sorted[i]->refcount, &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_GLOBALS_HASH_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_GLOBALS_HASH_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;
}
}
ret = true;
end:
free (sorted);
return ret;
}
/* Hash an entry in the globals list. */
static hashval_t
hash_global_entry (const void *p)
{
const struct global *g = (const struct global *) p;
uint16_t len = bfd_getl16 (g->data);
return iterative_hash (g->data, len, 0);
}
/* Compare an entry in the globals list with a symbol. */
static int
eq_global_entry (const void *a, const void *b)
{
const struct global *g = (const struct global *) a;
uint16_t len1, len2;
len1 = bfd_getl16 (g->data) + sizeof (uint16_t);
len2 = bfd_getl16 (b) + sizeof (uint16_t);
if (len1 != len2)
return 0;
return !memcmp (g->data, b, len1);
}
/* Stream 4 is the debug information (DBI) stream. */
static bool
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 string_table *strings,
struct types *types,
struct types *ids,
bfd *sym_rec_stream, const char *pdb_name)
{
struct pdb_dbi_stream_header h;
struct optional_dbg_header opt;
void *mod_info, *sc, *source_info;
uint32_t mod_info_size, sc_size, source_info_size;
struct source_files_info source;
struct globals glob;
uint16_t globals_stream_num;
source.mod_count = 0;
source.mods = NULL;
glob.num_entries = 0;
glob.first = NULL;
glob.last = NULL;
glob.hashmap = htab_create_alloc (0, hash_global_entry,
eq_global_entry, free, xcalloc, free);
if (!create_module_info_substream (abfd, pdb, &mod_info, &mod_info_size,
strings, &source, types, ids,
sym_rec_stream, &glob, pdb_name))
{
htab_delete (glob.hashmap);
return false;
}
if (!create_globals_stream (pdb, &glob, &globals_stream_num))
{
htab_delete (glob.hashmap);
for (unsigned int i = 0; i < source.mod_count; i++)
{
free (source.mods[i].files);
}
free (source.mods);
free (mod_info);
return false;
}
htab_delete (glob.hashmap);
if (!create_section_contrib_substream (abfd, &sc, &sc_size))
{
for (unsigned int i = 0; i < source.mod_count; i++)
{
free (source.mods[i].files);
}
free (source.mods);
free (mod_info);
return false;
}
create_source_info_substream (&source_info, &source_info_size, &source);
for (unsigned int i = 0; i < source.mod_count; i++)
{
free (source.mods[i].files);
}
free (source.mods);
bfd_putl32 (0xffffffff, &h.version_signature);
bfd_putl32 (DBI_STREAM_VERSION_70, &h.version_header);
bfd_putl32 (1, &h.age);
bfd_putl16 (globals_stream_num, &h.global_stream_index);
bfd_putl16 (0x8e1d, &h.build_number); // MSVC 14.29
bfd_putl16 (publics_stream_num, &h.public_stream_index);
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 (mod_info_size, &h.mod_info_size);
bfd_putl32 (sc_size, &h.section_contribution_size);
bfd_putl32 (0, &h.section_map_size);
bfd_putl32 (source_info_size, &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))
{
free (source_info);
free (sc);
free (mod_info);
return false;
}
if (bfd_bwrite (mod_info, mod_info_size, stream) != mod_info_size)
{
free (source_info);
free (sc);
free (mod_info);
return false;
}
free (mod_info);
if (bfd_bwrite (sc, sc_size, stream) != sc_size)
{
free (source_info);
free (sc);
return false;
}
free (sc);
if (bfd_bwrite (source_info, source_info_size, stream) != source_info_size)
{
free (source_info);
return false;
}
free (source_info);
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;
}
/* 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)
{
if (!in->outsymbols)
continue;
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
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;
}
/* Populate the "/names" named stream, which contains the string table. */
static bool
populate_names_stream (bfd *stream, struct string_table *strings)
{
char int_buf[sizeof (uint32_t)];
struct string_table_header h;
uint32_t num_strings = 0, num_buckets;
struct string **buckets;
bfd_putl32 (STRING_TABLE_SIGNATURE, &h.signature);
bfd_putl32 (STRING_TABLE_VERSION, &h.version);
if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h))
return false;
bfd_putl32 (strings->strings_len, int_buf);
if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
return false;
int_buf[0] = 0;
if (bfd_bwrite (int_buf, 1, stream) != 1)
return false;
for (struct string *s = strings->strings_head; s; s = s->next)
{
if (bfd_bwrite (s->s, s->len, stream) != s->len)
return false;
if (bfd_bwrite (int_buf, 1, stream) != 1)
return false;
num_strings++;
}
num_buckets = num_strings * 2;
buckets = xmalloc (sizeof (struct string *) * num_buckets);
memset (buckets, 0, sizeof (struct string *) * num_buckets);
for (struct string *s = strings->strings_head; s; s = s->next)
{
uint32_t bucket_num = s->hash % num_buckets;
while (buckets[bucket_num])
{
bucket_num++;
if (bucket_num == num_buckets)
bucket_num = 0;
}
buckets[bucket_num] = s;
}
bfd_putl32 (num_buckets, int_buf);
if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
{
free (buckets);
return false;
}
for (unsigned int i = 0; i < num_buckets; i++)
{
if (buckets[i])
bfd_putl32 (buckets[i]->offset, int_buf);
else
bfd_putl32 (0, int_buf);
if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) !=
sizeof (uint32_t))
{
free (buckets);
return false;
}
}
free (buckets);
bfd_putl32 (num_strings, int_buf);
if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
return false;
return true;
}
/* Calculate the hash of a type_entry. */
static hashval_t
hash_type_entry (const void *p)
{
const struct type_entry *e = (const struct type_entry *) p;
uint16_t size = bfd_getl16 (e->data) + sizeof (uint16_t);
return iterative_hash (e->data, size, 0);
}
/* Compare a type_entry with a type. */
static int
eq_type_entry (const void *a, const void *b)
{
const struct type_entry *e = (const struct type_entry *) a;
uint16_t size_a = bfd_getl16 (e->data);
uint16_t size_b = bfd_getl16 (b);
if (size_a != size_b)
return 0;
return memcmp (e->data + sizeof (uint16_t),
(const uint8_t *) b + sizeof (uint16_t), size_a) == 0;
}
/* 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, *sym_rec_stream,
*publics_stream, *tpi_stream, *ipi_stream;
uint16_t section_header_stream_num, sym_rec_stream_num, publics_stream_num;
struct string_table strings;
struct types types, ids;
pdb = bfd_openw (pdb_name, "pdb");
if (!pdb)
{
einfo (_("%P: warning: cannot create PDB file: %E\n"));
return false;
}
strings.strings_head = NULL;
strings.strings_tail = NULL;
strings.strings_len = 1;
strings.hashmap = htab_create_alloc (0, hash_string_table_entry,
eq_string_table_entry, free,
xcalloc, free);
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;
}
tpi_stream = add_stream (pdb, NULL, NULL);
if (!tpi_stream)
{
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;
}
ipi_stream = add_stream (pdb, NULL, NULL);
if (!ipi_stream)
{
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;
}
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 "
"in PDB file: %E\n"));
goto end;
}
types.num_types = 0;
types.hashmap = htab_create_alloc (0, hash_type_entry, eq_type_entry,
free, xcalloc, free);
types.first = types.last = NULL;
ids.num_types = 0;
ids.hashmap = htab_create_alloc (0, hash_type_entry, eq_type_entry,
free, xcalloc, free);
ids.first = ids.last = NULL;
if (!populate_dbi_stream (dbi_stream, abfd, pdb, section_header_stream_num,
sym_rec_stream_num, publics_stream_num,
&strings, &types, &ids, sym_rec_stream, pdb_name))
{
einfo (_("%P: warning: cannot populate DBI stream "
"in PDB file: %E\n"));
htab_delete (types.hashmap);
htab_delete (ids.hashmap);
goto end;
}
if (!populate_type_stream (pdb, tpi_stream, &types))
{
einfo (_("%P: warning: cannot populate TPI stream "
"in PDB file: %E\n"));
htab_delete (types.hashmap);
htab_delete (ids.hashmap);
goto end;
}
htab_delete (types.hashmap);
if (!populate_type_stream (pdb, ipi_stream, &ids))
{
einfo (_("%P: warning: cannot populate IPI stream "
"in PDB file: %E\n"));
htab_delete (ids.hashmap);
goto end;
}
htab_delete (ids.hashmap);
add_string ("", 0, &strings);
if (!populate_names_stream (names_stream, &strings))
{
einfo (_("%P: warning: cannot populate names 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 "
"in PDB file: %E\n"));
goto end;
}
ret = true;
end:
bfd_close (pdb);
htab_delete (strings.hashmap);
return ret;
}