/* * Copyright 1998-2002 by Albert Cahalan; all rights reserved. * This file may be used subject to the terms and conditions of the * GNU Library General Public License Version 2, or any later version * at your option, as published by the Free Software Foundation. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <stdarg.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <sys/stat.h> #include <sys/mman.h> #include <sys/utsname.h> #include "procps.h" #include "version.h" #include "sysinfo.h" /* smp_num_cpus */ #define KSYMS_FILENAME "/proc/ksyms" #if 0 #undef KSYMS_FILENAME #define KSYMS_FILENAME "/would/be/nice/to/have/this/file" #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-hacked" #define linux_version_code 131598 /* ? */ #define smp_num_cpus 2 #endif #if 0 #undef KSYMS_FILENAME #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.12" #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.12" #define linux_version_code 131852 /* 2.3.12 */ #define smp_num_cpus 2 #endif #if 0 #undef KSYMS_FILENAME #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.18ac8-MODVERS" #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.18ac8-MODVERS" #define linux_version_code 131858 /* 2.3.18ac8 */ #define smp_num_cpus 2 #endif #if 0 #undef KSYMS_FILENAME #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.18ac8-NOMODVERS" #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.18ac8-NOMODVERS" #define linux_version_code 131858 /* 2.3.18ac8 */ #define smp_num_cpus 2 #endif /* These are the symbol types, with relative popularity: * ? w machine type junk for Alpha -- odd syntax * ? S not for i386 * 4 W not for i386 * 60 R * 100 A * 125 r * 363 s not for i386 * 858 B * 905 g generated by modutils? * 929 G generated by modutils? * 1301 b * 2750 D * 4481 d * 11417 ? * 13666 t * 15442 T * * For i386, that is: "RArBbDd?tT" */ #define SYMBOL_TYPE_CHARS "Tt?dDbBrARGgsWS" /* * '?' is a symbol type * '.' is part of a name (versioning?) * "\t[]" are for the module name in /proc/ksyms */ #define LEGAL_SYSMAP_CHARS "0123456789_ ?.\n\t[]" \ "abcdefghijklmnopqrstuvwxyz" \ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" /* System.map lines look like: * hex num, space, one of SYMBOL_TYPE_CHARS, space, LEGAL_SYSMAP_CHARS, \n * * Alpha systems can start with a few lines that have the address replaced * by space padding and a 'w' for the type. For those lines, the last space * is followed by something like: mikasa_primo_mv p2k_mv sable_gamma_mv * (just one of those, always with a "_mv", then the newline) * * The /proc/ksyms lines are like System.map lines w/o the symbol type char. * When odd features are used, the name part contains: * "(.*)_R(smp_|smp2gig_|2gig_)?[0-9a-fA-F]{8,}" * It is likely that more crap will be added... */ typedef struct symb { const char *name; unsigned long addr; } symb; static const symb fail = { "?", 0 }; static const char dash[] = "-"; /* These mostly rely on POSIX to make them zero. */ static symb hashtable[256]; static char *sysmap_data; static unsigned sysmap_room; static symb *sysmap_index; static unsigned sysmap_count; static char *ksyms_data; static unsigned ksyms_room = 4096; static symb *ksyms_index; static unsigned ksyms_count; static unsigned idx_room; /*********************************/ /* Kill this: _R(smp_?|smp2gig_?|2gig_?)?[0-9a-f]{8,}$ * We kill: (_R[^A-Z]*[0-9a-f]{8,})+$ * * The loop should almost never be taken, but it has to be there. * It gets rid of anything that _looks_ like a version code, even * if a real version code has already been found. This is because * the inability to perfectly recognize a version code may lead to * symbol mangling, which in turn leads to mismatches between the * /proc/ksyms and System.map data files. */ #if 0 static void chop_version(char *arg) { char *cp; cp = strchr(arg, '\t'); if (cp) *cp = '\0'; /* kill trailing module name first */ for (;;) { char *p; int len = 0; cp = strrchr(arg, 'R'); if (!cp || cp <= arg + 1 || cp[-1] != '_') break; for (p = cp; *++p;) { switch (*p) { default: return; case '0' ... '9': case 'a' ... 'f': len++; continue; case 'g' ... 'z': case '_': len = 0; continue; } } if (len < 8) break; cp[-1] = '\0'; } } #endif static void chop_version(char *arg) { char *cp; cp = strchr(arg, '\t'); if (cp) *cp = '\0'; /* kill trailing module name first */ for (;;) { int len; cp = strrchr(arg, 'R'); if (!cp || cp <= arg + 1 || cp[-1] != '_') break; len = strlen(cp); if (len < 9) break; if (strpbrk(cp + 1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ")) break; if (strspn(cp + len - 8, "0123456789abcdef") != 8) break; cp[-1] = '\0'; } } /***********************************/ static const symb *search(unsigned long address, symb * idx, unsigned count) { unsigned left; unsigned mid; unsigned right; if (!idx) return NULL; /* maybe not allocated */ if (address < idx[0].addr) return NULL; if (address >= idx[count - 1].addr) return idx + count - 1; left = 0; right = count - 1; for (;;) { mid = (left + right) / 2; if (address >= idx[mid].addr) left = mid; if (address <= idx[mid].addr) right = mid; if (right - left <= 1) break; } if (address == idx[right].addr) return idx + right; return idx + left; } /*********************************/ /* allocate if needed, read, and return buffer size */ static void read_file(const char *restrict filename, char **bufp, unsigned *restrict roomp) { int fd = 0; ssize_t done; char *buf = *bufp; ssize_t total = 0; unsigned room = *roomp; if (!room) goto hell; /* failed before */ if (!buf) buf = malloc(room); if (!buf) goto hell; open_again: fd = open(filename, O_RDONLY | O_NOCTTY | O_NONBLOCK); if (fd < 0) { switch (errno) { case EINTR: goto open_again; default: _exit(101); case EACCES: /* somebody screwing around? */ /* FIXME: set a flag to disable symbol lookup? */ case ENOENT:; /* no module support */ } goto hell; } for (;;) { done = read(fd, buf + total, room - total - 1); if (done == 0) break; /* nothing left */ if (done == -1) { if (errno == EINTR) continue; /* try again */ perror(""); goto hell; } if (done == (ssize_t) room - total - 1) { char *tmp; total += done; /* more to go, but no room in buffer */ room *= 2; tmp = realloc(buf, room); if (!tmp) goto hell; buf = tmp; continue; } if (done > 0 && done < (ssize_t) room - total - 1) { total += done; continue; /* OK, we read some. Go do more. */ } fprintf(stderr, "%ld can't happen\n", (long)done); /* FIXME: memory leak */ _exit(42); } *bufp = buf; *roomp = room; close(fd); return; hell: free(buf); *bufp = NULL; *roomp = 0; /* this function will never work again */ total = 0; close(fd); return; } /*********************************/ static int parse_ksyms(void) { char *endp; if (!ksyms_room || !ksyms_data) goto quiet_goodbye; endp = ksyms_data; ksyms_count = 0; if (idx_room) goto bypass; /* some space already allocated */ idx_room = 512; for (;;) { void *vp; idx_room *= 2; vp = realloc(ksyms_index, sizeof(symb) * idx_room); if (!vp) goto bad_alloc; ksyms_index = vp; bypass: for (;;) { char *saved; if (!*endp) return 1; saved = endp; ksyms_index[ksyms_count].addr = strtoul(endp, &endp, 16); if (endp == saved || *endp != ' ') goto bad_parse; endp++; ksyms_index[ksyms_count].name = endp; saved = endp; endp = strchr(endp, '\n'); if (!endp) goto bad_parse; /* no newline */ *endp = '\0'; chop_version(saved); ++endp; if (++ksyms_count >= idx_room) break; /* need more space */ } } if (0) { bad_alloc: fprintf(stderr, "Warning: not enough memory available\n"); } if (0) { bad_parse: fprintf(stderr, "Warning: " KSYMS_FILENAME " not normal\n"); } quiet_goodbye: idx_room = 0; if (ksyms_data) free(ksyms_data), ksyms_data = NULL; ksyms_room = 0; if (ksyms_index) free(ksyms_index), ksyms_index = NULL; ksyms_count = 0; return 0; } /*********************************/ #define VCNT 16 static int sysmap_mmap(const char *restrict const filename, void (*message) (const char *restrict, ...)) { struct stat sbuf; char *endp; int fd; char Version[32]; fd = open(filename, O_RDONLY | O_NOCTTY | O_NONBLOCK); if (fd < 0) return 0; if (fstat(fd, &sbuf) < 0) goto bad_open; if (!S_ISREG(sbuf.st_mode)) goto bad_open; if (sbuf.st_size < 5000) goto bad_open; /* if way too small */ /* Would be shared read-only, but we want '\0' after each name. */ endp = mmap(0, sbuf.st_size + 1, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); sysmap_data = endp; while (*endp == ' ') { /* damn Alpha machine types */ if (strncmp(endp, " w ", 19)) goto bad_parse; endp += 19; endp = strchr(endp, '\n'); if (!endp) goto bad_parse; /* no newline */ if (strncmp(endp - 3, "_mv\n", 4)) goto bad_parse; endp++; } if (sysmap_data == (caddr_t) - 1) goto bad_open; close(fd); fd = -1; sprintf(Version, "Version_%d", linux_version_code); sysmap_room = 512; for (;;) { void *vp; sysmap_room *= 2; vp = realloc(sysmap_index, sizeof(symb) * sysmap_room); if (!vp) goto bad_alloc; sysmap_index = vp; for (;;) { char *vstart; if (endp - sysmap_data >= sbuf.st_size) { /* if we reached the end */ int i = VCNT; /* check VCNT times to verify this file */ if (*Version) goto bad_version; if (!ksyms_index) return 1; /* if can not verify, assume success */ while (i--) { #if 1 const symb *findme; const symb *map_symb; /* Choose VCNT entries from /proc/ksyms to test */ findme = ksyms_index + (ksyms_count * i / VCNT); /* Search for them in the System.map */ map_symb = search(findme->addr, sysmap_index, sysmap_count); if (map_symb) { if (map_symb->addr != findme->addr) continue; /* backup to first matching address */ while (map_symb != sysmap_index) { if (map_symb->addr != (map_symb - 1)->addr) break; map_symb--; } /* search for name in symbols with same address */ while (map_symb != (sysmap_index + sysmap_count)) { if (map_symb->addr != findme->addr) break; if (!strcmp (map_symb->name, findme->name)) goto good_match; map_symb++; } map_symb--; /* backup to last symbol with matching address */ message("{%s} {%s}\n", map_symb->name, findme->name); goto bad_match; } good_match: ; #endif } return 1; /* success */ } sysmap_index[sysmap_count].addr = strtoul(endp, &endp, 16); if (*endp != ' ') goto bad_parse; endp++; if (!strchr(SYMBOL_TYPE_CHARS, *endp)) goto bad_parse; endp++; if (*endp != ' ') goto bad_parse; endp++; sysmap_index[sysmap_count].name = endp; vstart = endp; endp = strchr(endp, '\n'); if (!endp) goto bad_parse; /* no newline */ *endp = '\0'; ++endp; chop_version(vstart); if (*vstart == 'V' && *Version && !strcmp(Version, vstart)) *Version = '\0'; if (++sysmap_count >= sysmap_room) break; /* need more space */ } } if (0) { bad_match: message("Warning: %s does not match kernel data.\n", filename); } if (0) { bad_version: message("Warning: %s has an incorrect kernel version.\n", filename); } if (0) { bad_alloc: message("Warning: not enough memory available\n"); } if (0) { bad_parse: message("Warning: %s not parseable as a System.map\n", filename); } if (0) { bad_open: message("Warning: %s could not be opened as a System.map\n", filename); } sysmap_room = 0; sysmap_count = 0; if (sysmap_index) free(sysmap_index); sysmap_index = NULL; if (fd >= 0) close(fd); if (sysmap_data) munmap(sysmap_data, sbuf.st_size + 1); sysmap_data = NULL; return 0; } /*********************************/ static void read_and_parse(void) { static time_t stamp; /* after data gets old, load /proc/ksyms again */ if (time(NULL) != stamp) { read_file(KSYMS_FILENAME, &ksyms_data, &ksyms_room); parse_ksyms(); memset((void *)hashtable, 0, sizeof(hashtable)); /* invalidate cache */ stamp = time(NULL); } } /*********************************/ static void default_message(const char *restrict format, ...) { va_list arg; va_start(arg, format); vfprintf(stderr, format, arg); va_end(arg); } /*********************************/ static int use_wchan_file; int open_psdb_message(const char *restrict override, void (*message) (const char *, ...)) { static const char *sysmap_paths[] = { "/boot/System.map-%s", "/boot/System.map", "/lib/modules/%s/System.map", "/usr/src/linux/System.map", "/System.map", NULL }; struct stat sbuf; struct utsname uts; char path[64]; const char **fmt = sysmap_paths; const char *sm; #ifdef SYSMAP_FILENAME /* debug feature */ override = SYSMAP_FILENAME; #endif // first allow for a user-selected System.map file if ((sm = override) || (sm = getenv("PS_SYSMAP")) || (sm = getenv("PS_SYSTEM_MAP")) ) { read_and_parse(); if (sysmap_mmap(sm, message)) return 0; /* failure is better than ignoring the user & using bad data */ return -1; /* ought to return "Namelist not found." */ } // next try the Linux 2.5.xx method if (!stat("/proc/self/wchan", &sbuf)) { use_wchan_file = 1; // hack return 0; } // finally, search for the System.map file uname(&uts); do { int did_ksyms = 0; snprintf(path, sizeof path, *fmt, uts.release); if (!stat(path, &sbuf)) { if (did_ksyms++) read_and_parse(); if (sysmap_mmap(path, message)) return 0; } } while (*++fmt); /* TODO: Without System.map, no need to keep ksyms loaded. */ return -1; } /***************************************/ int open_psdb(const char *restrict override) { return open_psdb_message(override, default_message); } /***************************************/ const char *read_wchan_file(unsigned pid) { static char buf[64]; const char *ret = buf; ssize_t num; int fd; snprintf(buf, sizeof buf, "/proc/%d/wchan", pid); fd = open(buf, O_RDONLY); if (fd == -1) return "?"; num = read(fd, buf, sizeof buf - 1); close(fd); if (num < 1) return "?"; // allow for "0" buf[num] = '\0'; if (buf[0] == '0' && buf[1] == '\0') return "-"; // would skip over numbers if they existed -- but no switch (*ret) { case 's': if (!strncmp(ret, "sys_", 4)) ret += 4; break; case 'd': if (!strncmp(ret, "do_", 3)) ret += 3; break; case '_': while (*ret == '_') ret++; break; } return ret; } /***************************************/ #define MAX_OFFSET (0x1000*sizeof(long)) /* past this is generally junk */ /* return pointer to temporary static buffer with function name */ const char *wchan(unsigned long address, unsigned pid) { const symb *mod_symb; const symb *map_symb; const symb *good_symb; const char *ret; unsigned hash; // can't cache it due to a race condition :-( if (use_wchan_file) return read_wchan_file(pid); if (!address) return dash; read_and_parse(); hash = (address >> 4) & 0xff; /* got 56/63 hits & 7/63 misses */ if (hashtable[hash].addr == address) return hashtable[hash].name; mod_symb = search(address, ksyms_index, ksyms_count); if (!mod_symb) mod_symb = &fail; map_symb = search(address, sysmap_index, sysmap_count); if (!map_symb) map_symb = &fail; /* which result is closest? */ good_symb = (mod_symb->addr > map_symb->addr) ? mod_symb : map_symb; if (address > good_symb->addr + MAX_OFFSET) good_symb = &fail; /* good_symb->name has the data, but needs to be trimmed */ ret = good_symb->name; switch (*ret) { case 's': if (!strncmp(ret, "sys_", 4)) ret += 4; break; case 'd': if (!strncmp(ret, "do_", 3)) ret += 3; break; case '_': while (*ret == '_') ret++; break; } /* if (!*ret) ret = fail.name; *//* not likely (name was "sys_", etc.) */ /* cache name after abbreviation */ hashtable[hash].addr = address; hashtable[hash].name = ret; return ret; }