/*
* Copyright (c) 2002 Peter Edwards
*
* 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.
*
* 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$
* $Id: elf.c,v 1.1.1.1 2002/10/02 09:25:02 pmedwards Exp $
*/
/*
* elf.c
* Peter Edwards, January 2002.
*
* Implementation of utlities for accessing ELF images.
*/
#include <sys/types.h>
#include <sys/mman.h>
#include <sys/procfs.h>
#include <sys/stat.h>
#include <elf.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "elfinfo.h"
static unsigned long elf_hash(const unsigned char *name);
/*
* Parse out an ELF file into an ElfObject structure.
* XXX: We probably don't use all the information we parse, and can probably
* pear this down a bit.
*/
int
elfLoadObject(const char *fileName, struct ElfObject **objp)
{
int file, i;
const unsigned char *p;
struct ElfObject *obj;
struct stat sb;
const Elf_Ehdr *eHdr;
const Elf_Shdr **sHdrs, *shdr;
const Elf_Phdr **pHdrs;
char *data;
if ((file = open(fileName, O_RDONLY)) == -1) {
warn("unable to open executable '%s'", fileName);
return (-1);
}
if (fstat(file, &sb) == -1) {
close(file);
warn("unable to stat executable '%s'", fileName);
return (-1);
}
data = mmap(0, sb.st_size, PROT_READ, MAP_SHARED, file, 0);
close(file);
if (data == MAP_FAILED) {
warn("unable to map executable '%s'", fileName);
return (-1);
}
obj = calloc(1, sizeof(*obj));
obj->fileSize = sb.st_size;
obj->fileData = data;
obj->elfHeader = eHdr = (const Elf_Ehdr *)data;
/* Validate the ELF header */
if (!IS_ELF(*obj->elfHeader) ||
eHdr->e_ident[EI_CLASS] != ELFCLASS32 ||
eHdr->e_ident[EI_VERSION] != EV_CURRENT) {
warnx("not an ELF image");
free(obj);
munmap(data, sb.st_size);
return (-1);
}
obj->programHeaders = pHdrs =
malloc(sizeof(Elf_Phdr *) * (eHdr->e_phnum + 1));
for (p = data + eHdr->e_phoff, i = 0; i < eHdr->e_phnum; i++) {
pHdrs[i] = (const Elf_Phdr *)p;
switch (pHdrs[i]->p_type) {
case PT_INTERP:
obj->interpreterName = data + pHdrs[i]->p_offset;
break;
case PT_DYNAMIC:
obj->dynamic = pHdrs[i];
break;
}
p += eHdr->e_phentsize;
}
pHdrs[i] = 0;
obj->sectionHeaders = sHdrs =
malloc(sizeof(Elf_Shdr *) * (eHdr->e_shnum + 1));
for (p = data + eHdr->e_shoff, i = 0; i < eHdr->e_shnum; i++) {
sHdrs[i] = (const Elf_Shdr *)p;
p += eHdr->e_shentsize;
}
sHdrs[i] = 0;
obj->sectionStrings = eHdr->e_shstrndx != SHN_UNDEF ?
data + sHdrs[eHdr->e_shstrndx]->sh_offset : 0;
obj->fileName = strdup(fileName);
*objp = obj;
if (elfFindSectionByName(obj, ".stab", &shdr) != -1) {
obj->stabs = (struct stab *)(obj->fileData + shdr->sh_offset);
obj->stabCount = shdr->sh_size / sizeof (struct stab);
if (shdr->sh_link)
obj->stabStrings = obj->fileData +
sHdrs[shdr->sh_link]->sh_offset;
else if (elfFindSectionByName(obj, ".stabstr", &shdr) != -1)
obj->stabStrings = obj->fileData + shdr->sh_offset;
else
obj->stabStrings = 0;
} else {
obj->stabs = 0;
obj->stabCount = 0;
}
return (0);
}
/*
* Given an Elf object, find a particular section.
*/
int
elfFindSectionByName(struct ElfObject *obj, const char *name,
const Elf_Shdr **shdrp)
{
int i;
for (i = 0; i < obj->elfHeader->e_shnum; i++)
if (strcmp(obj->sectionHeaders[i]->sh_name +
obj->sectionStrings, name) == 0) {
*shdrp = obj->sectionHeaders[i];
return (0);
}
return (-1);
}
/*
* Find the symbol that represents a particular address.
* If we fail to find a symbol whose virtual range includes our target address
* we will accept a symbol with the highest address less than or equal to our
* target. This allows us to match the dynamic "stubs" in code.
* A side-effect is a few false-positives: A stripped, dynamically linked,
* executable will typically report functions as being "_init", because it is
* the only symbol in the image, and it has no size.
*/
int
elfFindSymbolByAddress(struct ElfObject *obj, Elf_Addr addr,
int type, const Elf_Sym **symp, const char **namep)
{
const Elf_Shdr *symSection, **shdrs;
const Elf_Sym *sym, *endSym;
const char *symStrings;
const char *sectionNames[] = { ".dynsym", ".symtab", 0 };
int i, exact = 0;
/* Try to find symbols in these sections */
*symp = 0;
shdrs = obj->sectionHeaders;
for (i = 0; sectionNames[i] && !exact; i++) {
if (elfFindSectionByName(obj, sectionNames[i],
&symSection) != 0)
continue;
/*
* Found the section in question: get the associated
* string section's data, and a pointer to the start
* and end of the table
*/
symStrings = obj->fileData +
shdrs[symSection->sh_link]->sh_offset;
sym = (const Elf_Sym *)(obj->fileData +
symSection->sh_offset);
endSym = (const Elf_Sym *)(obj->fileData +
symSection->sh_offset + symSection->sh_size);
for (; sym < endSym; sym++) {
if ((type == STT_NOTYPE ||
ELF_ST_TYPE(sym->st_info) == type) &&
sym->st_value <= addr &&
(shdrs[sym->st_shndx]->sh_flags & SHF_ALLOC)) {
if (sym->st_size) {
if (sym->st_size +
sym->st_value > addr) {
*symp = sym;
*namep = symStrings +
sym->st_name;
exact = 1;
}
} else {
if ((*symp) == 0 || (*symp)->st_value <
sym->st_value) {
*symp = sym;
*namep = symStrings +
sym->st_name;
}
}
}
}
}
return (*symp ? 0 : -1);
}
int
elfLinearSymSearch(struct ElfObject *o, const Elf_Shdr *hdr,
const char *name, const Elf_Sym **symp)
{
const char *symStrings;
const Elf_Sym *sym, *endSym;
symStrings = o->fileData + o->sectionHeaders[hdr->sh_link]->sh_offset;
sym = (const Elf_Sym *)(o->fileData + hdr->sh_offset);
endSym = sym = (const Elf_Sym *)(o->fileData + hdr->sh_offset + hdr->sh_size);
for (; sym < endSym; sym++)
if (!strcmp(symStrings + sym->st_name, name)) {
*symp = sym;
return (0);
}
return (-1);
}
/*
* Locate a symbol in an ELF image.
*/
int
elfFindSymbolByName(struct ElfObject *o, const char *name, const Elf_Sym **symp)
{
const Elf_Shdr *hash, *syms;
const char *symStrings;
const Elf_Sym *sym;
Elf_Word nbucket, nchain, i;
const Elf_Word *buckets, *chains, *hashData;
unsigned long hashv;
/* First, search the hashed symbols in .dynsym. */
if (elfFindSectionByName(o, ".hash", &hash) == 0) {
syms = o->sectionHeaders[hash->sh_link];
hashData = (const Elf_Word *)(o->fileData + hash->sh_offset);
sym = (const Elf_Sym *)(o->fileData + syms->sh_offset);
symStrings = o->fileData +
o->sectionHeaders[syms->sh_link]->sh_offset;
nbucket = hashData[0];
nchain = hashData[1];
buckets = hashData + 2;
chains = buckets + nbucket;
hashv = elf_hash(name) % nbucket;
for (i = buckets[hashv]; i != STN_UNDEF; i = chains[i])
if (strcmp(symStrings + sym[i].st_name, name) == 0) {
*symp = sym + i;
return (0);
}
} else if (elfFindSectionByName(o, ".dynsym", &syms) == 0) {
/* No ".hash", but have ".dynsym": do linear search */
if (elfLinearSymSearch(o, syms, name, symp) == 0)
return (0);
}
/* Do a linear search of ".symtab" if present */
if (elfFindSectionByName(o, ".symtab", &syms) == 0 &&
elfLinearSymSearch(o, syms, name, symp) == 0) {
return (0);
}
return (-1);
}
/*
* Get the data and length from a specific "note" in the ELF file
*/
int
elfGetNote(struct ElfObject *obj, const char *name,
u_int32_t type, const void **datap, int *lenp)
{
const Elf_Phdr **phdr;
const Elf_Note *note;
const char *noteName, *data, *s, *e;
for (phdr = obj->programHeaders; *phdr; phdr++) {
if ((*phdr)->p_type == PT_NOTE) {
s = obj->fileData + (*phdr)->p_offset;
e = s + (*phdr)->p_filesz;
while (s < e) {
note = (const Elf_Note *)s;
s += sizeof(*note);
noteName = s;
s += roundup2(note->n_namesz, 4);
data = s;
s += roundup2(note->n_descsz, 4);
if (strcmp(name, noteName) == 0 &&
(note->n_type == type || type == -1)) {
*datap = data;
*lenp = note->n_descsz;
return (0);
}
}
}
}
return (-1);
}
/*
* Try to work out the name of the executable from a core file
* XXX: This is not particularly useful, because the pathname appears to get
* stripped.
*/
int
elfGetImageFromCore(struct ElfObject *obj, const char **name)
{
const prpsinfo_t *psinfo;
u_int32_t len;
if (!elfGetNote(obj, "FreeBSD", NT_PRPSINFO,
(const void **)&psinfo, &len) &&
psinfo->pr_version == PRPSINFO_VERSION) {
*name = psinfo->pr_fname;
return (0);
}
return (-1);
}
/*
* Attempt to find a prefix to an executable ABI's "emulation tree"
*/
const char *
elfGetAbiPrefix(struct ElfObject *obj)
{
int i;
static struct {
int brand;
const char *oldBrand;
const char *interpreter;
const char *prefix;
} knownABIs[] = {
{ ELFOSABI_FREEBSD, "FreeBSD", "/usr/libexec/ld-elf.so.1", 0},
{ ELFOSABI_LINUX, "Linux", "/lib/ld-linux.so.1", "/compat/linux"},
{ ELFOSABI_LINUX, "Linux", "/lib/ld-linux.so.2", "/compat/linux"},
{ -1,0,0 }
};
/* Trust EI_OSABI, or the 3.x brand string first */
for (i = 0; knownABIs[i].brand != -1; i++) {
if (knownABIs[i].brand == obj->elfHeader->e_ident[EI_OSABI] ||
strcmp(knownABIs[i].oldBrand,
obj->elfHeader->e_ident + OLD_EI_BRAND) == 0)
return knownABIs[i].prefix;
}
/* ... Then the interpreter */
if (obj->interpreterName) {
for (i = 0; knownABIs[i].brand != -1; i++) {
if (strcmp(knownABIs[i].interpreter,
obj->interpreterName) == 0)
return knownABIs[i].prefix;
}
}
/* No prefix */
return 0;
}
/*
* Free any resources assoiated with an ElfObject
*/
int
elfUnloadObject(struct ElfObject *obj)
{
free(obj->fileName);
free(obj->sectionHeaders);
free(obj->programHeaders);
munmap((void *)obj->fileData, obj->fileSize);
free(obj);
return (0);
}
/*
* Culled from System V Application Binary Interface
*/
static unsigned long elf_hash(const unsigned char *name)
{
unsigned long h = 0, g;
while (*name != '\0') {
h = (h << 4) + *name++;
if ((g = h & 0xf0000000) != 0)
h ^= g >> 24;
h &= ~g;
}
return (h);
}
/*
* Debug output of the contents of an ELF32 section
*/
void
elfDumpSection(FILE *f, struct ElfObject *obj, const Elf_Shdr *hdr,
int snapSize, int indent)
{
const Elf_Sym * sym, *esym;
int i;
const char *symStrings, *padding = pad(indent);
static const char *sectionTypeNames[] = {
"SHT_NULL",
"SHT_PROGBITS",
"SHT_SYMTAB",
"SHT_STRTAB",
"SHT_RELA",
"SHT_HASH",
"SHT_DYNAMIC",
"SHT_NOTE",
"SHT_NOBITS",
"SHT_REL",
"SHT_SHLIB",
"SHT_DYNSYM",
};
fprintf(f, "%sname= %s\n"
"%stype= %d (%s)\n"
"%sflags= %xH (%s%s%s)\n"
"%saddress= %xH\n"
"%soffset= %d (%xH)\n"
"%ssize= %d (%xH)\n"
"%slink= %d (%xH)\n"
"%sinfo= %d (%xH)\n" ,
padding, obj->sectionStrings + hdr->sh_name,
padding, hdr->sh_type, hdr->sh_type <= SHT_DYNSYM ?
sectionTypeNames[hdr->sh_type] : "unknown",
padding,
hdr->sh_flags,
hdr->sh_flags & SHF_WRITE ? "write " : "",
hdr->sh_flags & SHF_ALLOC ? "alloc " : "",
hdr->sh_flags & SHF_EXECINSTR ? "instructions " : "",
padding, hdr->sh_addr,
padding, hdr->sh_offset, hdr->sh_offset,
padding, hdr->sh_size, hdr->sh_size,
padding, hdr->sh_link, hdr->sh_link,
padding, hdr->sh_info, hdr->sh_info);
switch (hdr->sh_type) {
case SHT_SYMTAB:
case SHT_DYNSYM:
symStrings = obj->fileData +
obj->sectionHeaders[hdr->sh_link]->sh_offset;
sym = (const Elf_Sym *) (obj->fileData + hdr->sh_offset);
esym = (const Elf_Sym *) ((char *)sym + hdr->sh_size);
for (i = 0; sym < esym; i++, sym++) {
printf("%ssymbol %d:\n", padding, i);
elfDumpSymbol(f, sym, symStrings, indent + 4);
}
break;
}
fprintf(f,"%sstart of data:\n", padding);
hexdump(f, indent, obj->fileData + hdr->sh_offset,
MIN(hdr->sh_size, snapSize));
}
/*
* Debug output of an ELF32 program segment
*/
void
elfDumpProgramSegment(FILE *f, struct ElfObject *obj, const Elf_Phdr *hdr,
int indent)
{
const char *padding = pad(indent);
static const char *segmentTypeNames[] = {
"PT_NULL",
"PT_LOAD",
"PT_DYNAMIC",
"PT_INTERP",
"PT_NOTE",
"PT_SHLIB",
"PT_PHDR"
};
fprintf(f, "%stype = %xH (%s)\n"
"%soffset = %xH (%d)\n"
"%svirtual address = %xH (%d)\n"
"%sphysical address = %xH (%d)\n"
"%sfile size = %xH (%d)\n"
"%smemory size = %xH (%d)\n"
"%sflags = %xH (%s %s %s)\n"
"%salignment = %xH (%d)\n",
padding, hdr->p_type,
hdr->p_type <= PT_PHDR ? segmentTypeNames[hdr->p_type] : "unknown",
padding, hdr->p_offset, hdr->p_offset,
padding, hdr->p_vaddr, hdr->p_vaddr,
padding, hdr->p_paddr, hdr->p_paddr,
padding, hdr->p_filesz, hdr->p_filesz,
padding, hdr->p_memsz, hdr->p_memsz,
padding, hdr->p_flags,
hdr->p_flags & PF_R ? "PF_R" : "",
hdr->p_flags & PF_W ? "PF_W" : "",
hdr->p_flags & PF_X ? "PF_X" : "",
padding, hdr->p_align, hdr->p_align);
fprintf(f, "%sstart of data:\n", padding);
hexdump(f, indent, obj->fileData + hdr->p_offset,
MIN(hdr->p_filesz, 64));
}
/*
* Debug output of an Elf symbol.
*/
void
elfDumpSymbol(FILE *f, const Elf_Sym * sym, const char *strings, int indent)
{
static const char *bindingNames[] = {
"STB_LOCAL",
"STB_GLOBAL",
"STB_WEAK",
"unknown3",
"unknown4",
"unknown5",
"unknown6",
"unknown7",
"unknown8",
"unknown9",
"unknowna",
"unknownb",
"unknownc",
"STB_LOPROC",
"STB_LOPROC + 1",
"STB_HIPROC + 1",
};
static const char *typeNames[] = {
"STT_NOTYPE",
"STT_OBJECT",
"STT_FUNC",
"STT_SECTION",
"STT_FILE",
"STT_5",
"STT_6",
"STT_7",
"STT_8",
"STT_9",
"STT_A",
"STT_B",
"STT_C",
"STT_LOPROC",
"STT_LOPROC + 1",
"STT_HIPROC"
};
const char *padding = pad(indent);
fprintf(f,
"%sname = %s\n"
"%svalue = %d (%xH)\n"
"%ssize = %d (%xH)\n"
"%sinfo = %d (%xH)\n"
"%sbinding = %s\n"
"%stype = %s\n"
"%sother = %d (%xH)\n"
"%sshndx = %d (%xH)\n",
padding, sym->st_name ? strings + sym->st_name : "(unnamed)",
padding, sym->st_value, sym->st_value,
padding, sym->st_size, sym->st_size,
padding, sym->st_info, sym->st_info,
pad(indent + 4), bindingNames[sym->st_info >> 4],
pad(indent + 4), typeNames[sym->st_info & 0xf],
padding, sym->st_other, sym->st_other,
padding, sym->st_shndx, sym->st_shndx);
}
/*
* Debug output of an ELF32 dynamic item
*/
void
elfDumpDynamic(FILE *f, const Elf_Dyn *dyn, int indent)
{
const char *padding = pad(indent);
static const char *tagNames[] = {
"DT_NULL",
"DT_NEEDED",
"DT_PLTRELSZ",
"DT_PLTGOT",
"DT_HASH",
"DT_STRTAB",
"DT_SYMTAB",
"DT_RELA",
"DT_RELASZ",
"DT_RELAENT",
"DT_STRSZ",
"DT_SYMENT",
"DT_INIT",
"DT_FINI",
"DT_SONAME",
"DT_RPATH",
"DT_SYMBOLIC",
"DT_REL",
"DT_RELSZ",
"DT_RELENT",
"DT_PLTREL",
"DT_DEBUG",
"DT_TEXTREL",
"DT_JMPREL",
"DT_BIND_NOW"
};
fprintf(f, "%stag: %d (%s)\n", padding, dyn->d_tag,
dyn->d_tag >= 0 && dyn->d_tag <= DT_COUNT ?
tagNames[dyn->d_tag] : "(unknown)");
fprintf(f, "%sword/addr: %d (%x)\n",
padding, dyn->d_un.d_val, dyn->d_un.d_val);
}
/*
* Debug output of an ELF32 object.
*/
void
elfDumpObject(FILE *f, struct ElfObject *obj, int snaplen, int indent)
{
int brand, i;
static const char *typeNames[] = {
"ET_NONE",
"ET_REL",
"ET_EXEC",
"ET_DYN",
"ET_CORE"
};
static const char *abiNames[] = {
"SYSV/NONE",
"HP-UX",
"NetBSD",
"Linux",
"Hurd",
"86Open",
"Solaris",
"Monterey",
"Irix",
"FreeBSD",
"Tru64",
"Modesto",
"OpenBSD"
};
const Elf_Ehdr *ehdr = obj->elfHeader;
const Elf_Dyn *dyn, *edyn;
const char *padding = pad(indent);
brand = ehdr->e_ident[EI_OSABI];
fprintf(f, "%sType= %s\n", padding, typeNames[ehdr->e_type]);
fprintf(f, "%sEntrypoint= %x\n", padding, ehdr->e_entry);
fprintf(f, "%sExetype= %d (%s)\n", padding, brand,
brand >= 0 && brand <= ELFOSABI_OPENBSD ?
abiNames[brand] : "unknown");
for (i = 1; i < obj->elfHeader->e_shnum; i++) {
fprintf(f, "%ssection %d:\n", padding, i);
elfDumpSection(f, obj, obj->sectionHeaders[i], snaplen,
indent + 4);
}
for (i = 0; i < obj->elfHeader->e_phnum; i++) {
fprintf(f, "%ssegment %d:\n", padding, i);
elfDumpProgramSegment(f, obj, obj->programHeaders[i],
indent + 4);
}
if (obj->dynamic) {
dyn = (const Elf_Dyn *)
(obj->fileData + obj->dynamic->p_offset);
edyn = (const Elf_Dyn *)
((char *)dyn + obj->dynamic->p_filesz);
while (dyn < edyn) {
printf("%sdynamic entry\n", padding - 4);
elfDumpDynamic(f, dyn, indent + 8);
dyn++;
}
}
if (obj->interpreterName)
fprintf(f, "%sinterpreter %s\n", padding, obj->interpreterName);
}
/*
* Helps for pretty-printing
*/
const char *
pad(int size)
{
static const char padding[] =
" "
" ";
if (size > 80)
size = 80;
return (padding + 80 - size);
}
void
hexdump(FILE *f, int indent, const char *p, int len)
{
const unsigned char *cp = (const unsigned char *)p;
char hex[16 * 3 + 1], *hp, ascii[16 + 1], *ap;
int i, c;
if (!len)
return;
while (len) {
hp = hex;
ap = ascii;
for (i = 0; len && i < 16; i++) {
c = *cp++;
len--;
hp += sprintf(hp, "%02x ", c);
*ap++ = c < 127 && c >= 32 ? c : '.';
}
*ap = 0;
fprintf(f, "%s%-48s |%-16s|\n", pad(indent), hex, ascii);
}
}
syntax highlighted by Code2HTML, v. 0.9.1