2006-04-23 21:12:42 +04:00
|
|
|
static void glue(bswap_ehdr, SZ)(struct elfhdr *ehdr)
|
|
|
|
{
|
|
|
|
bswap16s(&ehdr->e_type); /* Object file type */
|
|
|
|
bswap16s(&ehdr->e_machine); /* Architecture */
|
|
|
|
bswap32s(&ehdr->e_version); /* Object file version */
|
|
|
|
bswapSZs(&ehdr->e_entry); /* Entry point virtual address */
|
|
|
|
bswapSZs(&ehdr->e_phoff); /* Program header table file offset */
|
|
|
|
bswapSZs(&ehdr->e_shoff); /* Section header table file offset */
|
|
|
|
bswap32s(&ehdr->e_flags); /* Processor-specific flags */
|
|
|
|
bswap16s(&ehdr->e_ehsize); /* ELF header size in bytes */
|
|
|
|
bswap16s(&ehdr->e_phentsize); /* Program header table entry size */
|
|
|
|
bswap16s(&ehdr->e_phnum); /* Program header table entry count */
|
|
|
|
bswap16s(&ehdr->e_shentsize); /* Section header table entry size */
|
|
|
|
bswap16s(&ehdr->e_shnum); /* Section header table entry count */
|
|
|
|
bswap16s(&ehdr->e_shstrndx); /* Section header string table index */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void glue(bswap_phdr, SZ)(struct elf_phdr *phdr)
|
|
|
|
{
|
|
|
|
bswap32s(&phdr->p_type); /* Segment type */
|
|
|
|
bswapSZs(&phdr->p_offset); /* Segment file offset */
|
|
|
|
bswapSZs(&phdr->p_vaddr); /* Segment virtual address */
|
|
|
|
bswapSZs(&phdr->p_paddr); /* Segment physical address */
|
|
|
|
bswapSZs(&phdr->p_filesz); /* Segment size in file */
|
|
|
|
bswapSZs(&phdr->p_memsz); /* Segment size in memory */
|
|
|
|
bswap32s(&phdr->p_flags); /* Segment flags */
|
|
|
|
bswapSZs(&phdr->p_align); /* Segment alignment */
|
|
|
|
}
|
|
|
|
|
|
|
|
static void glue(bswap_shdr, SZ)(struct elf_shdr *shdr)
|
|
|
|
{
|
|
|
|
bswap32s(&shdr->sh_name);
|
|
|
|
bswap32s(&shdr->sh_type);
|
|
|
|
bswapSZs(&shdr->sh_flags);
|
|
|
|
bswapSZs(&shdr->sh_addr);
|
|
|
|
bswapSZs(&shdr->sh_offset);
|
|
|
|
bswapSZs(&shdr->sh_size);
|
|
|
|
bswap32s(&shdr->sh_link);
|
|
|
|
bswap32s(&shdr->sh_info);
|
|
|
|
bswapSZs(&shdr->sh_addralign);
|
|
|
|
bswapSZs(&shdr->sh_entsize);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void glue(bswap_sym, SZ)(struct elf_sym *sym)
|
|
|
|
{
|
|
|
|
bswap32s(&sym->st_name);
|
|
|
|
bswapSZs(&sym->st_value);
|
|
|
|
bswapSZs(&sym->st_size);
|
|
|
|
bswap16s(&sym->st_shndx);
|
|
|
|
}
|
|
|
|
|
2015-03-09 13:12:52 +03:00
|
|
|
static void glue(bswap_rela, SZ)(struct elf_rela *rela)
|
|
|
|
{
|
|
|
|
bswapSZs(&rela->r_offset);
|
|
|
|
bswapSZs(&rela->r_info);
|
|
|
|
bswapSZs((elf_word *)&rela->r_addend);
|
|
|
|
}
|
|
|
|
|
2007-09-17 01:08:06 +04:00
|
|
|
static struct elf_shdr *glue(find_section, SZ)(struct elf_shdr *shdr_table,
|
2006-04-23 21:12:42 +04:00
|
|
|
int n, int type)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
for(i=0;i<n;i++) {
|
|
|
|
if (shdr_table[i].sh_type == type)
|
|
|
|
return shdr_table + i;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2008-10-22 19:11:31 +04:00
|
|
|
static int glue(symfind, SZ)(const void *s0, const void *s1)
|
|
|
|
{
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr addr = *(hwaddr *)s0;
|
2008-10-22 19:11:31 +04:00
|
|
|
struct elf_sym *sym = (struct elf_sym *)s1;
|
|
|
|
int result = 0;
|
2012-01-05 18:39:39 +04:00
|
|
|
if (addr < sym->st_value) {
|
2008-10-22 19:11:31 +04:00
|
|
|
result = -1;
|
2012-01-05 18:39:39 +04:00
|
|
|
} else if (addr >= sym->st_value + sym->st_size) {
|
2008-10-22 19:11:31 +04:00
|
|
|
result = 1;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2009-09-20 18:58:02 +04:00
|
|
|
static const char *glue(lookup_symbol, SZ)(struct syminfo *s,
|
2012-10-23 14:30:10 +04:00
|
|
|
hwaddr orig_addr)
|
2008-10-22 19:11:31 +04:00
|
|
|
{
|
|
|
|
struct elf_sym *syms = glue(s->disas_symtab.elf, SZ);
|
|
|
|
struct elf_sym *sym;
|
|
|
|
|
2012-01-05 18:39:39 +04:00
|
|
|
sym = bsearch(&orig_addr, syms, s->disas_num_syms, sizeof(*syms),
|
|
|
|
glue(symfind, SZ));
|
2009-08-01 01:16:51 +04:00
|
|
|
if (sym != NULL) {
|
2008-10-22 19:11:31 +04:00
|
|
|
return s->disas_strtab + sym->st_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
|
|
|
|
static int glue(symcmp, SZ)(const void *s0, const void *s1)
|
|
|
|
{
|
|
|
|
struct elf_sym *sym0 = (struct elf_sym *)s0;
|
|
|
|
struct elf_sym *sym1 = (struct elf_sym *)s1;
|
|
|
|
return (sym0->st_value < sym1->st_value)
|
|
|
|
? -1
|
|
|
|
: ((sym0->st_value > sym1->st_value) ? 1 : 0);
|
|
|
|
}
|
|
|
|
|
2020-04-03 22:11:39 +03:00
|
|
|
static void glue(load_symbols, SZ)(struct elfhdr *ehdr, int fd, int must_swab,
|
|
|
|
int clear_lsb, symbol_fn_t sym_cb)
|
2006-04-23 21:12:42 +04:00
|
|
|
{
|
2020-04-03 22:11:39 +03:00
|
|
|
struct elf_shdr *symtab, *strtab;
|
|
|
|
g_autofree struct elf_shdr *shdr_table = NULL;
|
|
|
|
g_autofree struct elf_sym *syms = NULL;
|
|
|
|
g_autofree char *str = NULL;
|
2006-04-23 21:12:42 +04:00
|
|
|
struct syminfo *s;
|
|
|
|
int nsyms, i;
|
|
|
|
|
2007-09-17 01:08:06 +04:00
|
|
|
shdr_table = load_at(fd, ehdr->e_shoff,
|
2006-04-23 21:12:42 +04:00
|
|
|
sizeof(struct elf_shdr) * ehdr->e_shnum);
|
2020-04-03 22:11:39 +03:00
|
|
|
if (!shdr_table) {
|
2022-10-24 10:28:02 +03:00
|
|
|
return;
|
2020-04-03 22:11:39 +03:00
|
|
|
}
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2006-04-23 21:12:42 +04:00
|
|
|
if (must_swab) {
|
|
|
|
for (i = 0; i < ehdr->e_shnum; i++) {
|
|
|
|
glue(bswap_shdr, SZ)(shdr_table + i);
|
|
|
|
}
|
|
|
|
}
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2006-04-23 21:12:42 +04:00
|
|
|
symtab = glue(find_section, SZ)(shdr_table, ehdr->e_shnum, SHT_SYMTAB);
|
2020-04-03 22:11:39 +03:00
|
|
|
if (!symtab) {
|
|
|
|
return;
|
|
|
|
}
|
2006-04-23 21:12:42 +04:00
|
|
|
syms = load_at(fd, symtab->sh_offset, symtab->sh_size);
|
2020-04-03 22:11:39 +03:00
|
|
|
if (!syms) {
|
|
|
|
return;
|
|
|
|
}
|
2006-04-23 21:12:42 +04:00
|
|
|
|
|
|
|
nsyms = symtab->sh_size / sizeof(struct elf_sym);
|
2008-10-22 19:11:31 +04:00
|
|
|
|
2018-03-02 15:31:12 +03:00
|
|
|
/* String table */
|
|
|
|
if (symtab->sh_link >= ehdr->e_shnum) {
|
2020-04-03 22:11:39 +03:00
|
|
|
return;
|
2018-03-02 15:31:12 +03:00
|
|
|
}
|
|
|
|
strtab = &shdr_table[symtab->sh_link];
|
|
|
|
|
|
|
|
str = load_at(fd, strtab->sh_offset, strtab->sh_size);
|
|
|
|
if (!str) {
|
2020-04-03 22:11:39 +03:00
|
|
|
return;
|
2018-03-02 15:31:12 +03:00
|
|
|
}
|
|
|
|
|
2008-10-22 19:11:31 +04:00
|
|
|
i = 0;
|
|
|
|
while (i < nsyms) {
|
2018-03-02 15:31:12 +03:00
|
|
|
if (must_swab) {
|
2006-04-23 21:12:42 +04:00
|
|
|
glue(bswap_sym, SZ)(&syms[i]);
|
2018-03-02 15:31:12 +03:00
|
|
|
}
|
|
|
|
if (sym_cb) {
|
|
|
|
sym_cb(str + syms[i].st_name, syms[i].st_info,
|
|
|
|
syms[i].st_value, syms[i].st_size);
|
|
|
|
}
|
2008-10-22 19:11:31 +04:00
|
|
|
/* We are only interested in function symbols.
|
|
|
|
Throw everything else away. */
|
|
|
|
if (syms[i].st_shndx == SHN_UNDEF ||
|
|
|
|
syms[i].st_shndx >= SHN_LORESERVE ||
|
|
|
|
ELF_ST_TYPE(syms[i].st_info) != STT_FUNC) {
|
|
|
|
nsyms--;
|
|
|
|
if (i < nsyms) {
|
|
|
|
syms[i] = syms[nsyms];
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
2009-09-20 18:58:02 +04:00
|
|
|
if (clear_lsb) {
|
|
|
|
/* The bottom address bit marks a Thumb or MIPS16 symbol. */
|
|
|
|
syms[i].st_value &= ~(glue(glue(Elf, SZ), _Addr))1;
|
|
|
|
}
|
2008-10-22 19:11:31 +04:00
|
|
|
i++;
|
2006-04-23 21:12:42 +04:00
|
|
|
}
|
2008-10-22 19:11:31 +04:00
|
|
|
|
2020-04-03 22:11:39 +03:00
|
|
|
/* check we have symbols left */
|
|
|
|
if (nsyms == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
syms = g_realloc(syms, nsyms * sizeof(*syms));
|
2014-08-20 22:38:11 +04:00
|
|
|
qsort(syms, nsyms, sizeof(*syms), glue(symcmp, SZ));
|
|
|
|
for (i = 0; i < nsyms - 1; i++) {
|
|
|
|
if (syms[i].st_size == 0) {
|
|
|
|
syms[i].st_size = syms[i + 1].st_value - syms[i].st_value;
|
2010-08-09 18:43:53 +04:00
|
|
|
}
|
2009-12-28 23:18:12 +03:00
|
|
|
}
|
2008-10-22 19:11:31 +04:00
|
|
|
|
2006-04-23 21:12:42 +04:00
|
|
|
/* Commit */
|
2011-08-21 07:09:37 +04:00
|
|
|
s = g_malloc0(sizeof(*s));
|
2008-10-22 19:11:31 +04:00
|
|
|
s->lookup_symbol = glue(lookup_symbol, SZ);
|
2020-04-03 22:11:39 +03:00
|
|
|
glue(s->disas_symtab.elf, SZ) = g_steal_pointer(&syms);
|
2006-04-23 21:12:42 +04:00
|
|
|
s->disas_num_syms = nsyms;
|
2020-04-03 22:11:39 +03:00
|
|
|
s->disas_strtab = g_steal_pointer(&str);
|
2006-04-23 21:12:42 +04:00
|
|
|
s->next = syminfos;
|
|
|
|
syminfos = s;
|
|
|
|
}
|
|
|
|
|
2015-03-09 13:12:52 +03:00
|
|
|
static int glue(elf_reloc, SZ)(struct elfhdr *ehdr, int fd, int must_swab,
|
|
|
|
uint64_t (*translate_fn)(void *, uint64_t),
|
|
|
|
void *translate_opaque, uint8_t *data,
|
|
|
|
struct elf_phdr *ph, int elf_machine)
|
|
|
|
{
|
|
|
|
struct elf_shdr *reltab, *shdr_table = NULL;
|
|
|
|
struct elf_rela *rels = NULL;
|
|
|
|
int nrels, i, ret = -1;
|
|
|
|
elf_word wordval;
|
|
|
|
void *addr;
|
|
|
|
|
|
|
|
shdr_table = load_at(fd, ehdr->e_shoff,
|
|
|
|
sizeof(struct elf_shdr) * ehdr->e_shnum);
|
|
|
|
if (!shdr_table) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (must_swab) {
|
|
|
|
for (i = 0; i < ehdr->e_shnum; i++) {
|
|
|
|
glue(bswap_shdr, SZ)(&shdr_table[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
reltab = glue(find_section, SZ)(shdr_table, ehdr->e_shnum, SHT_RELA);
|
|
|
|
if (!reltab) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
rels = load_at(fd, reltab->sh_offset, reltab->sh_size);
|
|
|
|
if (!rels) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
nrels = reltab->sh_size / sizeof(struct elf_rela);
|
|
|
|
|
|
|
|
for (i = 0; i < nrels; i++) {
|
|
|
|
if (must_swab) {
|
|
|
|
glue(bswap_rela, SZ)(&rels[i]);
|
|
|
|
}
|
|
|
|
if (rels[i].r_offset < ph->p_vaddr ||
|
|
|
|
rels[i].r_offset >= ph->p_vaddr + ph->p_filesz) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
addr = &data[rels[i].r_offset - ph->p_vaddr];
|
|
|
|
switch (elf_machine) {
|
|
|
|
case EM_S390:
|
|
|
|
switch (rels[i].r_info) {
|
|
|
|
case R_390_RELATIVE:
|
|
|
|
wordval = *(elf_word *)addr;
|
|
|
|
if (must_swab) {
|
|
|
|
bswapSZs(&wordval);
|
|
|
|
}
|
|
|
|
wordval = translate_fn(translate_opaque, wordval);
|
|
|
|
if (must_swab) {
|
|
|
|
bswapSZs(&wordval);
|
|
|
|
}
|
|
|
|
*(elf_word *)addr = wordval;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "Unsupported relocation type %i!\n",
|
|
|
|
(int)rels[i].r_info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = 0;
|
|
|
|
fail:
|
|
|
|
g_free(rels);
|
|
|
|
g_free(shdr_table);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2019-01-15 15:18:04 +03:00
|
|
|
/*
|
|
|
|
* Given 'nhdr', a pointer to a range of ELF Notes, search through them
|
|
|
|
* for a note matching type 'elf_note_type' and return a pointer to
|
|
|
|
* the matching ELF note.
|
|
|
|
*/
|
|
|
|
static struct elf_note *glue(get_elf_note_type, SZ)(struct elf_note *nhdr,
|
|
|
|
elf_word note_size,
|
|
|
|
elf_word phdr_align,
|
|
|
|
elf_word elf_note_type)
|
|
|
|
{
|
|
|
|
elf_word nhdr_size = sizeof(struct elf_note);
|
|
|
|
elf_word elf_note_entry_offset = 0;
|
|
|
|
elf_word note_type;
|
|
|
|
elf_word nhdr_namesz;
|
|
|
|
elf_word nhdr_descsz;
|
|
|
|
|
|
|
|
if (nhdr == NULL) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
note_type = nhdr->n_type;
|
|
|
|
while (note_type != elf_note_type) {
|
|
|
|
nhdr_namesz = nhdr->n_namesz;
|
|
|
|
nhdr_descsz = nhdr->n_descsz;
|
|
|
|
|
|
|
|
elf_note_entry_offset = nhdr_size +
|
|
|
|
QEMU_ALIGN_UP(nhdr_namesz, phdr_align) +
|
|
|
|
QEMU_ALIGN_UP(nhdr_descsz, phdr_align);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* If the offset calculated in this iteration exceeds the
|
|
|
|
* supplied size, we are done and no matching note was found.
|
|
|
|
*/
|
|
|
|
if (elf_note_entry_offset > note_size) {
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* skip to the next ELF Note entry */
|
|
|
|
nhdr = (void *)nhdr + elf_note_entry_offset;
|
|
|
|
note_type = nhdr->n_type;
|
|
|
|
}
|
|
|
|
|
|
|
|
return nhdr;
|
|
|
|
}
|
|
|
|
|
2021-10-14 22:43:25 +03:00
|
|
|
static ssize_t glue(load_elf, SZ)(const char *name, int fd,
|
|
|
|
uint64_t (*elf_note_fn)(void *, void *, bool),
|
|
|
|
uint64_t (*translate_fn)(void *, uint64_t),
|
|
|
|
void *translate_opaque,
|
|
|
|
int must_swab, uint64_t *pentry,
|
|
|
|
uint64_t *lowaddr, uint64_t *highaddr,
|
|
|
|
uint32_t *pflags, int elf_machine,
|
|
|
|
int clear_lsb, int data_swab,
|
|
|
|
AddressSpace *as, bool load_rom,
|
|
|
|
symbol_fn_t sym_cb)
|
2006-04-23 21:12:42 +04:00
|
|
|
{
|
|
|
|
struct elfhdr ehdr;
|
|
|
|
struct elf_phdr *phdr = NULL, *ph;
|
2021-10-14 22:43:25 +03:00
|
|
|
int size, i;
|
|
|
|
ssize_t total_size;
|
2019-07-24 17:31:04 +03:00
|
|
|
elf_word mem_size, file_size, data_offset;
|
2009-06-05 18:16:41 +04:00
|
|
|
uint64_t addr, low = (uint64_t)-1, high = 0;
|
2019-07-24 17:31:04 +03:00
|
|
|
GMappedFile *mapped_file = NULL;
|
2006-04-27 02:05:26 +04:00
|
|
|
uint8_t *data = NULL;
|
2021-10-14 22:43:25 +03:00
|
|
|
ssize_t ret = ELF_LOAD_FAILED;
|
2006-04-23 21:12:42 +04:00
|
|
|
|
|
|
|
if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr))
|
|
|
|
goto fail;
|
|
|
|
if (must_swab) {
|
|
|
|
glue(bswap_ehdr, SZ)(&ehdr);
|
|
|
|
}
|
|
|
|
|
2016-09-22 20:13:07 +03:00
|
|
|
if (elf_machine <= EM_NONE) {
|
|
|
|
/* The caller didn't specify an ARCH, we can figure it out */
|
|
|
|
elf_machine = ehdr.e_machine;
|
|
|
|
}
|
|
|
|
|
2009-09-20 18:58:02 +04:00
|
|
|
switch (elf_machine) {
|
2009-03-14 00:16:24 +03:00
|
|
|
case EM_PPC64:
|
2015-08-18 07:53:16 +03:00
|
|
|
if (ehdr.e_machine != EM_PPC64) {
|
|
|
|
if (ehdr.e_machine != EM_PPC) {
|
2014-02-04 08:04:18 +04:00
|
|
|
ret = ELF_LOAD_WRONG_ARCH;
|
2009-03-14 00:16:24 +03:00
|
|
|
goto fail;
|
2014-02-04 08:04:18 +04:00
|
|
|
}
|
2015-08-18 07:53:16 +03:00
|
|
|
}
|
2009-03-14 00:16:24 +03:00
|
|
|
break;
|
|
|
|
case EM_X86_64:
|
2015-08-18 07:53:16 +03:00
|
|
|
if (ehdr.e_machine != EM_X86_64) {
|
|
|
|
if (ehdr.e_machine != EM_386) {
|
2014-02-04 08:04:18 +04:00
|
|
|
ret = ELF_LOAD_WRONG_ARCH;
|
2009-03-14 00:16:24 +03:00
|
|
|
goto fail;
|
2014-02-04 08:04:18 +04:00
|
|
|
}
|
2015-08-18 07:53:16 +03:00
|
|
|
}
|
2009-03-14 00:16:24 +03:00
|
|
|
break;
|
2010-05-24 13:14:04 +04:00
|
|
|
case EM_MICROBLAZE:
|
2015-08-18 07:53:16 +03:00
|
|
|
if (ehdr.e_machine != EM_MICROBLAZE) {
|
|
|
|
if (ehdr.e_machine != EM_MICROBLAZE_OLD) {
|
2014-02-04 08:04:18 +04:00
|
|
|
ret = ELF_LOAD_WRONG_ARCH;
|
2015-08-29 22:07:50 +03:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
2018-08-07 14:03:13 +03:00
|
|
|
case EM_MIPS:
|
|
|
|
case EM_NANOMIPS:
|
|
|
|
if ((ehdr.e_machine != EM_MIPS) &&
|
|
|
|
(ehdr.e_machine != EM_NANOMIPS)) {
|
|
|
|
ret = ELF_LOAD_WRONG_ARCH;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
break;
|
2009-03-14 00:16:24 +03:00
|
|
|
default:
|
2014-02-04 08:04:18 +04:00
|
|
|
if (elf_machine != ehdr.e_machine) {
|
|
|
|
ret = ELF_LOAD_WRONG_ARCH;
|
2009-03-14 00:16:24 +03:00
|
|
|
goto fail;
|
2014-02-04 08:04:18 +04:00
|
|
|
}
|
2009-03-14 00:16:24 +03:00
|
|
|
}
|
2006-12-23 17:18:40 +03:00
|
|
|
|
2020-01-27 01:55:04 +03:00
|
|
|
if (pflags) {
|
2023-09-19 17:25:54 +03:00
|
|
|
*pflags = ehdr.e_flags;
|
|
|
|
}
|
|
|
|
if (pentry) {
|
|
|
|
*pentry = ehdr.e_entry;
|
2020-01-27 01:55:04 +03:00
|
|
|
}
|
2006-04-27 02:05:26 +04:00
|
|
|
|
2018-03-02 15:31:12 +03:00
|
|
|
glue(load_symbols, SZ)(&ehdr, fd, must_swab, clear_lsb, sym_cb);
|
2006-04-23 21:12:42 +04:00
|
|
|
|
|
|
|
size = ehdr.e_phnum * sizeof(phdr[0]);
|
2015-03-14 18:42:01 +03:00
|
|
|
if (lseek(fd, ehdr.e_phoff, SEEK_SET) != ehdr.e_phoff) {
|
|
|
|
goto fail;
|
|
|
|
}
|
2011-08-21 07:09:37 +04:00
|
|
|
phdr = g_malloc0(size);
|
2006-04-23 21:12:42 +04:00
|
|
|
if (!phdr)
|
|
|
|
goto fail;
|
|
|
|
if (read(fd, phdr, size) != size)
|
2006-12-23 18:18:47 +03:00
|
|
|
goto fail;
|
2006-04-23 21:12:42 +04:00
|
|
|
if (must_swab) {
|
|
|
|
for(i = 0; i < ehdr.e_phnum; i++) {
|
|
|
|
ph = &phdr[i];
|
|
|
|
glue(bswap_phdr, SZ)(ph);
|
|
|
|
}
|
|
|
|
}
|
2007-09-17 12:09:54 +04:00
|
|
|
|
2019-07-24 17:31:04 +03:00
|
|
|
/*
|
|
|
|
* Since we want to be able to modify the mapped buffer, we set the
|
2021-02-25 21:13:44 +03:00
|
|
|
* 'writable' parameter to 'true'. Modifications to the buffer are not
|
2019-07-24 17:31:04 +03:00
|
|
|
* written back to the file.
|
|
|
|
*/
|
|
|
|
mapped_file = g_mapped_file_new_from_fd(fd, true, NULL);
|
|
|
|
if (!mapped_file) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2006-04-23 21:12:42 +04:00
|
|
|
total_size = 0;
|
|
|
|
for(i = 0; i < ehdr.e_phnum; i++) {
|
|
|
|
ph = &phdr[i];
|
|
|
|
if (ph->p_type == PT_LOAD) {
|
2013-02-19 08:41:11 +04:00
|
|
|
mem_size = ph->p_memsz; /* Size of the ROM */
|
|
|
|
file_size = ph->p_filesz; /* Size of the allocated data */
|
2019-07-24 17:31:04 +03:00
|
|
|
data_offset = ph->p_offset; /* Offset where the data is located */
|
|
|
|
|
|
|
|
if (file_size > 0) {
|
|
|
|
if (g_mapped_file_get_length(mapped_file) <
|
|
|
|
file_size + data_offset) {
|
2006-12-23 18:18:47 +03:00
|
|
|
goto fail;
|
2013-02-19 08:41:11 +04:00
|
|
|
}
|
2019-07-24 17:31:04 +03:00
|
|
|
|
|
|
|
data = (uint8_t *)g_mapped_file_get_contents(mapped_file);
|
|
|
|
data += data_offset;
|
2006-04-23 21:12:42 +04:00
|
|
|
}
|
loader: Handle ELF files with overlapping zero-initialized data
For embedded systems, notably ARM, one common use of ELF
file segments is that the 'physical addresses' represent load addresses
and the 'virtual addresses' execution addresses, such that
the load addresses are packed into ROM or flash, and the
relocation and zero-initialization of data is done at runtime.
This means that the 'memsz' in the segment header represents
the runtime size of the segment, but the size that needs to
be loaded is only the 'filesz'. In particular, paddr+memsz
may overlap with the next segment to be loaded, as in this
example:
0x70000001 off 0x00007f68 vaddr 0x00008150 paddr 0x00008150 align 2**2
filesz 0x00000008 memsz 0x00000008 flags r--
LOAD off 0x000000f4 vaddr 0x00000000 paddr 0x00000000 align 2**2
filesz 0x00000124 memsz 0x00000124 flags r--
LOAD off 0x00000218 vaddr 0x00000400 paddr 0x00000400 align 2**3
filesz 0x00007d58 memsz 0x00007d58 flags r-x
LOAD off 0x00007f70 vaddr 0x20000140 paddr 0x00008158 align 2**3
filesz 0x00000a80 memsz 0x000022f8 flags rw-
LOAD off 0x000089f0 vaddr 0x20002438 paddr 0x00008bd8 align 2**0
filesz 0x00000000 memsz 0x00004000 flags rw-
LOAD off 0x000089f0 vaddr 0x20000000 paddr 0x20000000 align 2**0
filesz 0x00000000 memsz 0x00000140 flags rw-
where the segment at paddr 0x8158 has a memsz of 0x2258 and
would overlap with the segment at paddr 0x8bd8 if QEMU's loader
tried to honour it. (At runtime the segments will not overlap
since their vaddrs are more widely spaced than their paddrs.)
Currently if you try to load an ELF file like this with QEMU then
it will fail with an error "rom: requested regions overlap",
because we create a ROM image for each segment using the memsz
as the size.
Support ELF files using this scheme, by truncating the
zero-initialized part of the segment if it would overlap another
segment. This will retain the existing loader behaviour for
all ELF files we currently accept, and also accept ELF files
which only need 'filesz' bytes to be loaded.
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 1502116754-18867-2-git-send-email-peter.maydell@linaro.org
2017-09-04 17:21:53 +03:00
|
|
|
|
|
|
|
/* The ELF spec is somewhat vague about the purpose of the
|
|
|
|
* physical address field. One common use in the embedded world
|
|
|
|
* is that physical address field specifies the load address
|
|
|
|
* and the virtual address field specifies the execution address.
|
|
|
|
* Segments are packed into ROM or flash, and the relocation
|
|
|
|
* and zero-initialization of data is done at runtime. This
|
|
|
|
* means that the memsz header represents the runtime size of the
|
|
|
|
* segment, but the filesz represents the loadtime size. If
|
|
|
|
* we try to honour the memsz value for an ELF file like this
|
|
|
|
* we will end up with overlapping segments (which the
|
|
|
|
* loader.c code will later reject).
|
|
|
|
* We support ELF files using this scheme by by checking whether
|
|
|
|
* paddr + memsz for this segment would overlap with any other
|
|
|
|
* segment. If so, then we assume it's using this scheme and
|
|
|
|
* truncate the loaded segment to the filesz size.
|
|
|
|
* If the segment considered as being memsz size doesn't overlap
|
|
|
|
* then we use memsz for the segment length, to handle ELF files
|
|
|
|
* which assume that the loader will do the zero-initialization.
|
|
|
|
*/
|
|
|
|
if (mem_size > file_size) {
|
|
|
|
/* If this segment's zero-init portion overlaps another
|
|
|
|
* segment's data or zero-init portion, then truncate this one.
|
|
|
|
* Invalid ELF files where the segments overlap even when
|
|
|
|
* only file_size bytes are loaded will be rejected by
|
|
|
|
* the ROM overlap check in loader.c, so we don't try to
|
|
|
|
* explicitly detect those here.
|
|
|
|
*/
|
|
|
|
int j;
|
|
|
|
elf_word zero_start = ph->p_paddr + file_size;
|
|
|
|
elf_word zero_end = ph->p_paddr + mem_size;
|
|
|
|
|
|
|
|
for (j = 0; j < ehdr.e_phnum; j++) {
|
|
|
|
struct elf_phdr *jph = &phdr[j];
|
|
|
|
|
|
|
|
if (i != j && jph->p_type == PT_LOAD) {
|
|
|
|
elf_word other_start = jph->p_paddr;
|
|
|
|
elf_word other_end = jph->p_paddr + jph->p_memsz;
|
|
|
|
|
|
|
|
if (!(other_start >= zero_end ||
|
|
|
|
zero_start >= other_end)) {
|
|
|
|
mem_size = file_size;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-14 22:43:25 +03:00
|
|
|
if (mem_size > SSIZE_MAX - total_size) {
|
2019-09-10 17:22:23 +03:00
|
|
|
ret = ELF_LOAD_TOO_BIG;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
2008-10-22 22:20:20 +04:00
|
|
|
/* address_offset is hack for kernel images that are
|
|
|
|
linked at the wrong physical address. */
|
2010-03-14 23:20:59 +03:00
|
|
|
if (translate_fn) {
|
|
|
|
addr = translate_fn(translate_opaque, ph->p_paddr);
|
2015-03-09 13:12:52 +03:00
|
|
|
glue(elf_reloc, SZ)(&ehdr, fd, must_swab, translate_fn,
|
|
|
|
translate_opaque, data, ph, elf_machine);
|
2010-03-14 23:20:59 +03:00
|
|
|
} else {
|
|
|
|
addr = ph->p_paddr;
|
|
|
|
}
|
2006-04-23 21:12:42 +04:00
|
|
|
|
2016-03-04 14:30:21 +03:00
|
|
|
if (data_swab) {
|
2024-01-15 12:22:16 +03:00
|
|
|
elf_word j;
|
2016-03-04 14:30:21 +03:00
|
|
|
for (j = 0; j < file_size; j += (1 << data_swab)) {
|
|
|
|
uint8_t *dp = data + j;
|
|
|
|
switch (data_swab) {
|
|
|
|
case (1):
|
|
|
|
*(uint16_t *)dp = bswap16(*(uint16_t *)dp);
|
|
|
|
break;
|
|
|
|
case (2):
|
|
|
|
*(uint32_t *)dp = bswap32(*(uint32_t *)dp);
|
|
|
|
break;
|
|
|
|
case (3):
|
|
|
|
*(uint64_t *)dp = bswap64(*(uint64_t *)dp);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
g_assert_not_reached();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-09-05 16:56:39 +04:00
|
|
|
/* the entry pointer in the ELF header is a virtual
|
|
|
|
* address, if the text segments paddr and vaddr differ
|
|
|
|
* we need to adjust the entry */
|
|
|
|
if (pentry && !translate_fn &&
|
|
|
|
ph->p_vaddr != ph->p_paddr &&
|
|
|
|
ehdr.e_entry >= ph->p_vaddr &&
|
|
|
|
ehdr.e_entry < ph->p_vaddr + ph->p_filesz &&
|
|
|
|
ph->p_flags & PF_X) {
|
|
|
|
*pentry = ehdr.e_entry - ph->p_vaddr + ph->p_paddr;
|
|
|
|
}
|
|
|
|
|
2019-07-24 17:31:04 +03:00
|
|
|
/* Some ELF files really do have segments of zero size;
|
|
|
|
* just ignore them rather than trying to create empty
|
|
|
|
* ROM blobs, because the zero-length blob can falsely
|
|
|
|
* trigger the overlapping-ROM-blobs check.
|
|
|
|
*/
|
|
|
|
if (mem_size != 0) {
|
2017-09-04 17:21:53 +03:00
|
|
|
if (load_rom) {
|
2020-11-29 23:39:22 +03:00
|
|
|
g_autofree char *label =
|
2020-11-29 23:39:23 +03:00
|
|
|
g_strdup_printf("%s ELF program header segment %d",
|
|
|
|
name, i);
|
2017-09-04 17:21:53 +03:00
|
|
|
|
2019-07-24 17:31:04 +03:00
|
|
|
/*
|
|
|
|
* rom_add_elf_program() takes its own reference to
|
|
|
|
* 'mapped_file'.
|
|
|
|
*/
|
|
|
|
rom_add_elf_program(label, mapped_file, data, file_size,
|
|
|
|
mem_size, addr, as);
|
2017-09-04 17:21:53 +03:00
|
|
|
} else {
|
2020-05-18 18:53:05 +03:00
|
|
|
MemTxResult res;
|
|
|
|
|
|
|
|
res = address_space_write(as ? as : &address_space_memory,
|
|
|
|
addr, MEMTXATTRS_UNSPECIFIED,
|
|
|
|
data, file_size);
|
|
|
|
if (res != MEMTX_OK) {
|
|
|
|
goto fail;
|
|
|
|
}
|
2022-01-15 23:37:24 +03:00
|
|
|
/*
|
|
|
|
* We need to zero'ify the space that is not copied
|
|
|
|
* from file
|
|
|
|
*/
|
|
|
|
if (file_size < mem_size) {
|
|
|
|
res = address_space_set(as ? as : &address_space_memory,
|
|
|
|
addr + file_size, 0,
|
|
|
|
mem_size - file_size,
|
|
|
|
MEMTXATTRS_UNSPECIFIED);
|
|
|
|
if (res != MEMTX_OK) {
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
}
|
2017-09-04 17:21:53 +03:00
|
|
|
}
|
2016-11-07 18:50:30 +03:00
|
|
|
}
|
2006-04-23 21:12:42 +04:00
|
|
|
|
|
|
|
total_size += mem_size;
|
2009-06-05 18:16:41 +04:00
|
|
|
if (addr < low)
|
2007-04-01 21:56:37 +04:00
|
|
|
low = addr;
|
2009-06-05 18:16:41 +04:00
|
|
|
if ((addr + mem_size) > high)
|
2007-04-01 21:56:37 +04:00
|
|
|
high = addr + mem_size;
|
2006-04-23 21:12:42 +04:00
|
|
|
|
2006-04-27 02:05:26 +04:00
|
|
|
data = NULL;
|
2019-01-15 15:18:04 +03:00
|
|
|
|
|
|
|
} else if (ph->p_type == PT_NOTE && elf_note_fn) {
|
|
|
|
struct elf_note *nhdr = NULL;
|
|
|
|
|
|
|
|
file_size = ph->p_filesz; /* Size of the range of ELF notes */
|
2019-07-24 17:31:04 +03:00
|
|
|
data_offset = ph->p_offset; /* Offset where the notes are located */
|
|
|
|
|
|
|
|
if (file_size > 0) {
|
|
|
|
if (g_mapped_file_get_length(mapped_file) <
|
|
|
|
file_size + data_offset) {
|
2019-01-15 15:18:04 +03:00
|
|
|
goto fail;
|
|
|
|
}
|
2019-07-24 17:31:04 +03:00
|
|
|
|
|
|
|
data = (uint8_t *)g_mapped_file_get_contents(mapped_file);
|
|
|
|
data += data_offset;
|
2019-01-15 15:18:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Search the ELF notes to find one with a type matching the
|
|
|
|
* value passed in via 'translate_opaque'
|
|
|
|
*/
|
|
|
|
nhdr = (struct elf_note *)data;
|
|
|
|
assert(translate_opaque != NULL);
|
|
|
|
nhdr = glue(get_elf_note_type, SZ)(nhdr, file_size, ph->p_align,
|
|
|
|
*(uint64_t *)translate_opaque);
|
|
|
|
if (nhdr != NULL) {
|
2021-03-02 12:03:14 +03:00
|
|
|
elf_note_fn((void *)nhdr, (void *)&ph->p_align, SZ == 64);
|
2019-01-15 15:18:04 +03:00
|
|
|
}
|
|
|
|
data = NULL;
|
2006-04-23 21:12:42 +04:00
|
|
|
}
|
|
|
|
}
|
2019-01-15 15:18:03 +03:00
|
|
|
|
2023-09-19 17:25:54 +03:00
|
|
|
if (lowaddr) {
|
|
|
|
*lowaddr = low;
|
|
|
|
}
|
|
|
|
if (highaddr) {
|
|
|
|
*highaddr = high;
|
|
|
|
}
|
2019-07-24 17:31:04 +03:00
|
|
|
ret = total_size;
|
2006-12-23 18:18:47 +03:00
|
|
|
fail:
|
2020-04-23 23:20:11 +03:00
|
|
|
if (mapped_file) {
|
|
|
|
g_mapped_file_unref(mapped_file);
|
|
|
|
}
|
2011-08-21 07:09:37 +04:00
|
|
|
g_free(phdr);
|
2014-02-04 08:04:18 +04:00
|
|
|
return ret;
|
2006-04-23 21:12:42 +04:00
|
|
|
}
|