/* 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. * * Copyright (C) 1996 Charles Blake * Copyright (C) 1998 Michael K. Johnson * This file may be redistributed under the terms of the * GNU Library General Public License, see ../COPYING.LIB for precise terms. */ #include #include #include #include #include #include #include #include #include "psdata.h" #include "ps.h" #include "version.h" #define MAX_ADDR_SZ 32 static char *sysmap, *sysmap_last, sysmap_fmt[10]; static int sysmap_len, sysmap_mean = 32, sysmap_addrsz; static char buf[128]; /* 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; } /* Uhhh: This is a linear search. */ char* find_module_sym(unsigned long address) { FILE *fd; unsigned long adr, best_adr = 0; char mod[32], nm[32]; char c; *mod = 1; *buf = 0; /* sprintf (buf, "%08x", address); */ fd = fopen ("/proc/ksyms", "r"); if (!fd) return ("(no proc)"); while (!feof (fd) && !(*mod == 0)) { fscanf (fd, "%lx %s\n", &adr, nm); if ((c = fgetc (fd)) != '[') *mod = 0; else { fscanf (fd, "%s", mod); }; if (abs(address - adr) < abs(address - best_adr) /* && address >= adr */ && abs(address - adr) < 0xa00) { best_adr = adr; *buf = '['; if (address - adr > 0x100) strcpy (buf+1, mod); /* module name */ else strcpy(buf, nm); /* name of function/variable */ }; }; fclose (fd); return buf; }; 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; if (address > p[n-1].addr) return find_module_sym(address); 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