2 * Copyright 1998-2003 by Albert Cahalan; all rights reserved.
3 * This file may be used subject to the terms and conditions of the
4 * GNU Library General Public License Version 2, or any later version
5 * at your option, as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU Library General Public License for more details.
21 #include <sys/utsname.h>
24 #include "sysinfo.h" /* smp_num_cpus */
25 #include "wchan.h" // to verify prototypes
27 #define KSYMS_FILENAME "/proc/ksyms"
31 #define KSYMS_FILENAME "/would/be/nice/to/have/this/file"
32 #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-hacked"
33 #define linux_version_code 131598 /* ? */
34 #define smp_num_cpus 2
39 #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.12"
40 #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.12"
41 #define linux_version_code 131852 /* 2.3.12 */
42 #define smp_num_cpus 2
47 #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.18ac8-MODVERS"
48 #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.18ac8-MODVERS"
49 #define linux_version_code 131858 /* 2.3.18ac8 */
50 #define smp_num_cpus 2
55 #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.18ac8-NOMODVERS"
56 #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.18ac8-NOMODVERS"
57 #define linux_version_code 131858 /* 2.3.18ac8 */
58 #define smp_num_cpus 2
61 /* These are the symbol types, with relative popularity:
62 * ? w machine type junk for Alpha -- odd syntax
70 * 905 g generated by modutils?
71 * 929 G generated by modutils?
79 * For i386, that is: "RArBbDd?tT"
82 #define SYMBOL_TYPE_CHARS "Tt?dDbBrARGgsWS"
85 * '?' is a symbol type
86 * '.' is part of a name (versioning?)
87 * "\t[]" are for the module name in /proc/ksyms
89 #define LEGAL_SYSMAP_CHARS "0123456789_ ?.\n\t[]" \
90 "abcdefghijklmnopqrstuvwxyz" \
91 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
93 /* System.map lines look like:
94 * hex num, space, one of SYMBOL_TYPE_CHARS, space, LEGAL_SYSMAP_CHARS, \n
96 * Alpha systems can start with a few lines that have the address replaced
97 * by space padding and a 'w' for the type. For those lines, the last space
98 * is followed by something like: mikasa_primo_mv p2k_mv sable_gamma_mv
99 * (just one of those, always with a "_mv", then the newline)
101 * The /proc/ksyms lines are like System.map lines w/o the symbol type char.
102 * When odd features are used, the name part contains:
103 * "(.*)_R(smp_|smp2gig_|2gig_)?[0-9a-fA-F]{8,}"
104 * It is likely that more crap will be added...
107 typedef struct symb {
112 /* These mostly rely on POSIX to make them zero. */
114 static symb hashtable[256];
116 static char *sysmap_data;
117 static unsigned sysmap_room;
118 static symb *sysmap_index;
119 static unsigned sysmap_count;
121 static char *ksyms_data;
122 static unsigned ksyms_room = 4096;
123 static symb *ksyms_index;
124 static unsigned ksyms_count;
125 static unsigned idx_room;
127 /*********************************/
129 /* Kill this: _R(smp_?|smp2gig_?|2gig_?)?[0-9a-f]{8,}$
130 * We kill: (_R[^A-Z]*[0-9a-f]{8,})+$
132 * The loop should almost never be taken, but it has to be there.
133 * It gets rid of anything that _looks_ like a version code, even
134 * if a real version code has already been found. This is because
135 * the inability to perfectly recognize a version code may lead to
136 * symbol mangling, which in turn leads to mismatches between the
137 * /proc/ksyms and System.map data files.
140 static char *chop_version(char *arg){
142 cp = strchr(arg,'\t');
143 if(cp) *cp = '\0'; /* kill trailing module name first */
147 cp = strrchr(arg, 'R');
148 if(!cp || cp<=arg+1 || cp[-1]!='_') break;
168 int len = strlen(arg);
169 while( len>8 && !memcmp(arg,"GPLONLY_",8) ){
177 static char *chop_version(char *arg){
179 cp = strchr(arg,'\t');
180 if(cp) *cp = '\0'; /* kill trailing module name first */
183 cp = strrchr(arg, 'R');
184 if(!cp || cp<=arg+1 || cp[-1]!='_') break;
187 if(strpbrk(cp+1,"ABCDEFGHIJKLMNOPQRSTUVWXYZ")) break;
188 if(strspn(cp+len-8,"0123456789abcdef")!=8) break;
192 int len = strlen(arg);
193 while( len>8 && !memcmp(arg,"GPLONLY_",8) ){
201 /***********************************/
203 static const symb *search(unsigned KLONG address, symb *idx, unsigned count){
207 if(!idx) return NULL; /* maybe not allocated */
208 if(address < idx[0].addr) return NULL;
209 if(address >= idx[count-1].addr) return idx+count-1;
213 mid = (left + right) / 2;
214 if(address >= idx[mid].addr) left = mid;
215 if(address <= idx[mid].addr) right = mid;
216 if(right-left <= 1) break;
218 if(address == idx[right].addr) return idx+right;
222 /*********************************/
224 /* allocate if needed, read, and return buffer size */
225 static void read_file(const char *restrict filename, char **bufp, unsigned *restrict roomp) {
230 unsigned room = *roomp;
232 if(!room) goto hell; /* failed before */
233 if(!buf) buf = malloc(room);
236 fd = open(filename, O_RDONLY|O_NOCTTY|O_NONBLOCK);
239 case EINTR: goto open_again;
241 case EACCES: /* somebody screwing around? */
242 /* FIXME: set a flag to disable symbol lookup? */
243 case ENOENT:; /* no module support */
248 done = read(fd, buf+total, room-total-1);
249 if(done==0) break; /* nothing left */
251 if(errno==EINTR) continue; /* try again */
255 if(done==(ssize_t)room-total-1){
258 /* more to go, but no room in buffer */
260 tmp = realloc(buf, room);
265 if(done>0 && done<(ssize_t)room-total-1){
267 continue; /* OK, we read some. Go do more. */
269 fprintf(stderr,"%ld can't happen\n", (long)done);
270 /* FIXME: memory leak */
273 buf[total] = '\0'; // parse_ksyms() expects NUL-terminated file
281 *roomp = 0; /* this function will never work again */
287 /*********************************/
289 static int parse_ksyms(void) {
291 if(!ksyms_room || !ksyms_data) goto quiet_goodbye;
294 if(idx_room) goto bypass; /* some space already allocated */
299 vp = realloc(ksyms_index, sizeof(symb)*idx_room);
300 if(!vp) goto bad_alloc;
307 ksyms_index[ksyms_count].addr = STRTOUKL(endp, &endp, 16);
308 if(endp==saved || *endp != ' ') goto bad_parse;
311 endp = strchr(endp,'\n');
312 if(!endp) goto bad_parse; /* no newline */
314 ksyms_index[ksyms_count].name = chop_version(saved);
316 if(++ksyms_count >= idx_room) break; /* need more space */
322 fprintf(stderr, "Warning: not enough memory available\n");
326 fprintf(stderr, "Warning: "KSYMS_FILENAME" not normal\n");
330 if(ksyms_data) free(ksyms_data) , ksyms_data = NULL;
332 if(ksyms_index) free(ksyms_index) , ksyms_index = NULL;
337 /*********************************/
341 static int sysmap_mmap(const char *restrict const filename, message_fn message) {
346 fd = open(filename, O_RDONLY|O_NOCTTY|O_NONBLOCK);
348 if(fstat(fd, &sbuf) < 0) goto bad_open;
349 if(!S_ISREG(sbuf.st_mode)) goto bad_open;
350 if(sbuf.st_size < 5000) goto bad_open; /* if way too small */
351 /* Would be shared read-only, but we want '\0' after each name. */
352 endp = mmap(0, sbuf.st_size + 1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
354 while(*endp==' '){ /* damn Alpha machine types */
355 if(strncmp(endp," w ", 19)) goto bad_parse;
357 endp = strchr(endp,'\n');
358 if(!endp) goto bad_parse; /* no newline */
359 if(strncmp(endp-3, "_mv\n", 4)) goto bad_parse;
362 if(sysmap_data == (caddr_t) -1) goto bad_open;
365 sprintf(Version, "Version_%d", linux_version_code);
370 vp = realloc(sysmap_index, sizeof(symb)*sysmap_room);
371 if(!vp) goto bad_alloc;
375 if(endp - sysmap_data >= sbuf.st_size){ /* if we reached the end */
376 int i = VCNT; /* check VCNT times to verify this file */
377 if(*Version) goto bad_version;
378 if(!ksyms_index) return 1; /* if can not verify, assume success */
382 const symb *map_symb;
383 /* Choose VCNT entries from /proc/ksyms to test */
384 findme = ksyms_index + (ksyms_count*i/VCNT);
385 /* Search for them in the System.map */
386 map_symb = search(findme->addr, sysmap_index, sysmap_count);
388 if(map_symb->addr != findme->addr) continue;
389 /* backup to first matching address */
390 while (map_symb != sysmap_index){
391 if (map_symb->addr != (map_symb-1)->addr) break;
394 /* search for name in symbols with same address */
395 while (map_symb != (sysmap_index+sysmap_count)){
396 if (map_symb->addr != findme->addr) break;
397 if (!strcmp(map_symb->name,findme->name)) goto good_match;
400 map_symb--; /* backup to last symbol with matching address */
401 message("{%s} {%s}\n",map_symb->name,findme->name);
407 return 1; /* success */
409 sysmap_index[sysmap_count].addr = STRTOUKL(endp, &endp, 16);
410 if(*endp != ' ') goto bad_parse;
412 if(!strchr(SYMBOL_TYPE_CHARS, *endp)) goto bad_parse;
414 if(*endp != ' ') goto bad_parse;
417 endp = strchr(endp,'\n');
418 if(!endp) goto bad_parse; /* no newline */
421 vstart = chop_version(vstart);
422 sysmap_index[sysmap_count].name = vstart;
423 if(*vstart=='V' && *Version && !strcmp(Version,vstart)) *Version='\0';
424 if(++sysmap_count >= sysmap_room) break; /* need more space */
430 message("Warning: %s does not match kernel data.\n", filename);
434 message("Warning: %s has an incorrect kernel version.\n", filename);
438 message("Warning: not enough memory available\n");
442 message("Warning: %s not parseable as a System.map\n", filename);
446 message("Warning: %s could not be opened as a System.map\n", filename);
451 if(sysmap_index) free(sysmap_index);
454 if(sysmap_data) munmap(sysmap_data, sbuf.st_size + 1);
459 /*********************************/
461 static void read_and_parse(void){
462 static time_t stamp; /* after data gets old, load /proc/ksyms again */
463 if(time(NULL) != stamp){
464 read_file(KSYMS_FILENAME, &ksyms_data, &ksyms_room);
466 memset((void*)hashtable,0,sizeof(hashtable)); /* invalidate cache */
471 /*********************************/
473 static void default_message(const char *restrict format, ...) __attribute__((format(printf,1,2)));
474 static void default_message(const char *restrict format, ...) {
477 va_start (arg, format);
478 vfprintf (stderr, format, arg);
482 /*********************************/
484 static int use_wchan_file;
486 int open_psdb_message(const char *restrict override, message_fn message) {
487 static const char *sysmap_paths[] = {
488 "/boot/System.map-%s",
490 "/lib/modules/%s/System.map",
491 "/usr/src/linux/System.map",
498 const char **fmt = sysmap_paths;
501 #ifdef SYSMAP_FILENAME /* debug feature */
502 override = SYSMAP_FILENAME;
505 // first allow for a user-selected System.map file
509 (sm=getenv("PS_SYSMAP"))
511 (sm=getenv("PS_SYSTEM_MAP"))
515 if(sysmap_mmap(sm, message)) return 0;
517 /* failure is better than ignoring the user & using bad data */
518 return -1; /* ought to return "Namelist not found." */
521 // next try the Linux 2.5.xx method
522 if(!stat("/proc/self/wchan", &sbuf)){
523 use_wchan_file = 1; // hack
527 // finally, search for the System.map file
529 path[sizeof path - 1] = '\0';
532 snprintf(path, sizeof path - 1, *fmt, uts.release);
533 if(!stat(path, &sbuf)){
534 if (did_ksyms++) read_and_parse();
535 if (sysmap_mmap(path, message)) return 0;
538 /* TODO: Without System.map, no need to keep ksyms loaded. */
542 /***************************************/
544 int open_psdb(const char *restrict override) {
545 return open_psdb_message(override, default_message);
548 /***************************************/
550 static const char * read_wchan_file(unsigned pid){
552 const char *ret = buf;
556 snprintf(buf, sizeof buf, "/proc/%d/wchan", pid);
557 fd = open(buf, O_RDONLY);
558 if(fd==-1) return "?";
559 num = read(fd, buf, sizeof buf - 1);
561 if(num<1) return "?"; // allow for "0"
564 if(buf[0]=='0' && buf[1]=='\0') return "-";
566 // would skip over numbers if they existed -- but no
568 // lame ppc64 has a '.' in front of every name
571 case 's': if(!strncmp(ret, "sys_", 4)) ret += 4; break;
572 case 'd': if(!strncmp(ret, "do_", 3)) ret += 3; break;
573 case '_': while(*ret=='_') ret++; break;
578 /***************************************/
580 static const symb fail = { .name = "?" };
581 static const char dash[] = "-";
582 static const char star[] = "*";
584 #define MAX_OFFSET (0x1000*sizeof(long)) /* past this is generally junk */
586 /* return pointer to temporary static buffer with function name */
587 const char * lookup_wchan(unsigned KLONG address, unsigned pid) {
588 const symb *mod_symb;
589 const symb *map_symb;
590 const symb *good_symb;
594 // can't cache it due to a race condition :-(
595 if(use_wchan_file) return read_wchan_file(pid);
597 if(!address) return dash;
598 if(!~address) return star;
601 hash = (address >> 4) & 0xff; /* got 56/63 hits & 7/63 misses */
602 if(hashtable[hash].addr == address) return hashtable[hash].name;
603 mod_symb = search(address, ksyms_index, ksyms_count);
604 if(!mod_symb) mod_symb = &fail;
605 map_symb = search(address, sysmap_index, sysmap_count);
606 if(!map_symb) map_symb = &fail;
608 /* which result is closest? */
609 good_symb = (mod_symb->addr > map_symb->addr)
613 if(address > good_symb->addr + MAX_OFFSET) good_symb = &fail;
615 /* good_symb->name has the data, but needs to be trimmed */
616 ret = good_symb->name;
617 // lame ppc64 has a '.' in front of every name
620 case 's': if(!strncmp(ret, "sys_", 4)) ret += 4; break;
621 case 'd': if(!strncmp(ret, "do_", 3)) ret += 3; break;
622 case '_': while(*ret=='_') ret++; break;
624 /* if(!*ret) ret = fail.name; */ /* not likely (name was "sys_", etc.) */
626 /* cache name after abbreviation */
627 hashtable[hash].addr = address;
628 hashtable[hash].name = ret;