git: 0299afdff145 - main - kldxref: Make use of libelf to be a portable cross tool

From: John Baldwin <jhb_at_FreeBSD.org>
Date: Tue, 12 Dec 2023 23:45:07 UTC
The branch main has been updated by jhb:

URL: https://cgit.FreeBSD.org/src/commit/?id=0299afdff145e5d861797fe9c2de8b090c456fba

commit 0299afdff145e5d861797fe9c2de8b090c456fba
Author:     John Baldwin <jhb@FreeBSD.org>
AuthorDate: 2023-12-12 23:43:00 +0000
Commit:     John Baldwin <jhb@FreeBSD.org>
CommitDate: 2023-12-12 23:43:00 +0000

    kldxref: Make use of libelf to be a portable cross tool
    
    This allows kldxref to operate on kernel objects from any
    architecture, not just the native architecture.  In particular, this
    will permit generating linker.hints files as part of a cross-arch
    release build.
    
    - elf.c is a new file that includes various wrappers around libelf
      including routines to read ELF data structures such as program and
      section headers and ELF relocations into the "generic" forms
      described in <gelf.h>.  This file also provides routines for
      converting a linker set into an array of addresses (GElf_Addr)
      as well as reading architecture-specific mod_* structures and
      converting them into "generic" Gmod_* forms where pointers are
      replaced with addresses.
    
    - The various architecture-specific reloc handlers now use GElf_*
      types for most values (including GElf_Rel and GElf_Rela for
      relocation structures) and use routines from <sys/endian.h> to read
      and write target values.  A new linker set matches reloc handlers
      to specific ELF (class, encoding, machine) tuples.
    
    - The bits of kldxref.c that write out linker.hints now use the
      encoding (ELFDATA2[LM]SB) of the first file encountered in a
      directory to set the endianness of the output file.  Input files
      with a different architecture in the same directory are skipped with
      a warning.  In addition, the initial version record for the file
      must be deferred until the first record is finished since the
      architecture of the output file is not known until then.
    
    - Various places that used 'sizeof(void *)' throughout now use
      'elf_pointer_size()' to determine the size of a pointer in the
      target architecture.
    
    Tested by:      amd64 binary on both amd64 and i386 /boot/kernel
    Reviewed by:    imp
    Sponsored by:   DARPA
    Differential Revision:  https://reviews.freebsd.org/D42966
---
 usr.sbin/kldxref/Makefile     |   9 +-
 usr.sbin/kldxref/ef.c         | 730 ++++++++++++++++++++----------------------
 usr.sbin/kldxref/ef.h         | 291 ++++++++++++++---
 usr.sbin/kldxref/ef_aarch64.c |  32 +-
 usr.sbin/kldxref/ef_amd64.c   |  65 ++--
 usr.sbin/kldxref/ef_i386.c    |  56 ++--
 usr.sbin/kldxref/ef_mips.c    |  79 +++--
 usr.sbin/kldxref/ef_nop.c     |  40 ---
 usr.sbin/kldxref/ef_obj.c     | 341 ++++++--------------
 usr.sbin/kldxref/ef_powerpc.c |  62 ++--
 usr.sbin/kldxref/ef_riscv.c   |  35 +-
 usr.sbin/kldxref/elf.c        | 674 ++++++++++++++++++++++++++++++++++++++
 usr.sbin/kldxref/kldxref.c    | 196 +++++++-----
 13 files changed, 1685 insertions(+), 925 deletions(-)

diff --git a/usr.sbin/kldxref/Makefile b/usr.sbin/kldxref/Makefile
index 3d4ce7163b48..6e0a7244328b 100644
--- a/usr.sbin/kldxref/Makefile
+++ b/usr.sbin/kldxref/Makefile
@@ -2,14 +2,11 @@
 PACKAGE=	runtime
 PROG=	kldxref
 MAN=	kldxref.8
-SRCS=	kldxref.c ef.c ef_obj.c
+SRCS=	kldxref.c ef.c ef_obj.c elf.c
+SRCS+=	ef_aarch64.c ef_amd64.c ef_i386.c ef_mips.c ef_powerpc.c ef_riscv.c
 
 WARNS?=	2
 
-.if exists(ef_${MACHINE_CPUARCH}.c)
-SRCS+=	ef_${MACHINE_CPUARCH}.c
-.else
-SRCS+=	ef_nop.c
-.endif
+LIBADD=	elf
 
 .include <bsd.prog.mk>
diff --git a/usr.sbin/kldxref/ef.c b/usr.sbin/kldxref/ef.c
index 72e023e30783..aa9123d7f540 100644
--- a/usr.sbin/kldxref/ef.c
+++ b/usr.sbin/kldxref/ef.c
@@ -33,16 +33,13 @@
  */
 
 #include <sys/param.h>
-#include <sys/linker.h>
 
 #include <err.h>
 #include <errno.h>
-#include <fcntl.h>
+#include <gelf.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <unistd.h>
-#include <machine/elf.h>
 
 #include "ef.h"
 
@@ -50,76 +47,52 @@
 struct ef_file {
 	char		*ef_name;
 	struct elf_file *ef_efile;
-	Elf_Phdr	*ef_ph;
-	int		ef_fd;
-	int		ef_type;
-	Elf_Ehdr	ef_hdr;
+	GElf_Phdr	*ef_ph;
 	void		*ef_fpage;		/* First block of the file */
 	int		ef_fplen;		/* length of first block */
-	Elf_Dyn		*ef_dyn;		/* Symbol table etc. */
-	Elf_Hashelt	ef_nbuckets;
-	Elf_Hashelt	ef_nchains;
-	Elf_Hashelt	*ef_buckets;
-	Elf_Hashelt	*ef_chains;
-	Elf_Hashelt	*ef_hashtab;
-	Elf_Off		ef_stroff;
+	GElf_Hashelt	ef_nbuckets;
+	GElf_Hashelt	ef_nchains;
+	GElf_Hashelt	*ef_buckets;
+	GElf_Hashelt	*ef_chains;
+	GElf_Hashelt	*ef_hashtab;
 	caddr_t		ef_strtab;
-	int		ef_strsz;
-	Elf_Off		ef_symoff;
-	Elf_Sym		*ef_symtab;
+	long		ef_strsz;
+	GElf_Sym	*ef_symtab;
 	int		ef_nsegs;
-	Elf_Phdr	*ef_segs[MAXSEGS];
+	GElf_Phdr	*ef_segs[MAXSEGS];
 	int		ef_verbose;
-	Elf_Rel		*ef_rel;		/* relocation table */
-	int		ef_relsz;		/* number of entries */
-	Elf_Rela	*ef_rela;		/* relocation table */
-	int		ef_relasz;		/* number of entries */
+	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(Elf_Phdr *);
-static Elf_Off	ef_get_offset(elf_file_t, Elf_Off);
-static int	ef_parse_dynamic(elf_file_t);
+static void	ef_print_phdr(GElf_Phdr *);
+static GElf_Off	ef_get_offset(elf_file_t, GElf_Addr);
 
-static int	ef_get_type(elf_file_t ef);
-static int	ef_close(elf_file_t ef);
-static int	ef_read(elf_file_t ef, Elf_Off offset, size_t len, void *dest);
-static int	ef_read_entry(elf_file_t ef, Elf_Off offset, size_t len,
-		    void **ptr);
+static void	ef_close(elf_file_t ef);
 
-static int	ef_seg_read(elf_file_t ef, Elf_Off offset, size_t len,
+static int	ef_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len,
 		    void *dest);
-static int	ef_seg_read_rel(elf_file_t ef, Elf_Off offset, size_t len,
-		    void *dest);
-static int	ef_seg_read_string(elf_file_t ef, Elf_Off offset, size_t len,
+static int	ef_seg_read_string(elf_file_t ef, GElf_Addr address, size_t len,
 		    char *dest);
-static int	ef_seg_read_entry(elf_file_t ef, Elf_Off offset, size_t len,
-		    void **ptr);
-static int	ef_seg_read_entry_rel(elf_file_t ef, Elf_Off offset, size_t len,
-		    void **ptr);
-
-static Elf_Addr	ef_symaddr(elf_file_t ef, Elf_Size symidx);
-static int	ef_lookup_set(elf_file_t ef, const char *name, long *startp,
-		    long *stopp, long *countp);
+
+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,
-		    Elf_Sym **sym);
+		    GElf_Sym **sym);
 
 static struct elf_file_ops ef_file_ops = {
-	.get_type		= ef_get_type,
 	.close			= ef_close,
-	.read			= ef_read,
-	.read_entry		= ef_read_entry,
-	.seg_read		= ef_seg_read,
 	.seg_read_rel		= ef_seg_read_rel,
 	.seg_read_string	= ef_seg_read_string,
-	.seg_read_entry		= ef_seg_read_entry,
-	.seg_read_entry_rel	= ef_seg_read_entry_rel,
 	.symaddr		= ef_symaddr,
 	.lookup_set		= ef_lookup_set,
-	.lookup_symbol		= ef_lookup_symbol
 };
 
 static void
-ef_print_phdr(Elf_Phdr *phdr)
+ef_print_phdr(GElf_Phdr *phdr)
 {
 
 	if ((phdr->p_flags & PF_W) == 0) {
@@ -133,53 +106,29 @@ ef_print_phdr(Elf_Phdr *phdr)
 	}
 }
 
-static Elf_Off
-ef_get_offset(elf_file_t ef, Elf_Off off)
+static GElf_Off
+ef_get_offset(elf_file_t ef, GElf_Addr addr)
 {
-	Elf_Phdr *ph;
+	GElf_Phdr *ph;
 	int i;
 
 	for (i = 0; i < ef->ef_nsegs; i++) {
 		ph = ef->ef_segs[i];
-		if (off >= ph->p_vaddr && off < ph->p_vaddr + ph->p_memsz) {
-			return (ph->p_offset + (off - ph->p_vaddr));
+		if (addr >= ph->p_vaddr && addr < ph->p_vaddr + ph->p_memsz) {
+			return (ph->p_offset + (addr - ph->p_vaddr));
 		}
 	}
 	return (0);
 }
 
-static int
-ef_get_type(elf_file_t ef)
-{
-
-	return (ef->ef_type);
-}
-
 /*
- * next three functions copied from link_elf.c
+ * next two functions copied from link_elf.c
  */
-static unsigned long
-elf_hash(const char *name)
-{
-	unsigned long h, g;
-	const unsigned char *p;
-
-	h = 0;
-	p = (const unsigned char *)name;
-	while (*p != '\0') {
-		h = (h << 4) + *p++;
-		if ((g = h & 0xf0000000) != 0)
-			h ^= g >> 24;
-		h &= ~g;
-	}
-	return (h);
-}
-
 static int
-ef_lookup_symbol(elf_file_t ef, const char *name, Elf_Sym **sym)
+ef_lookup_symbol(elf_file_t ef, const char *name, GElf_Sym **sym)
 {
 	unsigned long hash, symnum;
-	Elf_Sym *symp;
+	GElf_Sym *symp;
 	char *strp;
 
 	/* First, search hashed global symbols */
@@ -205,7 +154,7 @@ ef_lookup_symbol(elf_file_t ef, const char *name, Elf_Sym **sym)
 		if (strcmp(name, strp) == 0) {
 			if (symp->st_shndx != SHN_UNDEF ||
 			    (symp->st_value != 0 &&
-				ELF_ST_TYPE(symp->st_info) == STT_FUNC)) {
+				GELF_ST_TYPE(symp->st_info) == STT_FUNC)) {
 				*sym = symp;
 				return (0);
 			} else
@@ -219,10 +168,10 @@ ef_lookup_symbol(elf_file_t ef, const char *name, Elf_Sym **sym)
 }
 
 static int
-ef_lookup_set(elf_file_t ef, const char *name, long *startp, long *stopp,
-    long *countp)
+ef_lookup_set(elf_file_t ef, const char *name, GElf_Addr *startp,
+    GElf_Addr *stopp, long *countp)
 {
-	Elf_Sym *sym;
+	GElf_Sym *sym;
 	char *setsym;
 	int error, len;
 
@@ -246,258 +195,340 @@ ef_lookup_set(elf_file_t ef, const char *name, long *startp, long *stopp,
 	*stopp = sym->st_value;
 
 	/* and the number of entries */
-	*countp = (*stopp - *startp) / sizeof(void *);
+	*countp = (*stopp - *startp) / elf_pointer_size(ef->ef_efile);
 
 out:
 	free(setsym);
 	return (error);
 }
 
-static Elf_Addr
-ef_symaddr(elf_file_t ef, Elf_Size symidx)
+static GElf_Addr
+ef_symaddr(elf_file_t ef, GElf_Size symidx)
 {
-	const Elf_Sym *sym;
+	const GElf_Sym *sym;
 
 	if (symidx >= ef->ef_nchains)
 		return (0);
 	sym = ef->ef_symtab + symidx;
 
-	if (ELF_ST_BIND(sym->st_info) == STB_LOCAL &&
+	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)
+ef_parse_dynamic(elf_file_t ef, const GElf_Phdr *phdyn)
 {
-	Elf_Dyn *dp;
-	Elf_Hashelt hashhdr[2];
+	GElf_Shdr *shdr;
+	GElf_Dyn *dyn, *dp;
+	size_t i, ndyn, nshdr, nsym;
 	int error;
-	Elf_Off rel_off;
-	Elf_Off rela_off;
+	GElf_Off hash_off, sym_off, str_off;
+	GElf_Off rel_off;
+	GElf_Off rela_off;
 	int rel_sz;
 	int rela_sz;
-	int rel_entry;
-	int rela_entry;
+	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) {
+			if (shdr[i].sh_offset != phdyn->p_offset ||
+			    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;
 
-	rel_off = rela_off = 0;
+	hash_off = rel_off = rela_off = sym_off = str_off = 0;
 	rel_sz = rela_sz = 0;
-	rel_entry = rela_entry = 0;
-	for (dp = ef->ef_dyn; dp->d_tag != DT_NULL; dp++) {
+	for (i = 0; i < ndyn; i++) {
+		dp = &dyn[i];
+		if (dp->d_tag == DT_NULL)
+			break;
+
 		switch (dp->d_tag) {
 		case DT_HASH:
-			error = ef_read(ef, ef_get_offset(ef, dp->d_un.d_ptr),
-			    sizeof(hashhdr),  hashhdr);
-			if (error != 0) {
-				warnx("can't read hash header (%jx)",
-				  (uintmax_t)ef_get_offset(ef, dp->d_un.d_ptr));
-				return (error);
-			}
-			ef->ef_nbuckets = hashhdr[0];
-			ef->ef_nchains = hashhdr[1];
-			error = ef_read_entry(ef, -1, 
-			    (hashhdr[0] + hashhdr[1]) * sizeof(Elf_Hashelt),
-			    (void **)&ef->ef_hashtab);
-			if (error != 0) {
-				warnx("can't read hash table");
-				return (error);
-			}
-			ef->ef_buckets = ef->ef_hashtab;
-			ef->ef_chains = ef->ef_buckets + ef->ef_nbuckets;
+			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:
-			ef->ef_stroff = dp->d_un.d_ptr;
-			break;
-		case DT_STRSZ:
-			ef->ef_strsz = dp->d_un.d_val;
+			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:
-			ef->ef_symoff = dp->d_un.d_ptr;
+			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 != sizeof(Elf_Sym))
-				return (EFTYPE);
+			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");
-			rel_off = dp->d_un.d_ptr;
+			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");
-			rel_sz = dp->d_un.d_val;
+			else
+				rel_sz = dp->d_un.d_val;
 			break;
 		case DT_RELENT:
-			if (rel_entry != 0)
-				warnx("second DT_RELENT entry ignored");
-			rel_entry = dp->d_un.d_val;
+			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");
-			rela_off = dp->d_un.d_ptr;
+			else
+				rela_off = ef_get_offset(ef, dp->d_un.d_ptr);
 			break;
 		case DT_RELASZ:
 			if (rela_sz != 0)
-				warnx("second DT_RELASZ entry ignored");
-			rela_sz = dp->d_un.d_val;
+				warnx("second DT_RELSZ entry ignored");
+			else
+				rela_sz = dp->d_un.d_val;
 			break;
 		case DT_RELAENT:
-			if (rela_entry != 0)
-				warnx("second DT_RELAENT entry ignored");
-			rela_entry = dp->d_un.d_val;
+			if (dp->d_un.d_val != elf_object_size(ef->ef_efile,
+			    ELF_T_RELA)) {
+				error = EFTYPE;
+				goto out;
+			}
 			break;
 		}
 	}
-	if (ef->ef_symoff == 0) {
+	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);
-		return (EFTYPE);
+		error = EFTYPE;
+		goto out;
 	}
-	if (ef->ef_stroff == 0) {
+	if (str_off == 0) {
 		warnx("%s: no .dynstr section found\n", ef->ef_name);
-		return (EFTYPE);
-	}
-	if (ef_read_entry(ef, ef_get_offset(ef, ef->ef_symoff),
-	    ef->ef_nchains * sizeof(Elf_Sym),
-		(void **)&ef->ef_symtab) != 0) {
-		if (ef->ef_verbose)
-			warnx("%s: can't load .dynsym section (0x%jx)",
-			    ef->ef_name, (uintmax_t)ef->ef_symoff);
-		return (EIO);
-	}
-	if (ef_read_entry(ef, ef_get_offset(ef, ef->ef_stroff), ef->ef_strsz,
-		(void **)&ef->ef_strtab) != 0) {
-		warnx("can't load .dynstr section");
-		return (EIO);
-	}
-	if (rel_off != 0) {
-		if (rel_entry == 0) {
-			warnx("%s: no DT_RELENT for DT_REL", ef->ef_name);
-			return (EFTYPE);
-		}
-		if (rel_entry != sizeof(Elf_Rel)) {
-			warnx("%s: inconsistent DT_RELENT value",
-			    ef->ef_name);
-			return (EFTYPE);
-		}
-		if (rel_sz % rel_entry != 0) {
-			warnx("%s: inconsistent values for DT_RELSZ and "
-			    "DT_RELENT", ef->ef_name);
-			return (EFTYPE);
-		}
-		if (ef_read_entry(ef, ef_get_offset(ef, rel_off), rel_sz,
-		    (void **)&ef->ef_rel) != 0) {
-			warnx("%s: cannot load DT_REL section", ef->ef_name);
-			return (EIO);
-		}
-		ef->ef_relsz = rel_sz / rel_entry;
-		if (ef->ef_verbose)
-			warnx("%s: %d REL entries", ef->ef_name,
-			    ef->ef_relsz);
+		error = EFTYPE;
+		goto out;
 	}
-	if (rela_off != 0) {
-		if (rela_entry == 0) {
-			warnx("%s: no DT_RELAENT for DT_RELA", ef->ef_name);
-			return (EFTYPE);
-		}
-		if (rela_entry != sizeof(Elf_Rela)) {
-			warnx("%s: inconsistent DT_RELAENT value",
-			    ef->ef_name);
-			return (EFTYPE);
-		}
-		if (rela_sz % rela_entry != 0) {
-			warnx("%s: inconsistent values for DT_RELASZ and "
-			    "DT_RELAENT", ef->ef_name);
-			return (EFTYPE);
-		}
-		if (ef_read_entry(ef, ef_get_offset(ef, rela_off), rela_sz,
-		    (void **)&ef->ef_rela) != 0) {
-			warnx("%s: cannot load DT_RELA section", ef->ef_name);
-			return (EIO);
-		}
-		ef->ef_relasz = rela_sz / rela_entry;
-		if (ef->ef_verbose)
-			warnx("%s: %d RELA entries", ef->ef_name,
-			    ef->ef_relasz);
+	if (rel_off == 0 && rela_off == 0) {
+		warnx("%s: no ELF relocation table found\n", ef->ef_name);
+		error = EFTYPE;
+		goto out;
 	}
-	return (0);
-}
 
-static int
-ef_read(elf_file_t ef, Elf_Off offset, size_t len, void *dest)
-{
-	ssize_t r;
+	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;
+			}
 
-	if (offset != (Elf_Off)-1) {
-		if (lseek(ef->ef_fd, offset, SEEK_SET) == -1)
-			return (EIO);
-	}
+			/*
+			 * 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;
+			}
 
-	r = read(ef->ef_fd, dest, len);
-	if (r != -1 && (size_t)r == len)
-		return (0);
-	else
-		return (EIO);
-}
+			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;
+		}
+	}
 
-static int
-ef_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void **ptr)
-{
-	int error;
+	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;
+	}
 
-	*ptr = malloc(len);
-	if (*ptr == NULL)
-		return (errno);
-	error = ef_read(ef, offset, len, *ptr);
-	if (error != 0)
-		free(*ptr);
+	error = 0;
+out:
+	free(dyn);
+	free(shdr);
 	return (error);
 }
 
 static int
-ef_seg_read(elf_file_t ef, Elf_Off offset, size_t len, void *dest)
+ef_seg_read_rel(elf_file_t ef, GElf_Addr address, size_t len, void *dest)
 {
-	Elf_Off ofs;
-
-	ofs = ef_get_offset(ef, offset);
-	if (ofs == 0) {
-		if (ef->ef_verbose)
-			warnx("ef_seg_read(%s): zero offset (%jx:%ju)",
-			    ef->ef_name, (uintmax_t)offset, (uintmax_t)ofs);
-		return (EFAULT);
-	}
-	return (ef_read(ef, ofs, len, dest));
-}
-
-static int
-ef_seg_read_rel(elf_file_t ef, Elf_Off offset, size_t len, void *dest)
-{
-	Elf_Off ofs;
-	const Elf_Rela *a;
-	const Elf_Rel *r;
+	GElf_Off ofs;
+	const GElf_Rela *a;
+	const GElf_Rel *r;
 	int error;
 
-	ofs = ef_get_offset(ef, offset);
+	ofs = ef_get_offset(ef, address);
 	if (ofs == 0) {
 		if (ef->ef_verbose)
 			warnx("ef_seg_read_rel(%s): zero offset (%jx:%ju)",
-			    ef->ef_name, (uintmax_t)offset, (uintmax_t)ofs);
+			    ef->ef_name, (uintmax_t)address, (uintmax_t)ofs);
 		return (EFAULT);
 	}
-	if ((error = ef_read(ef, ofs, len, dest)) != 0)
+	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 = ef_reloc(ef->ef_efile, r, EF_RELOC_REL, 0, offset, len,
-		    dest);
+		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 = ef_reloc(ef->ef_efile, a, EF_RELOC_RELA, 0, offset, len,
-		    dest);
+		error = elf_reloc(ef->ef_efile, a, ELF_T_RELA, 0, address,
+		    len, dest);
 		if (error != 0)
 			return (error);
 	}
@@ -505,168 +536,115 @@ ef_seg_read_rel(elf_file_t ef, Elf_Off offset, size_t len, void *dest)
 }
 
 static int
-ef_seg_read_string(elf_file_t ef, Elf_Off offset, size_t len, char *dest)
+ef_seg_read_string(elf_file_t ef, GElf_Addr address, size_t len, char *dest)
 {
-	Elf_Off ofs;
-	ssize_t r;
+	GElf_Off ofs;
+	int error;
 
-	ofs = ef_get_offset(ef, offset);
-	if (ofs == 0 || ofs == (Elf_Off)-1) {
+	ofs = ef_get_offset(ef, address);
+	if (ofs == 0 || ofs == (GElf_Off)-1) {
 		if (ef->ef_verbose)
 			warnx("ef_seg_read_string(%s): bad offset (%jx:%ju)",
-			    ef->ef_name, (uintmax_t)offset, (uintmax_t)ofs);
+			    ef->ef_name, (uintmax_t)address, (uintmax_t)ofs);
 		return (EFAULT);
 	}
 
-	r = pread(ef->ef_fd, dest, len, ofs);
-	if (r < 0)
-		return (errno);
+	error = elf_read_raw_data(ef->ef_efile, ofs, dest, len);
+	if (error != 0)
+		return (error);
 	if (strnlen(dest, len) == len)
 		return (EFAULT);
 
 	return (0);
 }
 
-static int
-ef_seg_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void **ptr)
-{
-	int error;
-
-	*ptr = malloc(len);
-	if (*ptr == NULL)
-		return (errno);
-	error = ef_seg_read(ef, offset, len, *ptr);
-	if (error != 0)
-		free(*ptr);
-	return (error);
-}
-
-static int
-ef_seg_read_entry_rel(elf_file_t ef, Elf_Off offset, size_t len, void **ptr)
-{
-	int error;
-
-	*ptr = malloc(len);
-	if (*ptr == NULL)
-		return (errno);
-	error = ef_seg_read_rel(ef, offset, len, *ptr);
-	if (error != 0)
-		free(*ptr);
-	return (error);
-}
-
 int
-ef_open(const char *filename, struct elf_file *efile, int verbose)
+ef_open(struct elf_file *efile, int verbose)
 {
 	elf_file_t ef;
-	Elf_Ehdr *hdr;
-	int fd;
+	GElf_Ehdr *hdr;
+	size_t i, nphdr, nsegs;
 	int error;
-	int phlen, res;
-	int nsegs;
-	Elf_Phdr *phdr, *phdyn, *phlimit;
+	GElf_Phdr *phdr, *phdyn;
 
-	if (filename == NULL)
-		return (EINVAL);
-	if ((fd = open(filename, O_RDONLY)) == -1)
-		return (errno);
+	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) {
-		close(fd);
+	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_fd = fd;
-	ef->ef_name = strdup(filename);
+	ef->ef_name = strdup(efile->ef_filename);
 	ef->ef_efile = efile;
-	hdr = (Elf_Ehdr *)&ef->ef_hdr;
-	do {
-		res = read(fd, hdr, sizeof(*hdr));
-		error = EFTYPE;
-		if (res != sizeof(*hdr))
-			break;
-		if (!IS_ELF(*hdr))
-			break;
-		if (hdr->e_ident[EI_CLASS] != ELF_TARG_CLASS ||
-		    hdr->e_ident[EI_DATA] != ELF_TARG_DATA ||
-		    hdr->e_ident[EI_VERSION] != EV_CURRENT ||
-		    hdr->e_version != EV_CURRENT ||
-		    hdr->e_machine != ELF_TARG_MACH ||
-		    hdr->e_phentsize != sizeof(Elf_Phdr))
-			break;
-		phlen = hdr->e_phnum * sizeof(Elf_Phdr);
-		if (ef_read_entry(ef, hdr->e_phoff, phlen,
-		    (void **)&ef->ef_ph) != 0)
-			break;
-		phdr = ef->ef_ph;
-		phlimit = phdr + hdr->e_phnum;
-		nsegs = 0;
-		phdyn = NULL;
-		while (phdr < phlimit) {
-			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;
-			}
-			phdr++;
-		}
+
+	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)
-			printf("\n");
-		if (phdyn == NULL) {
-			warnx("Skipping %s: not dynamically-linked",
-			    filename);
+			ef_print_phdr(phdr);
+		switch (phdr->p_type) {
+		case PT_LOAD:
+			if (nsegs < MAXSEGS)
+				ef->ef_segs[nsegs] = phdr;
+			nsegs++;
 			break;
-		} else if (nsegs > MAXSEGS) {
-			warnx("%s: too many segments", filename);
+		case PT_PHDR:
 			break;
-		}
-		ef->ef_nsegs = nsegs;
-		if (ef_read_entry(ef, phdyn->p_offset,
-			phdyn->p_filesz, (void **)&ef->ef_dyn) != 0) {
-			printf("ef_read_entry failed\n");
+		case PT_DYNAMIC:
+			phdyn = phdr;
 			break;
 		}
*** 2714 LINES SKIPPED ***