binutils-gdb/bfd/pdb.c
Alan Modra af92590521 Remove bfdwin.c
In commit b86d3af60f and 0ab0435fe6 I fixed SIGBUS errors found by
oss-fuzz now that --with-mmap defaults to enabled.  It turns out there
are further problems with the aout mmap code: aout_read_minisymbols
returns the external symbol array, which is later freed by nm.c.  If
the array is mmaped you can't free it.  Now this could be fixed by
making aout minisymbols an array of pointers, but I figure there's not
much point in expending effort on that.  So delete the aout mmap
support along with bfdwin.c and get_section_contents_in_window.
2024-04-11 17:05:16 +09:30

826 lines
19 KiB
C

/* BFD back-end for PDB Multi-Stream Format archives.
Copyright (C) 2022-2024 Free Software Foundation, Inc.
This file is part of BFD, the Binary File Descriptor library.
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. */
/* This describes the MSF file archive format, which is used for the
PDB debug info generated by MSVC. See https://llvm.org/docs/PDB/MsfFile.html
for a full description of the format. */
#include "sysdep.h"
#include "bfd.h"
#include "libbfd.h"
/* "Microsoft C/C++ MSF 7.00\r\n\x1a\x44\x53\0\0\0" */
static const uint8_t pdb_magic[] =
{ 0x4d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66,
0x74, 0x20, 0x43, 0x2f, 0x43, 0x2b, 0x2b, 0x20,
0x4d, 0x53, 0x46, 0x20, 0x37, 0x2e, 0x30, 0x30,
0x0d, 0x0a, 0x1a, 0x44, 0x53, 0x00, 0x00, 0x00 };
#define arch_eltdata(bfd) ((struct areltdata *) ((bfd)->arelt_data))
static bfd_cleanup
pdb_archive_p (bfd *abfd)
{
int ret;
char magic[sizeof (pdb_magic)];
ret = bfd_read (magic, sizeof (magic), abfd);
if (ret != sizeof (magic))
{
bfd_set_error (bfd_error_wrong_format);
return NULL;
}
if (memcmp (magic, pdb_magic, sizeof (magic)))
{
bfd_set_error (bfd_error_wrong_format);
return NULL;
}
void *tdata = bfd_zalloc (abfd, sizeof (struct artdata));
if (tdata == NULL)
return NULL;
bfd_ardata (abfd) = tdata;
return _bfd_no_cleanup;
}
static bfd *
pdb_get_elt_at_index (bfd *abfd, symindex sym_index)
{
char int_buf[sizeof (uint32_t)];
uint32_t block_size, block_map_addr, block, num_files;
uint32_t first_dir_block, dir_offset, file_size, block_off, left;
char name[10];
bfd *file;
char *buf;
/* Get block_size. */
if (bfd_seek (abfd, sizeof (pdb_magic), SEEK_SET))
return NULL;
if (bfd_read (int_buf, sizeof (uint32_t), abfd) != sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
return NULL;
}
block_size = bfd_getl32 (int_buf);
if ((block_size & -block_size) != block_size
|| block_size < 512
|| block_size > 4096)
{
bfd_set_error (bfd_error_malformed_archive);
return NULL;
}
/* Get block_map_addr. */
if (bfd_seek (abfd, 4 * sizeof (uint32_t), SEEK_CUR))
return NULL;
if (bfd_read (int_buf, sizeof (uint32_t), abfd) != sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
return NULL;
}
block_map_addr = bfd_getl32 (int_buf);
/* Get num_files. */
if (bfd_seek (abfd, block_map_addr * block_size, SEEK_SET))
return NULL;
if (bfd_read (int_buf, sizeof (uint32_t), abfd) != sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
return NULL;
}
first_dir_block = bfd_getl32 (int_buf);
if (bfd_seek (abfd, first_dir_block * block_size, SEEK_SET))
return NULL;
if (bfd_read (int_buf, sizeof (uint32_t), abfd) != sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
return NULL;
}
num_files = bfd_getl32 (int_buf);
if (sym_index >= num_files)
{
bfd_set_error (bfd_error_no_more_archived_files);
return NULL;
}
/* Read file size. */
dir_offset = sizeof (uint32_t) * (sym_index + 1);
if (dir_offset >= block_size)
{
uint32_t block_map_addr_off;
block_map_addr_off = ((dir_offset / block_size) * sizeof (uint32_t));
if (bfd_seek (abfd, (block_map_addr * block_size) + block_map_addr_off,
SEEK_SET))
return NULL;
if (bfd_read (int_buf, sizeof (uint32_t), abfd) != sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
return NULL;
}
block = bfd_getl32 (int_buf);
}
else
{
block = first_dir_block;
}
if (bfd_seek (abfd, (block * block_size) + (dir_offset % block_size),
SEEK_SET))
return NULL;
if (bfd_read (int_buf, sizeof (uint32_t), abfd) != sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
return NULL;
}
file_size = bfd_getl32 (int_buf);
/* Undocumented? Seen on PDBs created by MSVC 2022. */
if (file_size == 0xffffffff)
file_size = 0;
/* Create BFD. */
/* Four hex digits is enough - even though MSF allows for 32 bits, the
PDB format itself only uses 16 bits for stream numbers. */
sprintf (name, "%04lx", sym_index);
file = bfd_create (name, abfd);
if (!file)
return NULL;
if (!bfd_make_writable (file))
goto fail;
file->arelt_data =
(struct areltdata *) bfd_zmalloc (sizeof (struct areltdata));
if (!file->arelt_data)
goto fail;
arch_eltdata (file)->parsed_size = file_size;
arch_eltdata (file)->key = sym_index;
if (file_size == 0)
return file;
block_off = 0;
/* Sum number of blocks in previous files. */
if (sym_index != 0)
{
dir_offset = sizeof (uint32_t);
if (bfd_seek (abfd, (first_dir_block * block_size) + sizeof (uint32_t),
SEEK_SET))
goto fail;
for (symindex i = 0; i < sym_index; i++)
{
uint32_t size, num_blocks;
if ((dir_offset % block_size) == 0)
{
uint32_t block_map_addr_off;
block_map_addr_off =
((dir_offset / block_size) * sizeof (uint32_t));
if (bfd_seek
(abfd, (block_map_addr * block_size) + block_map_addr_off,
SEEK_SET))
goto fail;
if (bfd_read (int_buf, sizeof (uint32_t), abfd) !=
sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
goto fail;
}
block = bfd_getl32 (int_buf);
if (bfd_seek (abfd, block * block_size, SEEK_SET))
goto fail;
}
if (bfd_read (int_buf, sizeof (uint32_t), abfd) !=
sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
goto fail;
}
size = bfd_getl32 (int_buf);
if (size == 0xffffffff)
size = 0;
num_blocks = (size + block_size - 1) / block_size;
block_off += num_blocks;
dir_offset += sizeof (uint32_t);
}
}
/* Read blocks, and write into new BFD. */
dir_offset = sizeof (uint32_t) * (num_files + block_off + 1);
if (dir_offset >= block_size)
{
uint32_t block_map_addr_off;
block_map_addr_off = ((dir_offset / block_size) * sizeof (uint32_t));
if (bfd_seek (abfd, (block_map_addr * block_size) + block_map_addr_off,
SEEK_SET))
goto fail;
if (bfd_read (int_buf, sizeof (uint32_t), abfd) != sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
goto fail;
}
block = bfd_getl32 (int_buf);
}
else
{
block = first_dir_block;
}
buf = bfd_malloc (block_size);
if (!buf)
goto fail;
left = file_size;
do
{
uint32_t file_block, to_read;
if ((dir_offset % block_size) == 0 && left != file_size)
{
uint32_t block_map_addr_off;
block_map_addr_off =
((dir_offset / block_size) * sizeof (uint32_t));
if (bfd_seek
(abfd, (block_map_addr * block_size) + block_map_addr_off,
SEEK_SET))
goto fail2;
if (bfd_read (int_buf, sizeof (uint32_t), abfd) !=
sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
goto fail2;
}
block = bfd_getl32 (int_buf);
}
if (bfd_seek (abfd, (block * block_size) + (dir_offset % block_size),
SEEK_SET))
goto fail2;
if (bfd_read (int_buf, sizeof (uint32_t), abfd) != sizeof (uint32_t))
{
bfd_set_error (bfd_error_malformed_archive);
goto fail2;
}
file_block = bfd_getl32 (int_buf);
if (bfd_seek (abfd, file_block * block_size, SEEK_SET))
goto fail2;
to_read = left > block_size ? block_size : left;
if (bfd_read (buf, to_read, abfd) != to_read)
{
bfd_set_error (bfd_error_malformed_archive);
goto fail2;
}
if (bfd_write (buf, to_read, file) != to_read)
goto fail2;
if (left > block_size)
left -= block_size;
else
break;
dir_offset += sizeof (uint32_t);
}
while (left > 0);
free (buf);
return file;
fail2:
free (buf);
fail:
bfd_close (file);
return NULL;
}
static bfd *
pdb_openr_next_archived_file (bfd *archive, bfd *last_file)
{
if (!last_file)
return pdb_get_elt_at_index (archive, 0);
else
return pdb_get_elt_at_index (archive, arch_eltdata (last_file)->key + 1);
}
static int
pdb_generic_stat_arch_elt (bfd *abfd, struct stat *buf)
{
buf->st_mtime = 0;
buf->st_uid = 0;
buf->st_gid = 0;
buf->st_mode = 0644;
buf->st_size = arch_eltdata (abfd)->parsed_size;
return 0;
}
static uint32_t
pdb_allocate_block (uint32_t *num_blocks, uint32_t block_size)
{
uint32_t block;
block = *num_blocks;
(*num_blocks)++;
/* If new interval, skip two blocks for free space map. */
if ((block % block_size) == 1)
{
block += 2;
(*num_blocks) += 2;
}
return block;
}
static bool
pdb_write_directory (bfd *abfd, uint32_t block_size, uint32_t num_files,
uint32_t block_map_addr, uint32_t * num_blocks)
{
char tmp[sizeof (uint32_t)];
uint32_t block, left, block_map_off;
bfd *arelt;
char *buf;
/* Allocate first block for directory. */
block = pdb_allocate_block (num_blocks, block_size);
left = block_size;
/* Write allocated block no. at beginning of block map. */
if (bfd_seek (abfd, block_map_addr * block_size, SEEK_SET))
return false;
bfd_putl32 (block, tmp);
if (bfd_write (tmp, sizeof (uint32_t), abfd) != sizeof (uint32_t))
return false;
block_map_off = sizeof (uint32_t);
/* Write num_files at beginning of directory. */
if (bfd_seek (abfd, block * block_size, SEEK_SET))
return false;
bfd_putl32 (num_files, tmp);
if (bfd_write (tmp, sizeof (uint32_t), abfd) != sizeof (uint32_t))
return false;
left -= sizeof (uint32_t);
/* Write file sizes. */
arelt = abfd->archive_head;
while (arelt)
{
if (left == 0)
{
if (block_map_off == block_size) /* Too many blocks. */
{
bfd_set_error (bfd_error_invalid_operation);
return false;
}
block = pdb_allocate_block (num_blocks, block_size);
left = block_size;
if (bfd_seek
(abfd, (block_map_addr * block_size) + block_map_off, SEEK_SET))
return false;
bfd_putl32 (block, tmp);
if (bfd_write (tmp, sizeof (uint32_t), abfd) != sizeof (uint32_t))
return false;
block_map_off += sizeof (uint32_t);
if (bfd_seek (abfd, block * block_size, SEEK_SET))
return false;
}
bfd_putl32 (bfd_get_size (arelt), tmp);
if (bfd_write (tmp, sizeof (uint32_t), abfd) != sizeof (uint32_t))
return false;
left -= sizeof (uint32_t);
arelt = arelt->archive_next;
}
/* Write blocks. */
buf = bfd_malloc (block_size);
if (!buf)
return false;
arelt = abfd->archive_head;
while (arelt)
{
ufile_ptr size = bfd_get_size (arelt);
uint32_t req_blocks = (size + block_size - 1) / block_size;
if (bfd_seek (arelt, 0, SEEK_SET))
{
free (buf);
return false;
}
for (uint32_t i = 0; i < req_blocks; i++)
{
uint32_t file_block, to_read;
if (left == 0)
{
if (block_map_off == block_size) /* Too many blocks. */
{
bfd_set_error (bfd_error_invalid_operation);
free (buf);
return false;
}
block = pdb_allocate_block (num_blocks, block_size);
left = block_size;
if (bfd_seek
(abfd, (block_map_addr * block_size) + block_map_off,
SEEK_SET))
{
free (buf);
return false;
}
bfd_putl32 (block, tmp);
if (bfd_write (tmp, sizeof (uint32_t), abfd) !=
sizeof (uint32_t))
{
free (buf);
return false;
}
block_map_off += sizeof (uint32_t);
if (bfd_seek (abfd, block * block_size, SEEK_SET))
{
free (buf);
return false;
}
}
/* Allocate block and write number into directory. */
file_block = pdb_allocate_block (num_blocks, block_size);
bfd_putl32 (file_block, tmp);
if (bfd_write (tmp, sizeof (uint32_t), abfd) != sizeof (uint32_t))
{
free (buf);
return false;
}
left -= sizeof (uint32_t);
/* Read file contents into buffer. */
to_read = size > block_size ? block_size : size;
if (bfd_read (buf, to_read, arelt) != to_read)
{
free (buf);
return false;
}
size -= to_read;
if (to_read < block_size)
memset (buf + to_read, 0, block_size - to_read);
if (bfd_seek (abfd, file_block * block_size, SEEK_SET))
{
free (buf);
return false;
}
/* Write file contents into allocated block. */
if (bfd_write (buf, block_size, abfd) != block_size)
{
free (buf);
return false;
}
if (bfd_seek
(abfd, (block * block_size) + block_size - left, SEEK_SET))
{
free (buf);
return false;
}
}
arelt = arelt->archive_next;
}
memset (buf, 0, left);
if (bfd_write (buf, left, abfd) != left)
{
free (buf);
return false;
}
free (buf);
return true;
}
static bool
pdb_write_bitmap (bfd *abfd, uint32_t block_size, uint32_t num_blocks)
{
char *buf;
uint32_t num_intervals = (num_blocks + block_size - 1) / block_size;
buf = bfd_malloc (block_size);
if (!buf)
return false;
num_blocks--; /* Superblock not included. */
for (uint32_t i = 0; i < num_intervals; i++)
{
if (bfd_seek (abfd, ((i * block_size) + 1) * block_size, SEEK_SET))
{
free (buf);
return false;
}
/* All of our blocks are contiguous, making our free block map simple.
0 = used, 1 = free. */
if (num_blocks >= 8)
memset (buf, 0,
(num_blocks / 8) >
block_size ? block_size : (num_blocks / 8));
if (num_blocks < block_size * 8)
{
unsigned int off = num_blocks / 8;
if (num_blocks % 8)
{
buf[off] = (1 << (8 - (num_blocks % 8))) - 1;
off++;
}
if (off < block_size)
memset (buf + off, 0xff, block_size - off);
}
if (num_blocks < block_size * 8)
num_blocks = 0;
else
num_blocks -= block_size * 8;
if (bfd_write (buf, block_size, abfd) != block_size)
return false;
}
free (buf);
return true;
}
static bool
pdb_write_contents (bfd *abfd)
{
char tmp[sizeof (uint32_t)];
const uint32_t block_size = 0x400;
uint32_t block_map_addr;
uint32_t num_blocks;
uint32_t num_files = 0;
uint32_t num_directory_bytes = sizeof (uint32_t);
bfd *arelt;
if (bfd_write (pdb_magic, sizeof (pdb_magic), abfd) != sizeof (pdb_magic))
return false;
bfd_putl32 (block_size, tmp);
if (bfd_write (tmp, sizeof (uint32_t), abfd) != sizeof (uint32_t))
return false;
bfd_putl32 (1, tmp); /* Free block map block (always either 1 or 2). */
if (bfd_write (tmp, sizeof (uint32_t), abfd) != sizeof (uint32_t))
return false;
arelt = abfd->archive_head;
while (arelt)
{
uint32_t blocks_required =
(bfd_get_size (arelt) + block_size - 1) / block_size;
num_directory_bytes += sizeof (uint32_t); /* Size. */
num_directory_bytes += blocks_required * sizeof (uint32_t); /* Blocks. */
num_files++;
arelt = arelt->archive_next;
}
/* Superblock plus two bitmap blocks. */
num_blocks = 3;
/* Skip num_blocks for now. */
if (bfd_seek (abfd, sizeof (uint32_t), SEEK_CUR))
return false;
bfd_putl32 (num_directory_bytes, tmp);
if (bfd_write (tmp, sizeof (uint32_t), abfd) != sizeof (uint32_t))
return false;
/* Skip unknown uint32_t (always 0?). */
if (bfd_seek (abfd, sizeof (uint32_t), SEEK_CUR))
return false;
block_map_addr = pdb_allocate_block (&num_blocks, block_size);
bfd_putl32 (block_map_addr, tmp);
if (bfd_write (tmp, sizeof (uint32_t), abfd) != sizeof (uint32_t))
return false;
if (!pdb_write_directory
(abfd, block_size, num_files, block_map_addr, &num_blocks))
return false;
if (!pdb_write_bitmap (abfd, block_size, num_blocks))
return false;
/* Write num_blocks now we know it. */
if (bfd_seek
(abfd, sizeof (pdb_magic) + sizeof (uint32_t) + sizeof (uint32_t),
SEEK_SET))
return false;
bfd_putl32 (num_blocks, tmp);
if (bfd_write (tmp, sizeof (uint32_t), abfd) != sizeof (uint32_t))
return false;
return true;
}
#define pdb_bfd_free_cached_info _bfd_generic_bfd_free_cached_info
#define pdb_new_section_hook _bfd_generic_new_section_hook
#define pdb_get_section_contents _bfd_generic_get_section_contents
#define pdb_close_and_cleanup _bfd_generic_close_and_cleanup
#define pdb_slurp_armap _bfd_noarchive_slurp_armap
#define pdb_slurp_extended_name_table _bfd_noarchive_slurp_extended_name_table
#define pdb_construct_extended_name_table _bfd_noarchive_construct_extended_name_table
#define pdb_truncate_arname _bfd_noarchive_truncate_arname
#define pdb_write_armap _bfd_noarchive_write_armap
#define pdb_read_ar_hdr _bfd_noarchive_read_ar_hdr
#define pdb_write_ar_hdr _bfd_noarchive_write_ar_hdr
#define pdb_update_armap_timestamp _bfd_noarchive_update_armap_timestamp
const bfd_target pdb_vec =
{
"pdb",
bfd_target_unknown_flavour,
BFD_ENDIAN_LITTLE, /* target byte order */
BFD_ENDIAN_LITTLE, /* target headers byte order */
0, /* object flags */
0, /* section flags */
0, /* leading underscore */
' ', /* ar_pad_char */
16, /* ar_max_namelen */
0, /* match priority. */
TARGET_KEEP_UNUSED_SECTION_SYMBOLS, /* keep unused section symbols. */
bfd_getl64, bfd_getl_signed_64, bfd_putl64,
bfd_getl32, bfd_getl_signed_32, bfd_putl32,
bfd_getl16, bfd_getl_signed_16, bfd_putl16, /* Data. */
bfd_getl64, bfd_getl_signed_64, bfd_putl64,
bfd_getl32, bfd_getl_signed_32, bfd_putl32,
bfd_getl16, bfd_getl_signed_16, bfd_putl16, /* Hdrs. */
{ /* bfd_check_format */
_bfd_dummy_target,
_bfd_dummy_target,
pdb_archive_p,
_bfd_dummy_target
},
{ /* bfd_set_format */
_bfd_bool_bfd_false_error,
_bfd_bool_bfd_false_error,
_bfd_bool_bfd_true,
_bfd_bool_bfd_false_error
},
{ /* bfd_write_contents */
_bfd_bool_bfd_true,
_bfd_bool_bfd_false_error,
pdb_write_contents,
_bfd_bool_bfd_false_error
},
BFD_JUMP_TABLE_GENERIC (pdb),
BFD_JUMP_TABLE_COPY (_bfd_generic),
BFD_JUMP_TABLE_CORE (_bfd_nocore),
BFD_JUMP_TABLE_ARCHIVE (pdb),
BFD_JUMP_TABLE_SYMBOLS (_bfd_nosymbols),
BFD_JUMP_TABLE_RELOCS (_bfd_norelocs),
BFD_JUMP_TABLE_WRITE (_bfd_generic),
BFD_JUMP_TABLE_LINK (_bfd_nolink),
BFD_JUMP_TABLE_DYNAMIC (_bfd_nodynamic),
NULL,
NULL
};