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