#include "bfd.h"
#include "bucomm.h"
#include "libiberty.h"
#include "windres.h"
#include <assert.h>
#include "coff/internal.h"
#include "libcoff.h"
struct coff_file_info
{
const char *filename;
const bfd_byte *data;
const bfd_byte *data_end;
bfd_vma secaddr;
int big_endian;
};
struct extern_res_directory
{
bfd_byte characteristics[4];
bfd_byte time[4];
bfd_byte major[2];
bfd_byte minor[2];
bfd_byte name_count[2];
bfd_byte id_count[2];
};
struct extern_res_entry
{
bfd_byte name[4];
bfd_byte rva[4];
};
struct extern_res_data
{
bfd_byte rva[4];
bfd_byte size[4];
bfd_byte codepage[4];
bfd_byte reserved[4];
};
#define getfi_16(fi, s) ((fi)->big_endian ? bfd_getb16 (s) : bfd_getl16 (s))
#define getfi_32(fi, s) ((fi)->big_endian ? bfd_getb32 (s) : bfd_getl32 (s))
static void overrun (const struct coff_file_info *, const char *);
static struct res_directory *read_coff_res_dir
(const bfd_byte *, const struct coff_file_info *,
const struct res_id *, int);
static struct res_resource *read_coff_data_entry
(const bfd_byte *, const struct coff_file_info *, const struct res_id *);
struct res_directory *
read_coff_rsrc (const char *filename, const char *target)
{
bfd *abfd;
char **matching;
asection *sec;
bfd_size_type size;
bfd_byte *data;
struct coff_file_info finfo;
if (filename == NULL)
fatal (_("filename required for COFF input"));
abfd = bfd_openr (filename, target);
if (abfd == NULL)
bfd_fatal (filename);
if (! bfd_check_format_matches (abfd, bfd_object, &matching))
{
bfd_nonfatal (bfd_get_filename (abfd));
if (bfd_get_error () == bfd_error_file_ambiguously_recognized)
list_matching_formats (matching);
xexit (1);
}
sec = bfd_get_section_by_name (abfd, ".rsrc");
if (sec == NULL)
{
fatal (_("%s: no resource section"), filename);
}
size = bfd_section_size (abfd, sec);
data = (bfd_byte *) res_alloc (size);
if (! bfd_get_section_contents (abfd, sec, data, 0, size))
bfd_fatal (_("can't read resource section"));
finfo.filename = filename;
finfo.data = data;
finfo.data_end = data + size;
finfo.secaddr = (bfd_get_section_vma (abfd, sec)
- pe_data (abfd)->pe_opthdr.ImageBase);
finfo.big_endian = bfd_big_endian (abfd);
bfd_close (abfd);
return read_coff_res_dir (data, &finfo, (const struct res_id *) NULL, 0);
}
static void
overrun (const struct coff_file_info *finfo, const char *msg)
{
fatal (_("%s: %s: address out of bounds"), finfo->filename, msg);
}
static struct res_directory *
read_coff_res_dir (const bfd_byte *data, const struct coff_file_info *finfo,
const struct res_id *type, int level)
{
const struct extern_res_directory *erd;
struct res_directory *rd;
int name_count, id_count, i;
struct res_entry **pp;
const struct extern_res_entry *ere;
if ((size_t) (finfo->data_end - data) < sizeof (struct extern_res_directory))
overrun (finfo, _("directory"));
erd = (const struct extern_res_directory *) data;
rd = (struct res_directory *) res_alloc (sizeof *rd);
rd->characteristics = getfi_32 (finfo, erd->characteristics);
rd->time = getfi_32 (finfo, erd->time);
rd->major = getfi_16 (finfo, erd->major);
rd->minor = getfi_16 (finfo, erd->minor);
rd->entries = NULL;
name_count = getfi_16 (finfo, erd->name_count);
id_count = getfi_16 (finfo, erd->id_count);
pp = &rd->entries;
ere = (const struct extern_res_entry *) (erd + 1);
for (i = 0; i < name_count; i++, ere++)
{
unsigned long name, rva;
struct res_entry *re;
const bfd_byte *ers;
int length, j;
if ((const bfd_byte *) ere >= finfo->data_end)
overrun (finfo, _("named directory entry"));
name = getfi_32 (finfo, ere->name);
rva = getfi_32 (finfo, ere->rva);
name &=~ 0x80000000;
if (name > (size_t) (finfo->data_end - finfo->data))
overrun (finfo, _("directory entry name"));
ers = finfo->data + name;
re = (struct res_entry *) res_alloc (sizeof *re);
re->next = NULL;
re->id.named = 1;
length = getfi_16 (finfo, ers);
re->id.u.n.length = length;
re->id.u.n.name = (unichar *) res_alloc (length * sizeof (unichar));
for (j = 0; j < length; j++)
re->id.u.n.name[j] = getfi_16 (finfo, ers + j * 2 + 2);
if (level == 0)
type = &re->id;
if ((rva & 0x80000000) != 0)
{
rva &=~ 0x80000000;
if (rva >= (size_t) (finfo->data_end - finfo->data))
overrun (finfo, _("named subdirectory"));
re->subdir = 1;
re->u.dir = read_coff_res_dir (finfo->data + rva, finfo, type,
level + 1);
}
else
{
if (rva >= (size_t) (finfo->data_end - finfo->data))
overrun (finfo, _("named resource"));
re->subdir = 0;
re->u.res = read_coff_data_entry (finfo->data + rva, finfo, type);
}
*pp = re;
pp = &re->next;
}
for (i = 0; i < id_count; i++, ere++)
{
unsigned long name, rva;
struct res_entry *re;
if ((const bfd_byte *) ere >= finfo->data_end)
overrun (finfo, _("ID directory entry"));
name = getfi_32 (finfo, ere->name);
rva = getfi_32 (finfo, ere->rva);
re = (struct res_entry *) res_alloc (sizeof *re);
re->next = NULL;
re->id.named = 0;
re->id.u.id = name;
if (level == 0)
type = &re->id;
if ((rva & 0x80000000) != 0)
{
rva &=~ 0x80000000;
if (rva >= (size_t) (finfo->data_end - finfo->data))
overrun (finfo, _("ID subdirectory"));
re->subdir = 1;
re->u.dir = read_coff_res_dir (finfo->data + rva, finfo, type,
level + 1);
}
else
{
if (rva >= (size_t) (finfo->data_end - finfo->data))
overrun (finfo, _("ID resource"));
re->subdir = 0;
re->u.res = read_coff_data_entry (finfo->data + rva, finfo, type);
}
*pp = re;
pp = &re->next;
}
return rd;
}
static struct res_resource *
read_coff_data_entry (const bfd_byte *data, const struct coff_file_info *finfo, const struct res_id *type)
{
const struct extern_res_data *erd;
struct res_resource *r;
unsigned long size, rva;
const bfd_byte *resdata;
if (type == NULL)
fatal (_("resource type unknown"));
if ((size_t) (finfo->data_end - data) < sizeof (struct extern_res_data))
overrun (finfo, _("data entry"));
erd = (const struct extern_res_data *) data;
size = getfi_32 (finfo, erd->size);
rva = getfi_32 (finfo, erd->rva);
if (rva < finfo->secaddr
|| rva - finfo->secaddr >= (size_t) (finfo->data_end - finfo->data))
overrun (finfo, _("resource data"));
resdata = finfo->data + (rva - finfo->secaddr);
if (size > (size_t) (finfo->data_end - resdata))
overrun (finfo, _("resource data size"));
r = bin_to_res (*type, resdata, size, finfo->big_endian);
memset (&r->res_info, 0, sizeof (struct res_res_info));
r->coff_info.codepage = getfi_32 (finfo, erd->codepage);
r->coff_info.reserved = getfi_32 (finfo, erd->reserved);
return r;
}
struct bindata_build
{
struct bindata *d;
struct bindata *last;
unsigned long length;
};
struct coff_write_info
{
bfd *abfd;
int big_endian;
asymbol **sympp;
unsigned long dirsize;
unsigned long dirstrsize;
unsigned long dataentsize;
struct bindata_build dirs;
struct bindata_build dirstrs;
struct bindata_build dataents;
struct bindata_build resources;
arelent **relocs;
unsigned int reloc_count;
};
#define putcwi_16(cwi, v, s) \
((cwi->big_endian) ? bfd_putb16 ((v), (s)) : bfd_putl16 ((v), (s)))
#define putcwi_32(cwi, v, s) \
((cwi->big_endian) ? bfd_putb32 ((v), (s)) : bfd_putl32 ((v), (s)))
static void coff_bin_sizes
(const struct res_directory *, struct coff_write_info *);
static unsigned char *coff_alloc (struct bindata_build *, size_t);
static void coff_to_bin
(const struct res_directory *, struct coff_write_info *);
static void coff_res_to_bin
(const struct res_resource *, struct coff_write_info *);
void
write_coff_file (const char *filename, const char *target,
const struct res_directory *resources)
{
bfd *abfd;
asection *sec;
struct coff_write_info cwi;
struct bindata *d;
unsigned long length, offset;
if (filename == NULL)
fatal (_("filename required for COFF output"));
abfd = bfd_openw (filename, target);
if (abfd == NULL)
bfd_fatal (filename);
if (! bfd_set_format (abfd, bfd_object))
bfd_fatal ("bfd_set_format");
#if defined DLLTOOL_SH
if (! bfd_set_arch_mach (abfd, bfd_arch_sh, 0))
bfd_fatal ("bfd_set_arch_mach(sh)");
#elif defined DLLTOOL_MIPS
if (! bfd_set_arch_mach (abfd, bfd_arch_mips, 0))
bfd_fatal ("bfd_set_arch_mach(mips)");
#elif defined DLLTOOL_ARM
if (! bfd_set_arch_mach (abfd, bfd_arch_arm, 0))
bfd_fatal ("bfd_set_arch_mach(arm)");
#else
if (! bfd_set_arch_mach (abfd, bfd_arch_i386, 0))
bfd_fatal ("bfd_set_arch_mach(i386)");
#endif
if (! bfd_set_file_flags (abfd, HAS_SYMS | HAS_RELOC))
bfd_fatal ("bfd_set_file_flags");
sec = bfd_make_section (abfd, ".rsrc");
if (sec == NULL)
bfd_fatal ("bfd_make_section");
if (! bfd_set_section_flags (abfd, sec,
(SEC_HAS_CONTENTS | SEC_ALLOC
| SEC_LOAD | SEC_DATA)))
bfd_fatal ("bfd_set_section_flags");
if (! bfd_set_symtab (abfd, sec->symbol_ptr_ptr, 1))
bfd_fatal ("bfd_set_symtab");
sec->output_section = sec;
cwi.abfd = abfd;
cwi.big_endian = bfd_big_endian (abfd);
cwi.sympp = sec->symbol_ptr_ptr;
cwi.dirsize = 0;
cwi.dirstrsize = 0;
cwi.dataentsize = 0;
cwi.dirs.d = NULL;
cwi.dirs.last = NULL;
cwi.dirs.length = 0;
cwi.dirstrs.d = NULL;
cwi.dirstrs.last = NULL;
cwi.dirstrs.length = 0;
cwi.dataents.d = NULL;
cwi.dataents.last = NULL;
cwi.dataents.length = 0;
cwi.resources.d = NULL;
cwi.resources.last = NULL;
cwi.resources.length = 0;
cwi.relocs = NULL;
cwi.reloc_count = 0;
coff_bin_sizes (resources, &cwi);
cwi.dirstrsize = (cwi.dirstrsize + 3) &~ 3;
coff_to_bin (resources, &cwi);
if ((cwi.dirstrs.length & 3) != 0)
{
unsigned char *ex;
ex = coff_alloc (&cwi.dirstrs, 2);
ex[0] = 0;
ex[1] = 0;
}
assert (cwi.dirs.length == cwi.dirsize);
assert (cwi.dirstrs.length == cwi.dirstrsize);
assert (cwi.dataents.length == cwi.dataentsize);
length = (cwi.dirsize
+ cwi.dirstrsize
+ cwi.dataentsize
+ cwi.resources.length);
if (! bfd_set_section_size (abfd, sec, length))
bfd_fatal ("bfd_set_section_size");
bfd_set_reloc (abfd, sec, cwi.relocs, cwi.reloc_count);
offset = 0;
for (d = cwi.dirs.d; d != NULL; d = d->next)
{
if (! bfd_set_section_contents (abfd, sec, d->data, offset, d->length))
bfd_fatal ("bfd_set_section_contents");
offset += d->length;
}
for (d = cwi.dirstrs.d; d != NULL; d = d->next)
{
if (! bfd_set_section_contents (abfd, sec, d->data, offset, d->length))
bfd_fatal ("bfd_set_section_contents");
offset += d->length;
}
for (d = cwi.dataents.d; d != NULL; d = d->next)
{
if (! bfd_set_section_contents (abfd, sec, d->data, offset, d->length))
bfd_fatal ("bfd_set_section_contents");
offset += d->length;
}
for (d = cwi.resources.d; d != NULL; d = d->next)
{
if (! bfd_set_section_contents (abfd, sec, d->data, offset, d->length))
bfd_fatal ("bfd_set_section_contents");
offset += d->length;
}
assert (offset == length);
if (! bfd_close (abfd))
bfd_fatal ("bfd_close");
free (cwi.relocs);
}
static void
coff_bin_sizes (const struct res_directory *resdir,
struct coff_write_info *cwi)
{
const struct res_entry *re;
cwi->dirsize += sizeof (struct extern_res_directory);
for (re = resdir->entries; re != NULL; re = re->next)
{
cwi->dirsize += sizeof (struct extern_res_entry);
if (re->id.named)
cwi->dirstrsize += re->id.u.n.length * 2 + 2;
if (re->subdir)
coff_bin_sizes (re->u.dir, cwi);
else
cwi->dataentsize += sizeof (struct extern_res_data);
}
}
static unsigned char *
coff_alloc (struct bindata_build *bb, size_t size)
{
struct bindata *d;
d = (struct bindata *) reswr_alloc (sizeof *d);
d->next = NULL;
d->data = (unsigned char *) reswr_alloc (size);
d->length = size;
if (bb->d == NULL)
bb->d = d;
else
bb->last->next = d;
bb->last = d;
bb->length += size;
return d->data;
}
static void
coff_to_bin (const struct res_directory *resdir, struct coff_write_info *cwi)
{
struct extern_res_directory *erd;
int ci, cn;
const struct res_entry *e;
struct extern_res_entry *ere;
erd = ((struct extern_res_directory *)
coff_alloc (&cwi->dirs, sizeof (*erd)));
putcwi_32 (cwi, resdir->characteristics, erd->characteristics);
putcwi_32 (cwi, resdir->time, erd->time);
putcwi_16 (cwi, resdir->major, erd->major);
putcwi_16 (cwi, resdir->minor, erd->minor);
ci = 0;
cn = 0;
for (e = resdir->entries; e != NULL; e = e->next)
{
if (e->id.named)
++cn;
else
++ci;
}
putcwi_16 (cwi, cn, erd->name_count);
putcwi_16 (cwi, ci, erd->id_count);
ere = ((struct extern_res_entry *)
coff_alloc (&cwi->dirs, (ci + cn) * sizeof (*ere)));
for (e = resdir->entries; e != NULL; e = e->next, ere++)
{
if (! e->id.named)
putcwi_32 (cwi, e->id.u.id, ere->name);
else
{
unsigned char *str;
int i;
putcwi_32 (cwi,
0x80000000 | (cwi->dirsize + cwi->dirstrs.length),
ere->name);
str = coff_alloc (&cwi->dirstrs, e->id.u.n.length * 2 + 2);
putcwi_16 (cwi, e->id.u.n.length, str);
for (i = 0; i < e->id.u.n.length; i++)
putcwi_16 (cwi, e->id.u.n.name[i], str + i * 2 + 2);
}
if (e->subdir)
{
putcwi_32 (cwi, 0x80000000 | cwi->dirs.length, ere->rva);
coff_to_bin (e->u.dir, cwi);
}
else
{
putcwi_32 (cwi,
cwi->dirsize + cwi->dirstrsize + cwi->dataents.length,
ere->rva);
coff_res_to_bin (e->u.res, cwi);
}
}
}
static void
coff_res_to_bin (const struct res_resource *res, struct coff_write_info *cwi)
{
arelent *r;
struct extern_res_data *erd;
struct bindata *d;
unsigned long length;
r = (arelent *) reswr_alloc (sizeof (arelent));
r->sym_ptr_ptr = cwi->sympp;
r->address = cwi->dirsize + cwi->dirstrsize + cwi->dataents.length;
r->addend = 0;
r->howto = bfd_reloc_type_lookup (cwi->abfd, BFD_RELOC_RVA);
if (r->howto == NULL)
bfd_fatal (_("can't get BFD_RELOC_RVA relocation type"));
cwi->relocs = xrealloc (cwi->relocs,
(cwi->reloc_count + 2) * sizeof (arelent *));
cwi->relocs[cwi->reloc_count] = r;
cwi->relocs[cwi->reloc_count + 1] = NULL;
++cwi->reloc_count;
erd = (struct extern_res_data *) coff_alloc (&cwi->dataents, sizeof (*erd));
putcwi_32 (cwi,
(cwi->dirsize
+ cwi->dirstrsize
+ cwi->dataentsize
+ cwi->resources.length),
erd->rva);
putcwi_32 (cwi, res->coff_info.codepage, erd->codepage);
putcwi_32 (cwi, res->coff_info.reserved, erd->reserved);
d = res_to_bin (res, cwi->big_endian);
if (cwi->resources.d == NULL)
cwi->resources.d = d;
else
cwi->resources.last->next = d;
length = 0;
for (; d->next != NULL; d = d->next)
length += d->length;
length += d->length;
cwi->resources.last = d;
cwi->resources.length += length;
putcwi_32 (cwi, length, erd->size);
if ((length & 3) != 0)
{
int add;
unsigned char *ex;
add = 4 - (length & 3);
ex = coff_alloc (&cwi->resources, add);
memset (ex, 0, add);
}
}