- Local symbols could shadow globals in some instances. Fix it.

- mutex_enter() from ksyms_getval() could panic due to a change made
  in revision 1.40. Fix it.

- Replace the p-tree with a binary search of global symbols. Saves about
  250kB of wired memory on i386 and allows for faster lookups within
  module symbol tables.
This commit is contained in:
ad 2008-11-16 15:13:35 +00:00
parent e5c4df967a
commit 9485f42356
2 changed files with 102 additions and 177 deletions

View File

@ -1,4 +1,4 @@
/* $NetBSD: kern_ksyms.c,v 1.42 2008/11/12 12:36:16 ad Exp $ */
/* $NetBSD: kern_ksyms.c,v 1.43 2008/11/16 15:13:35 ad Exp $ */
/*-
* Copyright (c) 2008 The NetBSD Foundation, Inc.
@ -67,14 +67,11 @@
/*
* TODO:
*
* Consider replacing patricia tree with simpler binary search
* for symbol tables.
*
* Add support for mmap, poll.
*/
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kern_ksyms.c,v 1.42 2008/11/12 12:36:16 ad Exp $");
__KERNEL_RCSID(0, "$NetBSD: kern_ksyms.c,v 1.43 2008/11/16 15:13:35 ad Exp $");
#ifdef _KERNEL
#include "opt_ddb.h"
@ -134,21 +131,6 @@ TAILQ_HEAD(, ksyms_symtab) ksyms_symtabs =
TAILQ_HEAD_INITIALIZER(ksyms_symtabs);
static struct ksyms_symtab kernel_symtab;
/*
* Patricia-tree-based lookup structure for the in-kernel global symbols.
* Based on a design by Mikael Sundstrom, msm@sm.luth.se.
*/
struct ptree {
int16_t bitno;
int16_t lr[2];
} *symb;
static int16_t baseidx;
static int treex = 1;
#define P_BIT(key, bit) ((key[bit >> 3] >> (bit & 7)) & 1)
#define STRING(idx) (kernel_symtab.sd_symstart[idx].st_name + \
kernel_symtab.sd_strstart)
static int
ksyms_verify(void *symstart, void *strstart)
{
@ -166,146 +148,54 @@ ksyms_verify(void *symstart, void *strstart)
return 1;
}
/*
* Walk down the tree until a terminal node is found.
*/
static int
symbol_traverse(const char *key)
{
int16_t nb, rbit = baseidx;
while (rbit > 0) {
nb = symb[rbit].bitno;
rbit = symb[rbit].lr[P_BIT(key, nb)];
}
return -rbit;
}
static int
ptree_add(char *key, int val)
{
int idx;
int nix, cix, bit, rbit, sb, lastrbit, svbit = 0, ix;
char *m, *k;
if (baseidx == 0) {
baseidx = -val;
return 0; /* First element */
}
/* Get string to match against */
idx = symbol_traverse(key);
/* Find first mismatching bit */
m = STRING(idx);
k = key;
if (strcmp(m, k) == 0)
return 1;
for (cix = 0; *m && *k && *m == *k; m++, k++, cix += 8)
;
ix = ffs((int)*m ^ (int)*k) - 1;
cix += ix;
/* Create new node */
nix = treex++;
bit = P_BIT(key, cix);
symb[nix].bitno = cix;
symb[nix].lr[bit] = -val;
/* Find where to insert node */
rbit = baseidx;
lastrbit = 0;
for (;;) {
if (rbit < 0)
break;
sb = symb[rbit].bitno;
if (sb > cix)
break;
if (sb == cix)
printf("symb[rbit].bitno == cix!!!\n");
lastrbit = rbit;
svbit = P_BIT(key, sb);
rbit = symb[rbit].lr[svbit];
}
/* Do the actual insertion */
if (lastrbit == 0) {
/* first element */
symb[nix].lr[!bit] = baseidx;
baseidx = nix;
} else {
symb[nix].lr[!bit] = rbit;
symb[lastrbit].lr[svbit] = nix;
}
return 0;
}
static int
ptree_find(const char *key)
{
int idx;
if (baseidx == 0)
return 0;
idx = symbol_traverse(key);
if (strcmp(key, STRING(idx)) == 0)
return idx;
return 0;
}
static void
ptree_gen(char *off, struct ksyms_symtab *tab)
{
Elf_Sym *sym;
int i, nsym;
if (off != NULL)
symb = (struct ptree *)ALIGN(off);
else
symb = malloc((tab->sd_symsize/sizeof(Elf_Sym)) *
sizeof(struct ptree), M_DEVBUF, M_WAITOK);
symb--; /* sym index won't be 0 */
sym = tab->sd_symstart;
if ((nsym = tab->sd_symsize/sizeof(Elf_Sym)) > INT16_MAX) {
printf("Too many symbols for tree, skipping %d symbols\n",
nsym-INT16_MAX);
nsym = INT16_MAX;
}
for (i = 1; i < nsym; i++) {
if (ELF_ST_BIND(sym[i].st_info) != STB_GLOBAL)
continue;
ptree_add(tab->sd_strstart+sym[i].st_name, i);
if (tab->sd_minsym == NULL ||
sym[i].st_value < tab->sd_minsym->st_value)
tab->sd_minsym = &sym[i];
if (tab->sd_maxsym == NULL ||
sym[i].st_value > tab->sd_maxsym->st_value)
tab->sd_maxsym = &sym[i];
}
}
/*
* Finds a certain symbol name in a certain symbol table.
*/
static Elf_Sym *
findsym(const char *name, struct ksyms_symtab *table)
findsym(const char *name, struct ksyms_symtab *table, int type)
{
Elf_Sym *start = table->sd_symstart;
int i, sz = table->sd_symsize/sizeof(Elf_Sym);
char *np;
char *realstart = table->sd_strstart - table->sd_usroffset;
Elf_Sym *sym, *maxsym;
int low, mid, high, nglob;
char *str, *cmp;
if (table == &kernel_symtab && (i = ptree_find(name)) != 0)
return &start[i];
sym = table->sd_symstart;
str = table->sd_strstart - table->sd_usroffset;
nglob = table->sd_nglob;
low = 0;
high = nglob;
for (i = 0; i < sz; i++) {
np = realstart + start[i].st_name;
if (name[0] == np[0] && name[1] == np[1] &&
strcmp(name, np) == 0)
return &start[i];
/*
* Start with a binary search of all global symbols in this table.
* Global symbols must have unique names.
*/
while (low < high) {
mid = (low + high) >> 1;
cmp = sym[mid].st_name + str;
if (cmp[0] < name[0] || strcmp(cmp, name) < 0) {
low = mid + 1;
} else {
high = mid;
}
}
KASSERT(low == high);
if (__predict_true(low < nglob &&
strcmp(sym[low].st_name + str, name) == 0)) {
KASSERT(ELF_ST_BIND(sym[low].st_info) == STB_GLOBAL);
return &sym[low];
}
/*
* Perform a linear search of local symbols (rare). Many local
* symbols with the same name can exist so are not included in
* the binary search.
*/
if (type != KSYMS_EXTERN) {
maxsym = sym + table->sd_symsize / sizeof(Elf_Sym);
for (sym += nglob; sym < maxsym; sym++) {
if (strcmp(name, sym->st_name + str) == 0) {
return sym;
}
}
}
return NULL;
}
@ -317,8 +207,6 @@ void
ksymsattach(int arg)
{
if (baseidx == 0)
ptree_gen(0, &kernel_symtab);
}
/*
@ -334,13 +222,41 @@ ksymsattach(int arg)
* newstart - Address to which the symbol table has to be copied during
* shrinking. If NULL, it is not moved.
*/
static const char *addsymtab_strstart;
static int
addsymtab_compar(const void *a, const void *b)
{
const Elf_Sym *sa, *sb;
sa = a;
sb = b;
/*
* Split the symbol table into two, with globals at the start
* and locals at the end.
*/
if (ELF_ST_BIND(sa->st_info) != ELF_ST_BIND(sb->st_info)) {
if (ELF_ST_BIND(sa->st_info) == STB_GLOBAL) {
return -1;
}
if (ELF_ST_BIND(sb->st_info) == STB_GLOBAL) {
return 1;
}
}
/* Within each band, sort by name. */
return strcmp(sa->st_name + addsymtab_strstart,
sb->st_name + addsymtab_strstart);
}
static void
addsymtab(const char *name, void *symstart, size_t symsize,
void *strstart, size_t strsize, struct ksyms_symtab *tab,
void *newstart)
{
Elf_Sym *sym, *nsym;
int i, j, n;
int i, j, n, nglob;
char *str;
tab->sd_symstart = symstart;
@ -362,6 +278,7 @@ addsymtab(const char *name, void *symstart, size_t symsize,
sym = tab->sd_symstart;
nsym = (Elf_Sym *)newstart;
str = tab->sd_strstart;
nglob = 0;
for (i = n = 0; i < tab->sd_symsize/sizeof(Elf_Sym); i++) {
/*
* Remove useless symbols.
@ -384,14 +301,30 @@ addsymtab(const char *name, void *symstart, size_t symsize,
/* Save symbol. Set it as an absolute offset */
nsym[n] = sym[i];
nsym[n].st_shndx = SHN_ABS;
j = strlen(nsym[n].st_name + tab->sd_strstart) + 1;
j = strlen(nsym[n].st_name + str) + 1;
if (j > ksyms_maxlen)
ksyms_maxlen = j;
n++;
nglob += (ELF_ST_BIND(nsym[n].st_info) == STB_GLOBAL);
/* Compute min and max symbols. */
if (tab->sd_minsym == NULL ||
tab->sd_minsym->st_value > nsym[n].st_value) {
tab->sd_minsym = &nsym[n];
}
if (tab->sd_maxsym == NULL ||
tab->sd_maxsym->st_value < nsym[n].st_value) {
tab->sd_maxsym = &nsym[n];
}
n++;
}
/* Fill the rest of the record, and sort the symbols. */
tab->sd_symstart = nsym;
tab->sd_symsize = n * sizeof(Elf_Sym);
tab->sd_nglob = nglob;
addsymtab_strstart = str;
qsort(nsym, n, sizeof(Elf_Sym), addsymtab_compar);
/* ksymsread() is unlocked, so membar. */
membar_producer();
TAILQ_INSERT_TAIL(&ksyms_symtabs, tab, sd_queue);
@ -514,9 +447,6 @@ ksyms_getval_unlocked(const char *mod, const char *sym, unsigned long *val,
struct ksyms_symtab *st;
Elf_Sym *es;
if (!ksyms_initted)
return ENOENT;
#ifdef KSYMS_DEBUG
if (ksyms_debug & FOLLOW_CALLS)
printf("ksyms_getval_unlocked: mod %s sym %s valp %p\n",
@ -524,24 +454,15 @@ ksyms_getval_unlocked(const char *mod, const char *sym, unsigned long *val,
#endif
TAILQ_FOREACH(st, &ksyms_symtabs, sd_queue) {
if (st->sd_gone)
if (__predict_false(st->sd_gone))
continue;
if (mod && strcmp(st->sd_name, mod))
if (mod != NULL && strcmp(st->sd_name, mod))
continue;
if ((es = findsym(sym, st)) == NULL)
continue;
if (es->st_shndx == SHN_UNDEF)
continue;
/* Skip if bad binding */
if (type == KSYMS_EXTERN &&
ELF_ST_BIND(es->st_info) != STB_GLOBAL)
continue;
if (val)
if ((es = findsym(sym, st, type)) != NULL) {
*val = es->st_value;
return 0;
}
}
return ENOENT;
}
@ -550,6 +471,9 @@ ksyms_getval(const char *mod, const char *sym, unsigned long *val, int type)
{
int rc;
if (!ksyms_initted)
return ENOENT;
mutex_enter(&ksyms_lock);
rc = ksyms_getval_unlocked(mod, sym, val, type);
mutex_exit(&ksyms_lock);
@ -962,7 +886,7 @@ ksymsioctl(dev_t dev, u_long cmd, void *data, int fflag, struct lwp *l)
TAILQ_FOREACH(st, &ksyms_symtabs, sd_queue) {
if (st->sd_gone)
continue;
if ((sym = findsym(str, st)) == NULL)
if ((sym = findsym(str, st, KSYMS_ANY)) == NULL)
continue;
#ifdef notdef
/* Skip if bad binding */

View File

@ -1,4 +1,4 @@
/* $NetBSD: ksyms.h,v 1.18 2008/11/12 12:36:28 ad Exp $ */
/* $NetBSD: ksyms.h,v 1.19 2008/11/16 15:13:35 ad Exp $ */
/*
* Copyright (c) 2001, 2003 Anders Magnusson (ragge@ludd.luth.se).
@ -45,6 +45,7 @@ struct ksyms_symtab {
int sd_usroffset; /* Real address for userspace */
int sd_symsize; /* Size in bytes of symbol table */
int sd_strsize; /* Size of string table */
int sd_nglob; /* Number of global symbols */
bool sd_gone; /* dead but around for open() */
};