#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <gelf.h>
#include <libelf.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include "dis_target.h"
#include "dis_util.h"
typedef struct sym_entry {
GElf_Sym se_sym;
char *se_name;
int se_shndx;
} sym_entry_t;
typedef struct dis_shnmap {
const char *dm_name;
uint64_t dm_start;
size_t dm_length;
boolean_t dm_mapped;
} dis_shnmap_t;
struct dis_tgt {
Elf *dt_elf;
Elf *dt_elf_root;
const char *dt_filename;
int dt_fd;
size_t dt_shstrndx;
size_t dt_symidx;
sym_entry_t *dt_symcache;
sym_entry_t *dt_symtab;
int dt_symcount;
struct dis_tgt *dt_next;
Elf_Arhdr *dt_arhdr;
dis_shnmap_t *dt_shnmap;
size_t dt_shncount;
};
struct dis_func {
sym_entry_t *df_sym;
Elf_Data *df_data;
size_t df_offset;
};
struct dis_scn {
GElf_Shdr ds_shdr;
const char *ds_name;
Elf_Data *ds_data;
};
#define DATA_TYPES \
((1 << STT_OBJECT) | (1 << STT_FUNC) | (1 << STT_COMMON))
#define IS_DATA_TYPE(tp) (((1 << (tp)) & DATA_TYPES) != 0)
static void
tgt_scn_init(dis_tgt_t *tgt, dis_scn_t *scn, void *data)
{
int *index = data;
*index += 1;
tgt->dt_shnmap[*index].dm_name = scn->ds_name;
tgt->dt_shnmap[*index].dm_start = scn->ds_shdr.sh_addr;
tgt->dt_shnmap[*index].dm_length = scn->ds_shdr.sh_size;
tgt->dt_shnmap[*index].dm_mapped = B_FALSE;
if (scn->ds_shdr.sh_type == SHT_DYNSYM && tgt->dt_symidx == 0)
tgt->dt_symidx = *index;
else if (scn->ds_shdr.sh_type == SHT_SYMTAB)
tgt->dt_symidx = *index;
}
static int
sym_compare(const void *a, const void *b)
{
const sym_entry_t *syma = a;
const sym_entry_t *symb = b;
const char *aname = syma->se_name;
const char *bname = symb->se_name;
size_t alen;
size_t blen;
if (syma->se_sym.st_value < symb->se_sym.st_value)
return (-1);
if (syma->se_sym.st_value > symb->se_sym.st_value)
return (1);
if (GELF_ST_TYPE(syma->se_sym.st_info) !=
GELF_ST_TYPE(symb->se_sym.st_info)) {
if (GELF_ST_TYPE(syma->se_sym.st_info) == STT_FUNC)
return (-1);
if (GELF_ST_TYPE(symb->se_sym.st_info) == STT_FUNC)
return (1);
}
if (GELF_ST_BIND(syma->se_sym.st_info) !=
GELF_ST_BIND(symb->se_sym.st_info)) {
if (GELF_ST_BIND(syma->se_sym.st_info) == STB_WEAK)
return (-1);
if (GELF_ST_BIND(symb->se_sym.st_info) == STB_WEAK)
return (1);
if (GELF_ST_BIND(syma->se_sym.st_info) == STB_GLOBAL)
return (-1);
if (GELF_ST_BIND(symb->se_sym.st_info) == STB_GLOBAL)
return (1);
}
if (aname == NULL)
return (-1);
if (bname == NULL)
return (1);
while (*aname == '_' && *bname == '_') {
aname++;
bname++;
}
if (*bname == '_')
return (-1);
if (*aname == '_')
return (1);
if (syma->se_sym.st_size < symb->se_sym.st_size)
return (-1);
if (syma->se_sym.st_size > symb->se_sym.st_size)
return (1);
alen = strlen(syma->se_name);
blen = strlen(symb->se_name);
if (alen < blen)
return (-1);
else if (alen > blen)
return (1);
return (strcmp(syma->se_name, symb->se_name));
}
static void
construct_symtab(dis_tgt_t *tgt)
{
Elf_Scn *scn;
GElf_Shdr shdr;
Elf_Data *symdata;
int i;
GElf_Word *symshndx = NULL;
int symshndx_size;
sym_entry_t *sym;
sym_entry_t *p_symtab = NULL;
int nsym = 0;
for (scn = elf_nextscn(tgt->dt_elf, NULL); scn != NULL;
scn = elf_nextscn(tgt->dt_elf, scn)) {
if (gelf_getshdr(scn, &shdr) == NULL)
break;
if (shdr.sh_type == SHT_SYMTAB_SHNDX &&
shdr.sh_link == tgt->dt_symidx) {
Elf_Data *data;
if ((data = elf_getdata(scn, NULL)) != NULL) {
symshndx = (GElf_Word *)data->d_buf;
symshndx_size = data->d_size /
sizeof (GElf_Word);
break;
}
}
}
if ((scn = elf_getscn(tgt->dt_elf, tgt->dt_symidx)) == NULL)
die("%s: failed to get section information", tgt->dt_filename);
if (gelf_getshdr(scn, &shdr) == NULL)
die("%s: failed to get section header", tgt->dt_filename);
if (shdr.sh_entsize == 0)
die("%s: symbol table has zero size", tgt->dt_filename);
if ((symdata = elf_getdata(scn, NULL)) == NULL)
die("%s: failed to get symbol table", tgt->dt_filename);
tgt->dt_symcount = symdata->d_size / gelf_fsize(tgt->dt_elf, ELF_T_SYM,
1, EV_CURRENT);
p_symtab = safe_malloc(tgt->dt_symcount * sizeof (sym_entry_t));
for (i = 0, sym = p_symtab; i < tgt->dt_symcount; i++) {
if (gelf_getsym(symdata, i, &(sym->se_sym)) == NULL) {
warn("%s: gelf_getsym returned NULL for %d",
tgt->dt_filename, i);
nsym++;
continue;
}
if (!IS_DATA_TYPE(GELF_ST_TYPE(sym->se_sym.st_info))) {
nsym++;
continue;
}
if (sym->se_sym.st_shndx == SHN_XINDEX && symshndx != NULL) {
if (i > symshndx_size) {
warn("%s: bad SHNX_XINDEX %d",
tgt->dt_filename, i);
sym->se_shndx = -1;
} else {
sym->se_shndx = symshndx[i];
}
} else {
sym->se_shndx = sym->se_sym.st_shndx;
}
if (sym->se_shndx == SHN_ABS) {
if (sym->se_sym.st_value == 0) {
nsym++;
continue;
} else {
sym++;
continue;
}
}
if (sym->se_shndx == SHN_UNDEF ||
sym->se_shndx >= SHN_LORESERVE) {
nsym++;
continue;
}
if ((sym->se_name = elf_strptr(tgt->dt_elf, shdr.sh_link,
(size_t)sym->se_sym.st_name)) == NULL) {
warn("%s: failed to lookup symbol %d name",
tgt->dt_filename, i);
nsym++;
continue;
}
if (tgt->dt_shnmap[sym->se_shndx].dm_mapped)
sym->se_sym.st_value +=
tgt->dt_shnmap[sym->se_shndx].dm_start;
sym++;
}
tgt->dt_symcount -= nsym;
tgt->dt_symtab = realloc(p_symtab, tgt->dt_symcount *
sizeof (sym_entry_t));
qsort(tgt->dt_symtab, tgt->dt_symcount, sizeof (sym_entry_t),
sym_compare);
}
static void
create_addrmap(dis_tgt_t *tgt)
{
uint64_t addr;
int i;
if (tgt->dt_shnmap == NULL)
return;
for (addr = 0, i = 1; i < tgt->dt_shncount; i++)
if (tgt->dt_shnmap[i].dm_start > addr)
addr = tgt->dt_shnmap[i].dm_start +
tgt->dt_shnmap[i].dm_length;
addr = P2ROUNDUP(addr, 0x1000);
for (i = 1; i < tgt->dt_shncount; i++) {
if (tgt->dt_shnmap[i].dm_start != 0)
continue;
tgt->dt_shnmap[i].dm_start = addr;
tgt->dt_shnmap[i].dm_mapped = B_TRUE;
addr = P2ROUNDUP(addr + tgt->dt_shnmap[i].dm_length, 0x1000);
}
}
dis_tgt_t *
dis_tgt_create(const char *file)
{
dis_tgt_t *tgt, *current;
int idx;
Elf *elf;
GElf_Ehdr ehdr;
Elf_Arhdr *arhdr = NULL;
int cmd;
if (elf_version(EV_CURRENT) == EV_NONE)
die("libelf out of date");
tgt = safe_malloc(sizeof (dis_tgt_t));
if ((tgt->dt_fd = open(file, O_RDONLY)) < 0) {
warn("%s: failed opening file, reason: %s", file,
strerror(errno));
free(tgt);
return (NULL);
}
if ((tgt->dt_elf_root =
elf_begin(tgt->dt_fd, ELF_C_READ, NULL)) == NULL) {
warn("%s: invalid or corrupt ELF file", file);
dis_tgt_destroy(tgt);
return (NULL);
}
current = tgt;
cmd = ELF_C_READ;
while ((elf = elf_begin(tgt->dt_fd, cmd, tgt->dt_elf_root)) != NULL) {
size_t shnum = 0;
if (elf_kind(tgt->dt_elf_root) == ELF_K_AR &&
(arhdr = elf_getarhdr(elf)) == NULL) {
warn("%s: malformed archive", file);
dis_tgt_destroy(tgt);
return (NULL);
}
if (gelf_getehdr(elf, &ehdr) == NULL) {
if (arhdr != NULL) {
if (strcmp(arhdr->ar_name, "/") != 0 &&
strcmp(arhdr->ar_name, "//") != 0)
warn("%s[%s]: invalid file type",
file, arhdr->ar_name);
cmd = elf_next(elf);
(void) elf_end(elf);
continue;
}
warn("%s: invalid file type", file);
dis_tgt_destroy(tgt);
return (NULL);
}
if (current->dt_elf != NULL) {
dis_tgt_t *next = safe_malloc(sizeof (dis_tgt_t));
next->dt_elf_root = tgt->dt_elf_root;
next->dt_fd = -1;
current->dt_next = next;
current = next;
}
current->dt_elf = elf;
current->dt_arhdr = arhdr;
if (elf_getshdrstrndx(elf, ¤t->dt_shstrndx) == -1) {
warn("%s: failed to get section string table for "
"file", file);
dis_tgt_destroy(tgt);
return (NULL);
}
if (elf_getshdrnum(elf, &shnum) == -1) {
warn("%s: failed to get number of sections in file",
file);
dis_tgt_destroy(tgt);
return (NULL);
}
current->dt_shnmap = safe_malloc(sizeof (dis_shnmap_t) *
shnum);
current->dt_shncount = shnum;
idx = 0;
dis_tgt_section_iter(current, tgt_scn_init, &idx);
current->dt_filename = file;
create_addrmap(current);
if (current->dt_symidx != 0)
construct_symtab(current);
cmd = elf_next(elf);
}
if (tgt->dt_elf == NULL) {
warn("%s: empty archive\n", file);
dis_tgt_destroy(tgt);
return (NULL);
}
return (tgt);
}
const char *
dis_tgt_name(dis_tgt_t *tgt)
{
return (tgt->dt_filename);
}
const char *
dis_tgt_member(dis_tgt_t *tgt)
{
if (tgt->dt_arhdr)
return (tgt->dt_arhdr->ar_name);
else
return (NULL);
}
void
dis_tgt_ehdr(dis_tgt_t *tgt, GElf_Ehdr *ehdr)
{
(void) gelf_getehdr(tgt->dt_elf, ehdr);
}
dis_tgt_t *
dis_tgt_next(dis_tgt_t *tgt)
{
return (tgt->dt_next);
}
void
dis_tgt_destroy(dis_tgt_t *tgt)
{
dis_tgt_t *current, *next;
current = tgt->dt_next;
while (current != NULL) {
next = current->dt_next;
if (current->dt_elf)
(void) elf_end(current->dt_elf);
if (current->dt_symtab)
free(current->dt_symtab);
free(current);
current = next;
}
if (tgt->dt_elf)
(void) elf_end(tgt->dt_elf);
if (tgt->dt_elf_root)
(void) elf_end(tgt->dt_elf_root);
if (tgt->dt_symtab)
free(tgt->dt_symtab);
free(tgt);
}
const char *
dis_find_section(dis_tgt_t *tgt, uint64_t addr, off_t *offset)
{
int i;
for (i = 1; i < tgt->dt_shncount; i++) {
if ((addr >= tgt->dt_shnmap[i].dm_start) &&
(addr < tgt->dt_shnmap[i].dm_start +
tgt->dt_shnmap[i].dm_length)) {
*offset = addr - tgt->dt_shnmap[i].dm_start;
return (tgt->dt_shnmap[i].dm_name);
}
}
*offset = 0;
return (NULL);
}
const char *
dis_tgt_lookup(dis_tgt_t *tgt, uint64_t addr, off_t *offset, int cache_result,
size_t *size, int *isfunc)
{
int lo, hi, mid;
sym_entry_t *sym, *osym, *match;
int found;
*offset = 0;
*size = 0;
if (isfunc != NULL)
*isfunc = 0;
if (tgt->dt_symcache != NULL &&
addr >= tgt->dt_symcache->se_sym.st_value &&
addr < tgt->dt_symcache->se_sym.st_value +
tgt->dt_symcache->se_sym.st_size) {
sym = tgt->dt_symcache;
*offset = addr - sym->se_sym.st_value;
*size = sym->se_sym.st_size;
if (isfunc != NULL)
*isfunc = (GELF_ST_TYPE(sym->se_sym.st_info) ==
STT_FUNC);
return (sym->se_name);
}
lo = 0;
hi = (tgt->dt_symcount - 1);
found = 0;
match = osym = NULL;
while (lo <= hi) {
mid = (lo + hi) / 2;
sym = &tgt->dt_symtab[mid];
if (addr >= sym->se_sym.st_value &&
addr < sym->se_sym.st_value + sym->se_sym.st_size &&
(!found || sym->se_sym.st_value > osym->se_sym.st_value)) {
osym = sym;
found = 1;
} else if (addr == sym->se_sym.st_value) {
match = sym;
}
if (addr < sym->se_sym.st_value)
hi = mid - 1;
else
lo = mid + 1;
}
if (!found) {
if (match)
osym = match;
else
return (NULL);
}
do {
sym = osym;
if (osym == tgt->dt_symtab)
break;
osym = osym - 1;
} while ((sym->se_sym.st_value == osym->se_sym.st_value) &&
(addr >= osym->se_sym.st_value) &&
(addr < osym->se_sym.st_value + osym->se_sym.st_size));
if (cache_result)
tgt->dt_symcache = sym;
*offset = addr - sym->se_sym.st_value;
*size = sym->se_sym.st_size;
if (isfunc)
*isfunc = (GELF_ST_TYPE(sym->se_sym.st_info) == STT_FUNC);
return (sym->se_name);
}
off_t
dis_tgt_next_symbol(dis_tgt_t *tgt, uint64_t addr)
{
sym_entry_t *sym;
sym = (tgt->dt_symcache != NULL) ? tgt->dt_symcache : tgt->dt_symtab;
while (sym != (tgt->dt_symtab + tgt->dt_symcount)) {
if (sym->se_sym.st_value >= addr)
return (sym->se_sym.st_value - addr);
sym++;
}
return (0);
}
void
dis_tgt_section_iter(dis_tgt_t *tgt, section_iter_f func, void *data)
{
dis_scn_t sdata;
Elf_Scn *scn;
int idx;
for (scn = elf_nextscn(tgt->dt_elf, NULL), idx = 1; scn != NULL;
scn = elf_nextscn(tgt->dt_elf, scn), idx++) {
if (gelf_getshdr(scn, &sdata.ds_shdr) == NULL) {
warn("%s: failed to get section %d header",
tgt->dt_filename, idx);
continue;
}
if ((sdata.ds_name = elf_strptr(tgt->dt_elf, tgt->dt_shstrndx,
sdata.ds_shdr.sh_name)) == NULL) {
warn("%s: failed to get section %d name",
tgt->dt_filename, idx);
continue;
}
if ((sdata.ds_data = elf_getdata(scn, NULL)) == NULL) {
warn("%s: failed to get data for section '%s'",
tgt->dt_filename, sdata.ds_name);
continue;
}
if (sdata.ds_shdr.sh_addr == 0)
sdata.ds_shdr.sh_addr = tgt->dt_shnmap[idx].dm_start;
func(tgt, &sdata, data);
}
}
int
dis_section_istext(dis_scn_t *scn)
{
return ((scn->ds_shdr.sh_type == SHT_PROGBITS) &&
(scn->ds_shdr.sh_flags == (SHF_ALLOC | SHF_EXECINSTR)));
}
void *
dis_section_data(dis_scn_t *scn)
{
return (scn->ds_data->d_buf);
}
size_t
dis_section_size(dis_scn_t *scn)
{
return (scn->ds_data->d_size);
}
uint64_t
dis_section_addr(dis_scn_t *scn)
{
return (scn->ds_shdr.sh_addr);
}
const char *
dis_section_name(dis_scn_t *scn)
{
return (scn->ds_name);
}
dis_scn_t *
dis_section_copy(dis_scn_t *scn)
{
dis_scn_t *new;
new = safe_malloc(sizeof (dis_scn_t));
(void) memcpy(new, scn, sizeof (dis_scn_t));
return (new);
}
void
dis_section_free(dis_scn_t *scn)
{
free(scn);
}
void
dis_tgt_function_iter(dis_tgt_t *tgt, function_iter_f func, void *data)
{
int i;
sym_entry_t *sym;
dis_func_t df;
Elf_Scn *scn;
GElf_Shdr shdr;
for (i = 0, sym = tgt->dt_symtab; i < tgt->dt_symcount; i++, sym++) {
if ((GELF_ST_TYPE(sym->se_sym.st_info) != STT_FUNC) ||
(sym->se_name == NULL) ||
(sym->se_sym.st_size == 0) ||
(sym->se_shndx >= SHN_LORESERVE))
continue;
if ((scn = elf_getscn(tgt->dt_elf, sym->se_shndx)) == NULL ||
gelf_getshdr(scn, &shdr) == NULL ||
(df.df_data = elf_getdata(scn, NULL)) == NULL ||
df.df_data->d_size == 0) {
warn("%s: failed to read section %d",
tgt->dt_filename, sym->se_shndx);
continue;
}
if (tgt->dt_shnmap[sym->se_shndx].dm_mapped)
shdr.sh_addr = tgt->dt_shnmap[sym->se_shndx].dm_start;
if (sym->se_sym.st_value < shdr.sh_addr ||
(sym->se_sym.st_value + sym->se_sym.st_size) >
(shdr.sh_addr + shdr.sh_size)) {
warn("%s: bad section %d for address %p",
tgt->dt_filename, sym->se_sym.st_shndx,
sym->se_sym.st_value);
continue;
}
df.df_sym = sym;
df.df_offset = sym->se_sym.st_value - shdr.sh_addr;
func(tgt, &df, data);
}
}
void *
dis_function_data(dis_func_t *func)
{
return ((char *)func->df_data->d_buf + func->df_offset);
}
size_t
dis_function_size(dis_func_t *func)
{
return (func->df_sym->se_sym.st_size);
}
uint64_t
dis_function_addr(dis_func_t *func)
{
return (func->df_sym->se_sym.st_value);
}
const char *
dis_function_name(dis_func_t *func)
{
return (func->df_sym->se_name);
}
dis_func_t *
dis_function_copy(dis_func_t *func)
{
dis_func_t *new;
new = safe_malloc(sizeof (dis_func_t));
(void) memcpy(new, func, sizeof (dis_func_t));
return (new);
}
void
dis_function_free(dis_func_t *func)
{
free(func);
}