1 /* $OpenBSD: nlist.c,v 1.39 2009/11/11 16:44:59 deraadt Exp $ */ 2 3 /*- 4 * Copyright (c) 1990, 1993 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/param.h> 33 34 #include <a.out.h> 35 #include <db.h> 36 #include <err.h> 37 #include <errno.h> 38 #include <fcntl.h> 39 #include <kvm.h> 40 #include <limits.h> 41 #include <paths.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <unistd.h> 46 47 #include "extern.h" 48 49 #include <sys/mman.h> 50 #include <sys/stat.h> 51 #include <sys/file.h> 52 #include <sys/sysctl.h> 53 54 #ifdef _NLIST_DO_ELF 55 #include <elf_abi.h> 56 #endif 57 58 #ifdef _NLIST_DO_ECOFF 59 #include <sys/exec_ecoff.h> 60 #endif 61 62 typedef struct nlist NLIST; 63 #define _strx n_un.n_strx 64 #define _name n_un.n_name 65 66 static char *kfile; 67 static char *fmterr; 68 69 #if defined(_NLIST_DO_AOUT) 70 71 static u_long get_kerntext(char *kfn, u_int magic); 72 73 int 74 __aout_knlist(int fd, DB *db, int ksyms) 75 { 76 int nsyms; 77 struct exec ebuf; 78 FILE *fp; 79 NLIST nbuf; 80 DBT data, key; 81 int nr, strsize; 82 size_t len; 83 u_long kerntextoff; 84 size_t snamesize = 0; 85 char *strtab, buf[1024], *sname, *p; 86 87 /* Read in exec structure. */ 88 nr = read(fd, &ebuf, sizeof(struct exec)); 89 if (nr != sizeof(struct exec)) { 90 fmterr = "no exec header"; 91 return (1); 92 } 93 94 /* Check magic number and symbol count. */ 95 if (N_BADMAG(ebuf)) { 96 fmterr = "bad magic number"; 97 return (1); 98 } 99 100 /* Must have a symbol table. */ 101 if (!ebuf.a_syms) { 102 fmterr = "stripped"; 103 return (-1); 104 } 105 106 /* Seek to string table. */ 107 if (lseek(fd, N_STROFF(ebuf), SEEK_SET) == -1) { 108 fmterr = "corrupted string table"; 109 return (-1); 110 } 111 112 /* Read in the size of the string table. */ 113 nr = read(fd, (char *)&strsize, sizeof(strsize)); 114 if (nr != sizeof(strsize)) { 115 fmterr = "no symbol table"; 116 return (-1); 117 } 118 119 /* Read in the string table. */ 120 strsize -= sizeof(strsize); 121 if (!(strtab = malloc(strsize))) 122 errx(1, "cannot allocate %d bytes for string table", strsize); 123 if ((nr = read(fd, strtab, strsize)) != strsize) { 124 fmterr = "corrupted symbol table"; 125 free(strtab); 126 return (-1); 127 } 128 129 /* Seek to symbol table. */ 130 if (!(fp = fdopen(fd, "r"))) 131 err(1, "%s", kfile); 132 if (fseek(fp, N_SYMOFF(ebuf), SEEK_SET) == -1) { 133 free(strtab); 134 warn("fseek %s", kfile); 135 return (-1); 136 } 137 138 data.data = (u_char *)&nbuf; 139 data.size = sizeof(NLIST); 140 141 kerntextoff = get_kerntext(kfile, N_GETMAGIC(ebuf)); 142 143 /* Read each symbol and enter it into the database. */ 144 nsyms = ebuf.a_syms / sizeof(struct nlist); 145 sname = NULL; 146 while (nsyms--) { 147 if (fread((char *)&nbuf, sizeof (NLIST), 1, fp) != 1) { 148 if (feof(fp)) 149 fmterr = "corrupted symbol table"; 150 else 151 warn("%s", kfile); 152 free(strtab); 153 return (-1); 154 } 155 if (!nbuf._strx || (nbuf.n_type & N_STAB)) 156 continue; 157 158 /* If the symbol does not start with '_', add one */ 159 p = strtab + nbuf._strx - sizeof(int); 160 if (*p != '_') { 161 len = strlen(p) + 1; 162 if (len >= snamesize) { 163 char *newsname; 164 int newsnamesize = len + 1024; 165 166 newsname = realloc(sname, newsnamesize); 167 if (newsname == NULL) { 168 if (sname) 169 free(sname); 170 sname = NULL; 171 } else { 172 sname = newsname; 173 snamesize = newsnamesize; 174 } 175 } 176 if (sname == NULL) 177 errx(1, "cannot allocate memory"); 178 *sname = '_'; 179 strlcpy(sname + 1, p, snamesize - 1); 180 key.data = (u_char *)sname; 181 key.size = len; 182 } else { 183 key.data = (u_char *)p; 184 key.size = strlen((char *)key.data); 185 } 186 if (db->put(db, &key, &data, 0)) 187 err(1, "record enter"); 188 189 if (strcmp((char *)key.data, VRS_SYM) == 0) { 190 long cur_off; 191 192 if (!ksyms) { 193 /* 194 * Calculate offset relative to a normal 195 * (non-kernel) a.out. Kerntextoff is where the 196 * kernel is really loaded; N_TXTADDR is where 197 * a normal file is loaded. From there, locate 198 * file offset in text or data. 199 */ 200 long voff; 201 202 voff = nbuf.n_value-kerntextoff+N_TXTADDR(ebuf); 203 if ((nbuf.n_type & N_TYPE) == N_TEXT) 204 voff += N_TXTOFF(ebuf)-N_TXTADDR(ebuf); 205 else 206 voff += N_DATOFF(ebuf)-N_DATADDR(ebuf); 207 cur_off = ftell(fp); 208 if (fseek(fp, voff, SEEK_SET) == -1) { 209 fmterr = "corrupted string table"; 210 free(strtab); 211 return (-1); 212 } 213 214 /* 215 * Read version string up to, and including 216 * newline. This code assumes that a newline 217 * terminates the version line. 218 */ 219 if (fgets(buf, sizeof(buf), fp) == NULL) { 220 fmterr = "corrupted string table"; 221 free(strtab); 222 return (-1); 223 } 224 } else { 225 /* 226 * This is /dev/ksyms or a look alike. 227 * Use sysctl() to get version since we 228 * don't have real text or data. 229 */ 230 int mib[2]; 231 232 mib[0] = CTL_KERN; 233 mib[1] = KERN_VERSION; 234 len = sizeof(buf); 235 if (sysctl(mib, 2, buf, &len, NULL, 0) == -1) { 236 err(1, "sysctl can't find kernel " 237 "version string"); 238 } 239 if ((p = strchr(buf, '\n')) != NULL) 240 *(p+1) = '\0'; 241 } 242 key.data = (u_char *)VRS_KEY; 243 key.size = sizeof(VRS_KEY) - 1; 244 data.data = (u_char *)buf; 245 data.size = strlen(buf); 246 if (db->put(db, &key, &data, 0)) 247 err(1, "record enter"); 248 249 /* Restore to original values. */ 250 data.data = (u_char *)&nbuf; 251 data.size = sizeof(NLIST); 252 if (!ksyms && fseek(fp, cur_off, SEEK_SET) == -1) { 253 fmterr = "corrupted string table"; 254 free(strtab); 255 return (-1); 256 } 257 } 258 } 259 (void)fclose(fp); 260 return (0); 261 } 262 263 /* 264 * XXX: Using this value from machine/param.h introduces a 265 * XXX: machine dependency on this program, so /usr can not 266 * XXX: be shared between (i.e.) several m68k machines. 267 * Instead of compiling in KERNTEXTOFF or KERNBASE, try to 268 * determine the text start address from a standard symbol. 269 * For backward compatibility, use the old compiled-in way 270 * when the standard symbol name is not found. 271 */ 272 #ifndef KERNTEXTOFF 273 #define KERNTEXTOFF KERNBASE 274 #endif 275 276 static u_long 277 get_kerntext(char *name, u_int magic) 278 { 279 NLIST nl[2]; 280 281 bzero((caddr_t)nl, sizeof(nl)); 282 nl[0]._name = "_kernel_text"; 283 284 if (nlist(name, nl) != 0) 285 return (KERNTEXTOFF); 286 287 return (nl[0].n_value); 288 } 289 290 #endif /* _NLIST_DO_AOUT */ 291 292 #ifdef _NLIST_DO_ELF 293 int 294 __elf_knlist(int fd, DB *db, int ksyms) 295 { 296 caddr_t strtab = NULL; 297 off_t symstroff, symoff; 298 u_long symsize, symstrsize; 299 u_long kernvma, kernoffs; 300 int i, j, error = 0; 301 Elf_Sym sbuf; 302 char buf[1024]; 303 Elf_Ehdr eh; 304 Elf_Shdr *sh = NULL; 305 DBT data, key; 306 NLIST nbuf; 307 FILE *fp; 308 int usemalloc = 0; 309 310 if ((fp = fdopen(fd, "r")) == NULL) 311 err(1, "%s", kfile); 312 313 if (fseek(fp, (off_t)0, SEEK_SET) == -1 || 314 fread(&eh, sizeof(eh), 1, fp) != 1 || 315 !IS_ELF(eh)) 316 return (1); 317 318 sh = (Elf_Shdr *)calloc(sizeof(Elf_Shdr), eh.e_shnum); 319 if (sh == NULL) 320 errx(1, "cannot allocate %d bytes for symbol header", 321 sizeof(Elf_Shdr) * eh.e_shnum); 322 323 if (fseek (fp, eh.e_shoff, SEEK_SET) < 0) { 324 fmterr = "no exec header"; 325 error = -1; 326 goto done; 327 } 328 329 if (fread(sh, sizeof(Elf_Shdr) * eh.e_shnum, 1, fp) != 1) { 330 fmterr = "no exec header"; 331 error = -1; 332 goto done; 333 } 334 335 symstrsize = symsize = kernvma = 0; 336 for (i = 0; i < eh.e_shnum; i++) { 337 if (sh[i].sh_type == SHT_STRTAB) { 338 for (j = 0; j < eh.e_shnum; j++) 339 if (sh[j].sh_type == SHT_SYMTAB && 340 sh[j].sh_link == (unsigned)i) { 341 symstroff = sh[i].sh_offset; 342 symstrsize = sh[i].sh_size; 343 } 344 } else if (sh[i].sh_type == SHT_SYMTAB) { 345 symoff = sh[i].sh_offset; 346 symsize = sh[i].sh_size; 347 } else if (sh[i].sh_type == SHT_PROGBITS && 348 (sh[i].sh_flags & SHF_EXECINSTR)) { 349 kernvma = sh[i].sh_addr; 350 kernoffs = sh[i].sh_offset; 351 } 352 } 353 354 if (!symstrsize || !symsize || !kernvma) { 355 fmterr = "corrupt file"; 356 error = -1; 357 goto done; 358 } 359 360 /* 361 * Map string table into our address space. This gives us 362 * an easy way to randomly access all the strings, without 363 * making the memory allocation permanent as with malloc/free 364 * (i.e., munmap will return it to the system). 365 * 366 * XXX - we really want to check if this is a regular file. 367 * then we probably want a MAP_PRIVATE here. 368 */ 369 strtab = mmap(NULL, (size_t)symstrsize, PROT_READ, 370 MAP_SHARED|MAP_FILE, fileno(fp), symstroff); 371 if (strtab == MAP_FAILED) { 372 usemalloc = 1; 373 if ((strtab = malloc(symstrsize)) == NULL) { 374 fmterr = "out of memory"; 375 error = -1; 376 goto done; 377 } 378 if (fseek(fp, symstroff, SEEK_SET) == -1) { 379 fmterr = "corrupt file"; 380 error = -1; 381 goto done; 382 } 383 if (fread(strtab, symstrsize, 1, fp) != 1) { 384 fmterr = "corrupt file"; 385 error = -1; 386 goto done; 387 } 388 } 389 390 if (fseek(fp, symoff, SEEK_SET) == -1) { 391 fmterr = "corrupt file"; 392 error = -1; 393 goto done; 394 } 395 396 data.data = (u_char *)&nbuf; 397 data.size = sizeof(NLIST); 398 399 /* Read each symbol and enter it into the database. */ 400 while (symsize > 0) { 401 symsize -= sizeof(Elf_Sym); 402 if (fread((char *)&sbuf, sizeof(sbuf), 1, fp) != 1) { 403 if (feof(fp)) 404 fmterr = "corrupted symbol table"; 405 else 406 warn("%s", kfile); 407 error = -1; 408 goto done; 409 } 410 if (!sbuf.st_name) 411 continue; 412 413 nbuf.n_value = sbuf.st_value; 414 415 /* XXX type conversion is pretty rude... */ 416 switch(ELF_ST_TYPE(sbuf.st_info)) { 417 case STT_NOTYPE: 418 switch (sbuf.st_shndx) { 419 case SHN_UNDEF: 420 nbuf.n_type = N_UNDF; 421 break; 422 case SHN_ABS: 423 nbuf.n_type = N_ABS; 424 break; 425 case SHN_COMMON: 426 nbuf.n_type = N_COMM; 427 break; 428 default: 429 nbuf.n_type = N_COMM | N_EXT; 430 break; 431 } 432 break; 433 case STT_FUNC: 434 nbuf.n_type = N_TEXT; 435 break; 436 case STT_OBJECT: 437 nbuf.n_type = N_DATA; 438 break; 439 case STT_FILE: 440 nbuf.n_type = N_FN; 441 break; 442 } 443 if (ELF_ST_BIND(sbuf.st_info) == STB_LOCAL) 444 nbuf.n_type = N_EXT; 445 446 *buf = '_'; 447 strlcpy(buf + 1, strtab + sbuf.st_name, sizeof buf - 1); 448 key.data = (u_char *)buf; 449 key.size = strlen((char *)key.data); 450 if (db->put(db, &key, &data, 0)) 451 err(1, "record enter"); 452 453 if (strcmp((char *)key.data, VRS_SYM) == 0) { 454 long cur_off; 455 if (!ksyms) { 456 /* 457 * Calculate offset to the version string in 458 * the file. kernvma is where the kernel is 459 * really loaded; kernoffs is where in the 460 * file it starts. 461 */ 462 long voff; 463 voff = nbuf.n_value - kernvma + kernoffs; 464 cur_off = ftell(fp); 465 if (fseek(fp, voff, SEEK_SET) == -1) { 466 fmterr = "corrupted string table"; 467 error = -1; 468 goto done; 469 } 470 471 /* 472 * Read version string up to, and including 473 * newline. This code assumes that a newline 474 * terminates the version line. 475 */ 476 if (fgets(buf, sizeof(buf), fp) == NULL) { 477 fmterr = "corrupted string table"; 478 error = -1; 479 goto done; 480 } 481 } else { 482 /* 483 * This is /dev/ksyms or a look alike. 484 * Use sysctl() to get version since we 485 * don't have real text or data. 486 */ 487 int mib[2]; 488 size_t len; 489 char *p; 490 491 mib[0] = CTL_KERN; 492 mib[1] = KERN_VERSION; 493 len = sizeof(buf); 494 if (sysctl(mib, 2, buf, &len, NULL, 0) == -1) { 495 err(1, "sysctl can't find kernel " 496 "version string"); 497 } 498 if ((p = strchr(buf, '\n')) != NULL) 499 *(p+1) = '\0'; 500 } 501 502 key.data = (u_char *)VRS_KEY; 503 key.size = sizeof(VRS_KEY) - 1; 504 data.data = (u_char *)buf; 505 data.size = strlen(buf); 506 if (db->put(db, &key, &data, 0)) 507 err(1, "record enter"); 508 509 /* Restore to original values. */ 510 data.data = (u_char *)&nbuf; 511 data.size = sizeof(NLIST); 512 if (!ksyms && fseek(fp, cur_off, SEEK_SET) == -1) { 513 fmterr = "corrupted string table"; 514 error = -1; 515 goto done; 516 } 517 } 518 } 519 done: 520 if (strtab) { 521 if (usemalloc) 522 free(strtab); 523 else 524 munmap(strtab, symstrsize); 525 } 526 (void)fclose(fp); 527 if (sh) 528 free(sh); 529 return (error); 530 } 531 #endif /* _NLIST_DO_ELF */ 532 533 #ifdef _NLIST_DO_ECOFF 534 535 #define check(off, size) ((off < 0) || (off + size > mappedsize)) 536 #define BAD do { rv = -1; goto out; } while (0) 537 #define BADUNMAP do { rv = -1; goto unmap; } while (0) 538 #define ECOFF_INTXT(p, e) ((p) > (e)->a.text_start && \ 539 (p) < (e)->a.text_start + (e)->a.tsize) 540 #define ECOFF_INDAT(p, e) ((p) > (e)->a.data_start && \ 541 (p) < (e)->a.data_start + (e)->a.dsize) 542 543 int 544 __ecoff_knlist(int fd, DB *db, int ksyms) 545 { 546 struct ecoff_exechdr *exechdrp; 547 struct ecoff_symhdr *symhdrp; 548 struct ecoff_extsym *esyms; 549 struct stat st; 550 char *mappedfile, *cp; 551 size_t mappedsize; 552 u_long symhdroff, extstroff, off; 553 u_int symhdrsize; 554 int rv = 0; 555 long i, nesyms; 556 DBT data, key; 557 NLIST nbuf; 558 char *sname = NULL; 559 size_t len, snamesize = 0; 560 561 if (fstat(fd, &st) < 0) 562 err(1, "can't stat %s", kfile); 563 if (st.st_size > SIZE_T_MAX) { 564 fmterr = "file too large"; 565 BAD; 566 } 567 568 mappedsize = st.st_size; 569 mappedfile = mmap(NULL, mappedsize, PROT_READ, MAP_SHARED|MAP_FILE, fd, 0); 570 if (mappedfile == MAP_FAILED) { 571 fmterr = "unable to mmap"; 572 BAD; 573 } 574 575 if (check(0, sizeof *exechdrp)) 576 BADUNMAP; 577 exechdrp = (struct ecoff_exechdr *)&mappedfile[0]; 578 579 if (ECOFF_BADMAG(exechdrp)) 580 BADUNMAP; 581 582 symhdroff = exechdrp->f.f_symptr; 583 symhdrsize = exechdrp->f.f_nsyms; 584 if (symhdrsize == 0) { 585 fmterr = "stripped"; 586 return (-1); 587 } 588 589 if (check(symhdroff, sizeof *symhdrp) || 590 sizeof *symhdrp != symhdrsize) 591 BADUNMAP; 592 symhdrp = (struct ecoff_symhdr *)&mappedfile[symhdroff]; 593 594 nesyms = symhdrp->esymMax; 595 if (check(symhdrp->cbExtOffset, nesyms * sizeof *esyms)) 596 BADUNMAP; 597 esyms = (struct ecoff_extsym *)&mappedfile[symhdrp->cbExtOffset]; 598 extstroff = symhdrp->cbSsExtOffset; 599 600 data.data = (u_char *)&nbuf; 601 data.size = sizeof(NLIST); 602 603 for (sname = NULL, i = 0; i < nesyms; i++) { 604 /* Need to prepend a '_' */ 605 len = strlen(&mappedfile[extstroff + esyms[i].es_strindex]) + 1; 606 if (len >= snamesize) { 607 char *newsname; 608 int newsnamesize = len + 1024; 609 610 newsname = realloc(sname, newsnamesize); 611 if (newsname == NULL) { 612 if (sname) 613 free(sname); 614 sname = NULL; 615 } else { 616 sname = newsname; 617 snamesize = newsnamesize; 618 } 619 } 620 if (sname == NULL) 621 errx(1, "cannot allocate memory"); 622 *sname = '_'; 623 strlcpy(sname+1, &mappedfile[extstroff + esyms[i].es_strindex], 624 snamesize - 1); 625 626 /* Fill in NLIST */ 627 bzero(&nbuf, sizeof(nbuf)); 628 nbuf.n_value = esyms[i].es_value; 629 nbuf.n_type = N_EXT; /* XXX */ 630 631 /* Store entry in db */ 632 key.data = (u_char *)sname; 633 key.size = strlen(sname); 634 if (db->put(db, &key, &data, 0)) 635 err(1, "record enter"); 636 637 if (strcmp(sname, VRS_SYM) == 0) { 638 key.data = (u_char *)VRS_KEY; 639 key.size = sizeof(VRS_KEY) - 1; 640 641 /* Version string may be in either text or data segs */ 642 if (ECOFF_INTXT(nbuf.n_value, exechdrp)) 643 off = nbuf.n_value - exechdrp->a.text_start + 644 ECOFF_TXTOFF(exechdrp); 645 else if (ECOFF_INDAT(nbuf.n_value, exechdrp)) 646 off = nbuf.n_value - exechdrp->a.data_start + 647 ECOFF_DATOFF(exechdrp); 648 else 649 err(1, "unable to find version string"); 650 651 /* Version string should end in newline but... */ 652 data.data = &mappedfile[off]; 653 if ((cp = strchr(data.data, '\n')) != NULL) 654 data.size = cp - (char *)data.data; 655 else 656 data.size = strlen((char *)data.data); 657 658 if (db->put(db, &key, &data, 0)) 659 err(1, "record enter"); 660 661 /* Restore to original values */ 662 data.data = (u_char *)&nbuf; 663 data.size = sizeof(nbuf); 664 } 665 } 666 667 unmap: 668 munmap(mappedfile, mappedsize); 669 out: 670 return (rv); 671 } 672 #endif /* _NLIST_DO_ECOFF */ 673 674 static struct knlist_handlers { 675 int (*fn)(int fd, DB *db, int ksyms); 676 } nlist_fn[] = { 677 #ifdef _NLIST_DO_AOUT 678 { __aout_knlist }, 679 #endif 680 #ifdef _NLIST_DO_ELF 681 { __elf_knlist }, 682 #endif 683 #ifdef _NLIST_DO_ECOFF 684 { __ecoff_knlist }, 685 #endif 686 }; 687 688 int 689 create_knlist(char *name, int fd, DB *db) 690 { 691 int i, error, ksyms; 692 693 if (strcmp(name, _PATH_KSYMS) == 0) { 694 ksyms = 1; 695 } else { 696 ksyms = 0; 697 } 698 699 for (i = 0; i < sizeof(nlist_fn)/sizeof(nlist_fn[0]); i++) { 700 fmterr = NULL; 701 kfile = name; 702 /* rval of 1 means wrong executable type */ 703 if ((error = (nlist_fn[i].fn)(fd, db, ksyms)) != 1) 704 break; 705 } 706 if (fmterr != NULL) 707 warnx("%s: %s: %s", kfile, fmterr, strerror(EFTYPE)); 708 709 return(error); 710 } 711