253 lines
7.0 KiB
C
253 lines
7.0 KiB
C
/* kernel address -> symbol with next lower address. Charles Blake, 1996.
|
|
* Written to obviate the need for psdatabase initialization based upon kernel
|
|
* binary formats, etc.
|
|
*
|
|
* The basic algorithm is an approximate (intervals split vaguely 50-50) binary
|
|
* search taking advantage of the fact the System.map is already sorted in
|
|
* ascending order by the kernel makefile. It needs to assume an average symbol
|
|
* record length to avoid scanning the entire symbol table, but in practice the
|
|
* search time does not seem to be especially sensitive to this choice.
|
|
*
|
|
* The search could be an exact binary search if the lines of System.map were
|
|
* padded with blanks to the right. awk '{printf "%8s%2s %-21.21s\n",$1,$2,$3}'
|
|
* would do the trick for this but either makes the file large or truncates
|
|
* symbols. The approximate method seems to be plenty fast enough, costing
|
|
* only about as much as one extra fstat() or so per process.
|
|
*/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/utsname.h>
|
|
#include "proc/psdata.h"
|
|
#include "proc/ps.h"
|
|
#include "proc/version.h"
|
|
|
|
#define MAX_ADDR_SZ 32
|
|
static char *sysmap, *sysmap_last, sysmap_fmt[10];
|
|
static int sysmap_len, sysmap_mean = 32, sysmap_addrsz;
|
|
|
|
/* scan backward in a string no further than address beg looking for c */
|
|
static char *strchrrev(char *a, char *beg, char c) {
|
|
if (a)
|
|
while (--a > beg && *a != c) ;
|
|
return a;
|
|
}
|
|
|
|
/* return ptr to the beg of approximately the i-th record */
|
|
static char *addr_str(int i) {
|
|
char *guess = sysmap + sysmap_mean * i;
|
|
if (!i) return sysmap;
|
|
if (guess - sysmap > sysmap_len - 2) guess = sysmap + sysmap_len - 2;
|
|
for ( ; *guess != '\n' && guess > sysmap; guess--)
|
|
;
|
|
return guess + 1;
|
|
|
|
}
|
|
|
|
/* return ptr to symbol string (\n terminated) given beg of record ptr */
|
|
static char *sym_put(char *buf, int len, char *addrptr) {
|
|
char *s;
|
|
while (*addrptr++ != ' ') ;
|
|
while (*addrptr++ != ' ') ;
|
|
strncpy(buf, addrptr, len);
|
|
for (s = buf; s < buf + len; s++)
|
|
if (*s == '\n')
|
|
*s = '\0';
|
|
buf[len - 1] = '\0';
|
|
return buf;
|
|
}
|
|
|
|
/* Try to open and mmap a single symbol table file and initialize globals */
|
|
int sysmap_mmap(char *path) {
|
|
int fd;
|
|
struct stat sbuf;
|
|
char *p;
|
|
if (sysmap) /* do nothing if already mapped */
|
|
return 1;
|
|
if ((fd = open(path, O_RDONLY)) < 0
|
|
|| fstat(fd, &sbuf) < 0
|
|
|| (sysmap = mmap(0, sbuf.st_size,
|
|
PROT_READ, MAP_SHARED,
|
|
fd, 0)) == (caddr_t) -1)
|
|
{
|
|
close(fd);
|
|
sysmap = NULL;
|
|
return 0;
|
|
}
|
|
sysmap_len = sbuf.st_size;
|
|
sysmap_last = strchrrev(sysmap + sysmap_len - 2, sysmap, '\n') + 1;
|
|
|
|
/* Now check first line of sysmap for hex numbers in first column. Note:
|
|
0x/0X prefixes are disallowed, but easily addable. Capitalization is
|
|
irrelevant because strncasecmp(3) is used below instead of strncmp. */
|
|
for (p = sysmap; *p != ' '
|
|
&& ((*p >= '0' && *p <= '9') ||
|
|
(*p >= 'A' && *p <= 'F') ||
|
|
(*p >= 'a' && *p <= 'f'))
|
|
&& p < sysmap + MAX_ADDR_SZ;
|
|
p++) /* no-op */ ;
|
|
if (*p != ' ') { /* uh-oh: cannot understand format */
|
|
fprintf(stderr, "warning: %s not parseable as a System.map.\n", path);
|
|
munmap(sysmap, sysmap_len);
|
|
sysmap = NULL;
|
|
close(fd);
|
|
return 0;
|
|
}
|
|
sysmap_addrsz = p - sysmap;
|
|
snprintf(sysmap_fmt, sizeof sysmap_fmt, "%%0%dlx", sysmap_addrsz);
|
|
close(fd);
|
|
return 1;
|
|
}
|
|
|
|
/* kernel address -> name resolver.
|
|
returned value is only good until the next call to the function.
|
|
*/
|
|
char *sysmap_symbol(unsigned long address) {
|
|
static char rval[128], *pc, addr[MAX_ADDR_SZ];
|
|
int i, p, n = sysmap_len / (double)sysmap_mean;
|
|
|
|
sprintf(addr, sysmap_fmt, address);
|
|
p = 0; pc = sysmap;
|
|
while (n) {
|
|
i = p + (n >> 1);
|
|
if (strncasecmp(addr, pc = addr_str(i), sysmap_addrsz) > 0)
|
|
p = i + 1;
|
|
n >>= 1;
|
|
}
|
|
if (pc == sysmap_last) /* scan forward but not past end */
|
|
return sym_put(rval, sizeof rval, pc);
|
|
while (strncasecmp(addr, pc, sysmap_addrsz) > 0)
|
|
pc = strchr(pc, '\n') + 1;
|
|
if (pc == sysmap) /* scan backward but not past beg */
|
|
return sym_put(rval, sizeof rval, pc);
|
|
while (strncasecmp(addr, pc, sysmap_addrsz) < 0)
|
|
pc = strchrrev(pc - 1, sysmap, '\n') + 1;
|
|
return sym_put(rval, sizeof rval, pc);
|
|
}
|
|
|
|
/* extern struct nlist *namelist; */
|
|
struct tbl_s vars, fncs;
|
|
struct psdb_hdr db_hdr;
|
|
int psdb = -1;
|
|
|
|
int open_psdb(void) {
|
|
static char *sysmap_paths[] = {
|
|
"/boot/System.map-%s",
|
|
"/boot/System.map",
|
|
"/lib/modules/%s/System.map",
|
|
NULL
|
|
};
|
|
static char *psdb_paths[] = {
|
|
"/etc/psdatabase",
|
|
"/boot/psdatabase-%s",
|
|
"/boot/psdatabase",
|
|
"/lib/modules/%s/psdatabase",
|
|
NULL
|
|
};
|
|
char **fmt, *env, path[64];
|
|
struct utsname uts;
|
|
uname(&uts);
|
|
if ((env = getenv("PS_SYSMAP")) && sysmap_mmap(env))
|
|
return 0;
|
|
for (fmt = sysmap_paths; *fmt; fmt++) {
|
|
snprintf(path, sizeof path, *fmt, uts.release);
|
|
if (sysmap_mmap(path))
|
|
return 0;
|
|
}
|
|
for (fmt = psdb_paths; *fmt; fmt++) {
|
|
snprintf(path, sizeof path, *fmt, uts.release);
|
|
if ((psdb = open(path, O_RDONLY)) != -1 &&
|
|
read(psdb, (char*)&db_hdr, sizeof db_hdr) == sizeof db_hdr &&
|
|
strncmp(db_hdr.magic, procps_version, sizeof(db_hdr.magic)) == 0)
|
|
/* && version_cmp(kernel,psdatabase) */
|
|
return 0;
|
|
if (psdb != -1)
|
|
fprintf(stderr,
|
|
"psdatabase has magic no. %*s instead of %*s\n",
|
|
(int) sizeof db_hdr.magic, db_hdr.magic,
|
|
(int) sizeof db_hdr.magic, procps_version);
|
|
close(psdb);
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
void close_psdb(void) {
|
|
if (sysmap)
|
|
munmap(sysmap, sysmap_len);
|
|
else if (psdb != -1)
|
|
close(psdb);
|
|
psdb = -1;
|
|
sysmap = NULL;
|
|
}
|
|
|
|
int read_tbl(struct dbtbl_s *dbtbl, struct tbl_s *tbl) {
|
|
lseek(psdb, dbtbl->off, SEEK_SET);
|
|
tbl->tbl = (struct sym_s *) xmalloc(dbtbl->size);
|
|
if (read(psdb, (char *) tbl->tbl, dbtbl->size) != dbtbl->size) {
|
|
perror(PSDATABASE);
|
|
exit(1);
|
|
}
|
|
tbl->nsym = dbtbl->nsym;
|
|
tbl->strings = (char *) (tbl->tbl + tbl->nsym);
|
|
return 0;
|
|
}
|
|
|
|
char * find_func(unsigned long address) {
|
|
int n;
|
|
struct sym_s *p;
|
|
char *s;
|
|
|
|
if (sysmap)
|
|
return sysmap_symbol(address);
|
|
if (psdb == -1)
|
|
return "(no psdb)";
|
|
if (fncs.tbl == NULL)
|
|
read_tbl(&db_hdr.fncs, &fncs);
|
|
p = fncs.tbl;
|
|
n = fncs.nsym;
|
|
while (n) {
|
|
int i = n / 2;
|
|
if (p[i].addr < address) {
|
|
p = &p[i+1];
|
|
if (p->addr > address) {
|
|
--p;
|
|
break;
|
|
}
|
|
--n;
|
|
}
|
|
n /= 2;
|
|
}
|
|
s = p->name + fncs.strings;
|
|
return *s=='_' ? s+1 : s;
|
|
}
|
|
|
|
char * wchan(unsigned long address) {
|
|
static char zero = 0;
|
|
char *p;
|
|
|
|
if (address) {
|
|
p = find_func(address);
|
|
if (strncmp(p, "sys_", 4) == 0)
|
|
p += 4;
|
|
while (*p == '_' && *p)
|
|
++p;
|
|
} else /* 0 address means not in kernel space */
|
|
p = &zero;
|
|
return p;
|
|
}
|
|
|
|
#ifdef SYSMAP_TEST
|
|
int main(int ac, char** av) {
|
|
if (ac < 3) {printf("%s System.map lines hexaddr ...\n",av[0]); return 1;}
|
|
if (!sysmap_mmap(av[1])) return 1;
|
|
if ((sysmap_mean = atoi(av[2])) <= 0) return 1;
|
|
for (av += 3; *av; av++)
|
|
printf("%s %s\n", *av, sysmap_symbol(strtoul(*av, NULL, 16)));
|
|
return 0;
|
|
}
|
|
#endif
|