/* * 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 #include #include #include #include #include #include #include #include #include #include #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); } buf[total] = '\0'; // parse_ksyms() expects NUL-terminated file *bufp = buf; *roomp = room; close(fd); return; hell: if(buf) 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; }