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