1 /* $OpenBSD: nlist.c,v 1.40 2012/04/06 18:06:40 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, error = 0; 301 Elf32_Word j; 302 Elf_Sym sbuf; 303 char buf[1024]; 304 Elf_Ehdr eh; 305 Elf_Shdr *sh = NULL; 306 DBT data, key; 307 NLIST nbuf; 308 FILE *fp; 309 int usemalloc = 0; 310 311 if ((fp = fdopen(fd, "r")) == NULL) 312 err(1, "%s", kfile); 313 314 if (fseek(fp, (off_t)0, SEEK_SET) == -1 || 315 fread(&eh, sizeof(eh), 1, fp) != 1 || 316 !IS_ELF(eh)) 317 return (1); 318 319 sh = (Elf_Shdr *)calloc(sizeof(Elf_Shdr), eh.e_shnum); 320 if (sh == NULL) 321 errx(1, "cannot allocate %d bytes for symbol header", 322 sizeof(Elf_Shdr) * eh.e_shnum); 323 324 if (fseek (fp, eh.e_shoff, SEEK_SET) < 0) { 325 fmterr = "no exec header"; 326 error = -1; 327 goto done; 328 } 329 330 if (fread(sh, sizeof(Elf_Shdr) * eh.e_shnum, 1, fp) != 1) { 331 fmterr = "no exec header"; 332 error = -1; 333 goto done; 334 } 335 336 symstrsize = symsize = kernvma = 0; 337 for (i = 0; i < eh.e_shnum; i++) { 338 if (sh[i].sh_type == SHT_STRTAB) { 339 for (j = 0; j < eh.e_shnum; j++) 340 if (sh[j].sh_type == SHT_SYMTAB && 341 sh[j].sh_link == (unsigned)i) { 342 symstroff = sh[i].sh_offset; 343 symstrsize = sh[i].sh_size; 344 } 345 } else if (sh[i].sh_type == SHT_SYMTAB) { 346 symoff = sh[i].sh_offset; 347 symsize = sh[i].sh_size; 348 } else if (sh[i].sh_type == SHT_PROGBITS && 349 (sh[i].sh_flags & SHF_EXECINSTR)) { 350 kernvma = sh[i].sh_addr; 351 kernoffs = sh[i].sh_offset; 352 } 353 } 354 355 if (!symstrsize || !symsize || !kernvma) { 356 fmterr = "corrupt file"; 357 error = -1; 358 goto done; 359 } 360 361 /* 362 * Map string table into our address space. This gives us 363 * an easy way to randomly access all the strings, without 364 * making the memory allocation permanent as with malloc/free 365 * (i.e., munmap will return it to the system). 366 * 367 * XXX - we really want to check if this is a regular file. 368 * then we probably want a MAP_PRIVATE here. 369 */ 370 strtab = mmap(NULL, (size_t)symstrsize, PROT_READ, 371 MAP_SHARED|MAP_FILE, fileno(fp), symstroff); 372 if (strtab == MAP_FAILED) { 373 usemalloc = 1; 374 if ((strtab = malloc(symstrsize)) == NULL) { 375 fmterr = "out of memory"; 376 error = -1; 377 goto done; 378 } 379 if (fseek(fp, symstroff, SEEK_SET) == -1) { 380 fmterr = "corrupt file"; 381 error = -1; 382 goto done; 383 } 384 if (fread(strtab, symstrsize, 1, fp) != 1) { 385 fmterr = "corrupt file"; 386 error = -1; 387 goto done; 388 } 389 } 390 391 if (fseek(fp, symoff, SEEK_SET) == -1) { 392 fmterr = "corrupt file"; 393 error = -1; 394 goto done; 395 } 396 397 data.data = (u_char *)&nbuf; 398 data.size = sizeof(NLIST); 399 400 /* Read each symbol and enter it into the database. */ 401 while (symsize > 0) { 402 symsize -= sizeof(Elf_Sym); 403 if (fread((char *)&sbuf, sizeof(sbuf), 1, fp) != 1) { 404 if (feof(fp)) 405 fmterr = "corrupted symbol table"; 406 else 407 warn("%s", kfile); 408 error = -1; 409 goto done; 410 } 411 if (!sbuf.st_name) 412 continue; 413 414 nbuf.n_value = sbuf.st_value; 415 416 /* XXX type conversion is pretty rude... */ 417 switch(ELF_ST_TYPE(sbuf.st_info)) { 418 case STT_NOTYPE: 419 switch (sbuf.st_shndx) { 420 case SHN_UNDEF: 421 nbuf.n_type = N_UNDF; 422 break; 423 case SHN_ABS: 424 nbuf.n_type = N_ABS; 425 break; 426 case SHN_COMMON: 427 nbuf.n_type = N_COMM; 428 break; 429 default: 430 nbuf.n_type = N_COMM | N_EXT; 431 break; 432 } 433 break; 434 case STT_FUNC: 435 nbuf.n_type = N_TEXT; 436 break; 437 case STT_OBJECT: 438 nbuf.n_type = N_DATA; 439 break; 440 case STT_FILE: 441 nbuf.n_type = N_FN; 442 break; 443 } 444 if (ELF_ST_BIND(sbuf.st_info) == STB_LOCAL) 445 nbuf.n_type = N_EXT; 446 447 *buf = '_'; 448 strlcpy(buf + 1, strtab + sbuf.st_name, sizeof buf - 1); 449 key.data = (u_char *)buf; 450 key.size = strlen((char *)key.data); 451 if (db->put(db, &key, &data, 0)) 452 err(1, "record enter"); 453 454 if (strcmp((char *)key.data, VRS_SYM) == 0) { 455 long cur_off; 456 if (!ksyms) { 457 /* 458 * Calculate offset to the version string in 459 * the file. kernvma is where the kernel is 460 * really loaded; kernoffs is where in the 461 * file it starts. 462 */ 463 long voff; 464 voff = nbuf.n_value - kernvma + kernoffs; 465 cur_off = ftell(fp); 466 if (fseek(fp, voff, SEEK_SET) == -1) { 467 fmterr = "corrupted string table"; 468 error = -1; 469 goto done; 470 } 471 472 /* 473 * Read version string up to, and including 474 * newline. This code assumes that a newline 475 * terminates the version line. 476 */ 477 if (fgets(buf, sizeof(buf), fp) == NULL) { 478 fmterr = "corrupted string table"; 479 error = -1; 480 goto done; 481 } 482 } else { 483 /* 484 * This is /dev/ksyms or a look alike. 485 * Use sysctl() to get version since we 486 * don't have real text or data. 487 */ 488 int mib[2]; 489 size_t len; 490 char *p; 491 492 mib[0] = CTL_KERN; 493 mib[1] = KERN_VERSION; 494 len = sizeof(buf); 495 if (sysctl(mib, 2, buf, &len, NULL, 0) == -1) { 496 err(1, "sysctl can't find kernel " 497 "version string"); 498 } 499 if ((p = strchr(buf, '\n')) != NULL) 500 *(p+1) = '\0'; 501 } 502 503 key.data = (u_char *)VRS_KEY; 504 key.size = sizeof(VRS_KEY) - 1; 505 data.data = (u_char *)buf; 506 data.size = strlen(buf); 507 if (db->put(db, &key, &data, 0)) 508 err(1, "record enter"); 509 510 /* Restore to original values. */ 511 data.data = (u_char *)&nbuf; 512 data.size = sizeof(NLIST); 513 if (!ksyms && fseek(fp, cur_off, SEEK_SET) == -1) { 514 fmterr = "corrupted string table"; 515 error = -1; 516 goto done; 517 } 518 } 519 } 520 done: 521 if (strtab) { 522 if (usemalloc) 523 free(strtab); 524 else 525 munmap(strtab, symstrsize); 526 } 527 (void)fclose(fp); 528 if (sh) 529 free(sh); 530 return (error); 531 } 532 #endif /* _NLIST_DO_ELF */ 533 534 #ifdef _NLIST_DO_ECOFF 535 536 #define check(off, size) ((off < 0) || (off + size > mappedsize)) 537 #define BAD do { rv = -1; goto out; } while (0) 538 #define BADUNMAP do { rv = -1; goto unmap; } while (0) 539 #define ECOFF_INTXT(p, e) ((p) > (e)->a.text_start && \ 540 (p) < (e)->a.text_start + (e)->a.tsize) 541 #define ECOFF_INDAT(p, e) ((p) > (e)->a.data_start && \ 542 (p) < (e)->a.data_start + (e)->a.dsize) 543 544 int 545 __ecoff_knlist(int fd, DB *db, int ksyms) 546 { 547 struct ecoff_exechdr *exechdrp; 548 struct ecoff_symhdr *symhdrp; 549 struct ecoff_extsym *esyms; 550 struct stat st; 551 char *mappedfile, *cp; 552 size_t mappedsize; 553 u_long symhdroff, extstroff, off; 554 u_int symhdrsize; 555 int rv = 0; 556 long i, nesyms; 557 DBT data, key; 558 NLIST nbuf; 559 char *sname = NULL; 560 size_t len, snamesize = 0; 561 562 if (fstat(fd, &st) < 0) 563 err(1, "can't stat %s", kfile); 564 if (st.st_size > SIZE_T_MAX) { 565 fmterr = "file too large"; 566 BAD; 567 } 568 569 mappedsize = st.st_size; 570 mappedfile = mmap(NULL, mappedsize, PROT_READ, MAP_SHARED|MAP_FILE, fd, 0); 571 if (mappedfile == MAP_FAILED) { 572 fmterr = "unable to mmap"; 573 BAD; 574 } 575 576 if (check(0, sizeof *exechdrp)) 577 BADUNMAP; 578 exechdrp = (struct ecoff_exechdr *)&mappedfile[0]; 579 580 if (ECOFF_BADMAG(exechdrp)) 581 BADUNMAP; 582 583 symhdroff = exechdrp->f.f_symptr; 584 symhdrsize = exechdrp->f.f_nsyms; 585 if (symhdrsize == 0) { 586 fmterr = "stripped"; 587 return (-1); 588 } 589 590 if (check(symhdroff, sizeof *symhdrp) || 591 sizeof *symhdrp != symhdrsize) 592 BADUNMAP; 593 symhdrp = (struct ecoff_symhdr *)&mappedfile[symhdroff]; 594 595 nesyms = symhdrp->esymMax; 596 if (check(symhdrp->cbExtOffset, nesyms * sizeof *esyms)) 597 BADUNMAP; 598 esyms = (struct ecoff_extsym *)&mappedfile[symhdrp->cbExtOffset]; 599 extstroff = symhdrp->cbSsExtOffset; 600 601 data.data = (u_char *)&nbuf; 602 data.size = sizeof(NLIST); 603 604 for (sname = NULL, i = 0; i < nesyms; i++) { 605 /* Need to prepend a '_' */ 606 len = strlen(&mappedfile[extstroff + esyms[i].es_strindex]) + 1; 607 if (len >= snamesize) { 608 char *newsname; 609 int newsnamesize = len + 1024; 610 611 newsname = realloc(sname, newsnamesize); 612 if (newsname == NULL) { 613 if (sname) 614 free(sname); 615 sname = NULL; 616 } else { 617 sname = newsname; 618 snamesize = newsnamesize; 619 } 620 } 621 if (sname == NULL) 622 errx(1, "cannot allocate memory"); 623 *sname = '_'; 624 strlcpy(sname+1, &mappedfile[extstroff + esyms[i].es_strindex], 625 snamesize - 1); 626 627 /* Fill in NLIST */ 628 bzero(&nbuf, sizeof(nbuf)); 629 nbuf.n_value = esyms[i].es_value; 630 nbuf.n_type = N_EXT; /* XXX */ 631 632 /* Store entry in db */ 633 key.data = (u_char *)sname; 634 key.size = strlen(sname); 635 if (db->put(db, &key, &data, 0)) 636 err(1, "record enter"); 637 638 if (strcmp(sname, VRS_SYM) == 0) { 639 key.data = (u_char *)VRS_KEY; 640 key.size = sizeof(VRS_KEY) - 1; 641 642 /* Version string may be in either text or data segs */ 643 if (ECOFF_INTXT(nbuf.n_value, exechdrp)) 644 off = nbuf.n_value - exechdrp->a.text_start + 645 ECOFF_TXTOFF(exechdrp); 646 else if (ECOFF_INDAT(nbuf.n_value, exechdrp)) 647 off = nbuf.n_value - exechdrp->a.data_start + 648 ECOFF_DATOFF(exechdrp); 649 else 650 err(1, "unable to find version string"); 651 652 /* Version string should end in newline but... */ 653 data.data = &mappedfile[off]; 654 if ((cp = strchr(data.data, '\n')) != NULL) 655 data.size = cp - (char *)data.data; 656 else 657 data.size = strlen((char *)data.data); 658 659 if (db->put(db, &key, &data, 0)) 660 err(1, "record enter"); 661 662 /* Restore to original values */ 663 data.data = (u_char *)&nbuf; 664 data.size = sizeof(nbuf); 665 } 666 } 667 668 unmap: 669 munmap(mappedfile, mappedsize); 670 out: 671 return (rv); 672 } 673 #endif /* _NLIST_DO_ECOFF */ 674 675 static struct knlist_handlers { 676 int (*fn)(int fd, DB *db, int ksyms); 677 } nlist_fn[] = { 678 #ifdef _NLIST_DO_AOUT 679 { __aout_knlist }, 680 #endif 681 #ifdef _NLIST_DO_ELF 682 { __elf_knlist }, 683 #endif 684 #ifdef _NLIST_DO_ECOFF 685 { __ecoff_knlist }, 686 #endif 687 }; 688 689 int 690 create_knlist(char *name, int fd, DB *db) 691 { 692 int i, error, ksyms; 693 694 if (strcmp(name, _PATH_KSYMS) == 0) { 695 ksyms = 1; 696 } else { 697 ksyms = 0; 698 } 699 700 for (i = 0; i < sizeof(nlist_fn)/sizeof(nlist_fn[0]); i++) { 701 fmterr = NULL; 702 kfile = name; 703 /* rval of 1 means wrong executable type */ 704 if ((error = (nlist_fn[i].fn)(fd, db, ksyms)) != 1) 705 break; 706 } 707 if (fmterr != NULL) 708 warnx("%s: %s: %s", kfile, fmterr, strerror(EFTYPE)); 709 710 return(error); 711 } 712