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