123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- 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);
- }
- static void glue(bswap_rela, SZ)(struct elf_rela *rela)
- {
- bswapSZs(&rela->r_offset);
- bswapSZs(&rela->r_info);
- bswapSZs((elf_word *)&rela->r_addend);
- }
- static struct elf_shdr *glue(find_section, SZ)(struct elf_shdr *shdr_table,
- 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;
- }
- static int glue(symfind, SZ)(const void *s0, const void *s1)
- {
- hwaddr addr = *(hwaddr *)s0;
- struct elf_sym *sym = (struct elf_sym *)s1;
- int result = 0;
- if (addr < sym->st_value) {
- result = -1;
- } else if (addr >= sym->st_value + sym->st_size) {
- result = 1;
- }
- return result;
- }
- static const char *glue(lookup_symbol, SZ)(struct syminfo *s,
- hwaddr orig_addr)
- {
- struct elf_sym *syms = glue(s->disas_symtab.elf, SZ);
- struct elf_sym *sym;
- sym = bsearch(&orig_addr, syms, s->disas_num_syms, sizeof(*syms),
- glue(symfind, SZ));
- if (sym != NULL) {
- 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);
- }
- static void glue(load_symbols, SZ)(struct elfhdr *ehdr, int fd, int must_swab,
- int clear_lsb, symbol_fn_t sym_cb)
- {
- 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;
- struct syminfo *s;
- int nsyms, i;
- shdr_table = load_at(fd, ehdr->e_shoff,
- sizeof(struct elf_shdr) * ehdr->e_shnum);
- if (!shdr_table) {
- return;
- }
- if (must_swab) {
- for (i = 0; i < ehdr->e_shnum; i++) {
- glue(bswap_shdr, SZ)(shdr_table + i);
- }
- }
- symtab = glue(find_section, SZ)(shdr_table, ehdr->e_shnum, SHT_SYMTAB);
- if (!symtab) {
- return;
- }
- syms = load_at(fd, symtab->sh_offset, symtab->sh_size);
- if (!syms) {
- return;
- }
- nsyms = symtab->sh_size / sizeof(struct elf_sym);
- /* String table */
- if (symtab->sh_link >= ehdr->e_shnum) {
- return;
- }
- strtab = &shdr_table[symtab->sh_link];
- str = load_at(fd, strtab->sh_offset, strtab->sh_size);
- if (!str) {
- return;
- }
- i = 0;
- while (i < nsyms) {
- if (must_swab) {
- glue(bswap_sym, SZ)(&syms[i]);
- }
- if (sym_cb) {
- sym_cb(str + syms[i].st_name, syms[i].st_info,
- syms[i].st_value, syms[i].st_size);
- }
- /* 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;
- }
- if (clear_lsb) {
- /* The bottom address bit marks a Thumb or MIPS16 symbol. */
- syms[i].st_value &= ~(glue(glue(Elf, SZ), _Addr))1;
- }
- i++;
- }
- /* check we have symbols left */
- if (nsyms == 0) {
- return;
- }
- syms = g_realloc(syms, nsyms * sizeof(*syms));
- 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;
- }
- }
- /* Commit */
- s = g_malloc0(sizeof(*s));
- s->lookup_symbol = glue(lookup_symbol, SZ);
- glue(s->disas_symtab.elf, SZ) = g_steal_pointer(&syms);
- s->disas_num_syms = nsyms;
- s->disas_strtab = g_steal_pointer(&str);
- s->next = syminfos;
- syminfos = s;
- }
- 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;
- }
- /*
- * 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;
- }
- 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)
- {
- struct elfhdr ehdr;
- struct elf_phdr *phdr = NULL, *ph;
- int size, i;
- ssize_t total_size;
- elf_word mem_size, file_size, data_offset;
- uint64_t addr, low = (uint64_t)-1, high = 0;
- GMappedFile *mapped_file = NULL;
- uint8_t *data = NULL;
- ssize_t ret = ELF_LOAD_FAILED;
- if (read(fd, &ehdr, sizeof(ehdr)) != sizeof(ehdr))
- goto fail;
- if (must_swab) {
- glue(bswap_ehdr, SZ)(&ehdr);
- }
- if (elf_machine <= EM_NONE) {
- /* The caller didn't specify an ARCH, we can figure it out */
- elf_machine = ehdr.e_machine;
- }
- switch (elf_machine) {
- case EM_PPC64:
- if (ehdr.e_machine != EM_PPC64) {
- if (ehdr.e_machine != EM_PPC) {
- ret = ELF_LOAD_WRONG_ARCH;
- goto fail;
- }
- }
- break;
- case EM_X86_64:
- if (ehdr.e_machine != EM_X86_64) {
- if (ehdr.e_machine != EM_386) {
- ret = ELF_LOAD_WRONG_ARCH;
- goto fail;
- }
- }
- break;
- case EM_MICROBLAZE:
- if (ehdr.e_machine != EM_MICROBLAZE) {
- if (ehdr.e_machine != EM_MICROBLAZE_OLD) {
- ret = ELF_LOAD_WRONG_ARCH;
- goto fail;
- }
- }
- break;
- 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;
- default:
- if (elf_machine != ehdr.e_machine) {
- ret = ELF_LOAD_WRONG_ARCH;
- goto fail;
- }
- }
- if (pflags) {
- *pflags = ehdr.e_flags;
- }
- if (pentry) {
- *pentry = ehdr.e_entry;
- }
- glue(load_symbols, SZ)(&ehdr, fd, must_swab, clear_lsb, sym_cb);
- size = ehdr.e_phnum * sizeof(phdr[0]);
- if (lseek(fd, ehdr.e_phoff, SEEK_SET) != ehdr.e_phoff) {
- goto fail;
- }
- phdr = g_malloc0(size);
- if (!phdr)
- goto fail;
- if (read(fd, phdr, size) != size)
- goto fail;
- if (must_swab) {
- for(i = 0; i < ehdr.e_phnum; i++) {
- ph = &phdr[i];
- glue(bswap_phdr, SZ)(ph);
- }
- }
- /*
- * Since we want to be able to modify the mapped buffer, we set the
- * 'writable' parameter to 'true'. Modifications to the buffer are not
- * written back to the file.
- */
- mapped_file = g_mapped_file_new_from_fd(fd, true, NULL);
- if (!mapped_file) {
- goto fail;
- }
- total_size = 0;
- for(i = 0; i < ehdr.e_phnum; i++) {
- ph = &phdr[i];
- if (ph->p_type == PT_LOAD) {
- mem_size = ph->p_memsz; /* Size of the ROM */
- file_size = ph->p_filesz; /* Size of the allocated data */
- 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) {
- goto fail;
- }
- data = (uint8_t *)g_mapped_file_get_contents(mapped_file);
- data += data_offset;
- }
- /* 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;
- }
- }
- }
- }
- if (mem_size > SSIZE_MAX - total_size) {
- ret = ELF_LOAD_TOO_BIG;
- goto fail;
- }
- /* address_offset is hack for kernel images that are
- linked at the wrong physical address. */
- if (translate_fn) {
- addr = translate_fn(translate_opaque, ph->p_paddr);
- glue(elf_reloc, SZ)(&ehdr, fd, must_swab, translate_fn,
- translate_opaque, data, ph, elf_machine);
- } else {
- addr = ph->p_paddr;
- }
- if (data_swab) {
- elf_word j;
- 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();
- }
- }
- }
- /* 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;
- }
- /* 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) {
- if (load_rom) {
- g_autofree char *label =
- g_strdup_printf("%s ELF program header segment %d",
- name, i);
- /*
- * 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);
- } else {
- MemTxResult res;
- res = address_space_write(as ? as : &address_space_memory,
- addr, MEMTXATTRS_UNSPECIFIED,
- data, file_size);
- if (res != MEMTX_OK) {
- goto fail;
- }
- /*
- * 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;
- }
- }
- }
- }
- total_size += mem_size;
- if (addr < low)
- low = addr;
- if ((addr + mem_size) > high)
- high = addr + mem_size;
- data = NULL;
- } 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 */
- 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) {
- goto fail;
- }
- data = (uint8_t *)g_mapped_file_get_contents(mapped_file);
- data += data_offset;
- }
- /*
- * 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) {
- elf_note_fn((void *)nhdr, (void *)&ph->p_align, SZ == 64);
- }
- data = NULL;
- }
- }
- if (lowaddr) {
- *lowaddr = low;
- }
- if (highaddr) {
- *highaddr = high;
- }
- ret = total_size;
- fail:
- if (mapped_file) {
- g_mapped_file_unref(mapped_file);
- }
- g_free(phdr);
- return ret;
- }
|