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