pdp11v/usr/src/cmd/prof.c

Compare this file to the similar file:
Show the results in this format:

/*	@(#)prof.c	2.10	*/
/*
 *	Program profiling report generator.
 *
 *	Usage:
 *
 *	prof [ -[ntca] ] [ -[ox] ] [ -g ] [ -z ] [ -s ] [ -m mdata ] [ prog ]
 *
 *	Where "prog" is the program that was profiled; "a.out" by default.
 *	Options are:
 *
 *	-n	Sort by symbol name.
 *	-t	Sort by decreasing time.
 *	-c	Sort by decreasing number of calls.
 *	-a	Sort by increasing symbol address.
 *
 *	The options that determine the type of sorting are mutually exclusive.
 *	Additional options are:
 *
 *	-o	Include symbol addresses in output (in octal).
 *	-x	Include symbol addresses in output (in hexadecimal).
 *	-g	Include non-global T-type symbols in output.
 *	-z	Include all symbols in profiling range, even if zero
 *			number of calls or time.
 *	-h	Suppress table header.
 *	-s	Follow report with additional statistical information.
 *	-m mdata Use file "mdata" instead of MON_OUT for profiling data.
 */

#include <stdio.h>
#include <a.out.h>
#include <sys/types.h>			/* Needed for "stat.h". */
#include <sys/stat.h>
#include <sys/param.h>			/* for HZ */
#include <mon.h>

/* The ISMAGIC macro should be defined in some system (i.e. global)
 * header.  Until it is, it is defined here.  It can also be found
 * in various copies of sgs.h.  This macro requires a.out.h for
 * the magic numbers.
 */
#if vax
#define ISMAGIC(x)	((((unsigned short)x)==(unsigned short)VAXROMAGIC) || \
			  (((unsigned short)x)==(unsigned short)VAXWRMAGIC))
#endif
#if u3b
#define ISMAGIC(x)	((((unsigned short)x)==(unsigned short)N3BMAGIC) || \
			(((unsigned short)x)==(unsigned short)NTVMAGIC))
#endif

#define PROC				/* Mark procedure names. */
#define Print	(void)printf
#define Fprint	(void)fprintf

#if vax
	/* Max positive difference between a fnpc and sl_addr for match */
#define CCADIFF	22
	/* Type if n_type field in file symbol table entry. */
#endif

#if pdp11
	/* Max positive difference between a fnpc and sl_addr for match */
#define CCADIFF 16
	/* Type of n_type field in file symbol table. */
typedef int type_t;
#endif

#if (u3b)
	/* Max positive difference between a fnpc and sl_addr for match */
#define CCADIFF	20	/*   ?? (16 would probably do) */
	/* For u3b, the "type" is storage class + section number (no type_t) */
#endif

#define SEC(ticks) ((double)(ticks)/HZ)		/* Convert clock ticks to seconds. */

#if pdp11
	/* Title fragment used if symbol addresses included in output */
char atitle[] = " Addr. ";
	/* Format for addresses in output. */
char aformat[] = "%6o ";
#endif
#if (u3b || vax)
	/* Title fragment used if symbol addresses in output ("-o" or "-x"). */
char atitle[] = " Address ";
	/* Format for addresses in output */
char aformat[] = "%8o ";
#endif

#if !(vax || pdp11 || u3b)	  
	/* Make sure something we are set up for.  Else lay egg. */
#include "### No code for processor type ###"
#endif

   /* Used for "unsigned fixed-point fraction with binary scale at the left". */
#define BIAS	((long)0200000L)

#if pdp11
	/* Test proper symbol type. */
#define TXTSYM(t) (((t) & gmask) == gmatch)
	/* Default symbol type mask. */
# define STABTYPES	0	/* sdb not implemented */
type_t	gmask = STABTYPES | N_TYPE | N_EXT;
	/* Default symbol type when masked with above. */
type_t	gmatch = N_TEXT | N_EXT;
#endif

#if (u3b | vax)	  
	/* Test proper symbol type. */
#define TS1(s)	((s>0) && strcmp(scnhdrp[s-1].s_name, _TEXT) == 0)  
#define TS2(c)	(c == C_EXT || (c == C_STAT && gflag))
#define TS3(n)	(strcmp(n, _TEXT) != 0)		/* do not want ".text" */
#define TXTSYM(s,c,n)	(TS1(s) && TS2(c) && TS3(n))		 
int gflag = 0;			/*  replaces gmatch and gmask */
#endif

#ifdef ddt
int	debug;
#define DEBUG(exp)	exp
#else
#define DEBUG(exp)
#endif

FILE	*sym_iop;		/* For program ("a.out") file. */
FILE	*mon_iop;		/* For profile (MON_OUT) file. */
char	*sym_fn = "a.out";	/* Default program file name. */
char	*mon_fn = MON_OUT;	/* Default profile file name.
					May be changed by "-m file". */

extern char *realloc(), *strncpy(), *optarg;
extern int errno, optind;
extern long strtol();
extern void qsort(), exit(), perror();

	/* For symbol table entries read from program file. */
#if (u3b || vax)
#define nlist syment
#endif
struct nlist nl;

	/* Number of chars in a symbol. For subsequent sizes & counts. */
#define N_NAME	sizeof(nl.n_name)

/* Compare routines called from qsort(). */

int	c_ccaddr();	/* Compare fnpc fields of cnt structures. */

int	c_sladdr();	/* Compare   sl_addr fields of slist structures. */

int	c_time();	/*	"    sl_time    "    "   "	"      */

int	c_name();	/*	"    sl_name    "    "   "	"      */

int	c_ncalls();	/*	"    sl_count   "    "   "	"      */

	/* Other stuff. */

	/* Return size of open file (arg is file descriptor) */
off_t	fsize();

	/* Memory allocation. Like malloc(), but no return if error. */
char	*Malloc();

	/* Scan past path part (if any) in the ... */
char	*basename();

	/* command name, for error messages. */
char	*cmdname;

/* Structure of subroutine call counters (cnt) is defined in mon.h. */

/* Structure for header of mon.out (hdr) is defined in mon.h. */

	/* Local representation of symbols and call/time information. */
struct slist {
	char sl_name[N_NAME];	/* Symbol name. */
	char *sl_addr;		/* Address. */
	long sl_count;		/* Count of subroutine calls */
	float sl_time;		/* Count of clock ticks in this routine,
						converted to secs. */
};

#if (u3b | vax)
#define AOUTHSZ		(filhdr.f_opthdr)
FILHDR	filhdr;		/* a.out file header */
AOUTHDR	aouthdr;	/* a.out Unix (optional) header */
SCNHDR	*scnhdrp;	/* pointer to first section header (space by Malloc) */
#endif
#if (pdp11)
struct exec fhead;	/* Program file ("a.out") header. */
#endif

struct hdr head;	/* Profile file (MON_OUT) header. */

int	(*sort)() = NULL;	/* Compare routine for sorting output symbols.
						Set by "-[acnt]". */

int	flags;		/* Various flag bits. */

char	*pc_l;		/* From head.lpc. */

char	*pc_h;		/*   "  head.hpc. */

/* Bit macro and flag bit definitions. */

#define FBIT(pos)	(01 << (pos))	/* Returns value with bit pos set. */
#define F_SORT		FBIT(0)		/* Set if "-[acnt]" seen. */
#define F_VERBOSE	FBIT(1)		/* Set if "-s" seen. */
#define F_ZSYMS		FBIT(2)		/* Set if "-z" seen. */
#define F_PADDR		FBIT(3)		/* Set if "-o" or "-x" seen. */
#define F_NHEAD		FBIT(4)		/* Set if "-h" seen. */

PROC
main(argc, argv)
int argc;
char **argv;
{
	char buffer[BUFSIZ];	/* buffer for printf */

	WORD *pcounts;	/* Pointer to allocated area for
						pcounts: PC clock hit counts */

	register WORD *pcp;	/* For scanning pcounts. */

	struct cnt *ccounts;	/* Pointer to allocated area for cnt
					structures: subr PC-call counts. */

	register struct cnt *ccp;	/* For scanning ccounts. */

	struct slist *slist;	/* Pointer to allocated slist structures: symbol
						name/address/time/call counts */

	register struct slist *slp;	/* For scanning slist */

	int vn_cc, n_cc;	/* Number of cnt structures in profile data
						file (later # ones used). */

	int n_pc;	/* Number of pcounts in profile data file. */

	int n_syms;	/* Number of text symbols (of proper type)
					that fill in range of profiling. */

	int n_nonzero;	/* Number of (above symbols) actually printed
					because nonzero time or # calls. */

	int symttl;	/* Total # symbols in program file sym-table */

	int i;

	register int n;

	off_t symaddr;	/* Address of symbol table in program file. */

	long sf;	/* Scale for index into pcounts:
						i(pc) = ((pc - pc_l) * sf)/sf. */

	long s_inv;	/* Inverse: i_inv(i) =
					{pc00, pc00+1, ... pc00+s_inv-1}. */

	unsigned pc_m;	/* Range of PCs profiled: pc_m = pc_h - pc_l */

	float t, t0;
	float t_tot;	/* Total time: SEC(sum of all pcounts[i]) */

	setbuf(stdout, buffer);
	cmdname = basename(*argv);	/* command name. */

	while ((n = getopt(argc, argv, "canthsgzoxT:m:")) != EOF) {

		switch (n) {
		int (*fcn)();	/* For function to sort results. */

		case 'm':	/* Specify data file:	-m file */
			mon_fn = optarg;
			break;

#ifdef ddt
		case 'T':	/* Set trace flags: -T(octnum) */
			debug = (int)strtol(optarg, 0, 8);
			break;
#endif

		case 'n':	/* Sort by symbol name. */
			fcn = c_name;
			goto check;

		case 't':	/* Sort by decreasing time. */
			fcn = c_time;
			goto check;

		case 'c':	/* Sort by decreasing # calls. */
			fcn = c_ncalls;
			goto check;

		case 'a':	/* Sort by increasing symbol address
						(don't have to -- it will be) */
			fcn = NULL;
		check:		/* Here to check sort option conflicts. */
			if (sort != NULL && sort != fcn) {
				Fprint(stderr,
			   "%s: Warning: %c overrides previous specification\n",
				    cmdname, n);
			}
			sort = fcn;	/* Store sort routine */
			flags |= F_SORT; /* Note have done so */
			break;

		case 'o':	/* Include symbol addresses in output. */
		case 'x':	/* Include symbol addresses in output. */
			aformat[2] = n;	/* 'o' or 'x' in format */
			flags |= F_PADDR;	/* Set flag. */
			break;

		case 'g':	/* Include local T symbols as well as global*/
#if (pdp11)
			gmatch = N_TEXT; /* Value to match ...*/
			gmask &= ~N_EXT; /* using this mask. */
#endif
#if (u3b || vax)
			gflag++;	  
#endif
			break;

		case 'z':	/* Print all symbols in profiling range,
				 		 even if no time or # calls. */
			flags |= F_ZSYMS;	/* Set flag. */
			break;

		case 'h':	/* Suppress table header. */
			flags |= F_NHEAD;
			break;

		case 's':	/* Follow normal output with extra summary. */
			flags |= F_VERBOSE;	/* Set flag (...) */
			break;

		case '?':	/* But no good. */
			Fprint(stderr,
			    "%s: Unrecognized option: %c\n", cmdname, n);
			exit(1);

		}	/* End switch (n) */
	}	/* End while (getopt) */

	if (optind < argc)
		sym_fn = argv[optind];	/* name other than `a.out' */

	if (sort == NULL && !(flags & F_SORT))
				/* If have not specified sort mode ... */
		sort = c_time;		/* then sort by decreasing time. */

		/* Open program file (has symbols). */
	if ((sym_iop = fopen(sym_fn, "r")) == NULL)
		Perror(sym_fn);

		/* Open monitor data file (has counts). */
	if ((mon_iop = fopen(mon_fn, "r")) == NULL)
		Perror(mon_fn);

		/* Read header of executable file. */
#if (u3b || vax)	/*  read filhdr and aouthdr instead of fhead */
	(void)fread((char *)&filhdr, FILHSZ, 1, sym_iop);
	(void)fread((char *)&aouthdr, AOUTHSZ, 1, sym_iop);
	/*  read section headers to get true test for is a symbol .text */
	scnhdrp = (SCNHDR *) Malloc(filhdr.f_nscns, SCNHSZ);
	(void)fread((char *)scnhdrp, SCNHSZ, filhdr.f_nscns, sym_iop);
	if (!(ISMAGIC(filhdr.f_magic) && AOUTHSZ == sizeof aouthdr)) {
#endif
#if (pdp11)
	(void)fread((char *)&fhead, sizeof(struct exec), 1, sym_iop);
	if (BADMAG(fhead)) {		/* Verify proper "magic" number. */
#endif

		Fprint(stderr, "%s: %s: improper format\n", cmdname, sym_fn);
		exit(1);
	}

	/* Compute the file address of symbol table. Machine-dependent. */
#if pdp11
		/* Size of text & data. */
	symaddr = (long)fhead.a_text + (long)fhead.a_data;
	if (!fhead.a_flag)		/* If relocation bits there, */
		symaddr += symaddr;	/* then double the offset. */
	symaddr += sizeof(struct exec);	/* Now add in fixed header size. */
#endif
#if (u3b || vax)
	symaddr = filhdr.f_symptr;	  
#endif

		/* Number of symbols in file symbol table. */
#if (u3b || vax)
	symttl = filhdr.f_nsyms;	  
#endif
#if (pdp11)
	symttl = fhead.a_syms/sizeof(struct nlist);
#endif
	if (symttl == 0) {		/* This is possible. */
		Fprint(stderr, "%s: %s: no symbols\n", cmdname, sym_fn);
		exit(0);		/* Note zero exit code. */
	}

	/* Get size of file containing profiling data. Read header part. */
	n = fsize(fileno(mon_iop));
	if (fread((char *)&head, sizeof(struct hdr), 1, mon_iop) != 1)
		eofon(mon_iop, mon_fn);		/* Probably junk file. */

	/* Get # cnt structures (they follow header),
						and allocate space for them. */

	n_cc = head.nfns;
	ccounts = (struct cnt *)Malloc(n_cc, sizeof(struct cnt));

		/* Read the call addr-count pairs. */
	if (fread((char *)ccounts, sizeof(struct cnt), n_cc, mon_iop) != n_cc)
		eofon(mon_iop, mon_fn);

	/* Compute # PC counters (pcounts), which occupy whatever is left
				of the file after the header and call counts. */

	n_pc = (n - sizeof(head) - n_cc * sizeof(struct cnt))/sizeof(WORD);
	ccp = &ccounts[n_cc];	/* Point to last (+1) of call counters ... */
	do {		/* and scan backward until find highest one used. */
		if ((--ccp)->mcnt)
			break;		/* Stop when find nonzero count. */
	} while (--n_cc > 0);		/* Or all are zero. */

	/* If less than all cnt entries are used, return unused space. */
	if (n_cc < head.nfns) {
		if ((ccp = (struct cnt *)realloc((char *)ccounts,
		    (unsigned)n_cc * sizeof(struct cnt))) == NULL)
			snh();	/* Should not fail when reducing size. */
	}

	/* If more than 250 cnt entries used set verbose for warning */
	if (n_cc > (MPROGS0 * 5)/6)
		flags |= F_VERBOSE;

		/* Space for PC counts. */
	pcounts = (WORD *)Malloc(n_pc, sizeof(WORD));
		/* Read the PC counts from rest of MON_OUT file. */
	if (fread((char *)pcounts, sizeof(WORD), n_pc, mon_iop) != n_pc)
		eofon(mon_iop, mon_fn);

	/*
	Having gotten preliminaries out of the way, get down to business.
	The range pc_m of addresses over which profiling was done is
	computed from the low (pc_l) and high (pc_h) addresses, gotten
	from the MON_OUT header.  From this and the number of clock
	tick counters, n_pc, is computed the so-called "scale", sf, used
	in the mapping of addresses to indices, as follows:

			(pc - pc_l) * sf
		i(pc) = ----------------
			 0200000

	Also, the N-to-one value, s_inv, such that

		i(pc_l + K * s_inv + d) = K, for 0 <= d < s_inv

	Following this, the symbol table is scanned, and those symbols
	that qualify are counted.  These  are T-type symbols, excluding
	local (nonglobal) unless the "-g" option was given. Having thus
	determined the space requirements, space for symbols/times etc.
	is allocated, and the symbol table re-read, this time keeping
	qualified symbols.
	*/


	pc_l = head.lpc;	/* Low PC of range that was profiled. */
	pc_h = head.hpc;	/* First address past range of profiling. */
	pc_m = pc_h - pc_l;	/* Range of profiled addresses. */

DEBUG(if (debug) Fprint(stderr,
"low pc = %#o, high pc = %#o, range = %#o = %u\n\
call counts: %u, %u used; pc counters: %u\n",
pc_l, pc_h, pc_m, pc_m, head.nfns, n_cc, n_pc);)

	sf = (BIAS * n_pc)/pc_m;  /* The "scale" used to map PCs to indices. */
	s_inv = pc_m/n_pc;	/* Range of PCs mapped into one index. */

DEBUG(if (debug) Fprint(stderr, "sf = %#lo, s_inv = %ld\n", (long)sf, s_inv);)

		/* Prepare to read symbols from "a.out" (or whatever). */
	(void)fseek(sym_iop, symaddr, 0);
	n_syms = 0;			/* Init count of qualified symbols. */
	n = symttl;			/* Total symbols. */
	while (--n >= 0)			/* Scan symbol table. */
		if (readnl())	/* Read and examine symbol, count qualifiers */
			n_syms++;

DEBUG(if (debug) Fprint(stderr, "%u symbols, %u qualify\n", symttl, n_syms);)

		/* Allocate space for qualified symbols. */

	slist = slp = (struct slist *)Malloc(n_syms, sizeof(struct slist));
DEBUG(debug &= ~020;)

		/* Re-position to beginning of symbol table. */
	(void)fseek(sym_iop, symaddr, 0);
	for (n = n_syms; n > 0; ) {	/* Loop on number of qualified symbols. */
		if (readnl()) {	/* Get one. Check again. */
				/* Is qualified. Move name ... */
			(void)strncpy(slp->sl_name, nl.n_name, N_NAME);

				/* and address into slist structure. */
			slp->sl_addr = (char *)nl.n_value;

				/* set other slist fields to zero. */
			slp->sl_time = 0.0;
			slp->sl_count = 0;
DEBUG(if (debug & 02) Fprint(stderr,
"%-8.8s: %#8o\n", slp->sl_name, slp->sl_addr);)

			slp++;
			--n;
		}
	}

	/*
	Now attempt to match call counts with symbols.  To do this, it
	helps to first sort both the symbols and the call address/count
	pairs by ascending address, since they are generally not, to
	begin with.  The addresses associated with the counts are not,
	of course, the subroutine addresses associated with the symbols,
	but some address slightly past these. Therefore a given count
	address (in the fnpc field) is matched with the closest symbol
	address (sl_addr) that is:
		(1) less than the fnpc value but,
		(2) not more than CCADIFF bytes less than it.
	The value of CCADIFF is roughly the size of the code between
	the subroutine entry and that following the call to the mcount
	routine.  In other words, unreasonable matchups are avoided.
	Situations such as this could arise when static procedures are
	counted but the "-g" option was not given to this program,
	causing the symbol to fail to qualify.  Without this limitation,
	unmatched counts could be erroneously charged.
	*/


	ccp = ccounts;			/* Point to first call counter. */
	slp = slist;			/*   "		"   "   symbol. */
		/* Sort call counters and ... */
	qsort((char *)ccp, (unsigned)n_cc, sizeof(struct cnt), c_ccaddr);
		/* symbols by increasing address. */
	qsort((char *)slp, (unsigned)n_syms, sizeof(struct slist), c_sladdr);
	vn_cc = n_cc;			/* save this for verbose option */
		/* Loop to match up call counts & symbols. */
	for (n = n_syms; n > 0 && vn_cc > 0; ) {
		if (slp->sl_addr < ccp->fnpc &&
		    ccp->fnpc <= slp->sl_addr+CCADIFF) {

DEBUG(if (debug & 04) Fprint(stderr,
"Routine %-8.8s @ %#8x+%-2d matches count address %#8x\n",
slp->sl_name, slp->sl_addr, ccp->fnpc-slp->sl_addr, ccp->fnpc);)

			slp->sl_count = ccp->mcnt;	/* Copy count. */
			++ccp;
			++slp;
			--vn_cc;
			--n;
		} else if (ccp->fnpc < slp->sl_addr) {
			++ccp;
			--vn_cc;
		} else {
			++slp;
			--n;
		}
	}

	/*
	The distribution of times to addresses is done on a proportional
	basis as follows: The t counts in pcounts[i] correspond to clock
	ticks for values of pc in the range pc, pc+1, ..., pc+s_inv-1
	(odd addresses excluded for PDP11s).  Without more detailed
	information, it must be assumed that there is no greater probability
	of the clock ticking for any particular pc in this range than for
	any other.  Thus the t counts are considered to be equally distributed
	over the addresses in the range, and that the time for any given
	address in the range is pcounts[i]/s_inv.

	The values of the symbols that qualify, bounded below and above
	by pc_l and pc_h, respectively, partition the profiling range into
	regions to which are assigned the total times associated with the
	addresses they contain in the following way:

	The sum of all pcounts[i] for which the corresponding addresses are
	wholly within the partition are charged to the partition (the
	subroutine whose address is the lower bound of the partition).

	If the range of addresses corresponding to a given t = pcounts[i]
	lies astraddle the boundary of a partition, e.g., for some k such
	that 0 < k < s_inv-1, the addresses pc, pc+1, ..., pc+k-1 are in
	the lower partition, and the addresses pc+k, pc+k+1, ..., pc+s_inv-1
	are in the next partition, then k*pcounts[i]/s_inv time is charged
	to the lower partition, and (s_inv-k) * pcounts[i]/s_inv time to the
	upper.  It is conceivable, in cases of large granularity or small
	subroutines, for a range corresponding to a given pcounts[i] to
	overlap three regions, completely containing the (small) middle one.
	The algorithm is adjusted appropriately in this case.
	*/


	pcp = pcounts;				/* Reset to base. */
	slp = slist;				/* Ditto. */
	t0 = 0.0;				/* Time accumulator. */
	for (n = 0; n < n_syms; n++) {		/* Loop on symbols. */
			/* Start addr of region, low addr of overlap. */
		char *pc0, *pc00;
			/* Start addr of next region, low addr of overlap. */
		char *pc1, *pc10;
		 /* First index into pcounts for this region and next region. */
		register int i0, i1;
		long ticks;

			/* Address of symbol (subroutine). */
		pc0 = slp[n].sl_addr;

			/* Address of next symbol, if any or top of profile
								range, if not */
		pc1 = (n < n_syms - 1) ? slp[n+1].sl_addr : pc_h;

			/* Lower bound of indices into pcounts for this range */

		i0 = ((pc0 - pc_l) * sf)/BIAS;

			/* Upper bound (least or least + 1) of indices. */
		i1 = ((pc1 - pc_l) * sf)/BIAS;

		if (i1 >= n_pc)				/* If past top, */
			i1 = n_pc - 1;				/* adjust. */

			/* Lowest addr for which count maps to pcounts[i0]; */
		pc00 = pc_l + (long)((BIAS * i0)/sf);

			/* Lowest addr for which count maps to pcounts[i1]. */
		pc10 = pc_l + (long)((BIAS * i1)/sf);

DEBUG(if (debug & 010) Fprint(stderr,
"%-8.8s\ti0 = %4d, pc00 = %#6o, pc0 = %#6o\n\
\t\ti1 = %4d, pc10 = %#6o, pc1 = %#6o\n\t\t",
slp[n].sl_name, i0, pc00, pc0, i1, pc10, pc1);)

		t = 0;			/* Init time for this symbol. */
		if (i0 == i1) {
			/* Counter overlaps two areas? (unlikely unless large
								granularity). */
			ticks = pcp[i0];	/* # Times (clock ticks). */

			    /* Time less that which overlaps adjacent areas */
			t += (pc1 - pc0) * SEC(ticks)/s_inv;

DEBUG(if (debug & 010) Fprint(stderr, "%ld/%ld", (pc1 - pc0) * ticks, s_inv);)
		} else {
				/* Overlap with previous region? */
			if (pc00 < pc0) {
				ticks = pcp[i0];

				/* Get time of overlapping area and subtract
						proportion for lower region. */
				t += SEC(pcp[i0]) - (pc0 - pc00) * SEC(ticks)/s_inv;

				/* Do not count this time when summing times
						wholly within the region. */
				i0++;
DEBUG(if (debug & 010) Fprint(stderr, "%ld/%ld + ", (pc0 - pc00) * ticks, s_inv);)
			}

			/* Init sum of counts for PCs not shared w/other
								routines. */
			ticks = 0;

			/* Stop at first count that overlaps following
								routine. */
			for (i = i0; i < i1; i++)
				ticks += pcp[i];

			t += SEC(ticks);  /* Convert to secs & add to total. */
DEBUG(if (debug & 010) Fprint(stderr, "%ld", ticks);)
			/* Some overlap with low addresses of next routine? */
			if (pc10 < pc1) {
					/* Yes. Get total count ... */
				ticks = pcp[i1];

				/* and accumulate proportion for addresses in
							range of this routine */
				t += (pc1 - pc10) * SEC(ticks)/s_inv;
DEBUG(if (debug & 010) Fprint(stderr, " + %ld/%ld", (pc1 - pc10) * ticks, s_inv);)
			}
		}		/* End if (i0 == i1) ... else ... */

		slp[n].sl_time = t;	/* Store time for this routine. */
		t0 += t;		/* Accumulate total time. */
DEBUG(if (debug & 010) Fprint(stderr, " ticks = %.2f msec\n", t);)
	}	/* End for (n = 0; n < n_syms; n++) */

	/* Final pass to total up time. */

	for (n = n_pc, t_tot = 0.0; --n >= 0; t_tot += SEC(*pcp++));

	/*
	Most of the heavy work is done now.  Only minor stuff remains.
	The symbols are currently in address order and must be re-sorted
	if desired in a different order.  Report generating options
	include "-o" or "-x": Include symbol address, which causes another column
	in the output; and "-z": Include symbols in report even if zero
	time and call count.  Symbols not in profiling range are excluded
	in any case.  Following the main body of the report, the "-s"
	option causes certain additional information to be printed.
	*/

DEBUG(if (debug) Fprint(stderr,
"Time unaccounted for: %.7G\n", t_tot - t0);)

	if (sort)	/* If comparison routine given then use it. */
		qsort((char *)slp, (unsigned)n_syms, sizeof(struct slist), sort);

	if (!(flags & F_NHEAD)) {
		if (flags & F_PADDR)
			Print(atitle);	/* Title for addresses. */
		(void)puts("Name     %Time Seconds Cumsecs  #Calls   msec/call");
	}
	t = 0.0;			/* Init cumulative time. */
	if (t_tot != t)			/* Convert to percent. */
		t_tot = 100.0/t_tot;	/* Prevent divide-by-zero fault */
	n_nonzero = 0;	/* Number of symbols with nonzero time or # calls. */
	for (n = n_syms, slp = slist; --n >= 0; slp++) {
		long count = slp->sl_count;	/* # Calls. */

		t0 = slp->sl_time;	/* Time (sec). */
		if (t0 == 0.0 && count == 0 && !(flags & F_ZSYMS))
			continue; /* Don't do entries with no action. */
		n_nonzero++;		/* Count # entries printed. */
		if (flags & F_PADDR)	/* Printing address of symbol? */
			Print(aformat, slp->sl_addr);
		t += t0;	/*  move here; compiler bug  !! */
		Print("%-8.8s%6.1f%8.2f%8.2f",	/* Accumulate time. */
		    slp->sl_name, t0 * t_tot, t0, t);
		if (count) {		/* Any calls recorded? */
		/* Get reasonable number of fractional digits to print. */
			int fdigits = fprecision(count);
			Print("%8ld%#*.*f", count, 8 + fdigits, fdigits,
			    1000.0*t0/count);
		}
		(void)putchar('\n');		/* Wrap up line. */
	}

	if (flags & F_VERBOSE) {		/* Extra info? */
		Fprint(stderr, "%5d/%d call counts used\n", n_cc, head.nfns);
		Fprint(stderr, "%5d/%d symbols qualified", n_syms, symttl);
		if (n_nonzero < n_syms)
			Fprint(stderr,
			    ", %d had zero time and zero call-counts\n",
			    n_syms - n_nonzero);
		else
			(void)putc('\n', stderr);
		Fprint(stderr, "%#x scale factor\n", (long)sf);
	}
	exit(0);
}

/* Return size of file associated with file descriptor fd. */

PROC off_t
fsize(fd)
{
	struct stat sbuf;

	if (fstat(fd, &sbuf) < 0)		/* Status of open file. */
		Perror("stat");
	return (sbuf.st_size);			/* This is a long. */
}

/* Read symbol entry. Return TRUE if satisfies conditions. */
/*  readnl() entirely new for COFF */

PROC
readnl()
{

#if (u3b || vax)	/*  for COFF, special care for auxiliary entries */
	static char numaux = 0;	  
	if (numaux <= 0) {	/* a new symbol table entry */
		if (fread((char *)&nl, SYMESZ, 1, sym_iop) != 1)
			eofon(sym_iop, sym_fn);
		numaux = nl.n_numaux;
	}
	else {
		if (fread((char *)&nl, AUXESZ, 1, sym_iop) != 1)
			eofon(sym_iop, sym_fn);
		numaux--;
		return(0);	/*  skip over auxiliary entries */
	}
DEBUG(if (debug & 020)
Fprint(stderr, "`%-8.8s'\tclass=%#4o, value=%#8.6o\n",
nl.n_name, (unsigned char)nl.n_sclass, nl.n_value);)
	/*
	TXTSYM accepts global (and local, if "-g" given) T-type symbols.
	Only those in the profiling range are useful.
	*/
	return (TXTSYM(nl.n_scnum, nl.n_sclass, nl.n_name) &&
		pc_l <= (char *)nl.n_value && (char *)nl.n_value < pc_h &&
		(nl.n_sclass == C_EXT || strcmp(nl.n_name, "_eprol")));
#endif

#if pdp11
	if (fread((char *)&nl, sizeof(struct nlist), 1, sym_iop) != 1)
		eofon(sym_iop, sym_fn);
DEBUG(if (debug & 020)
Fprint(stderr, "`%-8.8s'\ttype=%#4o, value=%#8.6o\n",
nl.n_name, (unsigned char)nl.n_type, nl.n_value);)
	/*
	TXTSYM accepts global (and local, if "-g" given) T-type symbols.
	Only those in the profiling range are useful.
	*/
	return (TXTSYM(nl.n_type) &&
		pc_l <= (char *)nl.n_value && (char *)nl.n_value < pc_h &&
		(nl.n_type == N_EXT || strcmp(nl.n_name, "eprol")));
#endif

}

/* Error-checking memory allocator. Guarentees good return (else none at all).*/

PROC char *
Malloc(item_count, item_size)
{
	char *malloc();
	register char *p;

	if ((p = malloc((unsigned)item_count * (unsigned)item_size)) == NULL)
		Perror("Out of space");
	return (p);
}

/*
	Given the quotiant Q = N/D, where entier(N) == N and D > 0, an
	approximation of the "best" number of fractional digits to use
	in printing Q is f = entier(log10(D)), which is crudely produced
	by the following routine.
*/

PROC int
fprecision(count)
long count;
{
	return (count < 10 ? 0 : count < 100 ? 1 : count < 1000 ? 2 :
	    count < 10000 ? 3 : 4);
}

/*
	Return pointer to base name (name less path) of string s.
	Handles case of superfluous trailing '/'s, and unlikely
	case of s == "/".
*/

PROC char *
basename(s)
register char *s;
{
	register char *p;

	p = &s[strlen(s)];			/* End (+1) of string. */
	while (p > s && *--p == '/')		/* Trim trailing '/'s. */
		*p = '\0';
	p++;					/* New end (+1) of string. */
	while (p > s && *--p != '/');		/* Break backward on '/'. */
	if (*p == '/')		/* If found '/', point to 1st following. */
		p++;
	if (*p == '\0')
		p = "/";			/* If NULL, must be "/". (?) */
	return (p);
}

/* Here if unexpected read problem. */

PROC
eofon(iop, fn)
register FILE *iop;
register char *fn;
{
	if (ferror(iop))		/* Real error? */
		Perror(fn);		/* Yes. */
	Fprint(stderr, "%s: %s: Premature EOF\n", cmdname, fn);
	exit(1);
}

/* Version of perror() that prints cmdname first. */

PROC
Perror(s)
char *s;
{				/* Print system error message & exit. */
	register int err = errno;	/* Save current errno in case */

	Fprint(stderr, "%s: ", cmdname);
	errno = err;			/* Put real error back. */
	perror(s);			/* Print message. */
	exit(1);			/* Exit w/nonzero status. */
}

/* Here for things that "Should Never Happen". */

PROC
snh()
{
	Fprint(stderr, "%s: Internal error\n", cmdname);
	(void)abort();
}

/*
	Various comparison routines for qsort. Uses:

	c_ccaddr	- Compare fnpc fields of cnt structs to put
				call counters in increasing address order.
	c_sladdr	- Sort slist structures on increasing address.
	c_time		-  "	 "	  "      " decreasing time.
	c_ncalls	-  "	 "	  "      " decreasing # calls.
	c_name		-  "	 "	  "      " increasing symbol name
*/

#define CMP2(v1,v2)	((v1) < (v2) ? -1 : (v1) == (v2) ? 0 : 1)
#define CMP1(v)		CMP2(v, 0)

PROC
c_ccaddr(p1, p2)
register struct cnt *p1, *p2;
{
	return (CMP2(p1->fnpc, p2->fnpc));
}

PROC
c_sladdr(p1, p2)
register struct slist *p1, *p2;
{
	return (CMP2(p1->sl_addr, p2->sl_addr));
}

PROC
c_time(p1, p2)
register struct slist *p1, *p2;
{
	register float dtime = p2->sl_time - p1->sl_time; /* Decreasing time. */

	return (CMP1(dtime));
}

PROC
c_ncalls(p1, p2)
register struct slist *p1, *p2;
{
	register int diff = p2->sl_count - p1->sl_count; /* Decreasing # calls. */

	return (CMP1(diff));
}

PROC
c_name(p1, p2)
register struct slist *p1, *p2;
{
	register int diff = strncmp(p1->sl_name, p2->sl_name, N_NAME);

	return (CMP1(diff));
}