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
46int g_demangle;		/* Demangle C++ names */
47int g_quiet;		/* Quiet mode */
48int g_numeric;		/* Numeric mode */
49int g_flags;		/* libdisasm language flags */
50int g_doall;		/* true if no functions or sections were given */
51
52dis_namelist_t *g_funclist;	/* list of functions to disassemble, if any */
53dis_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 */
66typedef 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 */
80void
81getsymname(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 */
106static int
107insn_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 */
122void
123dis_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 */
272int
273do_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 */
307int
308do_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 */
331void
332dump_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 */
399void
400dis_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 */
419typedef 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 */
429void
430dis_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 */
457void
458dis_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 */
473void
474dis_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
667void
668usage(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
675typedef struct lib_node {
676	char *path;
677	struct lib_node *next;
678} lib_node_t;
679
680int
681main(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