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