1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 * 26 * Copyright 2011 Jason King. All rights reserved. 27 * Copyright 2012 Joshua M. Clulow <josh@sysmgr.org> 28 * Copyright 2015 Josef 'Jeff' Sipek <jeffpc@josefsipek.net> 29 * Copyright 2018, Joyent, Inc. 30 */ 31 32 #include <ctype.h> 33 #include <getopt.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <sys/sysmacros.h> 38 #include <sys/elf_SPARC.h> 39 40 #include <libdisasm.h> 41 42 #include "dis_target.h" 43 #include "dis_util.h" 44 #include "dis_list.h" 45 46 int g_demangle; /* Demangle C++ names */ 47 int g_quiet; /* Quiet mode */ 48 int g_numeric; /* Numeric mode */ 49 int g_flags; /* libdisasm language flags */ 50 int g_doall; /* true if no functions or sections were given */ 51 52 dis_namelist_t *g_funclist; /* list of functions to disassemble, if any */ 53 dis_namelist_t *g_seclist; /* list of sections to disassemble, if any */ 54 55 /* 56 * Section options for -d, -D, and -s 57 */ 58 #define DIS_DATA_RELATIVE 1 59 #define DIS_DATA_ABSOLUTE 2 60 #define DIS_TEXT 3 61 62 /* 63 * libdisasm callback data. Keeps track of current data (function or section) 64 * and offset within that data. 65 */ 66 typedef struct dis_buffer { 67 dis_tgt_t *db_tgt; /* current dis target */ 68 void *db_data; /* function or section data */ 69 uint64_t db_addr; /* address of function start */ 70 size_t db_size; /* size of data */ 71 uint64_t db_nextaddr; /* next address to be read */ 72 } dis_buffer_t; 73 74 #define MINSYMWIDTH 22 /* Minimum width of symbol portion of line */ 75 76 /* 77 * Given a symbol+offset as returned by dis_tgt_lookup(), print an appropriately 78 * formatted symbol, based on the offset and current setttings. 79 */ 80 void 81 getsymname(uint64_t addr, const char *symbol, off_t offset, char *buf, 82 size_t buflen) 83 { 84 if (symbol == NULL || g_numeric) { 85 if (g_flags & DIS_OCTAL) 86 (void) snprintf(buf, buflen, "0%llo", addr); 87 else 88 (void) snprintf(buf, buflen, "0x%llx", addr); 89 } else { 90 if (g_demangle) 91 symbol = dis_demangle(symbol); 92 93 if (offset == 0) 94 (void) snprintf(buf, buflen, "%s", symbol); 95 else if (g_flags & DIS_OCTAL) 96 (void) snprintf(buf, buflen, "%s+0%o", symbol, offset); 97 else 98 (void) snprintf(buf, buflen, "%s+0x%x", symbol, offset); 99 } 100 } 101 102 /* 103 * Determine if we are on an architecture with fixed-size instructions, 104 * and if so, what size they are. 105 */ 106 static int 107 insn_size(dis_handle_t *dhp) 108 { 109 int min = dis_min_instrlen(dhp); 110 int max = dis_max_instrlen(dhp); 111 112 if (min == max) 113 return (min); 114 115 return (0); 116 } 117 118 /* 119 * The main disassembly routine. Given a fixed-sized buffer and starting 120 * address, disassemble the data using the supplied target and libdisasm handle. 121 */ 122 void 123 dis_data(dis_tgt_t *tgt, dis_handle_t *dhp, uint64_t addr, void *data, 124 size_t datalen) 125 { 126 dis_buffer_t db = { 0 }; 127 char buf[BUFSIZE]; 128 char symbuf[BUFSIZE]; 129 const char *symbol; 130 const char *last_symbol; 131 off_t symoffset; 132 int i; 133 int bytesperline; 134 size_t symsize; 135 int isfunc; 136 size_t symwidth = 0; 137 int ret; 138 int insz = insn_size(dhp); 139 140 db.db_tgt = tgt; 141 db.db_data = data; 142 db.db_addr = addr; 143 db.db_size = datalen; 144 145 dis_set_data(dhp, &db); 146 147 if ((bytesperline = dis_max_instrlen(dhp)) > 6) 148 bytesperline = 6; 149 150 symbol = NULL; 151 152 while (addr < db.db_addr + db.db_size) { 153 154 ret = dis_disassemble(dhp, addr, buf, BUFSIZE); 155 if (ret != 0 && insz > 0) { 156 /* 157 * Since we know instructions are fixed size, we 158 * always know the address of the next instruction 159 */ 160 (void) snprintf(buf, sizeof (buf), 161 "*** invalid opcode ***"); 162 db.db_nextaddr = addr + insz; 163 164 } else if (ret != 0) { 165 off_t next; 166 167 (void) snprintf(buf, sizeof (buf), 168 "*** invalid opcode ***"); 169 170 /* 171 * On architectures with variable sized instructions 172 * we have no way to figure out where the next 173 * instruction starts if we encounter an invalid 174 * instruction. Instead we print the rest of the 175 * instruction stream as hex until we reach the 176 * next valid symbol in the section. 177 */ 178 if ((next = dis_tgt_next_symbol(tgt, addr)) == 0) { 179 db.db_nextaddr = db.db_addr + db.db_size; 180 } else { 181 if (next > db.db_size) 182 db.db_nextaddr = db.db_addr + 183 db.db_size; 184 else 185 db.db_nextaddr = addr + next; 186 } 187 } 188 189 /* 190 * Print out the line as: 191 * 192 * address: bytes text 193 * 194 * If there are more than 6 bytes in any given instruction, 195 * spread the bytes across two lines. We try to get symbolic 196 * information for the address, but if that fails we print out 197 * the numeric address instead. 198 * 199 * We try to keep the address portion of the text aligned at 200 * MINSYMWIDTH characters. If we are disassembling a function 201 * with a long name, this can be annoying. So we pick a width 202 * based on the maximum width that the current symbol can be. 203 * This at least produces text aligned within each function. 204 */ 205 last_symbol = symbol; 206 symbol = dis_tgt_lookup(tgt, addr, &symoffset, 1, &symsize, 207 &isfunc); 208 if (symbol == NULL) { 209 symbol = dis_find_section(tgt, addr, &symoffset); 210 symsize = symoffset; 211 } 212 213 if (symbol != last_symbol) 214 getsymname(addr, symbol, symsize, symbuf, 215 sizeof (symbuf)); 216 217 symwidth = MAX(symwidth, strlen(symbuf)); 218 getsymname(addr, symbol, symoffset, symbuf, sizeof (symbuf)); 219 220 /* 221 * If we've crossed a new function boundary, print out the 222 * function name on a blank line. 223 */ 224 if (!g_quiet && symoffset == 0 && symbol != NULL && isfunc) 225 (void) printf("%s()\n", symbol); 226 227 (void) printf(" %s:%*s ", symbuf, 228 symwidth - strlen(symbuf), ""); 229 230 /* print bytes */ 231 for (i = 0; i < MIN(bytesperline, (db.db_nextaddr - addr)); 232 i++) { 233 int byte = *((uchar_t *)data + (addr - db.db_addr) + i); 234 if (g_flags & DIS_OCTAL) 235 (void) printf("%03o ", byte); 236 else 237 (void) printf("%02x ", byte); 238 } 239 240 /* trailing spaces for missing bytes */ 241 for (; i < bytesperline; i++) { 242 if (g_flags & DIS_OCTAL) 243 (void) printf(" "); 244 else 245 (void) printf(" "); 246 } 247 248 /* contents of disassembly */ 249 (void) printf(" %s", buf); 250 251 /* excess bytes that spill over onto subsequent lines */ 252 for (; i < db.db_nextaddr - addr; i++) { 253 int byte = *((uchar_t *)data + (addr - db.db_addr) + i); 254 if (i % bytesperline == 0) 255 (void) printf("\n %*s ", symwidth, ""); 256 if (g_flags & DIS_OCTAL) 257 (void) printf("%03o ", byte); 258 else 259 (void) printf("%02x ", byte); 260 } 261 262 (void) printf("\n"); 263 264 addr = db.db_nextaddr; 265 } 266 } 267 268 /* 269 * libdisasm wrapper around symbol lookup. Invoke the target-specific lookup 270 * function, and convert the result using getsymname(). 271 */ 272 int 273 do_lookup(void *data, uint64_t addr, char *buf, size_t buflen, uint64_t *start, 274 size_t *symlen) 275 { 276 dis_buffer_t *db = data; 277 const char *symbol; 278 off_t offset; 279 size_t size; 280 281 /* 282 * If NULL symbol is returned, getsymname takes care of 283 * printing appropriate address in buf instead of symbol. 284 */ 285 symbol = dis_tgt_lookup(db->db_tgt, addr, &offset, 0, &size, NULL); 286 287 if (buf != NULL) 288 getsymname(addr, symbol, offset, buf, buflen); 289 290 if (start != NULL) 291 *start = addr - offset; 292 if (symlen != NULL) 293 *symlen = size; 294 295 if (symbol == NULL) 296 return (-1); 297 298 return (0); 299 } 300 301 /* 302 * libdisasm wrapper around target reading. libdisasm will always read data 303 * in order, so update our current offset within the buffer appropriately. 304 * We only support reading from within the current object; libdisasm should 305 * never ask us to do otherwise. 306 */ 307 int 308 do_read(void *data, uint64_t addr, void *buf, size_t len) 309 { 310 dis_buffer_t *db = data; 311 size_t offset; 312 313 if (addr < db->db_addr || addr >= db->db_addr + db->db_size) 314 return (-1); 315 316 offset = addr - db->db_addr; 317 len = MIN(len, db->db_size - offset); 318 319 (void) memcpy(buf, (char *)db->db_data + offset, len); 320 321 db->db_nextaddr = addr + len; 322 323 return (len); 324 } 325 326 /* 327 * Routine to dump raw data in a human-readable format. Used by the -d and -D 328 * options. We model our output after the xxd(1) program, which gives nicely 329 * formatted output, along with an ASCII translation of the result. 330 */ 331 void 332 dump_data(uint64_t addr, void *data, size_t datalen) 333 { 334 uintptr_t curaddr = addr & (~0xf); 335 uint8_t *bytes = data; 336 int i; 337 int width; 338 339 /* 340 * Determine if the address given to us fits in 32-bit range, in which 341 * case use a 4-byte width. 342 */ 343 if (((addr + datalen) & 0xffffffff00000000ULL) == 0ULL) 344 width = 8; 345 else 346 width = 16; 347 348 while (curaddr < addr + datalen) { 349 /* 350 * Display leading address 351 */ 352 (void) printf("%0*x: ", width, curaddr); 353 354 /* 355 * Print out data in two-byte chunks. If the current address 356 * is before the starting address or after the end of the 357 * section, print spaces. 358 */ 359 for (i = 0; i < 16; i++) { 360 if (curaddr + i < addr ||curaddr + i >= addr + datalen) 361 (void) printf(" "); 362 else 363 (void) printf("%02x", 364 bytes[curaddr + i - addr]); 365 366 if (i & 1) 367 (void) printf(" "); 368 } 369 370 (void) printf(" "); 371 372 /* 373 * Print out the ASCII representation 374 */ 375 for (i = 0; i < 16; i++) { 376 if (curaddr + i < addr || 377 curaddr + i >= addr + datalen) { 378 (void) printf(" "); 379 } else { 380 uint8_t byte = bytes[curaddr + i - addr]; 381 if (isprint(byte)) 382 (void) printf("%c", byte); 383 else 384 (void) printf("."); 385 } 386 } 387 388 (void) printf("\n"); 389 390 curaddr += 16; 391 } 392 } 393 394 /* 395 * Disassemble a section implicitly specified as part of a file. This function 396 * is called for all sections when no other flags are specified. We ignore any 397 * data sections, and print out only those sections containing text. 398 */ 399 void 400 dis_text_section(dis_tgt_t *tgt, dis_scn_t *scn, void *data) 401 { 402 dis_handle_t *dhp = data; 403 404 /* ignore data sections */ 405 if (!dis_section_istext(scn)) 406 return; 407 408 if (!g_quiet) 409 (void) printf("\nsection %s\n", dis_section_name(scn)); 410 411 dis_data(tgt, dhp, dis_section_addr(scn), dis_section_data(scn), 412 dis_section_size(scn)); 413 } 414 415 /* 416 * Structure passed to dis_named_{section,function} which keeps track of both 417 * the target and the libdisasm handle. 418 */ 419 typedef struct callback_arg { 420 dis_tgt_t *ca_tgt; 421 dis_handle_t *ca_handle; 422 } callback_arg_t; 423 424 /* 425 * Disassemble a section explicitly named with -s, -d, or -D. The 'type' 426 * argument contains the type of argument given. Pass the data onto the 427 * appropriate helper routine. 428 */ 429 void 430 dis_named_section(dis_scn_t *scn, int type, void *data) 431 { 432 callback_arg_t *ca = data; 433 434 if (!g_quiet) 435 (void) printf("\nsection %s\n", dis_section_name(scn)); 436 437 switch (type) { 438 case DIS_DATA_RELATIVE: 439 dump_data(0, dis_section_data(scn), dis_section_size(scn)); 440 break; 441 case DIS_DATA_ABSOLUTE: 442 dump_data(dis_section_addr(scn), dis_section_data(scn), 443 dis_section_size(scn)); 444 break; 445 case DIS_TEXT: 446 dis_data(ca->ca_tgt, ca->ca_handle, dis_section_addr(scn), 447 dis_section_data(scn), dis_section_size(scn)); 448 break; 449 } 450 } 451 452 /* 453 * Disassemble a function explicitly specified with '-F'. The 'type' argument 454 * is unused. 455 */ 456 /* ARGSUSED */ 457 void 458 dis_named_function(dis_func_t *func, int type, void *data) 459 { 460 callback_arg_t *ca = data; 461 462 dis_data(ca->ca_tgt, ca->ca_handle, dis_function_addr(func), 463 dis_function_data(func), dis_function_size(func)); 464 } 465 466 /* 467 * Disassemble a complete file. First, we determine the type of the file based 468 * on the ELF machine type, and instantiate a version of the disassembler 469 * appropriate for the file. We then resolve any named sections or functions 470 * against the file, and iterate over the results (or all sections if no flags 471 * were specified). 472 */ 473 void 474 dis_file(const char *filename) 475 { 476 dis_tgt_t *tgt, *current; 477 dis_scnlist_t *sections; 478 dis_funclist_t *functions; 479 dis_handle_t *dhp; 480 GElf_Ehdr ehdr; 481 482 /* 483 * First, initialize the target 484 */ 485 if ((tgt = dis_tgt_create(filename)) == NULL) 486 return; 487 488 if (!g_quiet) 489 (void) printf("disassembly for %s\n\n", filename); 490 491 /* 492 * A given file may contain multiple targets (if it is an archive, for 493 * example). We iterate over all possible targets if this is the case. 494 */ 495 for (current = tgt; current != NULL; current = dis_tgt_next(current)) { 496 dis_tgt_ehdr(current, &ehdr); 497 498 /* 499 * Eventually, this should probably live within libdisasm, and 500 * we should be able to disassemble targets from different 501 * architectures. For now, we only support objects as the 502 * native machine type. 503 */ 504 switch (ehdr.e_machine) { 505 case EM_SPARC: 506 if (ehdr.e_ident[EI_CLASS] != ELFCLASS32 || 507 ehdr.e_ident[EI_DATA] != ELFDATA2MSB) { 508 warn("invalid E_IDENT field for SPARC object"); 509 return; 510 } 511 g_flags |= DIS_SPARC_V8; 512 break; 513 514 case EM_SPARC32PLUS: 515 { 516 uint64_t flags = ehdr.e_flags & EF_SPARC_32PLUS_MASK; 517 518 if (ehdr.e_ident[EI_CLASS] != ELFCLASS32 || 519 ehdr.e_ident[EI_DATA] != ELFDATA2MSB) { 520 warn("invalid E_IDENT field for SPARC object"); 521 return; 522 } 523 524 if (flags != 0 && 525 (flags & (EF_SPARC_32PLUS | EF_SPARC_SUN_US1 | 526 EF_SPARC_SUN_US3)) != EF_SPARC_32PLUS) 527 g_flags |= DIS_SPARC_V9 | DIS_SPARC_V9_SGI; 528 else 529 g_flags |= DIS_SPARC_V9; 530 break; 531 } 532 533 case EM_SPARCV9: 534 if (ehdr.e_ident[EI_CLASS] != ELFCLASS64 || 535 ehdr.e_ident[EI_DATA] != ELFDATA2MSB) { 536 warn("invalid E_IDENT field for SPARC object"); 537 return; 538 } 539 540 g_flags |= DIS_SPARC_V9 | DIS_SPARC_V9_SGI; 541 break; 542 543 case EM_386: 544 g_flags |= DIS_X86_SIZE32; 545 break; 546 547 case EM_AMD64: 548 g_flags |= DIS_X86_SIZE64; 549 break; 550 551 case EM_S370: 552 g_flags |= DIS_S370; 553 554 if (ehdr.e_ident[EI_CLASS] != ELFCLASS32 || 555 ehdr.e_ident[EI_DATA] != ELFDATA2MSB) { 556 warn("invalid E_IDENT field for S370 object"); 557 return; 558 } 559 break; 560 561 case EM_S390: 562 /* 563 * Both 390 and z/Architecture use EM_S390, the only 564 * differences is the class: ELFCLASS32 for plain 565 * old s390 and ELFCLASS64 for z/Architecture (aka. 566 * s390x). 567 */ 568 if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) { 569 g_flags |= DIS_S390_31; 570 } else if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { 571 g_flags |= DIS_S390_64; 572 } else { 573 warn("invalid E_IDENT field for S390 object"); 574 return; 575 } 576 577 if (ehdr.e_ident[EI_DATA] != ELFDATA2MSB) { 578 warn("invalid E_IDENT field for S390 object"); 579 return; 580 } 581 break; 582 583 case EM_RISCV: 584 /* 585 * RISC-V is defined to be litle endian. The current ISA 586 * makes it clear that the 64-bit instructions can 587 * co-exist with the 32-bit ones and therefore we don't 588 * need a separate elf class at this time. 589 */ 590 if (ehdr.e_ident[EI_DATA] != ELFDATA2LSB) { 591 warn("invalid EI_DATA field for RISC-V object"); 592 return; 593 } 594 595 if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) { 596 g_flags |= DIS_RISCV_32; 597 } else if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) { 598 g_flags |= DIS_RISCV_64; 599 } else { 600 warn("invalid EI_CLASS field for RISC-V " 601 "object"); 602 return; 603 } 604 break; 605 606 default: 607 die("%s: unsupported ELF machine 0x%x", filename, 608 ehdr.e_machine); 609 } 610 611 /* 612 * If ET_REL (.o), printing immediate symbols is likely to 613 * result in garbage, as symbol lookups on unrelocated 614 * immediates find false and useless matches. 615 */ 616 617 if (ehdr.e_type == ET_REL) 618 g_flags |= DIS_NOIMMSYM; 619 620 if (!g_quiet && dis_tgt_member(current) != NULL) 621 (void) printf("\narchive member %s\n", 622 dis_tgt_member(current)); 623 624 /* 625 * Instantiate a libdisasm handle based on the file type. 626 */ 627 if ((dhp = dis_handle_create(g_flags, current, do_lookup, 628 do_read)) == NULL) 629 die("%s: failed to initialize disassembler: %s", 630 filename, dis_strerror(dis_errno())); 631 632 if (g_doall) { 633 /* 634 * With no arguments, iterate over all sections and 635 * disassemble only those that contain text. 636 */ 637 dis_tgt_section_iter(current, dis_text_section, dhp); 638 } else { 639 callback_arg_t ca; 640 641 ca.ca_tgt = current; 642 ca.ca_handle = dhp; 643 644 /* 645 * If sections or functions were explicitly specified, 646 * resolve those names against the object, and iterate 647 * over just the resulting data. 648 */ 649 sections = dis_namelist_resolve_sections(g_seclist, 650 current); 651 functions = dis_namelist_resolve_functions(g_funclist, 652 current); 653 654 dis_scnlist_iter(sections, dis_named_section, &ca); 655 dis_funclist_iter(functions, dis_named_function, &ca); 656 657 dis_scnlist_destroy(sections); 658 dis_funclist_destroy(functions); 659 } 660 661 dis_handle_destroy(dhp); 662 } 663 664 dis_tgt_destroy(tgt); 665 } 666 667 void 668 usage(void) 669 { 670 (void) fprintf(stderr, "usage: dis [-CVoqn] [-d sec] \n"); 671 (void) fprintf(stderr, "\t[-D sec] [-F function] [-t sec] file ..\n"); 672 exit(2); 673 } 674 675 typedef struct lib_node { 676 char *path; 677 struct lib_node *next; 678 } lib_node_t; 679 680 int 681 main(int argc, char **argv) 682 { 683 int optchar; 684 int i; 685 lib_node_t *libs = NULL; 686 687 g_funclist = dis_namelist_create(); 688 g_seclist = dis_namelist_create(); 689 690 while ((optchar = getopt(argc, argv, "Cd:D:F:l:Lot:Vqn")) != -1) { 691 switch (optchar) { 692 case 'C': 693 g_demangle = 1; 694 break; 695 case 'd': 696 dis_namelist_add(g_seclist, optarg, DIS_DATA_RELATIVE); 697 break; 698 case 'D': 699 dis_namelist_add(g_seclist, optarg, DIS_DATA_ABSOLUTE); 700 break; 701 case 'F': 702 dis_namelist_add(g_funclist, optarg, 0); 703 break; 704 case 'l': { 705 /* 706 * The '-l foo' option historically would attempt to 707 * disassemble '$LIBDIR/libfoo.a'. The $LIBDIR 708 * environment variable has never been supported or 709 * documented for our linker. However, until this 710 * option is formally EOLed, we have to support it. 711 */ 712 char *dir; 713 lib_node_t *node; 714 size_t len; 715 716 if ((dir = getenv("LIBDIR")) == NULL || 717 dir[0] == '\0') 718 dir = "/usr/lib"; 719 node = safe_malloc(sizeof (lib_node_t)); 720 len = strlen(optarg) + strlen(dir) + sizeof ("/lib.a"); 721 node->path = safe_malloc(len); 722 723 (void) snprintf(node->path, len, "%s/lib%s.a", dir, 724 optarg); 725 node->next = libs; 726 libs = node; 727 break; 728 } 729 case 'L': 730 /* 731 * The '-L' option historically would attempt to read 732 * the .debug section of the target to determine source 733 * line information in order to annotate the output. 734 * No compiler has emitted these sections in many years, 735 * and the option has never done what it purported to 736 * do. We silently consume the option for 737 * compatibility. 738 */ 739 break; 740 case 'n': 741 g_numeric = 1; 742 break; 743 case 'o': 744 g_flags |= DIS_OCTAL; 745 break; 746 case 'q': 747 g_quiet = 1; 748 break; 749 case 't': 750 dis_namelist_add(g_seclist, optarg, DIS_TEXT); 751 break; 752 case 'V': 753 (void) printf("Solaris disassembler version 1.0\n"); 754 return (0); 755 default: 756 usage(); 757 break; 758 } 759 } 760 761 argc -= optind; 762 argv += optind; 763 764 if (argc == 0 && libs == NULL) { 765 warn("no objects specified"); 766 usage(); 767 } 768 769 if (dis_namelist_empty(g_funclist) && dis_namelist_empty(g_seclist)) 770 g_doall = 1; 771 772 /* 773 * See comment for 'l' option, above. 774 */ 775 while (libs != NULL) { 776 lib_node_t *node = libs->next; 777 778 dis_file(libs->path); 779 free(libs->path); 780 free(libs); 781 libs = node; 782 } 783 784 for (i = 0; i < argc; i++) 785 dis_file(argv[i]); 786 787 dis_namelist_destroy(g_funclist); 788 dis_namelist_destroy(g_seclist); 789 790 return (g_error); 791 } 792