Updated with Tizen:Base source codes
[external/procps.git] / proc / ksym.c
1 /*
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.
10  */
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <time.h>
15 #include <stdarg.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 #include <unistd.h>
19 #include <sys/stat.h>
20 #include <sys/mman.h>
21 #include <sys/utsname.h>
22 #include "procps.h"
23 #include "version.h"
24 #include "sysinfo.h" /* smp_num_cpus */
25 #include "wchan.h"  // to verify prototypes
26
27 #define KSYMS_FILENAME "/proc/ksyms"
28
29 #if 0
30 #undef KSYMS_FILENAME
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
35 #endif
36
37 #if 0
38 #undef KSYMS_FILENAME
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
43 #endif
44
45 #if 0
46 #undef KSYMS_FILENAME
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
51 #endif
52
53 #if 0
54 #undef KSYMS_FILENAME
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
59 #endif
60
61 /* These are the symbol types, with relative popularity:
62  *     ? w  machine type junk for Alpha -- odd syntax
63  *     ? S  not for i386
64  *     4 W  not for i386
65  *    60 R
66  *   100 A
67  *   125 r
68  *   363 s  not for i386
69  *   858 B
70  *   905 g  generated by modutils?
71  *   929 G  generated by modutils?
72  *  1301 b
73  *  2750 D
74  *  4481 d
75  * 11417 ?
76  * 13666 t
77  * 15442 T
78  *
79  * For i386, that is: "RArBbDd?tT"
80  */
81
82 #define SYMBOL_TYPE_CHARS "Tt?dDbBrARGgsWS"
83
84 /*
85  * '?' is a symbol type
86  * '.' is part of a name (versioning?)
87  * "\t[]" are for the module name in /proc/ksyms
88  */
89 #define LEGAL_SYSMAP_CHARS "0123456789_ ?.\n\t[]" \
90                      "abcdefghijklmnopqrstuvwxyz" \
91                      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
92
93 /* System.map lines look like:
94  * hex num, space, one of SYMBOL_TYPE_CHARS, space, LEGAL_SYSMAP_CHARS, \n
95  *
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)
100  *
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...
105  */
106
107 typedef struct symb {
108   unsigned KLONG addr;
109   const char *name;
110 } symb;
111
112 /* These mostly rely on POSIX to make them zero. */
113
114 static symb hashtable[256];
115
116 static char       *sysmap_data;
117 static unsigned    sysmap_room;
118 static symb       *sysmap_index;
119 static unsigned    sysmap_count;
120
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;
126
127 /*********************************/
128
129 /* Kill this:  _R(smp_?|smp2gig_?|2gig_?)?[0-9a-f]{8,}$
130  * We kill:    (_R[^A-Z]*[0-9a-f]{8,})+$
131  *
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.
138  */
139 #if 0
140 static char *chop_version(char *arg){
141   char *cp;
142   cp = strchr(arg,'\t');
143   if(cp) *cp = '\0';  /* kill trailing module name first */
144   for(;;){
145     char *p;
146     int len = 0;
147     cp = strrchr(arg, 'R');
148     if(!cp || cp<=arg+1 || cp[-1]!='_') break;
149     for(p=cp; *++p; ){
150       switch(*p){
151       default:
152         goto out;
153       case '0' ... '9':
154       case 'a' ... 'f':
155         len++;
156         continue;
157       case 'g' ... 'z':
158       case '_':
159         len=0;
160         continue;
161       }
162     }
163     if(len<8) break;
164     cp[-1] = '\0';
165   }
166 out:
167   if(*arg=='G'){
168     int len = strlen(arg);
169     while( len>8 && !memcmp(arg,"GPLONLY_",8) ){
170       arg += 8;
171       len -= 8;
172     }
173   }
174   return arg;
175 }
176 #endif
177 static char *chop_version(char *arg){
178   char *cp;
179   cp = strchr(arg,'\t');
180   if(cp) *cp = '\0';  /* kill trailing module name first */
181   for(;;){
182     int len;
183     cp = strrchr(arg, 'R');
184     if(!cp || cp<=arg+1 || cp[-1]!='_') break;
185     len=strlen(cp);
186     if(len<9) break;
187     if(strpbrk(cp+1,"ABCDEFGHIJKLMNOPQRSTUVWXYZ")) break;
188     if(strspn(cp+len-8,"0123456789abcdef")!=8) break;
189     cp[-1] = '\0';
190   }
191   if(*arg=='G'){
192     int len = strlen(arg);
193     while( len>8 && !memcmp(arg,"GPLONLY_",8) ){
194       arg += 8;
195       len -= 8;
196     }
197   }
198   return arg;
199 }
200
201 /***********************************/
202
203 static const symb *search(unsigned KLONG address, symb *idx, unsigned count){
204   unsigned left;
205   unsigned mid;
206   unsigned right;
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;
210   left  = 0;
211   right = count-1;
212   for(;;){
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;
217   }
218   if(address == idx[right].addr) return idx+right;
219   return idx+left;
220 }
221
222 /*********************************/
223
224 /* allocate if needed, read, and return buffer size */
225 static void read_file(const char *restrict filename, char **bufp, unsigned *restrict roomp) {
226   int fd = 0;
227   ssize_t done;
228   char *buf = *bufp;
229   ssize_t total = 0;
230   unsigned room = *roomp;
231
232   if(!room) goto hell;     /* failed before */
233   if(!buf) buf = malloc(room);
234   if(!buf) goto hell;
235 open_again:
236   fd = open(filename, O_RDONLY|O_NOCTTY|O_NONBLOCK);
237   if(fd<0){
238     switch(errno){
239     case EINTR:  goto open_again;
240     default:     _exit(101);
241     case EACCES:   /* somebody screwing around? */
242       /* FIXME: set a flag to disable symbol lookup? */
243     case ENOENT:;  /* no module support */
244     }
245     goto hell;
246   }
247   for(;;){
248     done = read(fd, buf+total, room-total-1);
249     if(done==0) break;  /* nothing left */
250     if(done==-1){
251       if(errno==EINTR) continue;  /* try again */
252       perror("");
253       goto hell;
254     }
255     if(done==(ssize_t)room-total-1){
256       char *tmp;
257       total += done;
258       /* more to go, but no room in buffer */
259       room *= 2;
260       tmp = realloc(buf, room);
261       if(!tmp) goto hell;
262       buf = tmp;
263       continue;
264     }
265     if(done>0 && done<(ssize_t)room-total-1){
266       total += done; 
267       continue;   /* OK, we read some. Go do more. */
268     }
269     fprintf(stderr,"%ld can't happen\n", (long)done);
270     /* FIXME: memory leak */
271     _exit(42);
272   }
273   buf[total] = '\0';   // parse_ksyms() expects NUL-terminated file
274   *bufp = buf;
275   *roomp = room;
276   close(fd);
277   return;
278 hell:
279   if(buf) free(buf);
280   *bufp = NULL;
281   *roomp = 0;   /* this function will never work again */
282   total = 0;
283   if(fd>0) close(fd);
284   return;
285 }
286
287 /*********************************/
288
289 static int parse_ksyms(void) {
290   char *endp;
291   if(!ksyms_room || !ksyms_data) goto quiet_goodbye;
292   endp = ksyms_data;
293   ksyms_count = 0;
294   if(idx_room) goto bypass;  /* some space already allocated */
295   idx_room = 512;
296   for(;;){
297     void *vp;
298     idx_room *= 2;
299     vp = realloc(ksyms_index, sizeof(symb)*idx_room);
300     if(!vp) goto bad_alloc;
301     ksyms_index = vp;
302 bypass:
303     for(;;){
304       char *saved;
305       if(!*endp) return 1;
306       saved = endp;
307       ksyms_index[ksyms_count].addr = STRTOUKL(endp, &endp, 16);
308       if(endp==saved || *endp != ' ') goto bad_parse;
309       endp++;
310       saved = endp;
311       endp = strchr(endp,'\n');
312       if(!endp) goto bad_parse;   /* no newline */
313       *endp = '\0';
314       ksyms_index[ksyms_count].name = chop_version(saved);
315       ++endp;
316       if(++ksyms_count >= idx_room) break;  /* need more space */
317     }
318   }
319
320   if(0){
321 bad_alloc:
322     fprintf(stderr, "Warning: not enough memory available\n");
323   }
324   if(0){
325 bad_parse:
326     fprintf(stderr, "Warning: "KSYMS_FILENAME" not normal\n");
327   }
328 quiet_goodbye:
329   idx_room = 0;
330   if(ksyms_data) free(ksyms_data) , ksyms_data = NULL;
331   ksyms_room = 0;
332   if(ksyms_index) free(ksyms_index) , ksyms_index = NULL;
333   ksyms_count = 0;
334   return 0;
335 }
336
337 /*********************************/
338
339 #define VCNT 16
340
341 static int sysmap_mmap(const char *restrict const filename, message_fn message) {
342   struct stat sbuf;
343   char *endp;
344   int fd;
345   char Version[32];
346   fd = open(filename, O_RDONLY|O_NOCTTY|O_NONBLOCK);
347   if(fd<0) return 0;
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);
353   sysmap_data = endp;
354   while(*endp==' '){  /* damn Alpha machine types */
355     if(strncmp(endp,"                 w ", 19)) goto bad_parse;
356     endp += 19;
357     endp = strchr(endp,'\n');
358     if(!endp) goto bad_parse;   /* no newline */
359     if(strncmp(endp-3, "_mv\n", 4)) goto bad_parse;
360     endp++;
361   }
362   if(sysmap_data == (caddr_t) -1) goto bad_open;
363   close(fd);
364   fd = -1;
365   sprintf(Version, "Version_%d", linux_version_code);
366   sysmap_room = 512;
367   for(;;){
368     void *vp;
369     sysmap_room *= 2;
370     vp = realloc(sysmap_index, sizeof(symb)*sysmap_room);
371     if(!vp) goto bad_alloc;
372     sysmap_index = vp;
373     for(;;){
374       char *vstart;
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 */
379         while(i--){
380 #if 1
381           const symb *findme;
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);
387           if(map_symb){
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;
392               map_symb--;
393             }
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;
398               map_symb++;
399             }
400             map_symb--; /* backup to last symbol with matching address */
401             message("{%s} {%s}\n",map_symb->name,findme->name);
402             goto bad_match;
403           }
404 good_match:;
405 #endif
406         }
407         return 1; /* success */
408       }
409       sysmap_index[sysmap_count].addr = STRTOUKL(endp, &endp, 16);
410       if(*endp != ' ') goto bad_parse;
411       endp++;
412       if(!strchr(SYMBOL_TYPE_CHARS, *endp)) goto bad_parse;
413       endp++;
414       if(*endp != ' ') goto bad_parse;
415       endp++;
416       vstart = endp;
417       endp = strchr(endp,'\n');
418       if(!endp) goto bad_parse;   /* no newline */
419       *endp = '\0';
420       ++endp;
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 */
425     }
426   }
427
428   if(0){
429 bad_match:
430     message("Warning: %s does not match kernel data.\n", filename);
431   }
432   if(0){
433 bad_version:
434     message("Warning: %s has an incorrect kernel version.\n", filename);
435   }
436   if(0){
437 bad_alloc:
438     message("Warning: not enough memory available\n");
439   }
440   if(0){
441 bad_parse:
442     message("Warning: %s not parseable as a System.map\n", filename);
443   }
444   if(0){
445 bad_open:
446     message("Warning: %s could not be opened as a System.map\n", filename);
447   }
448
449   sysmap_room=0;
450   sysmap_count=0;
451   if(sysmap_index) free(sysmap_index);
452   sysmap_index = NULL;
453   if(fd>=0) close(fd);
454   if(sysmap_data) munmap(sysmap_data, sbuf.st_size + 1);
455   sysmap_data = NULL;
456   return 0;
457 }
458
459 /*********************************/
460
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);
465     parse_ksyms();
466     memset((void*)hashtable,0,sizeof(hashtable)); /* invalidate cache */
467     stamp = time(NULL);
468   }
469 }
470
471 /*********************************/
472
473 static void default_message(const char *restrict format, ...) __attribute__((format(printf,1,2)));
474 static void default_message(const char *restrict format, ...) {
475     va_list arg;
476
477     va_start (arg, format);
478     vfprintf (stderr, format, arg);
479     va_end (arg);
480 }
481
482 /*********************************/
483
484 static int use_wchan_file;
485
486 int open_psdb_message(const char *restrict override, message_fn message) {
487   static const char *sysmap_paths[] = {
488     "/boot/System.map-%s",
489     "/boot/System.map",
490     "/lib/modules/%s/System.map",
491     "/usr/src/linux/System.map",
492     "/System.map",
493     NULL
494   };
495   struct stat sbuf;
496   struct utsname uts;
497   char path[128];
498   const char **fmt = sysmap_paths;
499   const char *sm;
500
501 #ifdef SYSMAP_FILENAME    /* debug feature */
502   override = SYSMAP_FILENAME;
503 #endif
504
505   // first allow for a user-selected System.map file
506   if(
507     (sm=override)
508     ||
509     (sm=getenv("PS_SYSMAP"))
510     ||
511     (sm=getenv("PS_SYSTEM_MAP"))
512   ){
513     if(!have_privs){
514       read_and_parse();
515       if(sysmap_mmap(sm, message)) return 0;
516     }
517     /* failure is better than ignoring the user & using bad data */
518     return -1;           /* ought to return "Namelist not found." */
519   }
520
521   // next try the Linux 2.5.xx method
522   if(!stat("/proc/self/wchan", &sbuf)){
523     use_wchan_file = 1; // hack
524     return 0;
525   }
526
527   // finally, search for the System.map file
528   uname(&uts);
529   path[sizeof path - 1] = '\0';
530   do{
531     int did_ksyms = 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;
536     }
537   }while(*++fmt);
538   /* TODO: Without System.map, no need to keep ksyms loaded. */
539   return -1;
540 }
541
542 /***************************************/
543
544 int open_psdb(const char *restrict override) {
545     return open_psdb_message(override, default_message);
546 }
547
548 /***************************************/
549
550 static const char * read_wchan_file(unsigned pid){
551   static char buf[64];
552   const char *ret = buf;
553   ssize_t num;
554   int fd;
555
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);
560   close(fd);
561   if(num<1) return "?"; // allow for "0"
562   buf[num] = '\0';
563
564   if(buf[0]=='0' && buf[1]=='\0') return "-";
565
566   // would skip over numbers if they existed -- but no
567
568   // lame ppc64 has a '.' in front of every name
569   if(*ret=='.') ret++;
570   switch(*ret){
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;
574   }
575   return ret;
576 }
577
578 /***************************************/
579
580 static const symb fail = { .name = "?" };
581 static const char dash[] = "-";
582 static const char star[] = "*";
583
584 #define MAX_OFFSET (0x1000*sizeof(long))  /* past this is generally junk */
585
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;
591   const char *ret;
592   unsigned hash;
593
594   // can't cache it due to a race condition :-(
595   if(use_wchan_file) return read_wchan_file(pid);
596
597   if(!address)  return dash;
598   if(!~address) return star;
599
600   read_and_parse();
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;
607
608   /* which result is closest? */
609   good_symb = (mod_symb->addr > map_symb->addr)
610             ? mod_symb
611             : map_symb
612   ;
613   if(address > good_symb->addr + MAX_OFFSET) good_symb = &fail;
614
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
618   if(*ret=='.') ret++;
619   switch(*ret){
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;
623   }
624   /* if(!*ret) ret = fail.name; */  /* not likely (name was "sys_", etc.) */
625
626   /* cache name after abbreviation */
627   hashtable[hash].addr = address;
628   hashtable[hash].name = ret;
629
630   return ret;
631 }