aboutsummaryrefslogtreecommitdiff
path: root/lib/libkldelf/ef.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libkldelf/ef.c')
-rw-r--r--lib/libkldelf/ef.c652
1 files changed, 652 insertions, 0 deletions
diff --git a/lib/libkldelf/ef.c b/lib/libkldelf/ef.c
new file mode 100644
index 000000000000..28576df99bf2
--- /dev/null
+++ b/lib/libkldelf/ef.c
@@ -0,0 +1,652 @@
+/*-
+ * SPDX-License-Identifier: BSD-4-Clause
+ *
+ * Copyright (c) 2000, Boris Popov
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Boris Popov.
+ * 4. Neither the name of the author nor the names of any co-contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/param.h>
+
+#include <err.h>
+#include <errno.h>
+#include <gelf.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "kldelf.h"
+
+#define MAXSEGS 16
+struct ef_file {
+ char *ef_name;
+ struct elf_file *ef_efile;
+ GElf_Phdr *ef_ph;
+ void *ef_fpage; /* First block of the file */
+ int ef_fplen; /* length of first block */
+ GElf_Hashelt ef_nbuckets;
+ GElf_Hashelt ef_nchains;
+ GElf_Hashelt *ef_buckets;
+ GElf_Hashelt *ef_chains;
+ GElf_Hashelt *ef_hashtab;
+ caddr_t ef_strtab;
+ long ef_strsz;
+ GElf_Sym *ef_symtab;
+ int ef_nsegs;
+ GElf_Phdr *ef_segs[MAXSEGS];
+ int ef_verbose;
+ GElf_Rel *ef_rel; /* relocation table */
+ long ef_relsz; /* number of entries */
+ GElf_Rela *ef_rela; /* relocation table */
+ long ef_relasz; /* number of entries */
+};
+
+static void ef_print_phdr(GElf_Phdr *);
+static GElf_Off ef_get_offset(elf_file_t, GElf_Addr);
+
+static void ef_close(elf_file_t ef);
+
+static int ef_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len,
+ void *dest);
+static int ef_seg_read_string(elf_file_t ef, GElf_Addr address, size_t len,
+ char *dest);
+
+static GElf_Addr ef_symaddr(elf_file_t ef, GElf_Size symidx);
+static int ef_lookup_set(elf_file_t ef, const char *name,
+ GElf_Addr *startp, GElf_Addr *stopp, long *countp);
+static int ef_lookup_symbol(elf_file_t ef, const char *name,
+ GElf_Sym **sym, bool see_local);
+
+static struct elf_file_ops ef_file_ops = {
+ .close = ef_close,
+ .seg_read_rel = ef_seg_read_rel,
+ .seg_read_string = ef_seg_read_string,
+ .symaddr = ef_symaddr,
+ .lookup_set = ef_lookup_set,
+ .lookup_symbol = ef_lookup_symbol,
+};
+
+static void
+ef_print_phdr(GElf_Phdr *phdr)
+{
+
+ if ((phdr->p_flags & PF_W) == 0) {
+ printf("text=0x%jx ", (uintmax_t)phdr->p_filesz);
+ } else {
+ printf("data=0x%jx", (uintmax_t)phdr->p_filesz);
+ if (phdr->p_filesz < phdr->p_memsz)
+ printf("+0x%jx",
+ (uintmax_t)(phdr->p_memsz - phdr->p_filesz));
+ printf(" ");
+ }
+}
+
+static GElf_Off
+ef_get_offset(elf_file_t ef, GElf_Addr addr)
+{
+ GElf_Phdr *ph;
+ int i;
+
+ for (i = 0; i < ef->ef_nsegs; i++) {
+ ph = ef->ef_segs[i];
+ if (addr >= ph->p_vaddr && addr < ph->p_vaddr + ph->p_memsz) {
+ return (ph->p_offset + (addr - ph->p_vaddr));
+ }
+ }
+ return (0);
+}
+
+/*
+ * next two functions copied from link_elf.c
+ */
+static int
+ef_lookup_symbol(elf_file_t ef, const char *name, GElf_Sym **sym, bool see_local)
+{
+ unsigned long hash, symnum;
+ GElf_Sym *symp;
+ char *strp;
+
+ /* First, search hashed global symbols */
+ hash = elf_hash(name);
+ symnum = ef->ef_buckets[hash % ef->ef_nbuckets];
+
+ while (symnum != STN_UNDEF) {
+ if (symnum >= ef->ef_nchains) {
+ warnx("ef_lookup_symbol: file %s have corrupted symbol table\n",
+ ef->ef_name);
+ return (ENOENT);
+ }
+
+ symp = ef->ef_symtab + symnum;
+ if (symp->st_name == 0) {
+ warnx("ef_lookup_symbol: file %s have corrupted symbol table\n",
+ ef->ef_name);
+ return (ENOENT);
+ }
+
+ strp = ef->ef_strtab + symp->st_name;
+
+ if (strcmp(name, strp) == 0) {
+ if (symp->st_shndx != SHN_UNDEF ||
+ (symp->st_value != 0 &&
+ GELF_ST_TYPE(symp->st_info) == STT_FUNC)) {
+ if (see_local ||
+ GELF_ST_BIND(symp->st_info) != STB_LOCAL) {
+ *sym = symp;
+ return (0);
+ }
+ } else
+ return (ENOENT);
+ }
+
+ symnum = ef->ef_chains[symnum];
+ }
+
+ return (ENOENT);
+}
+
+static int
+ef_lookup_set(elf_file_t ef, const char *name, GElf_Addr *startp,
+ GElf_Addr *stopp, long *countp)
+{
+ GElf_Sym *sym;
+ char *setsym;
+ int error, len;
+
+ len = strlen(name) + sizeof("__start_set_"); /* sizeof includes \0 */
+ setsym = malloc(len);
+ if (setsym == NULL)
+ return (errno);
+
+ /* get address of first entry */
+ snprintf(setsym, len, "%s%s", "__start_set_", name);
+ error = ef_lookup_symbol(ef, setsym, &sym, true);
+ if (error != 0)
+ goto out;
+ *startp = sym->st_value;
+
+ /* get address of last entry */
+ snprintf(setsym, len, "%s%s", "__stop_set_", name);
+ error = ef_lookup_symbol(ef, setsym, &sym, true);
+ if (error != 0)
+ goto out;
+ *stopp = sym->st_value;
+
+ /* and the number of entries */
+ *countp = (*stopp - *startp) / elf_pointer_size(ef->ef_efile);
+
+out:
+ free(setsym);
+ return (error);
+}
+
+static GElf_Addr
+ef_symaddr(elf_file_t ef, GElf_Size symidx)
+{
+ const GElf_Sym *sym;
+
+ if (symidx >= ef->ef_nchains)
+ return (0);
+ sym = ef->ef_symtab + symidx;
+
+ if (GELF_ST_BIND(sym->st_info) == STB_LOCAL &&
+ sym->st_shndx != SHN_UNDEF && sym->st_value != 0)
+ return (sym->st_value);
+ return (0);
+}
+
+static int
+ef_parse_dynamic(elf_file_t ef, const GElf_Phdr *phdyn)
+{
+ GElf_Shdr *shdr;
+ GElf_Dyn *dyn, *dp;
+ size_t i, ndyn, nshdr, nsym;
+ int error;
+ GElf_Off hash_off, sym_off, str_off;
+ GElf_Off rel_off;
+ GElf_Off rela_off;
+ int rel_sz;
+ int rela_sz;
+ int dynamic_idx;
+
+ /*
+ * The kernel linker parses the PT_DYNAMIC segment to find
+ * various important tables. The gelf API of libelf is
+ * section-oriented and requires extracting data from sections
+ * instead of segments (program headers). As a result,
+ * iterate over section headers to read various tables after
+ * parsing values from PT_DYNAMIC.
+ */
+ error = elf_read_shdrs(ef->ef_efile, &nshdr, &shdr);
+ if (error != 0)
+ return (EFTYPE);
+ dyn = NULL;
+
+ /* Find section for .dynamic. */
+ dynamic_idx = -1;
+ for (i = 0; i < nshdr; i++) {
+ if (shdr[i].sh_type == SHT_DYNAMIC) {
+ /*
+ * PowerPC kernels contain additional sections
+ * beyond .dynamic in PT_DYNAMIC due to a linker
+ * script bug. Permit a section with a smaller
+ * size as a workaround.
+ */
+ if (shdr[i].sh_offset != phdyn->p_offset ||
+ ((elf_machine(ef->ef_efile) == EM_PPC ||
+ elf_machine(ef->ef_efile) == EM_PPC64) ?
+ shdr[i].sh_size > phdyn->p_filesz :
+ shdr[i].sh_size != phdyn->p_filesz)) {
+ warnx(".dynamic section doesn't match phdr");
+ error = EFTYPE;
+ goto out;
+ }
+ if (dynamic_idx != -1) {
+ warnx("multiple SHT_DYNAMIC sections");
+ error = EFTYPE;
+ goto out;
+ }
+ dynamic_idx = i;
+ }
+ }
+
+ error = elf_read_dynamic(ef->ef_efile, dynamic_idx, &ndyn, &dyn);
+ if (error != 0)
+ goto out;
+
+ hash_off = rel_off = rela_off = sym_off = str_off = 0;
+ rel_sz = rela_sz = 0;
+ for (i = 0; i < ndyn; i++) {
+ dp = &dyn[i];
+ if (dp->d_tag == DT_NULL)
+ break;
+
+ switch (dp->d_tag) {
+ case DT_HASH:
+ if (hash_off != 0)
+ warnx("second DT_HASH entry ignored");
+ else
+ hash_off = ef_get_offset(ef, dp->d_un.d_ptr);
+ break;
+ case DT_STRTAB:
+ if (str_off != 0)
+ warnx("second DT_STRTAB entry ignored");
+ else
+ str_off = ef_get_offset(ef, dp->d_un.d_ptr);
+ break;
+ case DT_SYMTAB:
+ if (sym_off != 0)
+ warnx("second DT_SYMTAB entry ignored");
+ else
+ sym_off = ef_get_offset(ef, dp->d_un.d_ptr);
+ break;
+ case DT_SYMENT:
+ if (dp->d_un.d_val != elf_object_size(ef->ef_efile,
+ ELF_T_SYM)) {
+ error = EFTYPE;
+ goto out;
+ }
+ break;
+ case DT_REL:
+ if (rel_off != 0)
+ warnx("second DT_REL entry ignored");
+ else
+ rel_off = ef_get_offset(ef, dp->d_un.d_ptr);
+ break;
+ case DT_RELSZ:
+ if (rel_sz != 0)
+ warnx("second DT_RELSZ entry ignored");
+ else
+ rel_sz = dp->d_un.d_val;
+ break;
+ case DT_RELENT:
+ if (dp->d_un.d_val != elf_object_size(ef->ef_efile,
+ ELF_T_REL)) {
+ error = EFTYPE;
+ goto out;
+ }
+ break;
+ case DT_RELA:
+ if (rela_off != 0)
+ warnx("second DT_RELA entry ignored");
+ else
+ rela_off = ef_get_offset(ef, dp->d_un.d_ptr);
+ break;
+ case DT_RELASZ:
+ if (rela_sz != 0)
+ warnx("second DT_RELSZ entry ignored");
+ else
+ rela_sz = dp->d_un.d_val;
+ break;
+ case DT_RELAENT:
+ if (dp->d_un.d_val != elf_object_size(ef->ef_efile,
+ ELF_T_RELA)) {
+ error = EFTYPE;
+ goto out;
+ }
+ break;
+ }
+ }
+ if (hash_off == 0) {
+ warnx("%s: no .hash section found\n", ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+ if (sym_off == 0) {
+ warnx("%s: no .dynsym section found\n", ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+ if (str_off == 0) {
+ warnx("%s: no .dynstr section found\n", ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+
+ nsym = 0;
+ for (i = 0; i < nshdr; i++) {
+ switch (shdr[i].sh_type) {
+ case SHT_HASH:
+ if (shdr[i].sh_offset != hash_off) {
+ warnx("%s: ignoring SHT_HASH at different offset from DT_HASH",
+ ef->ef_name);
+ break;
+ }
+
+ /*
+ * libelf(3) mentions ELF_T_HASH, but it is
+ * not defined.
+ */
+ if (shdr[i].sh_size < sizeof(*ef->ef_hashtab) * 2) {
+ warnx("hash section too small");
+ error = EFTYPE;
+ goto out;
+ }
+ error = elf_read_data(ef->ef_efile, ELF_T_WORD,
+ shdr[i].sh_offset, shdr[i].sh_size,
+ (void **)&ef->ef_hashtab);
+ if (error != 0) {
+ warnc(error, "can't read hash table");
+ goto out;
+ }
+ ef->ef_nbuckets = ef->ef_hashtab[0];
+ ef->ef_nchains = ef->ef_hashtab[1];
+ if ((2 + ef->ef_nbuckets + ef->ef_nchains) *
+ sizeof(*ef->ef_hashtab) != shdr[i].sh_size) {
+ warnx("inconsistent hash section size");
+ error = EFTYPE;
+ goto out;
+ }
+
+ ef->ef_buckets = ef->ef_hashtab + 2;
+ ef->ef_chains = ef->ef_buckets + ef->ef_nbuckets;
+ break;
+ case SHT_DYNSYM:
+ if (shdr[i].sh_offset != sym_off) {
+ warnx("%s: ignoring SHT_DYNSYM at different offset from DT_SYMTAB",
+ ef->ef_name);
+ break;
+ }
+ error = elf_read_symbols(ef->ef_efile, i, &nsym,
+ &ef->ef_symtab);
+ if (error != 0) {
+ if (ef->ef_verbose)
+ warnx("%s: can't load .dynsym section (0x%jx)",
+ ef->ef_name, (uintmax_t)sym_off);
+ goto out;
+ }
+ break;
+ case SHT_STRTAB:
+ if (shdr[i].sh_offset != str_off)
+ break;
+ error = elf_read_string_table(ef->ef_efile,
+ &shdr[i], &ef->ef_strsz, &ef->ef_strtab);
+ if (error != 0) {
+ warnx("can't load .dynstr section");
+ error = EIO;
+ goto out;
+ }
+ break;
+ case SHT_REL:
+ if (shdr[i].sh_offset != rel_off)
+ break;
+ if (shdr[i].sh_size != rel_sz) {
+ warnx("%s: size mismatch for DT_REL section",
+ ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+ error = elf_read_rel(ef->ef_efile, i, &ef->ef_relsz,
+ &ef->ef_rel);
+ if (error != 0) {
+ warnx("%s: cannot load DT_REL section",
+ ef->ef_name);
+ goto out;
+ }
+ break;
+ case SHT_RELA:
+ if (shdr[i].sh_offset != rela_off)
+ break;
+ if (shdr[i].sh_size != rela_sz) {
+ warnx("%s: size mismatch for DT_RELA section",
+ ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+ error = elf_read_rela(ef->ef_efile, i, &ef->ef_relasz,
+ &ef->ef_rela);
+ if (error != 0) {
+ warnx("%s: cannot load DT_RELA section",
+ ef->ef_name);
+ goto out;
+ }
+ break;
+ }
+ }
+
+ if (ef->ef_hashtab == NULL) {
+ warnx("%s: did not find a symbol hash table", ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+ if (ef->ef_symtab == NULL) {
+ warnx("%s: did not find a dynamic symbol table", ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+ if (nsym != ef->ef_nchains) {
+ warnx("%s: symbol count mismatch", ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+ if (ef->ef_strtab == NULL) {
+ warnx("%s: did not find a dynamic string table", ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+ if (rel_off != 0 && ef->ef_rel == NULL) {
+ warnx("%s: did not find a DT_REL relocation table",
+ ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+ if (rela_off != 0 && ef->ef_rela == NULL) {
+ warnx("%s: did not find a DT_RELA relocation table",
+ ef->ef_name);
+ error = EFTYPE;
+ goto out;
+ }
+
+ error = 0;
+out:
+ free(dyn);
+ free(shdr);
+ return (error);
+}
+
+static int
+ef_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len, void *dest)
+{
+ GElf_Off ofs;
+ const GElf_Rela *a;
+ const GElf_Rel *r;
+ int error;
+
+ ofs = ef_get_offset(ef, address);
+ if (ofs == 0) {
+ if (ef->ef_verbose)
+ warnx("ef_seg_read_rel(%s): bad address (%jx)",
+ ef->ef_name, (uintmax_t)address);
+ return (EFAULT);
+ }
+ error = elf_read_raw_data(ef->ef_efile, ofs, dest, len);
+ if (error != 0)
+ return (error);
+
+ for (r = ef->ef_rel; r < &ef->ef_rel[ef->ef_relsz]; r++) {
+ error = elf_reloc(ef->ef_efile, r, ELF_T_REL, 0, address,
+ len, dest);
+ if (error != 0)
+ return (error);
+ }
+ for (a = ef->ef_rela; a < &ef->ef_rela[ef->ef_relasz]; a++) {
+ error = elf_reloc(ef->ef_efile, a, ELF_T_RELA, 0, address,
+ len, dest);
+ if (error != 0)
+ return (error);
+ }
+ return (0);
+}
+
+static int
+ef_seg_read_string(elf_file_t ef, GElf_Addr address, size_t len, char *dest)
+{
+ GElf_Off ofs;
+
+ ofs = ef_get_offset(ef, address);
+ if (ofs == 0) {
+ if (ef->ef_verbose)
+ warnx("ef_seg_read_string(%s): bad offset (%jx:%ju)",
+ ef->ef_name, (uintmax_t)address, (uintmax_t)ofs);
+ return (EFAULT);
+ }
+
+ return (elf_read_raw_string(ef->ef_efile, ofs, dest, len));
+}
+
+int
+ef_open(struct elf_file *efile, int verbose)
+{
+ elf_file_t ef;
+ GElf_Ehdr *hdr;
+ size_t i, nphdr, nsegs;
+ int error;
+ GElf_Phdr *phdr, *phdyn;
+
+ hdr = &efile->ef_hdr;
+ if (hdr->e_phnum == 0 ||
+ hdr->e_phentsize != elf_object_size(efile, ELF_T_PHDR) ||
+ hdr->e_shnum == 0 || hdr->e_shoff == 0 ||
+ hdr->e_shentsize != elf_object_size(efile, ELF_T_SHDR))
+ return (EFTYPE);
+
+ ef = malloc(sizeof(*ef));
+ if (ef == NULL)
+ return (errno);
+
+ efile->ef_ef = ef;
+ efile->ef_ops = &ef_file_ops;
+
+ bzero(ef, sizeof(*ef));
+ ef->ef_verbose = verbose;
+ ef->ef_name = strdup(efile->ef_filename);
+ ef->ef_efile = efile;
+
+ error = elf_read_phdrs(efile, &nphdr, &ef->ef_ph);
+ if (error != 0) {
+ phdr = NULL;
+ goto out;
+ }
+
+ error = EFTYPE;
+ nsegs = 0;
+ phdyn = NULL;
+ phdr = ef->ef_ph;
+ for (i = 0; i < nphdr; i++, phdr++) {
+ if (verbose > 1)
+ ef_print_phdr(phdr);
+ switch (phdr->p_type) {
+ case PT_LOAD:
+ if (nsegs < MAXSEGS)
+ ef->ef_segs[nsegs] = phdr;
+ nsegs++;
+ break;
+ case PT_PHDR:
+ break;
+ case PT_DYNAMIC:
+ phdyn = phdr;
+ break;
+ }
+ }
+ if (verbose > 1)
+ printf("\n");
+ if (phdyn == NULL) {
+ warnx("Skipping %s: not dynamically-linked",
+ ef->ef_name);
+ goto out;
+ }
+
+ if (nsegs > MAXSEGS) {
+ warnx("%s: too many segments", ef->ef_name);
+ goto out;
+ }
+ ef->ef_nsegs = nsegs;
+
+ error = ef_parse_dynamic(ef, phdyn);
+out:
+ if (error != 0)
+ ef_close(ef);
+ return (error);
+}
+
+static void
+ef_close(elf_file_t ef)
+{
+ free(ef->ef_rela);
+ free(ef->ef_rel);
+ free(ef->ef_strtab);
+ free(ef->ef_symtab);
+ free(ef->ef_hashtab);
+ free(ef->ef_ph);
+ if (ef->ef_name)
+ free(ef->ef_name);
+ ef->ef_efile->ef_ops = NULL;
+ ef->ef_efile->ef_ef = NULL;
+ free(ef);
+}