[BACK]Return to nm.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / nm

File: [local] / src / usr.bin / nm / nm.c (download)

Revision 1.55, Sun Dec 4 23:50:49 2022 UTC (17 months, 2 weeks ago) by cheloha
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, OPENBSD_7_4_BASE, OPENBSD_7_4, OPENBSD_7_3_BASE, OPENBSD_7_3, HEAD
Changes since 1.54: +1 -2 lines

userspace: remove vestigial '?' cases from top-level getopt(3) loops

getopt(3) returns '?' when it encounters a flag not present in the in
the optstring or if a flag is missing its option argument.  We can
handle this case with the "default" failure case with no loss of
legibility.  Hence, remove all the redundant "case '?':" lines.

Prompted by dlg@.  With help from dlg@ and millert@.

Link: https://marc.info/?l=openbsd-tech&m=167011979726449&w=2

ok naddy@ millert@ dlg@

/*	$OpenBSD: nm.c,v 1.55 2022/12/04 23:50:49 cheloha 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 *);
char *otherstring(struct xnlist *);
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);
}