libproc: Cache symbol tables for mapped objects upon access.

Extend the file handle cache entries to include symbol tables as well. An
index is used to implement binary search by symbol value. Lookups by
name are comparatively rare and are thus still implemented with a linear
search, but support for a binary search by name would be straightforward
to add if needed.
This commit is contained in:
Mark Johnston 2016-12-06 04:21:35 +00:00
parent d42df2a447
commit c156354ff8
3 changed files with 161 additions and 168 deletions

View File

@ -39,10 +39,22 @@
struct procstat;
struct symtab {
Elf_Data *data;
u_int nsyms;
u_int *index;
u_long stridx;
};
struct file_info {
Elf *elf;
int fd;
u_int refs;
GElf_Ehdr ehdr;
/* Symbol tables, sorted by value. */
struct symtab dynsymtab;
struct symtab symtab;
};
struct map_info {

View File

@ -239,6 +239,10 @@ proc_free(struct proc_handle *phdl)
if (file->elf != NULL) {
(void)elf_end(file->elf);
(void)close(file->fd);
if (file->symtab.nsyms > 0)
free(file->symtab.index);
if (file->dynsymtab.nsyms > 0)
free(file->dynsymtab.index);
}
free(file);
}

View File

@ -100,6 +100,74 @@ demangle(const char *symbol, char *buf, size_t len)
strlcpy(buf, symbol, len);
}
static int
symvalcomp(void *thunk, const void *a1, const void *a2)
{
struct symtab *symtab;
GElf_Sym sym1, sym2;
u_int i1, i2;
int ret;
i1 = *(const u_int *)a1;
i2 = *(const u_int *)a2;
symtab = thunk;
(void)gelf_getsym(symtab->data, i1, &sym1);
(void)gelf_getsym(symtab->data, i2, &sym2);
if (sym1.st_value < sym2.st_value)
ret = -1;
else if (sym1.st_value == sym2.st_value)
ret = 0;
else
ret = 1;
return (ret);
}
static int
load_symtab(Elf *e, struct symtab *symtab, u_long sh_type)
{
GElf_Ehdr ehdr;
GElf_Shdr shdr;
Elf_Scn *scn;
u_int nsyms;
if (gelf_getehdr(e, &ehdr) == NULL)
return (-1);
scn = NULL;
while ((scn = elf_nextscn(e, scn)) != NULL) {
(void)gelf_getshdr(scn, &shdr);
if (shdr.sh_type == sh_type)
break;
}
if (scn == NULL)
return (-1);
if ((symtab->data = elf_getdata(scn, NULL)) == NULL)
return (-1);
nsyms = shdr.sh_size / shdr.sh_entsize;
symtab->index = calloc(nsyms, sizeof(u_int));
if (symtab->index == NULL)
return (-1);
for (u_int i = 0; i < nsyms; i++)
symtab->index[i] = i;
qsort_r(symtab->index, nsyms, sizeof(u_int), symtab, symvalcomp);
symtab->nsyms = nsyms;
symtab->stridx = shdr.sh_link;
return (0);
}
static void
load_symtabs(struct file_info *file)
{
file->symtab.nsyms = file->dynsymtab.nsyms = 0;
(void)load_symtab(file->elf, &file->symtab, SHT_SYMTAB);
(void)load_symtab(file->elf, &file->dynsymtab, SHT_DYNSYM);
}
static int
open_debug_file(char *path, const char *debugfile, uint32_t crc)
{
@ -159,6 +227,10 @@ open_object(struct map_info *mapping)
DPRINTFX("ERROR: elf_begin() failed: %s", elf_errmsg(-1));
goto err;
}
if (gelf_getehdr(e, &file->ehdr) != &file->ehdr) {
DPRINTFX("ERROR: elf_getehdr() failed: %s", elf_errmsg(-1));
goto err;
}
scn = NULL;
while ((scn = elf_nextscn(e, scn)) != NULL) {
@ -235,6 +307,7 @@ open_object(struct map_info *mapping)
/* We didn't find a debug file, just return the object's descriptor. */
file->elf = e;
file->fd = fd;
load_symtabs(file);
return (0);
external:
@ -247,6 +320,7 @@ open_object(struct map_info *mapping)
(void)close(fd);
file->elf = e2;
file->fd = fd2;
load_symtabs(file);
return (0);
err:
@ -333,55 +407,49 @@ proc_addr2map(struct proc_handle *p, uintptr_t addr)
}
/*
* Look up the symbol at addr, returning a copy of the symbol and its name.
* Look up the symbol at addr using a binary search, returning a copy of the
* symbol and its name.
*/
static int
lookup_addr(Elf *e, Elf_Scn *scn, u_long stridx, uintptr_t off, uintptr_t addr,
const char **name, GElf_Sym *symcopy)
lookup_symbol_by_addr(Elf *elf, struct symtab *symtab, uintptr_t addr,
const char **namep, GElf_Sym *sym)
{
GElf_Sym sym;
Elf_Data *data;
const char *s;
uint64_t rsym;
int i;
int min, max, mid;
if ((data = elf_getdata(scn, NULL)) == NULL) {
DPRINTFX("ERROR: elf_getdata() failed: %s", elf_errmsg(-1));
return (1);
}
for (i = 0; gelf_getsym(data, i, &sym) != NULL; i++) {
rsym = off + sym.st_value;
if (addr >= rsym && addr < rsym + sym.st_size) {
s = elf_strptr(e, stridx, sym.st_name);
if (s != NULL) {
*name = s;
memcpy(symcopy, &sym, sizeof(*symcopy));
/*
* DTrace expects the st_value to contain
* only the address relative to the start of
* the function.
*/
symcopy->st_value = rsym;
return (0);
}
data = symtab->data;
min = 0;
max = symtab->nsyms - 1;
while (min <= max) {
mid = (max + min) / 2;
(void)gelf_getsym(data, symtab->index[mid], sym);
if (addr >= sym->st_value &&
addr < sym->st_value + sym->st_size) {
s = elf_strptr(elf, symtab->stridx, sym->st_name);
if (s != NULL && namep != NULL)
*namep = s;
return (0);
}
if (addr < sym->st_value)
max = mid - 1;
else
min = mid + 1;
}
return (1);
return (ENOENT);
}
int
proc_addr2sym(struct proc_handle *p, uintptr_t addr, char *name,
size_t namesz, GElf_Sym *symcopy)
{
GElf_Ehdr ehdr;
GElf_Shdr shdr;
Elf *e;
Elf_Scn *scn, *dynsymscn, *symtabscn;
struct file_info *file;
struct map_info *mapping;
const char *s;
uintptr_t off;
u_long symtabstridx, dynsymstridx;
int error = -1;
int error;
if ((mapping = _proc_addr2map(p, addr)) == NULL) {
DPRINTFX("ERROR: proc_addr2map failed to resolve 0x%jx", addr);
@ -392,49 +460,22 @@ proc_addr2sym(struct proc_handle *p, uintptr_t addr, char *name,
mapping->map.pr_mapname);
return (-1);
}
e = mapping->file->elf;
if (gelf_getehdr(e, &ehdr) == NULL) {
DPRINTFX("ERROR: gelf_getehdr() failed: %s", elf_errmsg(-1));
goto err;
file = mapping->file;
off = file->ehdr.e_type == ET_DYN ? mapping->map.pr_vaddr : 0;
if (addr < off)
return (ENOENT);
addr -= off;
error = lookup_symbol_by_addr(file->elf, &file->dynsymtab, addr, &s,
symcopy);
if (error == ENOENT)
error = lookup_symbol_by_addr(file->elf, &file->symtab, addr,
&s, symcopy);
if (error == 0) {
symcopy->st_value += off;
demangle(s, name, namesz);
}
/*
* Find the index of the STRTAB and SYMTAB sections to locate
* symbol names.
*/
symtabstridx = dynsymstridx = 0;
scn = dynsymscn = symtabscn = NULL;
while ((scn = elf_nextscn(e, scn)) != NULL) {
gelf_getshdr(scn, &shdr);
switch (shdr.sh_type) {
case SHT_SYMTAB:
symtabscn = scn;
symtabstridx = shdr.sh_link;
break;
case SHT_DYNSYM:
dynsymscn = scn;
dynsymstridx = shdr.sh_link;
break;
}
}
off = ehdr.e_type == ET_EXEC ? 0 : mapping->map.pr_vaddr;
/*
* First look up the symbol in the dynsymtab, and fall back to the
* symtab if the lookup fails.
*/
error = lookup_addr(e, dynsymscn, dynsymstridx, off, addr, &s, symcopy);
if (error == 0)
goto out;
error = lookup_addr(e, symtabscn, symtabstridx, off, addr, &s, symcopy);
if (error != 0)
goto err;
out:
demangle(s, name, namesz);
err:
return (error);
}
@ -480,7 +521,7 @@ proc_name2map(struct proc_handle *p, const char *name)
* Look up the symbol with the given name and return a copy of it.
*/
static int
lookup_name(Elf *e, Elf_Scn *scn, u_long stridx, const char *symbol,
lookup_symbol_by_name(Elf *elf, struct symtab *symtab, const char *symbol,
GElf_Sym *symcopy, prsyminfo_t *si)
{
GElf_Sym sym;
@ -488,12 +529,11 @@ lookup_name(Elf *e, Elf_Scn *scn, u_long stridx, const char *symbol,
char *s;
int i;
if ((data = elf_getdata(scn, NULL)) == NULL) {
DPRINTFX("ERROR: elf_getdata() failed: %s", elf_errmsg(-1));
return (1);
}
if (symtab->nsyms == 0)
return (ENOENT);
data = symtab->data;
for (i = 0; gelf_getsym(data, i, &sym) != NULL; i++) {
s = elf_strptr(e, stridx, sym.st_name);
s = elf_strptr(elf, symtab->stridx, sym.st_name);
if (s != NULL && strcmp(s, symbol) == 0) {
memcpy(symcopy, &sym, sizeof(*symcopy));
if (si != NULL)
@ -501,21 +541,17 @@ lookup_name(Elf *e, Elf_Scn *scn, u_long stridx, const char *symbol,
return (0);
}
}
return (1);
return (ENOENT);
}
int
proc_name2sym(struct proc_handle *p, const char *object, const char *symbol,
GElf_Sym *symcopy, prsyminfo_t *si)
{
GElf_Ehdr ehdr;
GElf_Shdr shdr;
Elf *e;
Elf_Scn *scn, *dynsymscn, *symtabscn;
struct file_info *file;
struct map_info *mapping;
uintptr_t off;
u_long symtabstridx, dynsymstridx;
int error = -1;
int error;
if ((mapping = _proc_name2map(p, object)) == NULL) {
DPRINTFX("ERROR: proc_name2map failed to resolve %s", object);
@ -526,49 +562,17 @@ proc_name2sym(struct proc_handle *p, const char *object, const char *symbol,
mapping->map.pr_mapname);
return (-1);
}
e = mapping->file->elf;
if (gelf_getehdr(e, &ehdr) == NULL) {
DPRINTFX("ERROR: gelf_getehdr() failed: %s", elf_errmsg(-1));
goto err;
}
/*
* Find the index of the STRTAB and SYMTAB sections to locate
* symbol names.
*/
symtabstridx = dynsymstridx = 0;
scn = dynsymscn = symtabscn = NULL;
while ((scn = elf_nextscn(e, scn)) != NULL) {
gelf_getshdr(scn, &shdr);
switch (shdr.sh_type) {
case SHT_SYMTAB:
symtabscn = scn;
symtabstridx = shdr.sh_link;
break;
case SHT_DYNSYM:
dynsymscn = scn;
dynsymstridx = shdr.sh_link;
break;
}
}
file = mapping->file;
off = file->ehdr.e_type == ET_DYN ? mapping->map.pr_vaddr : 0;
/*
* First look up the symbol in the dynsymtab, and fall back to the
* symtab if the lookup fails.
*/
error = lookup_name(e, dynsymscn, dynsymstridx, symbol, symcopy, si);
error = lookup_symbol_by_name(file->elf, &file->dynsymtab, symbol,
symcopy, si);
if (error == ENOENT)
error = lookup_symbol_by_name(file->elf, &file->symtab, symbol,
symcopy, si);
if (error == 0)
goto out;
error = lookup_name(e, symtabscn, symtabstridx, symbol, symcopy, si);
if (error == 0)
goto out;
out:
off = ehdr.e_type == ET_EXEC ? 0 : mapping->map.pr_vaddr;
symcopy->st_value += off;
err:
symcopy->st_value += off;
return (error);
}
@ -596,16 +600,12 @@ int
proc_iter_symbyaddr(struct proc_handle *p, const char *object, int which,
int mask, proc_sym_f *func, void *cd)
{
GElf_Ehdr ehdr;
GElf_Shdr shdr;
GElf_Sym sym;
Elf *e;
Elf_Scn *scn, *foundscn;
Elf_Data *data;
struct file_info *file;
struct map_info *mapping;
char *s;
unsigned long stridx = -1;
int error = -1, i;
struct symtab *symtab;
const char *s;
int error, i;
if ((mapping = _proc_name2map(p, object)) == NULL) {
DPRINTFX("ERROR: proc_name2map failed to resolve %s", object);
@ -616,35 +616,14 @@ proc_iter_symbyaddr(struct proc_handle *p, const char *object, int which,
mapping->map.pr_mapname);
return (-1);
}
e = mapping->file->elf;
if (gelf_getehdr(e, &ehdr) == NULL) {
DPRINTFX("ERROR: gelf_getehdr() failed: %s", elf_errmsg(-1));
goto err;
}
/*
* Find the section we are looking for.
*/
foundscn = scn = NULL;
while ((scn = elf_nextscn(e, scn)) != NULL) {
gelf_getshdr(scn, &shdr);
if (which == PR_SYMTAB &&
shdr.sh_type == SHT_SYMTAB) {
foundscn = scn;
break;
} else if (which == PR_DYNSYM &&
shdr.sh_type == SHT_DYNSYM) {
foundscn = scn;
break;
}
}
if (!foundscn)
file = mapping->file;
symtab = which == PR_SYMTAB ? &file->symtab : &file->dynsymtab;
if (symtab->nsyms == 0)
return (-1);
stridx = shdr.sh_link;
if ((data = elf_getdata(foundscn, NULL)) == NULL) {
DPRINTFX("ERROR: elf_getdata() failed: %s", elf_errmsg(-1));
goto err;
}
for (i = 0; gelf_getsym(data, i, &sym) != NULL; i++) {
error = 0;
for (i = 0; gelf_getsym(symtab->data, i, &sym) != NULL; i++) {
if (GELF_ST_BIND(sym.st_info) == STB_LOCAL &&
(mask & BIND_LOCAL) == 0)
continue;
@ -669,13 +648,11 @@ proc_iter_symbyaddr(struct proc_handle *p, const char *object, int which,
if (GELF_ST_TYPE(sym.st_info) == STT_FILE &&
(mask & TYPE_FILE) == 0)
continue;
s = elf_strptr(e, stridx, sym.st_name);
if (ehdr.e_type != ET_EXEC)
s = elf_strptr(file->elf, symtab->stridx, sym.st_name);
if (file->ehdr.e_type == ET_DYN)
sym.st_value += mapping->map.pr_vaddr;
if ((error = (*func)(cd, &sym, s)) != 0)
goto err;
break;
}
error = 0;
err:
return (error);
}