1 /*
2 * Copyright 1998-2002 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
26 #define KSYMS_FILENAME "/proc/ksyms"
27
28 #if 0
29 #undef KSYMS_FILENAME
30 #define KSYMS_FILENAME "/would/be/nice/to/have/this/file"
31 #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-hacked"
32 #define linux_version_code 131598 /* ? */
33 #define smp_num_cpus 2
34 #endif
35
36 #if 0
37 #undef KSYMS_FILENAME
38 #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.12"
39 #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.12"
40 #define linux_version_code 131852 /* 2.3.12 */
41 #define smp_num_cpus 2
42 #endif
43
44 #if 0
45 #undef KSYMS_FILENAME
46 #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.18ac8-MODVERS"
47 #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.18ac8-MODVERS"
48 #define linux_version_code 131858 /* 2.3.18ac8 */
49 #define smp_num_cpus 2
50 #endif
51
52 #if 0
53 #undef KSYMS_FILENAME
54 #define KSYMS_FILENAME "/home/albert/ps/45621/ksyms-2.3.18ac8-NOMODVERS"
55 #define SYSMAP_FILENAME "/home/albert/ps/45621/System.map-2.3.18ac8-NOMODVERS"
56 #define linux_version_code 131858 /* 2.3.18ac8 */
57 #define smp_num_cpus 2
58 #endif
59
60 /* These are the symbol types, with relative popularity:
61 * ? w machine type junk for Alpha -- odd syntax
62 * ? S not for i386
63 * 4 W not for i386
64 * 60 R
65 * 100 A
66 * 125 r
67 * 363 s not for i386
68 * 858 B
69 * 905 g generated by modutils?
70 * 929 G generated by modutils?
71 * 1301 b
72 * 2750 D
73 * 4481 d
74 * 11417 ?
75 * 13666 t
76 * 15442 T
77 *
78 * For i386, that is: "RArBbDd?tT"
79 */
80
81 #define SYMBOL_TYPE_CHARS "Tt?dDbBrARGgsWS"
82
83 /*
84 * '?' is a symbol type
85 * '.' is part of a name (versioning?)
86 * "\t[]" are for the module name in /proc/ksyms
87 */
88 #define LEGAL_SYSMAP_CHARS "0123456789_ ?.\n\t[]" \
89 "abcdefghijklmnopqrstuvwxyz" \
90 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
91
92 /* System.map lines look like:
93 * hex num, space, one of SYMBOL_TYPE_CHARS, space, LEGAL_SYSMAP_CHARS, \n
94 *
95 * Alpha systems can start with a few lines that have the address replaced
96 * by space padding and a 'w' for the type. For those lines, the last space
97 * is followed by something like: mikasa_primo_mv p2k_mv sable_gamma_mv
98 * (just one of those, always with a "_mv", then the newline)
99 *
100 * The /proc/ksyms lines are like System.map lines w/o the symbol type char.
101 * When odd features are used, the name part contains:
102 * "(.*)_R(smp_|smp2gig_|2gig_)?[0-9a-fA-F]{8,}"
103 * It is likely that more crap will be added...
104 */
105
106 typedef struct symb {
107 const char *name;
108 unsigned long addr;
109 } symb;
110
111 static const symb fail = { "?", 0 };
112
113 static const char dash[] = "-";
114
115 /* These mostly rely on POSIX to make them zero. */
116
117 static symb hashtable[256];
118
119 static char *sysmap_data;
120 static unsigned sysmap_room;
121 static symb *sysmap_index;
122 static unsigned sysmap_count;
123
124 static char *ksyms_data;
125 static unsigned ksyms_room = 4096;
126 static symb *ksyms_index;
127 static unsigned ksyms_count;
128 static unsigned idx_room;
129
130 /*********************************/
131
132 /* Kill this: _R(smp_?|smp2gig_?|2gig_?)?[0-9a-f]{8,}$
133 * We kill: (_R[^A-Z]*[0-9a-f]{8,})+$
134 *
135 * The loop should almost never be taken, but it has to be there.
136 * It gets rid of anything that _looks_ like a version code, even
137 * if a real version code has already been found. This is because
138 * the inability to perfectly recognize a version code may lead to
139 * symbol mangling, which in turn leads to mismatches between the
140 * /proc/ksyms and System.map data files.
141 */
142 #if 0
143 static void chop_version(char *arg)
144 {
145 char *cp;
146 cp = strchr(arg, '\t');
147 if (cp)
148 *cp = '\0'; /* kill trailing module name first */
149 for (;;) {
150 char *p;
151 int len = 0;
152 cp = strrchr(arg, 'R');
153 if (!cp || cp <= arg + 1 || cp[-1] != '_')
154 break;
155 for (p = cp; *++p;) {
156 switch (*p) {
157 default:
158 return;
159 case '0' ... '9':
160 case 'a' ... 'f':
161 len++;
162 continue;
163 case 'g' ... 'z':
164 case '_':
165 len = 0;
166 continue;
167 }
168 }
169 if (len < 8)
170 break;
171 cp[-1] = '\0';
172 }
173 }
174 #endif
chop_version(char * arg)175 static void chop_version(char *arg)
176 {
177 char *cp;
178 cp = strchr(arg, '\t');
179 if (cp)
180 *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] != '_')
185 break;
186 len = strlen(cp);
187 if (len < 9)
188 break;
189 if (strpbrk(cp + 1, "ABCDEFGHIJKLMNOPQRSTUVWXYZ"))
190 break;
191 if (strspn(cp + len - 8, "0123456789abcdef") != 8)
192 break;
193 cp[-1] = '\0';
194 }
195 }
196
197 /***********************************/
198
search(unsigned long address,symb * idx,unsigned count)199 static const symb *search(unsigned long address, symb * idx, unsigned count)
200 {
201 unsigned left;
202 unsigned mid;
203 unsigned right;
204 if (!idx)
205 return NULL; /* maybe not allocated */
206 if (address < idx[0].addr)
207 return NULL;
208 if (address >= idx[count - 1].addr)
209 return idx + count - 1;
210 left = 0;
211 right = count - 1;
212 for (;;) {
213 mid = (left + right) / 2;
214 if (address >= idx[mid].addr)
215 left = mid;
216 if (address <= idx[mid].addr)
217 right = mid;
218 if (right - left <= 1)
219 break;
220 }
221 if (address == idx[right].addr)
222 return idx + right;
223 return idx + left;
224 }
225
226 /*********************************/
227
228 /* allocate if needed, read, and return buffer size */
read_file(const char * restrict filename,char ** bufp,unsigned * restrict roomp)229 static void read_file(const char *restrict filename, char **bufp,
230 unsigned *restrict roomp)
231 {
232 int fd = 0;
233 ssize_t done;
234 char *buf = *bufp;
235 ssize_t total = 0;
236 unsigned room = *roomp;
237
238 if (!room)
239 goto hell; /* failed before */
240 if (!buf)
241 buf = malloc(room);
242 if (!buf)
243 goto hell;
244 open_again:
245 fd = open(filename, O_RDONLY | O_NOCTTY | O_NONBLOCK);
246 if (fd < 0) {
247 switch (errno) {
248 case EINTR:
249 goto open_again;
250 default:
251 _exit(101);
252 case EACCES: /* somebody screwing around? */
253 /* FIXME: set a flag to disable symbol lookup? */
254 case ENOENT:; /* no module support */
255 }
256 goto hell;
257 }
258 for (;;) {
259 done = read(fd, buf + total, room - total - 1);
260 if (done == 0)
261 break; /* nothing left */
262 if (done == -1) {
263 if (errno == EINTR)
264 continue; /* try again */
265 perror("");
266 goto hell;
267 }
268 if (done == (ssize_t) room - total - 1) {
269 char *tmp;
270 total += done;
271 /* more to go, but no room in buffer */
272 room *= 2;
273 tmp = realloc(buf, room);
274 if (!tmp)
275 goto hell;
276 buf = tmp;
277 continue;
278 }
279 if (done > 0 && done < (ssize_t) room - total - 1) {
280 total += done;
281 continue; /* OK, we read some. Go do more. */
282 }
283 fprintf(stderr, "%ld can't happen\n", (long)done);
284 /* FIXME: memory leak */
285 _exit(42);
286 }
287 *bufp = buf;
288 *roomp = room;
289 close(fd);
290 return;
291 hell:
292 free(buf);
293 *bufp = NULL;
294 *roomp = 0; /* this function will never work again */
295 total = 0;
296 close(fd);
297 return;
298 }
299
300 /*********************************/
301
parse_ksyms(void)302 static int parse_ksyms(void)
303 {
304 char *endp;
305 if (!ksyms_room || !ksyms_data)
306 goto quiet_goodbye;
307 endp = ksyms_data;
308 ksyms_count = 0;
309 if (idx_room)
310 goto bypass; /* some space already allocated */
311 idx_room = 512;
312 for (;;) {
313 void *vp;
314 idx_room *= 2;
315 vp = realloc(ksyms_index, sizeof(symb) * idx_room);
316 if (!vp)
317 goto bad_alloc;
318 ksyms_index = vp;
319 bypass:
320 for (;;) {
321 char *saved;
322 if (!*endp)
323 return 1;
324 saved = endp;
325 ksyms_index[ksyms_count].addr =
326 strtoul(endp, &endp, 16);
327 if (endp == saved || *endp != ' ')
328 goto bad_parse;
329 endp++;
330 ksyms_index[ksyms_count].name = endp;
331 saved = endp;
332 endp = strchr(endp, '\n');
333 if (!endp)
334 goto bad_parse; /* no newline */
335 *endp = '\0';
336 chop_version(saved);
337 ++endp;
338 if (++ksyms_count >= idx_room)
339 break; /* need more space */
340 }
341 }
342
343 if (0) {
344 bad_alloc:
345 fprintf(stderr, "Warning: not enough memory available\n");
346 }
347 if (0) {
348 bad_parse:
349 fprintf(stderr, "Warning: " KSYMS_FILENAME " not normal\n");
350 }
351 quiet_goodbye:
352 idx_room = 0;
353 if (ksyms_data)
354 free(ksyms_data), ksyms_data = NULL;
355 ksyms_room = 0;
356 if (ksyms_index)
357 free(ksyms_index), ksyms_index = NULL;
358 ksyms_count = 0;
359 return 0;
360 }
361
362 /*********************************/
363
364 #define VCNT 16
365
sysmap_mmap(const char * restrict const filename,void (* message)(const char * restrict,...))366 static int sysmap_mmap(const char *restrict const filename,
367 void (*message) (const char *restrict, ...))
368 {
369 struct stat sbuf;
370 char *endp;
371 int fd;
372 char Version[32];
373 fd = open(filename, O_RDONLY | O_NOCTTY | O_NONBLOCK);
374 if (fd < 0)
375 return 0;
376 if (fstat(fd, &sbuf) < 0)
377 goto bad_open;
378 if (!S_ISREG(sbuf.st_mode))
379 goto bad_open;
380 if (sbuf.st_size < 5000)
381 goto bad_open; /* if way too small */
382 /* Would be shared read-only, but we want '\0' after each name. */
383 endp =
384 mmap(0, sbuf.st_size + 1, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd,
385 0);
386 sysmap_data = endp;
387 while (*endp == ' ') { /* damn Alpha machine types */
388 if (strncmp(endp, " w ", 19))
389 goto bad_parse;
390 endp += 19;
391 endp = strchr(endp, '\n');
392 if (!endp)
393 goto bad_parse; /* no newline */
394 if (strncmp(endp - 3, "_mv\n", 4))
395 goto bad_parse;
396 endp++;
397 }
398 if (sysmap_data == (caddr_t) - 1)
399 goto bad_open;
400 close(fd);
401 fd = -1;
402 sprintf(Version, "Version_%d", linux_version_code);
403 sysmap_room = 512;
404 for (;;) {
405 void *vp;
406 sysmap_room *= 2;
407 vp = realloc(sysmap_index, sizeof(symb) * sysmap_room);
408 if (!vp)
409 goto bad_alloc;
410 sysmap_index = vp;
411 for (;;) {
412 char *vstart;
413 if (endp - sysmap_data >= sbuf.st_size) { /* if we reached the end */
414 int i = VCNT; /* check VCNT times to verify this file */
415 if (*Version)
416 goto bad_version;
417 if (!ksyms_index)
418 return 1; /* if can not verify, assume success */
419 while (i--) {
420 #if 1
421 const symb *findme;
422 const symb *map_symb;
423 /* Choose VCNT entries from /proc/ksyms to test */
424 findme =
425 ksyms_index +
426 (ksyms_count * i / VCNT);
427 /* Search for them in the System.map */
428 map_symb =
429 search(findme->addr, sysmap_index,
430 sysmap_count);
431 if (map_symb) {
432 if (map_symb->addr !=
433 findme->addr)
434 continue;
435 /* backup to first matching address */
436 while (map_symb != sysmap_index) {
437 if (map_symb->addr !=
438 (map_symb -
439 1)->addr)
440 break;
441 map_symb--;
442 }
443 /* search for name in symbols with same address */
444 while (map_symb !=
445 (sysmap_index +
446 sysmap_count)) {
447 if (map_symb->addr !=
448 findme->addr)
449 break;
450 if (!strcmp
451 (map_symb->name,
452 findme->name))
453 goto good_match;
454 map_symb++;
455 }
456 map_symb--; /* backup to last symbol with matching address */
457 message("{%s} {%s}\n",
458 map_symb->name,
459 findme->name);
460 goto bad_match;
461 }
462 good_match: ;
463 #endif
464 }
465 return 1; /* success */
466 }
467 sysmap_index[sysmap_count].addr =
468 strtoul(endp, &endp, 16);
469 if (*endp != ' ')
470 goto bad_parse;
471 endp++;
472 if (!strchr(SYMBOL_TYPE_CHARS, *endp))
473 goto bad_parse;
474 endp++;
475 if (*endp != ' ')
476 goto bad_parse;
477 endp++;
478 sysmap_index[sysmap_count].name = endp;
479 vstart = endp;
480 endp = strchr(endp, '\n');
481 if (!endp)
482 goto bad_parse; /* no newline */
483 *endp = '\0';
484 ++endp;
485 chop_version(vstart);
486 if (*vstart == 'V' && *Version
487 && !strcmp(Version, vstart))
488 *Version = '\0';
489 if (++sysmap_count >= sysmap_room)
490 break; /* need more space */
491 }
492 }
493
494 if (0) {
495 bad_match:
496 message("Warning: %s does not match kernel data.\n", filename);
497 }
498 if (0) {
499 bad_version:
500 message("Warning: %s has an incorrect kernel version.\n",
501 filename);
502 }
503 if (0) {
504 bad_alloc:
505 message("Warning: not enough memory available\n");
506 }
507 if (0) {
508 bad_parse:
509 message("Warning: %s not parseable as a System.map\n",
510 filename);
511 }
512 if (0) {
513 bad_open:
514 message("Warning: %s could not be opened as a System.map\n",
515 filename);
516 }
517
518 sysmap_room = 0;
519 sysmap_count = 0;
520 if (sysmap_index)
521 free(sysmap_index);
522 sysmap_index = NULL;
523 if (fd >= 0)
524 close(fd);
525 if (sysmap_data)
526 munmap(sysmap_data, sbuf.st_size + 1);
527 sysmap_data = NULL;
528 return 0;
529 }
530
531 /*********************************/
532
read_and_parse(void)533 static void read_and_parse(void)
534 {
535 static time_t stamp; /* after data gets old, load /proc/ksyms again */
536 if (time(NULL) != stamp) {
537 read_file(KSYMS_FILENAME, &ksyms_data, &ksyms_room);
538 parse_ksyms();
539 memset((void *)hashtable, 0, sizeof(hashtable)); /* invalidate cache */
540 stamp = time(NULL);
541 }
542 }
543
544 /*********************************/
545
default_message(const char * restrict format,...)546 static void default_message(const char *restrict format, ...)
547 {
548 va_list arg;
549
550 va_start(arg, format);
551 vfprintf(stderr, format, arg);
552 va_end(arg);
553 }
554
555 /*********************************/
556
557 static int use_wchan_file;
558
open_psdb_message(const char * restrict override,void (* message)(const char *,...))559 int open_psdb_message(const char *restrict override,
560 void (*message) (const char *, ...))
561 {
562 static const char *sysmap_paths[] = {
563 "/boot/System.map-%s",
564 "/boot/System.map",
565 "/lib/modules/%s/System.map",
566 "/usr/src/linux/System.map",
567 "/System.map",
568 NULL
569 };
570 struct stat sbuf;
571 struct utsname uts;
572 char path[64];
573 const char **fmt = sysmap_paths;
574 const char *sm;
575
576 #ifdef SYSMAP_FILENAME /* debug feature */
577 override = SYSMAP_FILENAME;
578 #endif
579
580 // first allow for a user-selected System.map file
581 if ((sm = override)
582 || (sm = getenv("PS_SYSMAP"))
583 || (sm = getenv("PS_SYSTEM_MAP"))
584 ) {
585 read_and_parse();
586 if (sysmap_mmap(sm, message))
587 return 0;
588 /* failure is better than ignoring the user & using bad data */
589 return -1; /* ought to return "Namelist not found." */
590 }
591 // next try the Linux 2.5.xx method
592 if (!stat("/proc/self/wchan", &sbuf)) {
593 use_wchan_file = 1; // hack
594 return 0;
595 }
596 // finally, search for the System.map file
597 uname(&uts);
598 do {
599 int did_ksyms = 0;
600 snprintf(path, sizeof path, *fmt, uts.release);
601 if (!stat(path, &sbuf)) {
602 if (did_ksyms++)
603 read_and_parse();
604 if (sysmap_mmap(path, message))
605 return 0;
606 }
607 } while (*++fmt);
608 /* TODO: Without System.map, no need to keep ksyms loaded. */
609 return -1;
610 }
611
612 /***************************************/
613
open_psdb(const char * restrict override)614 int open_psdb(const char *restrict override)
615 {
616 return open_psdb_message(override, default_message);
617 }
618
619 /***************************************/
620
read_wchan_file(unsigned pid)621 const char *read_wchan_file(unsigned pid)
622 {
623 static char buf[64];
624 const char *ret = buf;
625 ssize_t num;
626 int fd;
627
628 snprintf(buf, sizeof buf, "/proc/%d/wchan", pid);
629 fd = open(buf, O_RDONLY);
630 if (fd == -1)
631 return "?";
632 num = read(fd, buf, sizeof buf - 1);
633 close(fd);
634 if (num < 1)
635 return "?"; // allow for "0"
636 buf[num] = '\0';
637
638 if (buf[0] == '0' && buf[1] == '\0')
639 return "-";
640
641 // would skip over numbers if they existed -- but no
642
643 switch (*ret) {
644 case 's':
645 if (!strncmp(ret, "sys_", 4))
646 ret += 4;
647 break;
648 case 'd':
649 if (!strncmp(ret, "do_", 3))
650 ret += 3;
651 break;
652 case '_':
653 while (*ret == '_')
654 ret++;
655 break;
656 }
657 return ret;
658 }
659
660 /***************************************/
661
662 #define MAX_OFFSET (0x1000*sizeof(long)) /* past this is generally junk */
663
664 /* return pointer to temporary static buffer with function name */
wchan(unsigned long address,unsigned pid)665 const char *wchan(unsigned long address, unsigned pid)
666 {
667 const symb *mod_symb;
668 const symb *map_symb;
669 const symb *good_symb;
670 const char *ret;
671 unsigned hash;
672
673 // can't cache it due to a race condition :-(
674 if (use_wchan_file)
675 return read_wchan_file(pid);
676
677 if (!address)
678 return dash;
679
680 read_and_parse();
681 hash = (address >> 4) & 0xff; /* got 56/63 hits & 7/63 misses */
682 if (hashtable[hash].addr == address)
683 return hashtable[hash].name;
684 mod_symb = search(address, ksyms_index, ksyms_count);
685 if (!mod_symb)
686 mod_symb = &fail;
687 map_symb = search(address, sysmap_index, sysmap_count);
688 if (!map_symb)
689 map_symb = &fail;
690
691 /* which result is closest? */
692 good_symb = (mod_symb->addr > map_symb->addr)
693 ? mod_symb : map_symb;
694 if (address > good_symb->addr + MAX_OFFSET)
695 good_symb = &fail;
696
697 /* good_symb->name has the data, but needs to be trimmed */
698 ret = good_symb->name;
699 switch (*ret) {
700 case 's':
701 if (!strncmp(ret, "sys_", 4))
702 ret += 4;
703 break;
704 case 'd':
705 if (!strncmp(ret, "do_", 3))
706 ret += 3;
707 break;
708 case '_':
709 while (*ret == '_')
710 ret++;
711 break;
712 }
713 /* if (!*ret) ret = fail.name; *//* not likely (name was "sys_", etc.) */
714
715 /* cache name after abbreviation */
716 hashtable[hash].addr = address;
717 hashtable[hash].name = ret;
718
719 return ret;
720 }
721