Edit

IABSD.fr/src/usr.bin/nm/nm.c

Branch :

  • Show log

    Commit

  • Author : jsg
    Date : 2024-05-21 05:00:47
    Hash : ce7279d8
    Message : remove prototypes with no matching function and externs with no var partly checked by millert@

  • usr.bin/nm/nm.c
  • /*	$OpenBSD: nm.c,v 1.56 2024/05/21 05:00:48 jsg Exp $	*/
    /*	$NetBSD: nm.c,v 1.7 1996/01/14 23:04:03 pk Exp $	*/
    
    /*
     * Copyright (c) 1989, 1993
     *	The Regents of the University of California.  All rights reserved.
     *
     * This code is derived from software contributed to Berkeley by
     * Hans Huebner.
     *
     * Redistribution and use in source and binary forms, with or without
     * modification, are permitted provided that the following conditions
     * are met:
     * 1. Redistributions of source code must retain the above copyright
     *    notice, this list of conditions and the following disclaimer.
     * 2. Redistributions in binary form must reproduce the above copyright
     *    notice, this list of conditions and the following disclaimer in the
     *    documentation and/or other materials provided with the distribution.
     * 3. Neither the name of the University nor the names of its contributors
     *    may be used to endorse or promote products derived from this software
     *    without specific prior written permission.
     *
     * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     * SUCH DAMAGE.
     */
    
    #include <sys/types.h>
    #include <sys/mman.h>
    #include <a.out.h>
    #include <elf.h>
    #include <ar.h>
    #include <ranlib.h>
    #include <unistd.h>
    #include <err.h>
    #include <errno.h>
    #include <ctype.h>
    #include <link.h>
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <getopt.h>
    #include "util.h"
    #include "elfuncs.h"
    
    #define	SYMTABMAG	"/ "
    #define	STRTABMAG	"//"
    #define	SYM64MAG	"/SYM64/         "
    
    union hdr {
    	Elf32_Ehdr elf32;
    	Elf64_Ehdr elf64;
    };
    
    int armap;
    int demangle;
    int non_object_warning;
    int print_only_external_symbols;
    int print_only_undefined_symbols;
    int print_all_symbols;
    int print_file_each_line;
    int show_extensions;
    int issize;
    char posix_fmtstr[6];
    int posix_output;
    char posix_radix = 'x';
    int usemmap = 1;
    int dynamic_only;
    
    /* size vars */
    unsigned long total_text, total_data, total_bss, total_total;
    int non_object_warning, print_totals;
    
    int rev;
    int fname(const void *, const void *);
    int rname(const void *, const void *);
    int value(const void *, const void *);
    int (*sfunc)(const void *, const void *) = fname;
    char typeletter(struct xnlist *);
    int mmbr_name(struct ar_hdr *, char **, int, int *, FILE *);
    int show_symtab(off_t, u_long, const char *, FILE *);
    int show_symdef(off_t, u_long, const char *, FILE *);
    
    /* some macros for symbol type (nlist.n_type) handling */
    #define	IS_EXTERNAL(x)		((x) & N_EXT)
    #define	SYMBOL_TYPE(x)		((x) & (N_TYPE | N_STAB))
    
    void	 pipe2cppfilt(void);
    void	 usage(void);
    char	*symname(struct xnlist *);
    int	process_file(int, const char *);
    int	show_archive(int, const char *, FILE *);
    int	show_file(int, int, const char *, FILE *fp, off_t, union hdr *);
    void	print_symbol(const char *, struct xnlist *);
    
    #define	OPTSTRING_NM	"aABCDegnopPrst:uvw"
    const struct option longopts_nm[] = {
    	{ "debug-syms",		no_argument,		0,	'a' },
    	{ "demangle",		no_argument,		0,	'C' },
    	{ "dynamic",		no_argument,		0,	'D' },
    	{ "extern-only",	no_argument,		0,	'g' },
    /*	{ "line-numbers",	no_argument,		0,	'l' }, */
    	{ "no-sort",		no_argument,		0,	'p' },
    	{ "numeric-sort",	no_argument,		0,	'n' },
    	{ "print-armap",	no_argument,		0,	's' },
    	{ "print-file-name",	no_argument,		0,	'o' },
    	{ "reverse-sort",	no_argument,		0,	'r' },
    /*	{ "size-sort",		no_argument,		&szval,	1 }, */
    	{ "undefined-only",	no_argument,		0,	'u' },
    	{ "help",		no_argument,		0,	'?' },
    	{ NULL }
    };
    
    /*
     * main()
     *	parse command line, execute process_file() for each file
     *	specified on the command line.
     */
    int
    main(int argc, char *argv[])
    {
    	extern char *__progname;
    	extern int optind;
    	const char *optstr;
    	const struct option *lopts;
    	int ch, eval;
    
    	if (pledge("stdio rpath proc exec", NULL) == -1)
    		err(1, "pledge");
    
    	optstr = OPTSTRING_NM;
    	lopts = longopts_nm;
    	if (!strcmp(__progname, "size")) {
    		if (pledge("stdio rpath", NULL) == -1)
    			err(1, "pledge");
    
    		issize = 1;
    		optstr = "tw";
    		lopts = NULL;
    	}
    
    	while ((ch = getopt_long(argc, argv, optstr, lopts, NULL)) != -1) {
    		switch (ch) {
    		case 'a':
    			print_all_symbols = 1;
    			break;
    		case 'B':
    			/* no-op, compat with gnu-nm */
    			break;
    		case 'C':
    			demangle = 1;
    			break;
    		case 'D':
    			dynamic_only = 1;
    			break;
    		case 'e':
    			show_extensions = 1;
    			break;
    		case 'g':
    			print_only_external_symbols = 1;
    			break;
    		case 'n':
    		case 'v':
    			sfunc = value;
    			break;
    		case 'A':
    		case 'o':
    			print_file_each_line = 1;
    			break;
    		case 'p':
    			sfunc = NULL;
    			break;
    		case 'P':
    			posix_output = 1;
    			break;
    		case 'r':
    			rev = 1;
    			break;
    		case 's':
    			armap = 1;
    			break;
    		case 'u':
    			print_only_undefined_symbols = 1;
    			break;
    		case 'w':
    			non_object_warning = 1;
    			break;
    		case 't':
    			if (issize) {
    				print_totals = 1;
    			} else {
    				posix_radix = *optarg;
    				if (strlen(optarg) != 1 ||
    				    (posix_radix != 'd' && posix_radix != 'o' &&
    				     posix_radix != 'x'))
    					usage();
    			}
    			break;
    		default:
    			usage();
    		}
    	}
    
    	if (posix_output)
    		(void)snprintf(posix_fmtstr, sizeof posix_fmtstr, "%%%c %%%c",
    		    posix_radix, posix_radix);
    	if (demangle)
    		pipe2cppfilt();
    
    	if (pledge("stdio rpath", NULL) == -1)
    		err(1, "pledge");
    
    	argv += optind;
    	argc -= optind;
    
    	if (rev && sfunc == fname)
    		sfunc = rname;
    
    	eval = 0;
    	if (*argv)
    		do {
    			eval |= process_file(argc, *argv);
    		} while (*++argv);
    	else
    		eval |= process_file(1, "a.out");
    
    	if (issize && print_totals)
    		printf("\n%lu\t%lu\t%lu\t%lu\t%lx\tTOTAL\n",
    		    total_text, total_data, total_bss,
    		    total_total, total_total);
    	exit(eval);
    }
    
    /*
     * process_file()
     *	show symbols in the file given as an argument.  Accepts archive and
     *	object files as input.
     */
    int
    process_file(int count, const char *fname)
    {
    	union hdr exec_head;
    	FILE *fp;
    	int retval;
    	size_t bytes;
    	char magic[SARMAG];
    
    	if (!(fp = fopen(fname, "r"))) {
    		warn("cannot read %s", fname);
    		return(1);
    	}
    
    	if (!issize && count > 1)
    		(void)printf("\n%s:\n", fname);
    
    	/*
    	 * first check whether this is an object file - read a object
    	 * header, and skip back to the beginning
    	 */
    	bzero(&exec_head, sizeof(exec_head));
    	bytes = fread((char *)&exec_head, 1, sizeof(exec_head), fp);
    	if (bytes < sizeof(exec_head)) {
    		if (bytes < sizeof(exec_head.elf32) || IS_ELF(exec_head.elf32)) {
    			warnx("%s: bad format", fname);
    			(void)fclose(fp);
    			return(1);
    		}
    	}
    	rewind(fp);
    
    	/* this could be an archive */
    	if (!IS_ELF(exec_head.elf32)) {
    		if (fread(magic, sizeof(magic), (size_t)1, fp) != 1 ||
    		    strncmp(magic, ARMAG, SARMAG)) {
    			warnx("%s: not object file or archive", fname);
    			(void)fclose(fp);
    			return(1);
    		}
    		retval = show_archive(count, fname, fp);
    	} else
    		retval = show_file(count, 1, fname, fp, 0, &exec_head);
    	(void)fclose(fp);
    	return(retval);
    }
    
    char *nametab;
    
    /*
     *
     *	given the archive member header -- produce member name
     */
    int
    mmbr_name(struct ar_hdr *arh, char **name, int baselen, int *namelen, FILE *fp)
    {
    	char *p = *name + strlen(*name);
    	long i;
    
    	if (nametab && arh->ar_name[0] == '/') {
    		int len;
    
    		i = atol(&arh->ar_name[1]);
    		len = strlen(&nametab[i]) + 1;
    		if (len > *namelen) {
    			p -= (long)*name;
    			if ((*name = realloc(*name, baselen+len)) == NULL)
    				err(1, NULL);
    			*namelen = len;
    			p += (long)*name;
    		}
    		strlcpy(p, &nametab[i], len);
    		p += len - 1;
    	} else
    #ifdef AR_EFMT1
    	/*
    	 * BSD 4.4 extended AR format: #1/<namelen>, with name as the
    	 * first <namelen> bytes of the file
    	 */
    	if ((arh->ar_name[0] == '#') &&
    	    (arh->ar_name[1] == '1') &&
    	    (arh->ar_name[2] == '/') &&
    	    (isdigit((unsigned char)arh->ar_name[3]))) {
    		int len = atoi(&arh->ar_name[3]);
    
    		if (len > *namelen) {
    			p -= (long)*name;
    			if ((*name = realloc(*name, baselen+len)) == NULL)
    				err(1, NULL);
    			*namelen = len;
    			p += (long)*name;
    		}
    		if (fread(p, len, 1, fp) != 1) {
    			warnx("%s: premature EOF", *name);
    			free(*name);
    			return(1);
    		}
    		p += len;
    	} else
    #endif
    	for (i = 0; i < sizeof(arh->ar_name); ++i)
    		if (arh->ar_name[i] && arh->ar_name[i] != ' ')
    			*p++ = arh->ar_name[i];
    	*p = '\0';
    	if (p[-1] == '/')
    		*--p = '\0';
    
    	return (0);
    }
    
    /*
     * show_symtab()
     *	show archive ranlib index (fs5)
     */
    int
    show_symtab(off_t off, u_long len, const char *name, FILE *fp)
    {
    	struct ar_hdr ar_head;
    	int *symtab, *ps;
    	char *strtab, *p;
    	int num, rval = 0;
    	int namelen;
    	off_t restore;
    
    	restore = ftello(fp);
    
    	MMAP(symtab, len, PROT_READ, MAP_PRIVATE|MAP_FILE, fileno(fp), off);
    	if (symtab == MAP_FAILED)
    		return (1);
    
    	namelen = sizeof(ar_head.ar_name);
    	if ((p = malloc(sizeof(ar_head.ar_name))) == NULL) {
    		warn("%s: malloc", name);
    		MUNMAP(symtab, len);
    		return (1);
    	}
    
    	printf("\nArchive index:\n");
    	num = betoh32(*symtab);
    	strtab = (char *)(symtab + num + 1);
    	for (ps = symtab + 1; num--; ps++, strtab += strlen(strtab) + 1) {
    		if (fseeko(fp, betoh32(*ps), SEEK_SET)) {
    			warn("%s: fseeko", name);
    			rval = 1;
    			break;
    		}
    
    		if (fread(&ar_head, sizeof(ar_head), 1, fp) != 1 ||
    		    memcmp(ar_head.ar_fmag, ARFMAG, sizeof(ar_head.ar_fmag))) {
    			warnx("%s: member fseeko", name);
    			rval = 1;
    			break;
    		}
    
    		*p = '\0';
    		if (mmbr_name(&ar_head, &p, 0, &namelen, fp)) {
    			rval = 1;
    			break;
    		}
    
    		printf("%s in %s\n", strtab, p);
    	}
    
    	fseeko(fp, restore, SEEK_SET);
    
    	free(p);
    	MUNMAP(symtab, len);
    	return (rval);
    }
    
    /*
     * show_symdef()
     *	show archive ranlib index (gob)
     */
    int
    show_symdef(off_t off, u_long len, const char *name, FILE *fp)
    {
    	struct ranlib *prn, *eprn;
    	struct ar_hdr ar_head;
    	char *symdef;
    	char *strtab, *p;
    	u_long size;
    	int namelen, rval = 0;
    
    	MMAP(symdef, len, PROT_READ, MAP_PRIVATE|MAP_FILE, fileno(fp), off);
    	if (symdef == MAP_FAILED)
    		return (1);
    	if (usemmap)
    		(void)madvise(symdef, len, MADV_SEQUENTIAL);
    
    	namelen = sizeof(ar_head.ar_name);
    	if ((p = malloc(sizeof(ar_head.ar_name))) == NULL) {
    		warn("%s: malloc", name);
    		MUNMAP(symdef, len);
    		return (1);
    	}
    
    	size = *(u_long *)symdef;
    	prn = (struct ranlib *)(symdef + sizeof(u_long));
    	eprn = prn + size / sizeof(*prn);
    	strtab = symdef + sizeof(u_long) + size + sizeof(u_long);
    
    	printf("\nArchive index:\n");
    	for (; prn < eprn; prn++) {
    		if (fseeko(fp, prn->ran_off, SEEK_SET)) {
    			warn("%s: fseeko", name);
    			rval = 1;
    			break;
    		}
    
    		if (fread(&ar_head, sizeof(ar_head), 1, fp) != 1 ||
    		    memcmp(ar_head.ar_fmag, ARFMAG, sizeof(ar_head.ar_fmag))) {
    			warnx("%s: member fseeko", name);
    			rval = 1;
    			break;
    		}
    
    		*p = '\0';
    		if (mmbr_name(&ar_head, &p, 0, &namelen, fp)) {
    			rval = 1;
    			break;
    		}
    
    		printf("%s in %s\n", strtab + prn->ran_un.ran_strx, p);
    	}
    
    	free(p);
    	MUNMAP(symdef, len);
    	return (rval);
    }
    
    /*
     * show_archive()
     *	show symbols in the given archive file
     */
    int
    show_archive(int count, const char *fname, FILE *fp)
    {
    	struct ar_hdr ar_head;
    	union hdr exec_head;
    	int i, rval;
    	off_t last_ar_off, foff, symtaboff;
    	char *name;
    	int baselen, namelen;
    	u_long mmbrlen, symtablen;
    
    	baselen = strlen(fname) + 3;
    	if (posix_output)
    		baselen += 2;
    	namelen = sizeof(ar_head.ar_name);
    	if ((name = malloc(baselen + namelen)) == NULL)
    		err(1, NULL);
    
    	rval = 0;
    	nametab = NULL;
    	symtaboff = 0;
    	symtablen = 0;
    
    	/* while there are more entries in the archive */
    	while (fread(&ar_head, sizeof(ar_head), 1, fp) == 1) {
    		/* bad archive entry - stop processing this archive */
    		if (memcmp(ar_head.ar_fmag, ARFMAG, sizeof(ar_head.ar_fmag))) {
    			warnx("%s: bad format archive header", fname);
    			rval = 1;
    			break;
    		}
    
    		/* remember start position of current archive object */
    		last_ar_off = ftello(fp);
    		mmbrlen = atol(ar_head.ar_size);
    
    		if (strncmp(ar_head.ar_name, RANLIBMAG,
    		    sizeof(RANLIBMAG) - 1) == 0) {
    			if (!issize && armap &&
    			    show_symdef(last_ar_off, mmbrlen, fname, fp)) {
    				rval = 1;
    				break;
    			}
    			goto skip;
    		} else if (strncmp(ar_head.ar_name, SYMTABMAG,
    		    sizeof(SYMTABMAG) - 1) == 0) {
    			/* if nametab hasn't been seen yet -- doit later */
    			if (!nametab) {
    				symtablen = mmbrlen;
    				symtaboff = last_ar_off;
    				goto skip;
    			}
    
    			/* load the Sys5 long names table */
    		} else if (strncmp(ar_head.ar_name, STRTABMAG,
    		    sizeof(STRTABMAG) - 1) == 0) {
    			char *p;
    
    			if ((nametab = malloc(mmbrlen)) == NULL) {
    				warn("%s: nametab", fname);
    				rval = 1;
    				break;
    			}
    
    			if (fread(nametab, mmbrlen, (size_t)1, fp) != 1) {
    				warnx("%s: premature EOF", fname);
    				rval = 1;
    				break;
    			}
    
    			for (p = nametab, i = mmbrlen; i--; p++)
    				if (*p == '\n')
    					*p = '\0';
    
    			if (issize || !armap || !symtablen || !symtaboff)
    				goto skip;
    		}
    #ifdef __mips64
    		else if (memcmp(ar_head.ar_name, SYM64MAG,
    		    sizeof(ar_head.ar_name)) == 0) {
    			/* IRIX6-compatible archive map */
    			goto skip;
    		}
    #endif
    
    		if (!issize && armap && symtablen && symtaboff) {
    			if (show_symtab(symtaboff, symtablen, fname, fp)) {
    				rval = 1;
    				break;
    			} else {
    				symtaboff = 0;
    				symtablen = 0;
    			}
    		}
    
    		/*
    		 * construct a name of the form "archive.a:obj.o:" for the
    		 * current archive entry if the object name is to be printed
    		 * on each output line
    		 */
    		*name = '\0';
    		if (posix_output)
    			snprintf(name, baselen - 1, "%s[", fname);
    		else if (count > 1)
    			snprintf(name, baselen - 1, "%s:", fname);
    
    		if (mmbr_name(&ar_head, &name, baselen, &namelen, fp)) {
    			rval = 1;
    			break;
    		}
    
    		if (posix_output)
    			strlcat(name, "]", baselen + namelen);
    
    		foff = ftello(fp);
    
    		/* get and check current object's header */
    		if (fread((char *)&exec_head, sizeof(exec_head),
    		    (size_t)1, fp) != 1) {
    			warnx("%s: premature EOF", fname);
    			rval = 1;
    			break;
    		}
    
    		rval |= show_file(2, non_object_warning, name, fp, foff, &exec_head);
    		/*
    		 * skip to next archive object - it starts at the next
    		 * even byte boundary
    		 */
    #define even(x) (((x) + 1) & ~1)
    skip:		if (fseeko(fp, last_ar_off + even(mmbrlen), SEEK_SET)) {
    			warn("%s", fname);
    			rval = 1;
    			break;
    		}
    	}
    	free(nametab);
    	nametab = NULL;
    	free(name);
    	return(rval);
    }
    
    char *stab;
    
    /*
     * show_file()
     *	show symbols from the object file pointed to by fp.  The current
     *	file pointer for fp is expected to be at the beginning of an object
     *	file header.
     */
    int
    show_file(int count, int warn_fmt, const char *name, FILE *fp, off_t foff, union hdr *head)
    {
    	u_long text, data, bss, total;
    	struct xnlist *np, *names, **snames;
    	int i, nrawnames, nnames;
    	size_t stabsize;
    
    	if (IS_ELF(head->elf32) &&
    	    head->elf32.e_ident[EI_CLASS] == ELFCLASS32 &&
    	    head->elf32.e_ident[EI_VERSION] == ELF_TARG_VER) {
    		void *shdr;
    
    		if (!(shdr = elf32_load_shdrs(name, fp, foff, &head->elf32)))
    			return (1);
    
    		i = issize?
    		    elf32_size(&head->elf32, shdr, &text, &data, &bss) :
    		    elf32_symload(name, fp, foff, &head->elf32, shdr,
    			&names, &snames, &stabsize, &nrawnames);
    		free(shdr);
    		if (i)
    			return (i);
    
    	} else if (IS_ELF(head->elf64) &&
    	    head->elf64.e_ident[EI_CLASS] == ELFCLASS64 &&
    	    head->elf64.e_ident[EI_VERSION] == ELF_TARG_VER) {
    		void *shdr;
    
    		if (!(shdr = elf64_load_shdrs(name, fp, foff, &head->elf64)))
    			return (1);
    
    		i = issize?
    		    elf64_size(&head->elf64, shdr, &text, &data, &bss) :
    		    elf64_symload(name, fp, foff, &head->elf64, shdr,
    			&names, &snames, &stabsize, &nrawnames);
    		free(shdr);
    		if (i)
    			return (i);
    	} else {
    		if (warn_fmt)
    			warnx("%s: bad format", name);
    		return (1);
    	}
    
    	if (issize) {
    		static int first = 1;
    
    		if (first) {
    			first = 0;
    			printf("text\tdata\tbss\tdec\thex\n");
    		}
    
    		total = text + data + bss;
    		printf("%lu\t%lu\t%lu\t%lu\t%lx",
    		    text, data, bss, total, total);
    		if (count > 1)
    			(void)printf("\t%s", name);
    
    		total_text += text;
    		total_data += data;
    		total_bss += bss;
    		total_total += total;
    
    		printf("\n");
    		return (0);
    	}
    	/* else we are nm */
    
    	/*
    	 * it seems that string table is sequential
    	 * relative to the symbol table order
    	 */
    	if (sfunc == NULL && usemmap)
    		(void)madvise(stab, stabsize, MADV_SEQUENTIAL);
    
    	/*
    	 * fix up the symbol table and filter out unwanted entries
    	 *
    	 * common symbols are characterized by a n_type of N_UNDF and a
    	 * non-zero n_value -- change n_type to N_COMM for all such
    	 * symbols to make life easier later.
    	 *
    	 * filter out all entries which we don't want to print anyway
    	 */
    	for (np = names, i = nnames = 0; i < nrawnames; np++, i++) {
    		/*
    		 * make n_un.n_name a character pointer by adding the string
    		 * table's base to n_un.n_strx
    		 *
    		 * don't mess with zero offsets
    		 */
    		if (np->nl.n_un.n_strx)
    			np->nl.n_un.n_name = stab + np->nl.n_un.n_strx;
    		else
    			np->nl.n_un.n_name = "";
    		if (print_only_external_symbols && !IS_EXTERNAL(np->nl.n_type))
    			continue;
    		if (print_only_undefined_symbols &&
    		    SYMBOL_TYPE(np->nl.n_type) != N_UNDF)
    			continue;
    
    		snames[nnames++] = np;
    	}
    
    	/* sort the symbol table if applicable */
    	if (sfunc)
    		qsort(snames, (size_t)nnames, sizeof(*snames), sfunc);
    
    	if (count > 1)
    		(void)printf("\n%s:\n", name);
    
    	/* print out symbols */
    	for (i = 0; i < nnames; i++)
    		print_symbol(name, snames[i]);
    
    	free(snames);
    	free(names);
    	MUNMAP(stab, stabsize);
    	return(0);
    }
    
    char *
    symname(struct xnlist *sym)
    {
    	return sym->nl.n_un.n_name;
    }
    
    /*
     * print_symbol()
     *	show one symbol
     */
    void
    print_symbol(const char *name, struct xnlist *sym)
    {
    	if (print_file_each_line) {
    		if (posix_output)
    			(void)printf("%s: ", name);
    		else
    			(void)printf("%s:", name);
    	}
    
    	if (posix_output) {
    		(void)printf("%s %c ", symname(sym), typeletter(sym));
    		if (SYMBOL_TYPE(sym->nl.n_type) != N_UNDF)
    			(void)printf(posix_fmtstr, sym->nl.n_value,
    			    sym->n_size);
    		(void)printf("\n");
    	} else {
    		/*
    		 * handle undefined-only format especially (no space is
    		 * left for symbol values, no type field is printed)
    		 */
    		if (!print_only_undefined_symbols) {
    			/* print symbol's value */
    			if (SYMBOL_TYPE(sym->nl.n_type) == N_UNDF)
    				(void)printf("        ");
    			else
    				(void)printf("%08lx", sym->nl.n_value);
    
    			/* print type information */
    			if (show_extensions)
    				(void)printf(" %c   ", typeletter(sym));
    			else
    				(void)printf(" %c ", typeletter(sym));
    		}
    
    		(void)puts(symname(sym));
    	}
    }
    
    /*
     * typeletter()
     *	return a description letter for the given basic type code of an
     *	symbol table entry.  The return value will be upper case for
     *	external, lower case for internal symbols.
     */
    char
    typeletter(struct xnlist *np)
    {
    	int ext = IS_EXTERNAL(np->nl.n_type);
    
    	if (np->nl.n_other)
    		return np->nl.n_other;
    
    	switch(SYMBOL_TYPE(np->nl.n_type)) {
    	case N_ABS:
    		return(ext? 'A' : 'a');
    	case N_BSS:
    		return(ext? 'B' : 'b');
    	case N_COMM:
    		return(ext? 'C' : 'c');
    	case N_DATA:
    		return(ext? 'D' : 'd');
    	case N_FN:
    		/* NOTE: N_FN == N_WARNING,
    		 * in this case, the N_EXT bit is to considered as
    		 * part of the symbol's type itself.
    		 */
    		return(ext? 'F' : 'W');
    	case N_TEXT:
    		return(ext? 'T' : 't');
    	case N_SIZE:
    		return(ext? 'S' : 's');
    	case N_UNDF:
    		return(ext? 'U' : 'u');
    	}
    	return('?');
    }
    
    int
    fname(const void *a0, const void *b0)
    {
    	struct xnlist * const *a = a0, * const *b = b0;
    
    	return(strcmp((*a)->nl.n_un.n_name, (*b)->nl.n_un.n_name));
    }
    
    int
    rname(const void *a0, const void *b0)
    {
    	struct xnlist * const *a = a0, * const *b = b0;
    
    	return(strcmp((*b)->nl.n_un.n_name, (*a)->nl.n_un.n_name));
    }
    
    int
    value(const void *a0, const void *b0)
    {
    	struct xnlist * const *a = a0, * const *b = b0;
    
    	if (SYMBOL_TYPE((*a)->nl.n_type) == N_UNDF)
    		if (SYMBOL_TYPE((*b)->nl.n_type) == N_UNDF)
    			return(0);
    		else
    			return(-1);
    	else if (SYMBOL_TYPE((*b)->nl.n_type) == N_UNDF)
    		return(1);
    	if (rev) {
    		if ((*a)->nl.n_value == (*b)->nl.n_value)
    			return(rname(a0, b0));
    		return((*b)->nl.n_value > (*a)->nl.n_value ? 1 : -1);
    	} else {
    		if ((*a)->nl.n_value == (*b)->nl.n_value)
    			return(fname(a0, b0));
    		return((*a)->nl.n_value > (*b)->nl.n_value ? 1 : -1);
    	}
    }
    
    #define CPPFILT	"/usr/bin/c++filt"
    
    void
    pipe2cppfilt(void)
    {
    	int pip[2];
    	char *argv[2];
    
    	argv[0] = "c++filt";
    	argv[1] = NULL;
    
    	if (pipe(pip) == -1)
    		err(1, "pipe");
    	switch(fork()) {
    	case -1:
    		err(1, "fork");
    	default:
    		dup2(pip[0], 0);
    		close(pip[0]);
    		close(pip[1]);
    		execve(CPPFILT, argv, NULL);
    		err(1, "execve");
    	case 0:
    		dup2(pip[1], 1);
    		close(pip[1]);
    		close(pip[0]);
    	}
    }
    
    void
    usage(void)
    {
    	extern char *__progname;
    
    	if (issize)
    		fprintf(stderr, "usage: %s [-tw] [file ...]\n", __progname);
    	else
    		fprintf(stderr, "usage: %s [-AaCDegnoPprsuw] [-t d|o|x] [file ...]\n",
    		    __progname);
    	exit(1);
    }