Home | History | Annotate | Download | only in proc
      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
    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 
    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 */
    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 
    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 
    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 
    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 
    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 
    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 
    614 int open_psdb(const char *restrict override)
    615 {
    616 	return open_psdb_message(override, default_message);
    617 }
    618 
    619 /***************************************/
    620 
    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 */
    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