/*	Copyright (c) 1990 UNIX System Laboratories, Inc.	*/
/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF     	*/
/*	UNIX System Laboratories, Inc.                     	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)prof:prof.c	2.30"

#include "hidelibc.h"		/* uses "_" to hide libc functions */
#include "hidelibelf.h"		/* uses "_" to hide libelf functions */

/*
 *	Program profiling report generator.
 *
 *	Usage:
 *
 *	prof [ -V ] [ -[ntca] ] [ -[ox] ] [ -g ] [ -l ] [ -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.
 *	-l	Do NOT inlcude local T-type symbols in output (default).
 *	-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.
 *	-V	print version information for prof (and exit, if only V spec'd)
 */

#include <stdio.h>
#include "symint.h"
#include "sys/param.h"			/* for HZ */
#include "mon.h"
#include "sgs.h"
#include "sys/stat.h"
#include "debug.h"


#define OLD_DEBUG(x)

#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 (u3b || u3b15 || u3b2 || i386)
	/* 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. */

	/* Title fragment used if symbol addresses in output ("-o" or "-x"). */
char atitle[] = " Address ";
	/* Format for addresses in output */
char aformat[] = "%8o ";

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


   /* Shorthand to gimme the Precise #of addresses per cells */ 
#define DBL_ADDRPERCELL		(((double) bias)/sf)
 
 
   /* Used for unsigned fixed-point fraction with binary scale at
      the left of 15'th bit (0 as least significant bit) . */
#define BIAS		((long)0200000L)

/*
*	TS1 insures that the symbols section is executable.
*/
#define TS1(s) (((s)>0) && (scnhdrp[(s)-1].sh_flags & SHF_EXECINSTR))
/*
*	TS2 insures that the symbol should be reported.  We want
*	to report only those symbols that are functions (STT_FUNC)
*	or "notype" (STT_NOTYPE... "printf", for example).  Also,
*	unless the gflag is set, the symbol must be global.
*/
#define TS2(i)	\
	((							\
		(ELF32_ST_TYPE(i) == STT_FUNC)			\
		|| (ELF32_ST_TYPE(i) == STT_NOTYPE)		\
	) && (							\
		(ELF32_ST_BIND(i) == STB_GLOBAL)		\
		|| (gflag && (ELF32_ST_BIND(i) == STB_LOCAL))	\
	))

#define TXTSYM(s,i)	(TS1(s) && TS2(i))

int gflag = 0;			/*  replaces gmatch and gmask */

PROF_FILE	*ldptr; 		/* 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". */

long bias;	/* adjusted bias */
long temp;	/* for bias adjust */
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. */
PROF_SYMBOL nl;

/* 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	*_prof_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;		/* 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. */
};

	/* local structure for tracking synonyms in our symbol list */
struct snymEntry
{
	char	*sym_addr;	/* address which has a synonym */
	int	howMany;	/* # of synonyms for this symbol */
	int	snymReported;	/* 'was printed in a report line already' flag,
				 *   >0	report line printed for these syns.
				 *  ==0 not printed yet.
				 */
	long	tot_sl_count;	/* total subr calls for these snyms */
	float	tot_sl_time;	/* total clock ticks (a la sl_time) */
};


#define AOUTHSZ		(filhdr.f_opthdr)
PROF_FILE	filhdr;			/* profile file descriptor */
Elf32_Shdr	*scnhdrp;	/* pointer to first section header */
					/* (space by _prof_Malloc) */

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. */

short	VwasSpecified=0;/* 1 if -V was specified */

/* 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. */


struct snymEntry *snymList;	/* Pointer to allocated list of
				 * synonym entries.
				 */
struct snymEntry *snymp;
				/* for scanning entries. */

int snymCapacity;		/* #slots in snymList */
int n_snyms;			/* #used slots in snymList */

	/* printSnymNames - print a comma-seperated list of snym names.
	 * This routine hunts down all the synonyms for the given
	 * symbol, and prints them as a comma-seperated list.
	 * NB we assume that all the synonyms _Follow_ this one,
	 * since they are only printed when the First one
	 * is seen.
	 */
PROC
void
printSnymNames( slp, snymp )
struct	slist       *slp;
struct	snymEntry   *snymp;
{
	 /* how many snyms for this addr, total, and their shared address */
	int i = snymp->howMany;
	char *sharedaddr = snymp->sym_addr;

	 /* put out first name - it counts as one, so decr count */
	(void) fputs( slp->sl_name, stdout );
	i--;

	 /* for the others: find each, print each. */
	while( --i >= 0 ) {
		while( (++slp)->sl_addr != sharedaddr );
		Print(", %s", slp->sl_name );
	}
	 /* finally.. the trailing newline */
	putchar('\n');	
}


	/* getSnymEntry - see if addr was noted as a aliased address
	 * (i.e. a synonym symbol) and return the address of the
	 * snym entry if it was.
	 */
PROC
struct snymEntry
*getSnymEntry( sl_addr )
char *sl_addr;
{
	struct snymEntry *p;
	int i;

	for( p=snymList, i=n_snyms; --i >= 0; p++ )
		if( sl_addr == p->sym_addr )
			return( p );

	return( (struct snymEntry *) 0 );
}


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;

	int fdigits = 0; /* # of digits of precision for print msecs/call */

	register int n, symct;

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

	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]) */
	char *getname();	/* get name from symbol */

	DEBUG_LOC("main: top");
	setbuf(stdout, buffer);
	cmdname = basename(*argv);	/* command name. */

	while ((n = getopt(argc, argv, "canthsglzoxT:m:V")) != 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_value = (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 ((flags & F_SORT) && 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*/
			gflag = 1;
			break;

		case 'l':	/* Do NOT include local T symbols */
			gflag = 0;
			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 'V':
			(void) fprintf(stderr, "prof: %s %s\n", CPPT_PKG, CPPT_REL);
			VwasSpecified=1;
			break;

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

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

	DEBUG_LOC("main: following getopt");

	/* if -V the only argument, just exit. */
	if (VwasSpecified && argc==2 && !flags )
		exit(1);

	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 monitor data file (has counts). */
	if ((mon_iop = fopen(mon_fn, "r")) == NULL)
		Perror(mon_fn);

	DEBUG_LOC("main: before _symintOpen");
	if ((ldptr = _symintOpen(sym_fn)) == NULL) {
		Perror("_symintOpen failed");
	}
	DEBUG_LOC("main: after _symintOpen");
	filhdr = *ldptr;

	scnhdrp = ldptr->pf_shdarr_p;
	
	{
	Elf_Kind k = elf_kind(filhdr.pf_elf_p);

	DEBUG(printf("elf_kind = %d\n",k));
	DEBUG(printf("elf_type = %d\n",filhdr.pf_elfhd_p->e_type));
	if (
		((k != ELF_K_ELF) && (k != ELF_K_COFF))
		|| (filhdr.pf_elfhd_p->e_type != ET_EXEC)
	) {
		Fprint(stderr, "%s: %s: improper format\n", cmdname, sym_fn);
		exit(1);
	}
	if (k == ELF_K_COFF) {
		fprintf(stderr,
		"%s: %s: Warning - internal conversion of COFF file to ELF\n",
		cmdname, sym_fn
		);
	}
	}

	/* Compute the file address of symbol table. Machine-dependent. */

	DEBUG(printf("number of symbols (pf_nsyms) = %d\n",filhdr.pf_nsyms));

		/* Number of symbols in file symbol table. */
	symttl = filhdr.pf_nsyms;
	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 *)_prof_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 ((ccounts = (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 *)_prof_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.

	NB s_inv, as actually computed, is not sufficiently accurate
	(since it is truncated) for many calculations.  Since it is 
	logically equivalent to 1/(sf/bias), and the latter is much 
	more accurate, therefore the latter will often appear in 
	the code when 's_inv' is mentioned.  dween
	*/


	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. */

OLD_DEBUG(if (debug_value) 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 * (double)n_pc)/pc_m;
	/*
	   Now adjust bias and sf so that there is no overflow
	   when calculating indices.
	*/
	bias = BIAS;
	temp = pc_m;
	while ((temp >>= 1) > 0x7fff) {
		sf >>= 1;
		bias >>= 1;
	}
	s_inv = pc_m/n_pc;	/* Range of PCs mapped into one index. */

OLD_DEBUG(
	if (debug_value) {
		Fprint(
			stderr,
			"sf = %#lo,
			s_inv = %ld bias = %lo\n",
			(long)sf, s_inv, bias
		);
	}
);

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

OLD_DEBUG(
	if (debug_value) {
		Fprint(stderr, "%u symbols, %u qualify\n", symttl, n_syms);
	}
);

		/* Allocate space for qualified symbols. */

	slist = slp =
		(struct slist *)_prof_Malloc(n_syms, sizeof(struct slist));

		/* Allocate space for synonym symbols
		 * (i.e. symbols that refer to the same address).
		 * NB there can be no more than n_syms/2 addresses
		 * with symbols, That Have Aliases, that refer to them!
		 */

	snymCapacity = n_syms/2 ;
	snymList = snymp =
	   (struct snymEntry *)_prof_Malloc(snymCapacity, sizeof(struct snymEntry));
	n_snyms=0;

/* OLD_DEBUG(debug_value &= ~020); */

	/* Loop on number of qualified symbols. */
	for (n = n_syms, symct = 0; n > 0; symct++) {	
		if (readnl(symct)) {	/* Get one. Check again. */
				/* Is qualified. Move name ... */
			slp->sl_name = getname(ldptr, nl);

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

				/* set other slist fields to zero. */
			slp->sl_time = 0.0;
			slp->sl_count = 0;
OLD_DEBUG(
	if (debug_value & 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) {
					/* got a candidate: find Closest. */
			struct slist *closest_symp;
			do {
				closest_symp = slp;
				slp++;
				--n;
			} while( n>0 && slp->sl_addr < ccp->fnpc );

OLD_DEBUG(
if (debug_value & 04) {
	Fprint(
		stderr,
		"Routine %-8.8s @ %#8x+%-2d matches count address %#8x\n",
		closest_symp->sl_name,
		closest_symp->sl_addr,
		ccp->fnpc-slp->sl_addr,
		ccp->fnpc
	);
}
);
			closest_symp->sl_count = ccp->mcnt;  /* Copy count. */
			++ccp;
			--vn_cc;
		} 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 = (((unsigned) pc0 - (unsigned) pc_l) * sf)/bias;

			/* Upper bound (least or least + 1) of indices. */
		i1 = (((unsigned) pc1 - (unsigned) 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 + (unsigned long)((bias * i0)/sf);

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

OLD_DEBUG(if (debug_value & 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). */
OLD_DEBUG(if (debug_value & 010) fprintf(stderr,"ticks = %d\n",ticks));

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

OLD_DEBUG(if (debug_value & 010)
	Fprint(stderr, "%ld/(%.1f)", (pc1 - pc0) * ticks, DBL_ADDRPERCELL)
);
		} else {
				/* Overlap with previous region? */
			if (pc00 < pc0) {
				ticks = pcp[i0];
OLD_DEBUG(if (debug_value & 010) fprintf(stderr,"pc00 < pc0 ticks = %d\n",ticks));

				/* Get time of overlapping area and subtract
						proportion for lower region. */
 				t += SEC(
					ticks*(1-((double) (pc0-pc00) *sf)/bias)
				);

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

			/* 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. */
OLD_DEBUG(if (debug_value & 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 += SEC(((double) ticks * (pc1 - pc10)*sf)/bias);
OLD_DEBUG(if (debug_value & 010) fprintf(stderr,"ticks = %d\n",ticks));
OLD_DEBUG(if (debug_value & 010)
	Fprint(stderr, " + %ld/(%.1f)", (pc1 - pc10) * ticks, DBL_ADDRPERCELL)
);
			}
		}		/* End if (i0 == i1) ... else ... */

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

	/* Final pass to total up time. */
 	/* Sum ticks, then convert to seconds. */

 	for (n = n_pc, temp = 0; --n >= 0; temp += *(pcp++) );

 	t_tot = SEC(temp);
 
	/* Now, whilst we still have the symbols sorted 
	 * in address order.. 
	 * Loop to record duplicates, so we can display
	 * synonym symbols correctly.
	 * Synonym symbols, or symbols with the same address,
	 * are to be displayed by prof on the same line, with
	 * one statistics line, as below:
	 *			... 255  ldaopen, ldaopen
	 * The way this will be implemented, is as follows:
	 *
	 * Pass 1 - while the symbols are in address order, we
	 *  do a pre-pass through them, to determine for which
	 *  addresses there are more than one symbol (i.e. synonyms).
	 *  During this prepass we collect summary statistics in
	 *  the synonym entry, for all the synonyms.
	 *
	 * 'Pass' 2 - while printing a report,  for each report line,
	 *  if the current symbol is a synonym symbol (i.e. in the
	 *  snymList) then we scan forward and pick up all the names
	 *  which map to this address, and print them too.
	 *  If the address' synonyms have already been printed, then
	 *  we just skip this symbol and go on to process the next.
	 * 
	 */

	{
	/* pass 1 */
	char *thisaddr;
	char *lastaddr = slist->sl_addr;/* use 1st sym as 'last/prior symbol' */
	int lastWasSnym=0;		/* 1st can't be snym yet-no aliases seen!*/
	int thisIsSnym;

OLD_DEBUG(	int totsnyms=0; int totseries=0; struct slist *lastslp= slist; );

		/* NB loop starts with 2nd symbol, loops over n_syms-1 symbols! */
	for( n=n_syms-1, slp=slist+1; --n >=0; slp++ ) {
		thisaddr = slp->sl_addr;
		thisIsSnym = (thisaddr == lastaddr);

		if ( thisIsSnym ) {
			/* gotta synonym */
			if( !lastWasSnym ) {
OLD_DEBUG(
if (debug_value)  {
	Fprint(
		stderr,
		"Synonym series:\n1st->\t%s at address %x, ct=%ld, time=%f\n", 
		lastslp->sl_name,lastaddr,lastslp->sl_count,lastslp->sl_time
	); 
	totseries++;
	totsnyms++;
}
);
				 /* this is the Second! of a series */
				snymp = ( n_snyms++ == 0 ? snymList : snymp+1 );
				snymp->howMany=1;   /*gotta count 1st one!! */
				snymp->sym_addr = slp->sl_addr;
				 /*zero summary statistics */
				snymp->tot_sl_count=0;
				snymp->tot_sl_time=0.0;
				 /*Offen the Reported flag */
				snymp->snymReported=0;	
			}
OLD_DEBUG(
if (debug_value)  {
	Fprint(
		stderr,
		"\t%s at address %x, ct=%ld, time=%f\n",
		slp->sl_name,
		thisaddr,
		slp->sl_count,
		slp->sl_time
	);
	totsnyms++;
}
);
			 /* ok - bump count for snym, and note its Finding */
			snymp->howMany++;
			 /* and update the summary statistics */
			snymp->tot_sl_count += slp->sl_count;
			 snymp->tot_sl_time += slp->sl_time;
		}

		lastaddr = thisaddr;
		lastWasSnym = thisIsSnym ;
OLD_DEBUG( if (debug_value) lastslp = slp; );

	}
OLD_DEBUG(
if (debug_value)  {
	Fprint(stderr,"Total #series %d, #synonyms %d\n", totseries, totsnyms);
}
);
	}

 	/*
 	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.
	*/

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

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

	if (!(flags & F_NHEAD)) {
		if (flags & F_PADDR)
			Print(atitle);	/* Title for addresses. */
		(void)puts(" %Time Seconds Cumsecs  #Calls   msec/call  Name");
	}
	t = 0.0;			/* Init cumulative time. */
	if (t_tot != 0.0)		/* 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;	 /* # Calls. */
		/* t0, time in seconds. */

		 /* if a snym symbol, use summarized stats, else use indiv. */
		if( (snymp=getSnymEntry( slp->sl_addr )) != 0 ) {
			count = snymp->tot_sl_count;
			   t0 = snymp->tot_sl_time;

		} else {
			count = slp->sl_count;
			   t0 = slp->sl_time;
		}

		 /* if a snym and already reported, skip this entry */
		if ( snymp && snymp->snymReported )
			continue;
		 /* Don't do entries with no action. */
		if (t0 == 0.0 && count == 0 && !(flags & F_ZSYMS))
			continue;

		 /* count number of entries (i.e. symbols) printed */
		if ( snymp )
			n_nonzero += snymp->howMany; /* add for each snym */
		else
			n_nonzero++;

		if (flags & F_PADDR)	/* Printing address of symbol? */
			Print(aformat, slp->sl_addr);
		t += t0;	/*  move here; compiler bug  !! */
		Print("%6.1f%8.2f%8.2f", t0 * t_tot, t0, t);
		fdigits = 0;
		if (count) {		/* Any calls recorded? */
		/* Get reasonable number of fractional digits to print. */
			fdigits = fprecision(count);
			Print("%8ld%#*.*f", count, fdigits+8, fdigits,
			    1000.0*t0/count);
			Print("%*s", 6-fdigits, " ");
		} else {
			Print("%22s", " ");
		}
		 /* now print the name (or comma-seperate list of names,
		  * for synonym symbols). 
		  */
		if( snymp ) {
			printSnymNames( slp, snymp );	/* print it, then */
			snymp->snymReported = 1;	/* mark it Done */
		}
		else
			(void)puts(slp->sl_name);	/* print the one name */
	}
	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);
	}

	_symintClose(ldptr);
	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(symindex)
int symindex;
{
	nl = ldptr->pf_symarr_p[symindex];

OLD_DEBUG(
	if (debug_value & 020) {
		Fprint(
			stderr,
			"`%-8.8s'\tst_info=%#4o, value=%#8.6o\n",
			ldptr->pf_symstr_p[nl.ps_sym.st_name],
			(unsigned char) nl.ps_sym.st_info,
			nl.ps_sym.st_value
		);
	}
);
	/*
	TXTSYM accepts global (and local, if "-g" given) T-type symbols.
	Only those in the profiling range are useful.
	*/
	return(
		nl.ps_sym.st_shndx < SHN_LORESERVE
		&& TXTSYM(
			nl.ps_sym.st_shndx,
			nl.ps_sym.st_info
		)
		&& (pc_l <= (char *)nl.ps_sym.st_value)
		&& ((char *)nl.ps_sym.st_value < pc_h)
	);

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

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

	if ((p = malloc((unsigned)item_count * (unsigned)item_size)) == NULL)  {
		(void)fprintf(stderr, "%s: Out of space\n", cmdname);
		exit(1);
	}
	return (p);
}



/*
	Given the quotient 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. */
	_symintClose(ldptr);		/* cleanup symbol information */
	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;

		/* flex names has variable length strings for names */
	diff = strcmp(p1->sl_name, p2->sl_name);
	return (CMP1(diff));
}
#define STRSPACE 2400		/* guess at amount of string space */
/*	getname - get the name of a symbol in a permanent fashion
*/
char *
getname(ldpter, symbol)
PROF_FILE *ldpter;
PROF_SYMBOL symbol;
{
	static char *strtable = NULL;	/* space for names */
	static int sp_used = 0;		/* space used so far */
	static int size = 0;		/* size of string table */
	char *name, *strcpy();		/* name to store */
	int lth;			/* space needed for name */
	int get;			/* amount of space to get */

	name = &(ldpter->pf_symstr_p)[symbol.ps_sym.st_name];
	if (name == NULL)  {
		return "<<bad symbol name>>";
	}
	lth = strlen(name) + 1;
	if ((sp_used + lth) > size)  {	 /* if need more space */
		/* just in case very long name */
		get = lth > STRSPACE ? lth : STRSPACE;
		strtable = _prof_Malloc(1, get);
		size = get;
		sp_used = 0;
	}
	(void)strcpy(&(strtable[sp_used]), name);
	name = &(strtable[sp_used]);
	sp_used += lth;
	return name;
}
