/*
 *				n m . c
 */

/*)BUILD	$(TKBOPTIONS) = {
			TASK	= ...NMX
		}
*/

/*
 * nm [-1bcdfgruv] file
 *
 *		nm	file[.obj]
 * 
 * This program reads through an object file or a TKB symbol table and
 * prints a list of all the symbols it finds. I have found that these are
 * a reasonable alternative to load maps, except for the fact that TKB does
 * not put the undefined symbols in the symbol table file.
 *
 * Note:  modified 25-Apr-80 for the newer C format.
 * modified 21-Feb-85 JSL - add VAX C redirection call.  No guarantees this
 *		produces a program that's good for anything!
 * 
 * It has dozens of options. They are:
 * 
 *	-1		Output names in 1 column, default is 4 column
 *	-b		Includes RSX control block symbols. These are
 *			symbols of the form x.xxxx x$xxxx xx.xxx and
 *			xx$xxx. Normally these symbols are supressed.
 *	-c		Include C compiler internal symbols. These are
 *			symbols that begin with a "." and contain
 *			only digits. Normally supressed.
 *	-d		Differences. The symbol table is sorted by
 *			address, each symbol is replaced by the 
 *			difference between it and the next symbol.
 *			This is used to see how large your routines
 *			are.
 *	-f		Print symbols in file order. No sort is
 *			performed.
 *	-g		Print only global symbols. The default is
 *			to print globals and internals.
 *	-m		Map underscore to '_' for C symbols.
 *	-r		Reverse the direction of the sort.
 *	-u		Print undefined symbols. Undefined symbols
 *			are normally supressed.
 *	-x		Cross reference list.
 *	-v		Sort by value, the default is sort by name.
 * 
 */

#ifdef	DOCUMENTATION

title	nm	Print Object Module Symbol Names
index	nm	Print Object Module Symbol Names

synopsis

	nm [-options] file[.obj]

description

	nm (NameList) reads through an object file or a TKB symbol table and
	prints a list of all the symbols it finds. This is a reasonable
	alternative to load maps and is useful in determining what global
	symbols are defined or referenced by an object module.
	.s
	It has dozens of options. They are:
	.lm +8
	.s.i -8;-1	Output names in one column, default is four columns
	per line.
	.s.i -8;-b	Include RSX-11M control block symbols.  These are of
	the form "x.xxxx", "x$xxxx", "xx.xxx", and "xx$xxx".  Normally, these
	are supressed by nm.  Note that -b is appropriate if you are
	examining objects produced by C compilers.
	.s.i -8;-c	Include C compiler internal symbols.  These begin
	with a "." and contain only digits.  Normally supressed.
	.s.i -8;-d	Differences.  The symbol table is sorted by
	address and the symbol value printed is the difference between it and
	the next symbol.  This may be used to see how large each module is.
	.s.i -8;-f	Print symbols in file order.  No sort is performed.
	.s.i -8;-g	Print only global symbols.  The default is to print
	both globals and internals.
	.s.i -8;-m	Map Radix-50 "." to underscore for C symbols.
	.s.i -8;-r	Reverse the direction of the sort.
	.s.i -8;-u	Print undefined symbols.  These are normally supressed.
	.s.i -8;-v	Sort by value; the default is sort by name.
	.s.i -8;-x	Print an inter-module cross reference list of all
	symbols defined in all input files.  Forces -b and -g.
	.s.lm -8
	nm accepts multiple input file names.  Names may contain wildcards.
	Note that, if compiled under RT11, nm accepts only "RT11-style"
	object modules, while, if compiled under RSX, nm accepts only
	"RSX-style" object modules.  Vax native has never been tested;
	it might read RSX (compatiblity) modules, however.

diagnostics

	.lm +8
	.s.i -8;"file name": cannot open
	.s.i -8;"Usage: ..."
	.s
	This message is printed on illegal flags or other command errors.
	.s.i -8;"file name": bad file format
	.s
	The input file was not in the necessary object-module format.  This
	could mean that the program was compiled by the wrong operating
	system.
	.s.i -8;Too many symbols
	.s.i -8;Too many sections
	.s
	.lm -8
	All errors are fatal.

author

	David Conroy, Martin Minow

bugs

#endif

#include <stdio.h>
#ifdef vms
#include		<ssdef.h>
#include		<stsdef.h>
#define	IO_SUCCESS	(SS$_NORMAL | STS$M_INHIB_MSG)
#define	IO_ERROR	SS$_ABORT
#endif
/*
 * Note: IO_SUCCESS and IO_ERROR are defined in the Decus C stdio.h file
 */
#ifndef	IO_SUCCESS
#define	IO_SUCCESS	0
#endif
#ifndef	IO_ERROR
#define	IO_ERROR	1
#endif

#define	NSY	500
#define	NPS	20
#define	AB0	0127400
#define AB1	0003243
#define AB2	0127401
#define AB3	0007624
#define RBMAX	128

#define	GSD	1
#define	G_CSN	1
#define	G_ISN	2
#define	G_GSN	4
#define	G_PSN	5
#define	G_DEF	010

#define	FALSE	0
#define	TRUE	1
#define	EOS	0

typedef struct symbol {
	struct symbol	*s_next;
	char		*s_name;
	struct module	*s_defn;	/* Where it's defined		*/
	struct refer	*s_refer;	/* Where it's referenced	*/
} SYMBOL;

typedef struct module {
	struct module	*m_next;
	char		*m_name;
	int		m_index;
} MODULE;

typedef struct refer {
	struct refer	*r_next;
	struct module	*r_module;
} REFERENCE;

extern char	*strsave();
extern char	*myalloc();

typedef unsigned addr_t;
typedef unsigned r50_t;

struct	rb
{
	int	type;
	char	data[RBMAX];
};

struct	gsde
{
	r50_t	g_name[2];
	char	g_flag;
	char	g_type;
	addr_t	g_value;
};

struct	sym
{
	char	s_name[6];
	char	s_flag;
	char	s_type;
	int	s_ps;
	addr_t	s_value;
};

struct	pst
{
	r50_t	p_name[2];
	addr_t	p_size;
};

struct	rb	rb;
struct	sym	sym[NSY];
struct	pst	pst[NPS];
char	*rbe;
int	nsy;
int	nps;
int	cps;
char	*fn;
FILE	*fp;
int	oflag;
int	bflag;
int	cflag;
int	dflag;
int	fflag;
int	gflag;
int	mflag;
int	rflag;
int	uflag;
int	vflag;
int	xflag;
char	filename[80];

SYMBOL		*sfirst		= NULL;
MODULE		*mfirst		= NULL;
REFERENCE	*rfirst		= NULL;

char		workingtext[133];
char		wildname[80];
#define		CROSS_MAX	30
int		cross[CROSS_MAX][CROSS_MAX];
char		*mname[CROSS_MAX];
int		cmax		= -1;

main(argc, argv)
char *argv[];
{
	register int i, c;
	register char *p;
	MODULE		*mp, *newmodule();
	int		multi, nwild, nfiles;
	extern char	*instr();

#ifdef vax
	/*
	 * Do redirection for VAX C.  Note that this program may still
	 * not do anything useful when compiled this way, but at least
	 * it will "do it consistently"!
	 */
	argc = getredirection(argc,argv);
#endif

	multi = 0;
	fn = NULL;
	for(i=1; i<argc; ++i) {
		p = argv[i];
		if(*p == '-') {
			++p;
			while(c = *p++) {
				switch(tolower(c)) {

				case '1':
					++oflag;
					break;

				case 'b':
					++bflag;
					break;

				case 'c':
					++cflag;
					break;

				case 'd':
					++dflag;
					break;

				case 'f':
					++fflag;
					break;

				case 'g':
					++gflag;
					break;

				case 'm':
					++mflag;
					break;

				case 'r':
					++rflag;
					break;

				case 'u':
					++uflag;
					break;

				case 'v':
					++vflag;
					break;

				case 'x':
					++xflag;
					++gflag;	/* Globals	*/
					++bflag;	/* xx_xxx, too	*/
					break;

				default:
					usage();
				}
			}
			argv[i] = NULL;
		}
		else {
			multi++;
			if (instr(argv[i], '*') != NULL
			 || instr(argv[i], '?') != NULL)
				multi++;
		}
	}
	if (multi == 0) {
		fprintf(stderr, "Need a file name argument\n");
		exit(IO_ERROR);
	}
	nwild = 0;
	for (i = 1; i < argc; i++) {
		if (argv[i] == NULL)
			continue;
		strcpy(wildname, argv[i]);
		if (instr(wildname, '.') == NULL)
			strcat(wildname, ".obj");
		if ((fp = fwild(wildname, "run")) == NULL) {
			perror(wildname);
			continue;
		}
		for (nfiles = 0; fnext(fp) != NULL; nfiles++) {
			fgetname(fp, filename);
			if (xflag) {
				mp = newmodule();
				readin(mp);
			}
			else {
				readin(NULL);
				if (dflag)
					deltas();
				if (!fflag)
					sort(vflag, rflag);
				if (multi > 1) {
					printf("%s%s\n\n",
						((nfiles + nwild) > 0) ?
							"\f" : "",
						filename);
				}
				output();
			}
		}
		if (nfiles == 0) {
			fprintf(stderr, "no files matched \"%s\"\n",
				wildname);
		}
		nwild++;
	}
	if (nwild == 0) {
		fprintf(stderr, "no files present\n");
	}
	else if (xflag) {
		outref();
	}
}

/*
 * Put out usage message.
 * Just exit.
 */
usage()
{
	error("Usage: nm [-1bcdfgmruvx] file_list\n");
}

/*
 * Read through the object or
 * symbol table file.
 * Save all of the symbols in
 * the big buffer.
 * Just exit if the file format
 * is wrong, or if a table
 * overflows.
 */
readin(mp)
MODULE		*mp;		/* Module pointer if non-null		*/
{
	register struct gsde *p;
	register struct sym  *sp;
	register int t;
	char ab[6];

	if(!getrec() || rb.type != GSD) {
		fprintf(stderr, "%s: bad file format\n", filename);
		return;
	}
	do {
		if (rb.type != GSD)
			continue;
		for (p = rb.data; p < rbe; ++p) {
			t = p->g_type;
			if (t == G_CSN || t == G_PSN) {
				if (!xflag) {
					cps = lookup(p);
				}
			}
			else if (t == G_GSN || t == G_ISN) {
				if (gflag && t == G_ISN)
					continue;
				if (uflag && (p->g_flag & G_DEF) != 0)
					continue;
				convert(ab, p->g_name);
				if(!cflag && csym(ab))
					continue;
				if(!bflag && bsym(ab))
					continue;
				if (xflag) {
				    newref(ab, (p->g_flag & G_DEF) != 0, mp);
				    continue;
				}
				if(nsy >= NSY)
					error("Too many symbols!\n");
				sp = &sym[nsy++];
				copy(sp->s_name, ab, 6);
				sp->s_type = t;
				sp->s_flag = p->g_flag;
				sp->s_ps = cps;
				sp->s_value = p->g_value;
			}
		}
	} while(getrec());
}

/*
 * Read a record from the file into the record buffer.
 * True return if the read was ok.
 * Set up rbe to point just beyond the record.
 *
 * RT11 object module format:
 *	(optional string of NULL bytes)
 *	001 000		Header
 *	low high	Size low, high bytes
 *	(size-4 bytes)	Data record
 *	byte		Checksum (ignored)
 */
int
getrec()
{
#ifdef	rt11
	register int	n;
	register char	*rbp;
	register int	c;

	while (!feof(fp)) {
		if ((c = getc(fp)) != 0)	/* Skip leading nulls	*/
			break;

	}
	if (feof(fp))
		return(FALSE);
	if (c != 1) {				/* Header ok?		*/
		fprintf(stderr, "Illegal rt11 .obj format in \"%s\"\n",
			filename);
		return(FALSE);
	}
	getc(fp);				/* Should be a zero	*/
	c = getc(fp) & 0377;			/* Size low byte	*/
	n = (getc(fp) << 8) + c - 4;		/* Record size		*/
	rbp = (char *)&rb;
	while (--n >= 0) {
		*rbp++ = getc(fp);
	}
	getc(fp);				/* Skip over checksum	*/
	if (feof(fp)) {
		fprintf(stderr, "Incomplete final record in %s\n", filename);
		return (FALSE);
	}
	else {
		rbe = rbp;
		return (TRUE);
	}
#else
	register int n;

	n = fget(&rb, sizeof(rb), fp);
	if(feof(fp))
		return(FALSE);
	rbe = (char *)&rb;
	rbe += n;
	return(TRUE);
#endif
}

/*
 * Convert 6 radix 50 characters
 * to ascii.
 * If -m, map `.' to `_' to agree
 * with the (new) C format.
 */
convert(a, r)
register char *a;
r50_t *r;
{
	register int n;

	r50toa(a, r, 2);
	n = 6;
	do {
		if (mflag && *a == '.')
			*a = '_';
		if (*a >= 'A' && *a <= 'Z')
			*a += ('a' - 'A');
		++a;
	} while(--n);
}

/*
 * Test if a 6 character symbol
 * is a C internal symbol.
 * These symbols are all numeric
 * except for a leading dot.
 */
csym(p)
register char *p;
{
	register int c, n;

	if(*p++ != '.')
		return(0);
	n = 5;
	do {
		c = *p++;
		if(c!=' ' && (c<'0' || c>'9'))
			return(0);
	} while(--n);
	return(1);
}

/*
 * Test if a 6 character symbol
 * is an RSX control block
 * symbol.
 * These symbols have the form
 * x.xxxx xx.xxx x_xxxx xx_xxx x$xxxx or xx$xxx
 */
bsym(p)
register char *p;
{
	if(p[1] == '.' || p[1] == '_' || p[1] == '$'
			|| p[2] == '.' || p[2] == '_' || p[2] == '$')
		return(1);
	return(0);
}

/*
 * Lookup a psection.
 * Make new entry if new psection.
 * In any casee return the
 * psection number.
 */
lookup(g)
register struct gsde *g;
{
	register struct pst *p;
	register int i;

	for(i=0; i<nps; ++i)
		if(match(pst[i].p_name, g->g_name))
			return(i);
	if(nps >= NPS)
		error("Too many sections!\n");
	p = &pst[nps];
	p->p_name[0] = g->g_name[0];
	p->p_name[1] = g->g_name[1];
	p->p_size = g->g_value;
	return(nps++);
}

/*
 * Compare psection names.
 * Names are 2 words of (ugh)
 * radix 50.
 */
match(a, b)
register int *a, *b;
{
	if(a[0]==b[0] && a[1]==b[1])
		return(1);
	return(0);
}

/*
 * Run through the symbols, 
 * computing the first order
 * differencees for the -d option.
 * The last symbol requires
 * special handling.
 * For unknown reasons, the
 * last symbol sometimes gets
 * a funny size in spite of
 * this.
 */
deltas()
{
	register struct sym *p;

	sort(1, 0);
	for(p = &sym[0]; p < &sym[nsy-1]; ++p)
		p->s_value = (p+1)->s_value - p->s_value;
	p->s_value = pst[p->s_ps].p_size - p->s_value;
}

/*
 * Sort symbols.
 * v is 1 for sort on value.
 * r is 1 for reverse sort.
 */
sort(v, r)
{
	register struct sym *b, *m, *p;
	struct sym ts;

	for(b = &sym[0]; b < &sym[nsy-1]; ++b) {
		m = b;
		for(p=b+1; p<&sym[nsy]; ++p)
			if(cmp(v, r, m, p))
				m = p;
		if(m != b) {
			copy(&ts, m, sizeof(ts));
			copy(m,   b, sizeof(ts));
			copy(b, &ts, sizeof(ts));
		}
	}
}

/*
 * Compare routine for
 * sort.
 */
cmp(v, r, a, b)
register struct sym *a, *b;
{
	register int n;
	int c;

	c = 0;
	if(v) {
		if(a->s_value > b->s_value)
			c = 1;
	} else
		for(n=0; n<6; ++n) {
			if(a->s_name[n] > b->s_name[n]) {
				c = 1;
				break;
			}
			if(a->s_name[n] < b->s_name[n])
				break;
		}
	if(r)
		c = !c;
	return(c);
}

/*
 * Walk through the symbols, 
 * formatting them and writing
 * the result to the standard
 * output.
 * Undefined symbols get stars
 * as a value.
 */
output()
{
	register struct sym *p;
	register r50_t *np;
	register int ntl;
	int max;
	char ab[7];		/* newref() needs the 7'th byte	*/

	max = oflag ? 1 : 3;
	ntl = 0;
	for(p = &sym[0]; p < &sym[nsy]; ++p) {
		if(ntl >= max) {
			putchar('\n');
			ntl = 0;
		} else if(p != &sym[0])
			putchar(' ');
		printf("%.6s", p->s_name);
		printf(" %c ", (p->s_type == G_GSN) ? 'g' : ' ');
		if((p->s_flag & G_DEF) != 0)
			printf("%06o", p->s_value);
		else
			printf("******");
		np = pst[p->s_ps].p_name;
		if((np[0]==AB0 && np[1]==AB1) || (np[0]==AB2 && np[1]==AB3))
			printf("         ");
		else {
			convert(ab, np);
			printf(" (%.6s)", ab);
		}
		++ntl;
	}
	if(ntl)
		putchar('\n');
}

char *
instr(item, c)
register char	*item;
register char	c;
/*
 * If 'c' is in item, return a pointer to it, else NULL.
 */
{
	while (*item) {
		if (*item == c)
			return (item);
		item++;
	}
	return (NULL);
}


/*
 * Stuff for the cross reference listing
 */
newref(name, value, mp)
char	name[7];
int	value;
MODULE	*mp;
{
	register SYMBOL	*sy;
	extern SYMBOL	*sylookup();
	register char	*np;

	/*
	 * First, strip out trailing ' ' from name
	 */
	name[6] = EOS;
	if ((np = instr(&name[0], ' ')) != NULL)
		*np = EOS;
	sy = sylookup(&name[0]);
	if (!value) {
		addref(sy, mp);		/* Undefined here	*/
	}
	else {				/* Defined here		*/
		if (sy->s_defn != NULL) {
			printf("** symbol %s defined in %s and %s\n",
				sy->s_name, (sy->s_defn)->m_name,
				mp->m_name);
			if (!ftty(stdout)) {
				fprintf(stderr,
					"** symbol %s defined in %s and %s\n",
					sy->s_name, (sy->s_defn)->m_name,
					mp->m_name);
			}
		}
		else {
			sy->s_defn = mp;
		}
	}
}

outref()
{
	register SYMBOL		*sy;
	register int		nsymb;
	register REFERENCE	*rp;
	int			i, j;

	mnames();
	/*
	 * Pass 1 for "library" symbols only
	 */
	printf("The following are not defined:\n");
	nsymb = 0;
	for (sy = sfirst; sy != NULL; sy = sy->s_next) {
		if (sy->s_defn == NULL) {
			printf("%c%s",
				((nsymb & 7) == 0) ? '\n' : '\t', sy->s_name);
			nsymb++;
		}
	}
	printf("\n\n");
	/*
	 * Pass 2 for symbols not used elsewhere
	 */
	printf("The following are not used outside of their module:\n");
	nsymb = 0;
	for (sy = sfirst; sy != NULL; sy = sy->s_next) {
		if (sy->s_defn != NULL && sy->s_refer == NULL) {
			printf("%c%s\t%s",
				((nsymb & 3) == 0) ? '\n' : '\t',
					sy->s_name, (sy->s_defn)->m_name);
			nsymb++;
		}
	}
	/*
	 * Pass 3 -- all the rest
	 */
	printf("\n\nThe following are defined and used elsewhere\n");
	for (sy = sfirst; sy != NULL; sy = sy->s_next) {
		if (sy->s_defn == NULL || sy->s_refer == NULL) {
			continue;
		}
		i = (sy->s_defn)->m_index;
		printf("\n%s\t%s:", sy->s_name, (sy->s_defn)->m_name);
		nsymb = 0;
		for (rp = sy->s_refer; rp != NULL; rp = rp->r_next) {
			printf("%s%s",
				((++nsymb % 6) == 0) ? "\n\t\t" : "\t",
				(rp->r_module)->m_name);
			j = (rp->r_module)->m_index;
			cross[i][j]++;
		}
	}
	printf("\n");
	/*
	 * Now for the map
	 */
	printf("\nmodule");
	for (i = 0; i <= cmax; i++) {
		printf("\n%s\t", mname[i]);
		for (j = 0; j < i; j++) {
			if ((nsymb = cross[i][j] + cross[j][i]) == 0)
				printf("    ");
			else
				printf("%4d", nsymb);
		}
		printf("   %s", mname[i]);
	}
}

/*
 * Manage symbol table
 */
MODULE *
newmodule()
/*
 * Make a new module entry for this file.
 */
{
	register MODULE		*mp;
	register MODULE		**mplast;
	register MODULE		*new;
	int			i;

	getfilename(fp, workingtext);
	new = myalloc(sizeof (MODULE));
	new->m_name = strsave(workingtext);
	for (mplast = &mfirst; (mp = *mplast) != NULL; mplast = &mp->m_next) {
		if ((i = strcmp(workingtext, mp->m_name)) < 0)
			break;
		else if (i == 0) {
			fprintf(stderr, "duplicate module name \"%s\"\n",
				workingtext);
			return(mp);
		}
	}
	new->m_next = mp;
	*mplast = new;
	cmax++;
	if (cmax >= CROSS_MAX)
		error("too many modules");
	return(new);
}

addref(sy, mp)
SYMBOL		*sy;
MODULE		*mp;
/*
 * The module references this symbol.
 */
{
	register REFERENCE	*rp;
 	REFERENCE		**rplast;
	register REFERENCE	*new;
	register int		i;

	new = myalloc(sizeof (REFERENCE));
	new->r_module = mp;
	for (rplast = &sy->s_refer;
			(rp = *rplast) != NULL;
			rplast = &rp->r_next) {
		if ((i = strcmp(mp->m_name, (rp->r_module)->m_name)) == 0) {
			fprintf(stderr, "\"%s\" references \"%s\" twice\n",
				sy->s_name, mp->m_name);
		}
		else if (i < 0) {
			break;
		}
	}
	new->r_next = rp;
	*rplast = new;
}

SYMBOL *
sylookup(text)
char		*text;
/*
 * Return pointer to this symbol, insert if new
 */
{
	register SYMBOL		*sy;
 	SYMBOL			**sylast;
	register SYMBOL		*new;
	register int		i;

	for (sylast = &sfirst; (sy = *sylast) != NULL; sylast = &sy->s_next) {
		if ((i = strcmp(text, sy->s_name)) == 0) {
			return(sy);
		}
		else if (i < 0) {
			break;
		}
	}
	new = myalloc(sizeof (SYMBOL));
	new->s_name = strsave(text);
	new->s_next = sy;
	*sylast = new;
	return (new);
}

mnames()
/*
 * Build mname[] vector
 */
{
	register int		i;
	register MODULE		*mp;

	i = 0;
	for (mp = mfirst; mp != NULL; mp = mp->m_next, i++) {
		mname[i] = mp->m_name;
		mp->m_index = i;
	}
}

/*
 * Support routines
 */
getfilename(filedes, buffer)
FILE	*filedes;
char	*buffer;
{
	register char *tp;
	register char c;

	fgetname(filedes, buffer);
	/*
	 * Skip over device name
	 */
	for (tp = buffer; (c = *tp) != EOS && c != ':'; tp++);
	if (c)	tp++;
	else	tp = buffer;
	/*
	 * Skip over [UIC] or
	 * or [PPN] if present
	 */
	if (*tp == '[' || *tp == '(') {
		while ((c = *tp++)
				&& c != ']'
				&& c != ')');
		if (c == 0) {
			error("Can't happen");
			tp--;
		}
	}
	strcpy(buffer, tp);
	/*
	 * Don't include version
	 */
	for (tp = buffer; (c = *tp) && c != ';'; tp++);
	*tp = 0;
	/*
	 * Don't include .ext
	 */
	for (tp = buffer; (c = *tp) && c != '.'; tp++);
	*tp = 0;
	/*
	 * Now, buffer has the file name,
	 * tp - buffer, its length.
	 */
	return(buffer);
}

char *
strsave(text)
char		*text;
/*
 * Save this text
 */
{
	register char	*p;

	if ((p = malloc(strlen(text) + 1)) == NULL)
		error("No room for string alloc.");
	strcpy(p, text);
	return (p);
}

char *
myalloc(size)
int		size;
/*
 * Allocate or die
 */
{
	register char	*p;

	if ((p = calloc(1, size)) == NULL) {
		fprintf(stderr, "Can't allocate %d bytes\n", size);
		error("Fatal");
	}
	return (p);
}

