freebsd-skq/usr.sbin/kldxref/ef.c
jake af44b94319 Fix kldxref on sparc64 by allowing non-trivial relocations to be performed
on variables read out of raw kld files.  Unlike other platforms the value
will be in an Elf_Rela, not in the data section of the elf file.

Submitted by:	Hartmut Brandt <brandt@fokus.gmd.de>
PR:		46730
Tested on:	alpha (obrien), i386, sparc64
2003-01-21 03:51:53 +00:00

506 lines
12 KiB
C

/*
* 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.
*
* $FreeBSD$
*/
#include <sys/param.h>
#include <sys/linker.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <machine/elf.h>
#define FREEBSD_ELF
#include <link.h>
#include <err.h>
#include "ef.h"
static void ef_print_phdr(Elf_Phdr *);
static u_long ef_get_offset(elf_file_t, Elf_Off);
static int ef_parse_dynamic(elf_file_t);
void
ef_print_phdr(Elf_Phdr *phdr)
{
if ((phdr->p_flags & PF_W) == 0) {
printf("text=0x%lx ", (long)phdr->p_filesz);
} else {
printf("data=0x%lx", (long)phdr->p_filesz);
if (phdr->p_filesz < phdr->p_memsz)
printf("+0x%lx", (long)(phdr->p_memsz - phdr->p_filesz));
printf(" ");
}
}
u_long
ef_get_offset(elf_file_t ef, Elf_Off off)
{
Elf_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);
}
}
return 0;
}
/*
* next three functions copied from link_elf.c
*/
static unsigned long
elf_hash(const char *name)
{
const unsigned char *p = (const unsigned char *) name;
unsigned long h = 0;
unsigned long g;
while (*p != '\0') {
h = (h << 4) + *p++;
if ((g = h & 0xf0000000) != 0)
h ^= g >> 24;
h &= ~g;
}
return h;
}
int
ef_lookup_symbol(elf_file_t ef, const char* name, Elf_Sym** sym)
{
unsigned long symnum;
Elf_Sym* symp;
char *strp;
unsigned long hash;
/* 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 &&
ELF_ST_TYPE(symp->st_info) == STT_FUNC)) {
*sym = symp;
return 0;
} else
return ENOENT;
}
symnum = ef->ef_chains[symnum];
}
return ENOENT;
}
int
ef_parse_dynamic(elf_file_t ef)
{
Elf_Dyn *dp;
Elf_Hashelt hashhdr[2];
/* int plttype = DT_REL;*/
int error;
Elf_Off rel_off;
Elf_Off rela_off;
int rel_sz;
int rela_sz;
int rel_entry;
int rela_entry;
rel_off = rela_off = 0;
rel_sz = rela_sz = 0;
rel_entry = rela_entry = 0;
for (dp = ef->ef_dyn; dp->d_tag != DT_NULL; dp++) {
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) {
warnx("can't read hash header (%lx)",
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) {
warnx("can't read hash table");
return error;
}
ef->ef_buckets = ef->ef_hashtab;
ef->ef_chains = ef->ef_buckets + ef->ef_nbuckets;
break;
case DT_STRTAB:
ef->ef_stroff = dp->d_un.d_ptr;
break;
case DT_STRSZ:
ef->ef_strsz = dp->d_un.d_val;
break;
case DT_SYMTAB:
ef->ef_symoff = dp->d_un.d_ptr;
break;
case DT_SYMENT:
if (dp->d_un.d_val != sizeof(Elf_Sym))
return EFTYPE;
break;
case DT_REL:
if (rel_off != 0)
warnx("second DT_REL entry ignored");
rel_off = 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;
break;
case DT_RELENT:
if (rel_entry != 0)
warnx("second DT_RELENT entry ignored");
rel_entry = dp->d_un.d_val;
break;
case DT_RELA:
if (rela_off != 0)
warnx("second DT_RELA entry ignored");
rela_off = 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;
break;
case DT_RELAENT:
if (rela_entry != 0)
warnx("second DT_RELAENT entry ignored");
rela_entry = dp->d_un.d_val;
break;
}
}
if (ef->ef_symoff == 0) {
warnx("%s: no .dynsym section found\n", ef->ef_name);
return EFTYPE;
}
if (ef->ef_stroff == 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%lx)",
ef->ef_name, (long)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);
}
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);
}
return 0;
}
int
ef_read(elf_file_t ef, Elf_Off offset, size_t len, void*dest)
{
ssize_t r;
if (offset != (Elf_Off)-1) {
if (lseek(ef->ef_fd, offset, SEEK_SET) == -1)
return EIO;
}
r = read(ef->ef_fd, dest, len);
if (r != -1 && (size_t)r == len)
return 0;
else
return EIO;
}
int
ef_read_entry(elf_file_t ef, Elf_Off offset, size_t len, void**ptr)
{
int error;
*ptr = malloc(len);
if (*ptr == NULL)
return ENOMEM;
error = ef_read(ef, offset, len, *ptr);
if (error)
free(*ptr);
return error;
}
int
ef_seg_read(elf_file_t ef, Elf_Off offset, size_t len, void*dest)
{
u_long ofs = ef_get_offset(ef, offset);
if (ofs == 0) {
if (ef->ef_verbose)
warnx("ef_seg_read(%s): zero offset (%lx:%ld)",
ef->ef_name, (long)offset, ofs);
return EFAULT;
}
return ef_read(ef, ofs, len, dest);
}
int
ef_seg_read_rel(elf_file_t ef, Elf_Off offset, size_t len, void*dest)
{
u_long ofs = ef_get_offset(ef, offset);
int error;
if (ofs == 0) {
if (ef->ef_verbose)
warnx("ef_seg_read(%s): zero offset (%lx:%ld)",
ef->ef_name, (long)offset, ofs);
return EFAULT;
}
if ((error = ef_read(ef, ofs, len, dest)) != 0)
return (error);
return (ef_reloc(ef, offset, len, dest));
}
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 ENOMEM;
error = ef_seg_read(ef, offset, len, *ptr);
if (error)
free(*ptr);
return error;
}
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 ENOMEM;
error = ef_seg_read_rel(ef, offset, len, *ptr);
if (error)
free(*ptr);
return error;
}
int
ef_open(const char *filename, elf_file_t ef, int verbose)
{
Elf_Ehdr *hdr;
int fd;
int error;
int phlen, res;
int nsegs;
Elf_Phdr *phdr, *phdyn, *phphdr, *phlimit;
bzero(ef, sizeof(*ef));
if (filename == NULL)
return EFTYPE;
ef->ef_verbose = verbose;
if ((fd = open(filename, O_RDONLY)) == -1)
return errno;
ef->ef_fd = fd;
ef->ef_name = strdup(filename);
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;
phphdr = NULL;
while (phdr < phlimit) {
if (verbose > 1)
ef_print_phdr(phdr);
switch (phdr->p_type) {
case PT_LOAD:
if (nsegs == 2) {
warnx("%s: too many sections",
filename);
break;
}
ef->ef_segs[nsegs++] = phdr;
break;
case PT_PHDR:
phphdr = phdr;
break;
case PT_DYNAMIC:
phdyn = phdr;
break;
}
phdr++;
}
if (verbose > 1)
printf("\n");
ef->ef_nsegs = nsegs;
if (phdyn == NULL) {
warnx("file isn't dynamically-linked");
break;
}
if (ef_read_entry(ef, phdyn->p_offset,
phdyn->p_filesz, (void**)&ef->ef_dyn) != 0) {
printf("ef_read_entry failed\n");
break;
}
error = ef_parse_dynamic(ef);
if (error)
break;
if (hdr->e_type == ET_DYN) {
ef->ef_type = EFT_KLD;
/* pad = (u_int)dest & PAGE_MASK;
if (pad)
dest += PAGE_SIZE - pad;*/
error = 0;
} else if (hdr->e_type == ET_EXEC) {
/* dest = hdr->e_entry;
if (dest == 0)
break;*/
ef->ef_type = EFT_KERNEL;
error = 0;
} else
break;
} while(0);
if (error) {
ef_close(ef);
if (ef->ef_verbose)
warnc(error, "elf_open(%s)", filename);
}
return error;
}
int
ef_close(elf_file_t ef)
{
close(ef->ef_fd);
/* if (ef->ef_fpage)
free(ef->ef_fpage);*/
if (ef->ef_name)
free(ef->ef_name);
return 0;
}