ld: Add module information substream to PDB files

This commit is contained in:
Mark Harmstone 2022-11-03 02:46:04 +00:00
parent 76a95facf1
commit 5967ca921c
5 changed files with 433 additions and 50 deletions

214
ld/pdb.c
View File

@ -383,6 +383,196 @@ get_arch_number (bfd *abfd)
return IMAGE_FILE_MACHINE_I386;
}
/* Populate the module stream, which consists of the transformed .debug$S
data for each object file. */
static bool
populate_module_stream (bfd *stream, uint32_t *sym_byte_size)
{
uint8_t int_buf[sizeof (uint32_t)];
*sym_byte_size = sizeof (uint32_t);
/* Write the signature. */
bfd_putl32 (CV_SIGNATURE_C13, int_buf);
if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
return false;
/* Write the global refs size. */
bfd_putl32 (0, int_buf);
if (bfd_bwrite (int_buf, sizeof (uint32_t), stream) != sizeof (uint32_t))
return false;
return true;
}
/* Create the module info substream within the DBI. */
static bool
create_module_info_substream (bfd *abfd, bfd *pdb, void **data,
uint32_t *size)
{
uint8_t *ptr;
static const char linker_fn[] = "* Linker *";
*size = 0;
for (bfd *in = coff_data (abfd)->link_info->input_bfds; in;
in = in->link.next)
{
size_t len = sizeof (struct module_info);
if (!strcmp (bfd_get_filename (in), "dll stuff"))
{
len += sizeof (linker_fn); /* Object name. */
len++; /* Empty module name. */
}
else if (in->my_archive)
{
char *name = lrealpath (bfd_get_filename (in));
len += strlen (name) + 1; /* Object name. */
free (name);
name = lrealpath (bfd_get_filename (in->my_archive));
len += strlen (name) + 1; /* Archive name. */
free (name);
}
else
{
char *name = lrealpath (bfd_get_filename (in));
size_t name_len = strlen (name) + 1;
len += name_len; /* Object name. */
len += name_len; /* And again as the archive name. */
free (name);
}
if (len % 4)
len += 4 - (len % 4);
*size += len;
}
*data = xmalloc (*size);
ptr = *data;
for (bfd *in = coff_data (abfd)->link_info->input_bfds; in;
in = in->link.next)
{
struct module_info *mod = (struct module_info *) ptr;
uint16_t stream_num;
bfd *stream;
uint32_t sym_byte_size;
uint8_t *start = ptr;
stream = add_stream (pdb, NULL, &stream_num);
if (!stream)
{
free (*data);
return false;
}
if (!populate_module_stream (stream, &sym_byte_size))
{
free (*data);
return false;
}
bfd_putl32 (0, &mod->unused1);
/* These are dummy values - MSVC copies the first section contribution
entry here, but doesn't seem to use it for anything. */
bfd_putl16 (0xffff, &mod->sc.section);
bfd_putl16 (0, &mod->sc.padding1);
bfd_putl32 (0, &mod->sc.offset);
bfd_putl32 (0xffffffff, &mod->sc.size);
bfd_putl32 (0, &mod->sc.characteristics);
bfd_putl16 (0xffff, &mod->sc.module_index);
bfd_putl16 (0, &mod->sc.padding2);
bfd_putl32 (0, &mod->sc.data_crc);
bfd_putl32 (0, &mod->sc.reloc_crc);
bfd_putl16 (0, &mod->flags);
bfd_putl16 (stream_num, &mod->module_sym_stream);
bfd_putl32 (sym_byte_size, &mod->sym_byte_size);
bfd_putl32 (0, &mod->c11_byte_size);
bfd_putl32 (0, &mod->c13_byte_size);
bfd_putl16 (0, &mod->source_file_count);
bfd_putl16 (0, &mod->padding);
bfd_putl32 (0, &mod->unused2);
bfd_putl32 (0, &mod->source_file_name_index);
bfd_putl32 (0, &mod->pdb_file_path_name_index);
ptr += sizeof (struct module_info);
if (!strcmp (bfd_get_filename (in), "dll stuff"))
{
/* Object name. */
memcpy (ptr, linker_fn, sizeof (linker_fn));
ptr += sizeof (linker_fn);
/* Empty module name. */
*ptr = 0;
ptr++;
}
else if (in->my_archive)
{
char *name = lrealpath (bfd_get_filename (in));
size_t name_len = strlen (name) + 1;
/* Object name. */
memcpy (ptr, name, name_len);
ptr += name_len;
free (name);
name = lrealpath (bfd_get_filename (in->my_archive));
name_len = strlen (name) + 1;
/* Archive name. */
memcpy (ptr, name, name_len);
ptr += name_len;
free (name);
}
else
{
char *name = lrealpath (bfd_get_filename (in));
size_t name_len = strlen (name) + 1;
/* Object name. */
memcpy (ptr, name, name_len);
ptr += name_len;
/* Object name again as archive name. */
memcpy (ptr, name, name_len);
ptr += name_len;
free (name);
}
/* Pad to next four-byte boundary. */
if ((ptr - start) % 4)
{
memset (ptr, 0, 4 - ((ptr - start) % 4));
ptr += 4 - ((ptr - start) % 4);
}
}
return true;
}
/* Return the index of a given output section. */
static uint16_t
find_section_number (bfd *abfd, asection *sect)
@ -404,13 +594,18 @@ find_section_number (bfd *abfd, asection *sect)
/* Stream 4 is the debug information (DBI) stream. */
static bool
populate_dbi_stream (bfd *stream, bfd *abfd,
populate_dbi_stream (bfd *stream, bfd *abfd, bfd *pdb,
uint16_t section_header_stream_num,
uint16_t sym_rec_stream_num,
uint16_t publics_stream_num)
{
struct pdb_dbi_stream_header h;
struct optional_dbg_header opt;
void *mod_info;
uint32_t mod_info_size;
if (!create_module_info_substream (abfd, pdb, &mod_info, &mod_info_size))
return false;
bfd_putl32 (0xffffffff, &h.version_signature);
bfd_putl32 (DBI_STREAM_VERSION_70, &h.version_header);
@ -421,7 +616,7 @@ populate_dbi_stream (bfd *stream, bfd *abfd,
bfd_putl16 (0, &h.pdb_dll_version);
bfd_putl16 (sym_rec_stream_num, &h.sym_record_stream);
bfd_putl16 (0, &h.pdb_dll_rbld);
bfd_putl32 (0, &h.mod_info_size);
bfd_putl32 (mod_info_size, &h.mod_info_size);
bfd_putl32 (0, &h.section_contribution_size);
bfd_putl32 (0, &h.section_map_size);
bfd_putl32 (0, &h.source_info_size);
@ -434,7 +629,18 @@ populate_dbi_stream (bfd *stream, bfd *abfd,
bfd_putl32 (0, &h.padding);
if (bfd_bwrite (&h, sizeof (h), stream) != sizeof (h))
return false;
{
free (mod_info);
return false;
}
if (bfd_bwrite (mod_info, mod_info_size, stream) != mod_info_size)
{
free (mod_info);
return false;
}
free (mod_info);
bfd_putl16 (0xffff, &opt.fpo_stream);
bfd_putl16 (0xffff, &opt.exception_stream);
@ -888,7 +1094,7 @@ create_pdb_file (bfd *abfd, const char *pdb_name, const unsigned char *guid)
goto end;
}
if (!populate_dbi_stream (dbi_stream, abfd, section_header_stream_num,
if (!populate_dbi_stream (dbi_stream, abfd, pdb, section_header_stream_num,
sym_rec_stream_num, publics_stream_num))
{
einfo (_("%P: warning: cannot populate DBI stream "

View File

@ -153,6 +153,39 @@ struct optional_dbg_header
uint16_t orig_section_header_stream;
};
#define CV_SIGNATURE_C13 4
/* SC in dbicommon.h */
struct section_contribution
{
uint16_t section;
uint16_t padding1;
uint32_t offset;
uint32_t size;
uint32_t characteristics;
uint16_t module_index;
uint16_t padding2;
uint32_t data_crc;
uint32_t reloc_crc;
};
/* MODI_60_Persist in dbi.h */
struct module_info
{
uint32_t unused1;
struct section_contribution sc;
uint16_t flags;
uint16_t module_sym_stream;
uint32_t sym_byte_size;
uint32_t c11_byte_size;
uint32_t c13_byte_size;
uint16_t source_file_count;
uint16_t padding;
uint32_t unused2;
uint32_t source_file_name_index;
uint32_t pdb_file_path_name_index;
};
extern bool create_pdb_file (bfd *, const char *, const unsigned char *);
#endif

View File

@ -494,55 +494,189 @@ proc check_publics_stream { pdb } {
return 1
}
if ![ld_assemble $as $srcdir/$subdir/pdb1.s tmpdir/pdb1.o] {
unsupported "Build pdb1.o"
return
proc test1 { } {
global as
global ld
global srcdir
global subdir
if ![ld_assemble $as $srcdir/$subdir/pdb1.s tmpdir/pdb1.o] {
unsupported "Build pdb1.o"
return
}
if ![ld_link $ld "tmpdir/pdb1.exe" "--pdb=tmpdir/pdb1.pdb --gc-sections -e foo tmpdir/pdb1.o"] {
fail "Could not create a PE image with a PDB file"
return
}
if ![string equal [get_pdb_name "tmpdir/pdb1.exe"] "pdb1.pdb"] {
fail "PDB filename not found in CodeView debug info"
return
}
pass "PDB filename present in CodeView debug info"
if [check_pdb_info_stream tmpdir/pdb1.pdb [get_pdb_guid "tmpdir/pdb1.exe"]] {
pass "Valid PDB info stream"
} else {
fail "Invalid PDB info stream"
}
if [check_type_stream tmpdir/pdb1.pdb "0002"] {
pass "Valid TPI stream"
} else {
fail "Invalid TPI stream"
}
if [check_type_stream tmpdir/pdb1.pdb "0004"] {
pass "Valid IPI stream"
} else {
fail "Invalid IPI stream"
}
if [check_dbi_stream tmpdir/pdb1.pdb] {
pass "Valid DBI stream"
} else {
fail "Invalid DBI stream"
}
if [check_section_stream tmpdir/pdb1.exe tmpdir/pdb1.pdb] {
pass "Valid section stream"
} else {
fail "Invalid section stream"
}
if [check_publics_stream tmpdir/pdb1.pdb] {
pass "Valid publics stream"
} else {
fail "Invalid publics stream"
}
}
if ![ld_link $ld "tmpdir/pdb1.exe" "--pdb=tmpdir/pdb1.pdb --gc-sections -e foo tmpdir/pdb1.o"] {
fail "Could not create a PE image with a PDB file"
return
proc test_mod_info { mod_info } {
# check filenames in mod_info
set off 64
set obj1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
incr off [expr [string length $obj1] + 1]
set ar1 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
incr off [expr [string length $ar1] + 1]
if [string match "*pdb2a.o" $obj1] {
pass "Correct name for first object file"
} else {
fail "Incorrect name for first object file"
}
if [string equal $obj1 $ar1] {
pass "Correct archive name for first object file"
} else {
fail "Incorrect archive name for first object file"
}
if { [expr $off % 4] != 0 } {
set off [expr $off + 4 - ($off % 4)]
}
incr off 64
set obj2 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
incr off [expr [string length $obj2] + 1]
set ar2 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
incr off [expr [string length $ar2] + 1]
if [string match "*pdb2b.o" $obj2] {
pass "Correct name for second object file"
} else {
fail "Incorrect name for second object file"
}
if [string match "*pdb2b.a" $ar2] {
pass "Correct archive name for second object file"
} else {
fail "Incorrect archive name for second object file"
}
if { [expr $off % 4] != 0 } {
set off [expr $off + 4 - ($off % 4)]
}
incr off 64
set obj3 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
incr off [expr [string length $obj3] + 1]
set ar3 [string range $mod_info $off [expr [string first \000 $mod_info $off] - 1]]
incr off [expr [string length $ar3] + 1]
if [string equal $obj3 "* Linker *"] {
pass "Correct name for dummy object file"
} else {
fail "Incorrect name for dummy object file"
}
if [string equal $ar3 ""] {
pass "Correct archive name for dummy object file"
} else {
fail "Incorrect archive name for dummy object file"
}
}
if ![string equal [get_pdb_name "tmpdir/pdb1.exe"] "pdb1.pdb"] {
fail "PDB filename not found in CodeView debug info"
return
proc test2 { } {
global as
global ar
global ld
global srcdir
global subdir
if ![ld_assemble $as $srcdir/$subdir/pdb2a.s tmpdir/pdb2a.o] {
unsupported "Build pdb2a.o"
return
}
if ![ld_assemble $as $srcdir/$subdir/pdb2b.s tmpdir/pdb2b.o] {
unsupported "Build pdb2b.o"
return
}
set exec_output [run_host_cmd "$ar" "cr tmpdir/pdb2b.a tmpdir/pdb2b.o"]
if ![string match "" $exec_output] {
unsupported "Create pdb2b.a"
return
}
if ![ld_link $ld "tmpdir/pdb2.exe" "--pdb=tmpdir/pdb2.pdb -e foo tmpdir/pdb2a.o tmpdir/pdb2b.a"] {
unsupported "Create PE image with PDB file"
return
}
set exec_output [run_host_cmd "$ar" "x --output tmpdir tmpdir/pdb2.pdb 0003"]
if ![string match "" $exec_output] {
return 0
}
set fi [open tmpdir/0003]
fconfigure $fi -translation binary
seek $fi 24
set data [read $fi 4]
binary scan $data i mod_info_size
seek $fi 36 current
set mod_info [read $fi $mod_info_size]
close $fi
test_mod_info $mod_info
}
pass "PDB filename present in CodeView debug info"
if [check_pdb_info_stream tmpdir/pdb1.pdb [get_pdb_guid "tmpdir/pdb1.exe"]] {
pass "Valid PDB info stream"
} else {
fail "Invalid PDB info stream"
}
if [check_type_stream tmpdir/pdb1.pdb "0002"] {
pass "Valid TPI stream"
} else {
fail "Invalid TPI stream"
}
if [check_type_stream tmpdir/pdb1.pdb "0004"] {
pass "Valid IPI stream"
} else {
fail "Invalid IPI stream"
}
if [check_dbi_stream tmpdir/pdb1.pdb] {
pass "Valid DBI stream"
} else {
fail "Invalid DBI stream"
}
if [check_section_stream tmpdir/pdb1.exe tmpdir/pdb1.pdb] {
pass "Valid section stream"
} else {
fail "Invalid section stream"
}
if [check_publics_stream tmpdir/pdb1.pdb] {
pass "Valid publics stream"
} else {
fail "Invalid publics stream"
}
test1
test2

View File

@ -0,0 +1,5 @@
.text
.global foo
foo:
.secrel32 bar

View File

@ -0,0 +1,5 @@
.text
.global bar
bar:
.long 0x12345678