[comp.sources.misc] v09i009: exec profiler for MSDOS

allbery@uunet.UU.NET (Brandon S. Allbery - comp.sources.misc) (11/27/89)

Posting-number: Volume 9, Issue 9
Submitted-by: bl@infovox.se (Bj|rn Larsson)
Archive-name: proft.ms

This is an execution time profiler for DOS and TurboC/MicroSoft C. Tested
under both the compilers under PCDOS 3.30.

# ----------------------------- cut here -----------------------------
#! /bin/sh
# This is a shell archive. Remove anything before the `cut' line,
# then unpack by saving it into a file and typing `sh file'. The
# archive ends by exit(0), so don't worry about trailing junk.
# 
# Contents:
# 
#   makefile
#   makefile.msc
#   profile.c
#   profile.man
#   README
# 
# Wrapped by USER@MS-DOS --- Tue Nov 21 21:23:30 1989
# 
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f makefile -a "${1}" != "-c" ; then 
  echo Will not over-write existing file \"makefile\"
else
echo Extracting - \"makefile\"
sed "s/^X//" >makefile <<'END_OF_makefile'
X#################################################################
X# Makefile for PROFILE, Turbo-C 2.0 version.			#
X# Revised:						890904	#
X#################################################################
X
XCC=tcc
XCFLAGS=-ml -DTRC_2_0=1 -w -w-pro -w-use -w-par -c
XLINK=tlink
XLFLAGS=/m/c
XLIBDIR=\trc\lib
X
Xdefault:	proft.exe
X
Xproft.exe:	proft.obj
X	$(LINK) $(LFLAGS) $(LIBDIR)\c0l.obj proft.obj,proft.x,\
X	proft.map,$(LIBDIR)\emu.lib $(LIBDIR)\mathl.lib $(LIBDIR)\cl.lib
X	exepack proft.x proft.exe
X	rm -il proft.x
X
Xproft.obj:	profile.c
X	$(CC) $(CFLAGS) -oproft.obj profile.c
END_OF_makefile
if test 572 -ne `wc -c <makefile`; then
    echo \"makefile\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f makefile.msc -a "${1}" != "-c" ; then 
  echo Will not over-write existing file \"makefile.msc\"
else
echo Extracting - \"makefile.msc\"
sed "s/^X//" >makefile.msc <<'END_OF_makefile.msc'
X#################################################################
X# Makefile for PROFILE, MicroSoft C 5.1 version.		#
X# Revised:						890902	#
X#################################################################
X
XCC=cl
XCFLAGS=-Ml -DMSC_5_1=1 -W2 -c
XLINK=link
XLFLAGS=/MAP /EXEPACK
XLIBDIR=\msc\lib
X
Xdefault:	profm.exe
X
Xprofm.exe:	profm.obj
X	$(LINK) $(LFLAGS) profm.obj,profm.exe,profm.map;
X
Xprofm.obj:	profile.c
X	$(CC) $(CFLAGS) -Foprofm.obj profile.c
END_OF_makefile.msc
if test 447 -ne `wc -c <makefile.msc`; then
    echo \"makefile.msc\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f profile.c -a "${1}" != "-c" ; then 
  echo Will not over-write existing file \"profile.c\"
else
echo Extracting - \"profile.c\"
sed "s/^X//" >profile.c <<'END_OF_profile.c'
X/****************************************************************/
X/*			   PROFILE.C				*/
X/*								*/
X/* Execution time profiler. Reads an executable and it's link	*/
X/* map and produces an output file with hit counts for all the	*/
X/* functions listed in the map file. Handles (by option selec-	*/
X/* tion) both MicroSoft LINK and Borland TLINK map files.	*/
X/* PROF only profiles '.EXE' files.				*/
X/****************************************************************/
X/* Command line:						*/
X/*								*/
X/* prof [-adin0$?] [-#<n>] [-m<mapfile>] [-o<outfile>] <cmd>	*/
X/*								*/
X/* -d	Include DOS areas (DOS and BIOSes) in the profiling.	*/
X/* -a	Sort output table by address, not by frequency.		*/
X/* -i	Include intrinsic functions (name starting with '__,	*/
X/*	or containing '@' or '$')' in the profiling.		*/
X/* -n	Sort output table by name, not by frequency.		*/ 
X/* -0	List also functions that got no hits.			*/
X/* -#<n>							*/
X/*	Execute the profiled command <n> times to increase	*/
X/*	statistical confidence.					*/
X/* -?	Report the address of the own PSP and  main() function.	*/
X/* -$	Report the address of the top DOS aloccation.		*/ 
X/* -m<mapfile>							*/
X/*	Read link map from file <mapfile> (default <cmd>.MAP).	*/
X/* -o<outfile>							*/
X/*	Output profile table in file <outfile> (default		*/
X/*	<cmd.PRF>).						*/
X/* <cmd>							*/
X/*	The normal command line for the profiled command.	*/
X/****************************************************************/
X/* Exit codes delivered by prof:				*/
X/*								*/
X/*  0:	No error.						*/
X/*  1:	Illegal command line arguments.				*/
X/*  2:	Cannot open command binary.				*/
X/*  3:	Cannot open linker map file.				*/
X/*  4:	Cannot open output file.				*/
X/*  5:	Invalid map file format.				*/
X/*  6:	Insufficient memory.					*/
X/*  7:	Invalid EXE file format.				*/
X/*  8:	EXEC error						*/
X/*  9:	Program too fast - no hits.				*/
X/****************************************************************/
X/* Revised:							*/
X/* 1.02: Doesn't show functions that were not hit, thus		*/
X/*	 reducing the amount of output data:		890909	*/
X/* 1.01: Attempts to make load address more certain:	890906	*/
X/* 1.00: Functional:					890904	*/
X/****************************************************************/
X#include <stdio.h>
X#include <stdlib.h>
X#include <string.h>
X#include <ctype.h>
X#include <io.h>
X#include <fcntl.h>
X#include <sys/types.h>
X#include <sys/stat.h>
X#include <process.h>
X#include <dos.h>
X#include <errno.h>
X#if TRC_2_0
X#include <alloc.h>
X#endif
X#if MSC_5_1
X#include <malloc.h>
X#include <signal.h>
X#endif
X
X/****************************************************************/
X/*          Adjuct constant for file load addresses.		*/
X/*   THIS IS COMPILER SPECIFIC AND MUST BE FOUND EMPIRICALLY!	*/
X/****************************************************************/
X
X#if TRC_2_0
X#define	ADJ_CONST	0L
X#endif
X#if MSC_5_1
X#define	ADJ_CONST	0x160L
X#endif
X
X/* Compiler-specific #defines for DOS access functions */
X
X#if TRC_2_0
X#define	ENABLE()	enable()
X#define	DISABLE()	disable()
X#define	ALLMEM(s,p,r)	(r = allocmem(0xffff,p))
X#define	ALLMEMERR(s,p)	(allocmem(s,p) != -1)
X#define	FREEMEM(p)	freemem(p)
X#define	CTRLBREAK(f)	ctrlbrk(f)
X#define	SETVECT(i,f)	setvect(i,f)
X#define	GETVECT(i)	getvect(i)
X#endif
X#if MSC_5_1
X#define	ENABLE()	_enable()
X#define	DISABLE()	_disable()
X#define	ALLMEM(s,p,r)	(_dos_allocmem(0xffff,&r))
X#define	ALLMEMERR(s,p)	(_dos_allocmem(s,p) != 0)
X#define	FREEMEM(p)	_dos_freemem(p)
X#define	CTRLBREAK(f)	signal(SIGINT,f)
X#define	SETVECT(i,f)	_dos_setvect(i,f)
X#define	GETVECT(i)	_dos_getvect(i)
X#endif
X
X#define	TMRINT	8				/* Timer hardware interrupt */
X#define	MAXFUNC 2000				/* Max 2000 functions... */
X#define	CMDSIZ	130				/* Cmd's command line size */
X#define	FNSIZ	130				/* Max file name size */
X#define	LNSIZ	130				/* Max map file line length */
X#define	STSIZ	35				/* Size of small strings */
X
X#define	ABSTYP	1				/* Table entry is absolute */
X#define	USRTYP	2				/* Table entry is USR func */
X#define	INRTYP	4				/* Table entry is INR type */
X#define	DOSTYP	8				/* Table entry is DOS type */
X
Xtypedef struct					/* Entry in funcion table */
X  {
X  unsigned long addr;				/* Function start address */
X  unsigned long hits;				/* Hit count */
X  char	       *name;				/* Function name */
X  char		typ;				/* Function properties */
X  char		dummy;				/* For word-aligning */
X  } fdesc;					/* Function descriptor */
X
Xstatic	char	exename[FNSIZ]  = {0};		/* Prof'ed command's binary */
Xstatic	char	mapname[FNSIZ]  = {0};		/* Prof'ed command's map */
Xstatic	char	outname[FNSIZ]  = {0};		/* Prof output table file */
X
Xstatic	FILE   *file = NULL;			/* For read/write of files */
Xstatic	struct stat statinfo;			/* Stat() buffer */
Xstatic	fdesc  *descs;				/* Start of desc table */
Xstatic	int	exe_count = 1;			/* # times to exec command */
Xstatic	int	nfuncs;				/* Number of functions */
Xstatic	char	name_ordr = 0;			/* If sorting address-wise */
Xstatic	char	addr_ordr = 0;			/* If sorting adress.-wise */
Xstatic	char	tell_psp = 0;			/* Tell load PSP address */
Xstatic	char	list_nulls = 0;			/* IF listing no-hit funcs */
Xstatic	char	wrt_msk = USRTYP;		/* Default only user funcs */
Xstatic	double	tot_hits = 0.0;			/* Total function hits */
Xstatic	double	dsp_hits = 0.0;			/* Total displayed hits */
Xstatic	double	usr_hits = 0.0;			/* Total user pgm hits */
Xstatic	char  **cmdline;			/* Command line array ptr */
Xstatic	unsigned freemem_pg = 0;		/* At beg of free memory */
Xstatic	unsigned load_psp = 0;			/* Paragr of load area */
X
Xstatic void (interrupt *old_tmr_handler)() = NULL; /* Original handler */
X
Xstatic	char   *msgs[] =
X  {
X  "profile: ",					/* 0 */
X  "Illegal command line option: `-%s'\n",	/* 1 */
X  "No command specified for profiling\n",	/* 2 */
X  "Cannot find executable `%s'\n",		/* 3 */
X  "`%s' is a directory\n",			/* 4 */
X  "Cannot find linker map file `%s'\n",		/* 5 */
X  "Output file `%s' is write-protected\n",	/* 6 */
X  "Invalid map file format in `%s'",		/* 7 */
X  "Insufficient memory for function tables\n",	/* 8 */
X  "Not enough function table slots\n",		/* 9 */
X  "Cannot open output file `%s'\n",		/* 10 */
X  "Insufficient memory to execute command",	/* 11 */
X  "Error executing command %s\n",		/* 12 */
X  "Invalid EXE file format in `%s'\n",		/* 13 */
X  "@(#)profile.c v.1.02 - 890909",		/* 14 */
X  "Program ran too fast - no hits!\n"		/* 15 */
X  } ; /* msgs */
X
Xstatic	void	get_cmdline();			/* Interpret command line */
Xstatic	void	set_filenames();		/* Fix definitive filenames */
Xstatic	void	check_files();			/* Check files are OK */
Xstatic	void	read_map();			/* Read map, build tables */
Xstatic	void	add_func();			/* add function to table */
Xstatic	int	blankline();			/* Check if line is blank */
Xstatic	void	adjust();			/* Set correct func addrs */
Xstatic	void	profile();			/* Do the actual profiling */
Xstatic	void	write_result();			/* Output result */
Xstatic	int	fadr_comp();			/* Compare func addresses */
Xstatic	int	fhit_comp();			/* Compare func hitcounts */
Xstatic	int	fnam_comp();			/* Compare func names */
Xstatic	void	usage();			/* Help routine */
Xstatic	void	error();			/* Error/Abort routine */
X
Xstatic	int	brk_handler();			/* SIGINT handler */
Xstatic	void    interrupt tmr_handler();	/* Timer interrupt handler */
X
X/****************************************************************/
X
Xvoid	main(narg, args)
X  int	narg;
X  char *args[];
X  {
X  CTRLBREAK(brk_handler);
X  get_cmdline(narg, args);
X  set_filenames();
X  check_files();
X  read_map();
X  adjust();
X  profile();
X  write_result();
X  error(0,"");
X  } /* main */
X
X/****************************************************************/
X/* Get_cmdline() extracts all switches etc, gets the name of	*/
X/* the command to be profiled, and sets up file names. It also	*/
X/* builds the command line for the command to be profiled.	*/
X/****************************************************************/
X
Xstatic	void	get_cmdline(narg, args)
X  int		  narg;
X  char		 *args[];
X  {
X  int		  i = 1;
X  char		 *p;
X  unsigned long   m;
X
X  while ((i < narg) && (*args[i] == '-'))	/* Extract switches */
X    {
X    args[i]++;
X    while (*args[i])				/* Get switches */
X      {
X      switch (*args[i])
X	{
X	case '#':   if (sscanf(args[i]+1, "%d", &exe_count) != 1)
X		      error(1, msgs[1], args[i]);
X		    args[i] = " ";
X		    break;
X	case '?':   printf("Actual PSP is at absolute address 0x%05x0\n",
X						    _psp);
X		    p = (char *) main;		/* Trick for MSC */
X		    m = FP_SEG(p);
X		    m <<= 4;
X		    m += FP_OFF(p);
X		    printf("Main() fnc is at absolute address 0x%06lx\n",
X						    m);
X		    error(0,"");
X	case '$':   tell_psp = 1;		/* Tell DOS alloc address */
X		    break;
X	case 'a':   name_ordr = 0;		/* Sort table by freq */
X		    addr_ordr = 1;
X		    break;
X	case 'd':   wrt_msk |= DOSTYP;		/* Include DOS areas */
X		    break;
X	case 'i':   wrt_msk |= INRTYP;		/* Include intrinsic funcs */
X		    break;
X	case 'n':   name_ordr = 1;		/* Sort table by name */
X		    addr_ordr = 0;
X		    break;
X	case '0':   list_nulls = 1;		/* List no-hit funcs */
X		    break;
X	case 'm':   strcpy(mapname, args[i]+1);	/* Map file name */
X		    args[i] = " ";
X		    break;
X	case 'o':   strcpy(outname, args[i]+1);	/* Output table file name */
X		    args[i] = " ";
X		    break;
X	default:    error(1, msgs[1], args[i]);
X	} /* switch */
X      args[i]++;
X      } /* while */
X    i++;
X  } /* while */
X
X  if (i >= narg)				/* Check there is a command */
X    error(1, msgs[2]);
X  strcpy(exename,args[i]);
X  cmdline = args + i;
X  } /* get_cmdline */
X
X/****************************************************************/
X/* Set_filenames() adjust names of needed file names based on	*/
X/* defaults and options.					*/
X/****************************************************************/
X
Xstatic	void	set_filenames()
X  {
X  if (!mapname[0])				/* Set default mapfile name */
X    {
X    strcpy(mapname, exename);
X    strcat(mapname, ".map");
X    } /* if */
X  if (!outname[0])				/* Set default outfile name */
X    {
X    strcpy(outname, exename);
X    strcat(outname, ".prf");
X    } /* if */
X  strcat(exename,".exe");			/* It's an 'EXE' file */
X  } /* set_filenames */
X
X/****************************************************************/
X/* Check_files() checks that all files are available, readable	*/
X/* and writeable as required.					*/
X/****************************************************************/
X
Xstatic	void	check_files()
X  {
X  if (stat(exename, &statinfo))			/* Check executable exists */
X    error(2, msgs[3], exename);
X  if (statinfo.st_mode & S_IFDIR)
X    error(2, msgs[4], exename);
X  if (stat(mapname, &statinfo))			/* Check mapfile exists */
X    error(3, msgs[5], mapname);
X  if (statinfo.st_mode & S_IFDIR)
X    error(3, msgs[4], mapname);
X  if (stat(outname, &statinfo))			/* Check outfile writeable */
X    return;
X  if (statinfo.st_mode & S_IFDIR)
X    error(4, msgs[4], outname);
X  if (!(statinfo.st_mode & S_IWRITE))
X    error(4, msgs[6], outname);
X  } /* check_files */
X
X/****************************************************************/
X/* Read_map() reads the map file into memory and builds the	*/
X/* linked list of entries.					*/
X/****************************************************************/
X
Xstatic	void	read_map()
X  {
X  char	 line[LNSIZ+1];
X  char	 str1[STSIZ],str2[STSIZ],str3[STSIZ],str4[STSIZ];
X  fdesc *p;
X  long	 ofs, seg;
X
X  if ((p = descs = calloc(MAXFUNC,sizeof(fdesc))) == NULL)
X    error(6, msgs[8]);
X
X  if ((file = fopen(mapname,"r")) == NULL)	/* 'Impossible' */
X    error(3, msgs[5], mapname);
X
X  while (fgets(line, LNSIZ, file) != NULL)	/* Find 'Add Pub by Val' */
X    {
X    if (
X         (sscanf(line, " %s %s %s %s ", str1, str2, str3, str4) == 4)
X	   && 
X	 (stricmp(str1, "Address") == 0)
X	   && 
X	 (stricmp(str2, "Publics") == 0)
X	   && 
X	 (strcmp(str3, "by") == 0)
X	   && 
X	 (strcmp(str4, "Value") == 0)
X       )
X      break;
X    } /* while */
X  if (feof(file))
X    error(5, msgs[7], mapname);
X
X  while (fgets(line, LNSIZ, file) != NULL)	/* Find Non-blank line */
X    if (!blankline(line))
X      break;
X  if (feof(file))
X    error(5, msgs[7], mapname);
X
X  add_func(p++, "Low Mem", 0l, ABSTYP|DOSTYP);	/* Make entry for low mem */
X  add_func(p++, "DOS", 0x400l, ABSTYP|DOSTYP);	/* Make entry for low mem */
X  seg = _psp;					/* Get profiler's psp */
X  add_func(p++,"Profiler",seg<<4,ABSTYP|DOSTYP);/* Make entry for prof */
X  
X  nfuncs = 2;
X  do						/* Process and read another */
X    {
X    if (blankline(line))			/* Blank line end of data */
X      break;
X    if (sscanf(line, " %lx:%lx Abs %s ", &seg, &ofs, str1) != 3)
X      if (sscanf(line, " %lx:%lx    %s ", &seg, &ofs, str1) != 3)
X	error(5, msgs[7], mapname);
X    if (str1[0] == '_')				/* Play with '_' for */
X      str1[0] = 1;				/* alpha sorting */
X    if (str1[1] == '_')				/* This is converted back */
X      str1[1] = 0x7f;				/* on output */
X
X    if (					/* Intrinsic function */
X         ((str1[0] == 1) && (str1[1] == 0x7f))	/* with '__' */
X	   ||
X         (strchr(str1,'@') != NULL)		/* or with '@' */
X	   ||
X         (strchr(str1,'$') != NULL)		/* or with '$' */
X       )
X      add_func(p++, str1, (seg << 4) + ofs, INRTYP);/* Make entry */
X    else					/* User function */
X      add_func(p++, str1, (seg << 4) + ofs, USRTYP);/* Make entry */
X    nfuncs++;
X    if (nfuncs > (MAXFUNC - 10))
X      error(6, msgs[9]);
X    }
X  while (fgets(line, LNSIZ, file) != NULL);
X
X  add_func(p++,"EGA BIOS", 0xc0000l, ABSTYP|DOSTYP);
X  add_func(p++,"Fixed Disk BIOS", 0xc8000l, ABSTYP|DOSTYP);
X  add_func(p++,"System ROM", 0xf0000l, ABSTYP|DOSTYP);
X  add_func(p++,"System BIOS", 0xfe000l, ABSTYP|DOSTYP);
X  nfuncs += 4;
X
X  fclose(file);
X  file = (FILE *) NULL;
X  } /* read_map */
X
X/****************************************************************/
X/* Add_func() adds a function to the function table.		*/
X/****************************************************************/
X
Xstatic	void	add_func(p, nam, addr, typ)
X  fdesc	*p;
X  char	*nam;
X  long	 addr;
X  char	 typ;
X  {
X  p->addr = addr;
X  p->hits = 0l;
X  if ((p->name = calloc(1,strlen(nam)+1)) == NULL)
X    error(6, msgs[8]);
X  strcpy(p->name, nam);
X  p->typ = typ;
X  } /* add_func */
X
X/****************************************************************/
X/* Blankline() returns 1 if the passed line is entirely blank.	*/
X/****************************************************************/
X
Xstatic	int	blankline(s)
X  char	*s;
X  {
X  while (*s)
X    {
X    if (!isspace(*s))
X      return(0);
X    s++;
X    } /* while */
X  return(1);
X  } /* blankline */
X
X/****************************************************************/
X/* Adjust() finds out where in memory the executable will be	*/
X/* loaded, and adjust function addresses accordingly. Does this	*/
X/* By allocating first a hole, then sys memory 1, and then sys	*/
X/* memory 2. Sys memory 2 indicates first free address, and is	*/
X/* immediately re-freed. The hole is also freed to function as	*/
X/* work space for functions that will run before the actual	*/
X/* execution of the profiled program. THIS IS TRICKY AND COM-	*/
X/* PILER DEPENDENT...						*/
X/****************************************************************/
X
Xstatic	void	adjust()
X  {
X  char	  *hole;
X  long	   adj;
X  int	   i;
X  int	   maxsz;
X
X  if ((hole = malloc(0x800)) == NULL)		/* Fix workspace for others */
X    error(6, msgs[11]);
X  if (ALLMEMERR(0x20, &freemem_pg))		/* Grab small mem over it */
X    error(6, msgs[11]);
X  ALLMEM(0xffff, &load_psp, maxsz);		/* See what max space is */
X  if (ALLMEMERR(maxsz, &load_psp))		/* Grab it to know address */
X    error(6, msgs[11]);
X  free (hole);					/* Make workspace available */
X
X  adj = load_psp;
X  adj <<= 4;
X  adj += 0x100 + ADJ_CONST;
X
X  if (tell_psp)					/* If display free start */
X    printf("Expecting PSP at absolute address 0x%06lx\n", adj-0x100L);
X	   
X  for (i = 0; i < nfuncs; i++)			/* Add adj to func addr:s */
X    if (!((descs + i)->typ & ABSTYP))		/* Only relocatable ones */
X      (descs + i)->addr += adj;
X
X  qsort(descs,nfuncs,sizeof(fdesc),fadr_comp);	/* Sort in address order */
X  } /* adjust */
X
X/****************************************************************/
X/* Profile() does the profiling. It finds out where the pro-	*/
X/* filed command will be loaded, adjusts function addresses	*/
X/* accordingly, starts timer interrupts, and executes the com-	*/
X/* mand as a subshell.						*/
X/****************************************************************/
X
Xstatic	void	profile()
X  {
X  int	i = 0;
X
X  old_tmr_handler = GETVECT(TMRINT);		/* Save old int vector */
X  SETVECT(TMRINT,tmr_handler);			/* Start profiling */
X  DISABLE();
X  FREEMEM(load_psp);				/* Free the load area */
X  load_psp = 0;					/* To not free it at exit */
X  ENABLE();
X  while(i++ < exe_count)
X    {
X    fprintf(stderr, "%-3d ----- Executing %s\n", i, *cmdline);
X    if (spawnv(P_WAIT, *cmdline, cmdline) == -1)
X      {
X      switch(errno)
X	{
X	case ENOEXEC:	error(7,msgs[13], exename);
X			break;
X        case ENOMEM:	error(6,msgs[11]);
X			break;
X        default:		error(8,msgs[12]);
X        } /* switch */
X      } /* if */
X    } /* switch */
X
X  SETVECT(TMRINT,old_tmr_handler);
X  (char *) old_tmr_handler = NULL;
X
X  DISABLE();
X  FREEMEM(freemem_pg);
X  freemem_pg = 0;
X  ENABLE();
X  fprintf(stderr, "--------- Executing completed\n");
X  } /* profile */
X
X/****************************************************************/
X/* Write_result() sorts the data, computes profiling percen-	*/
X/* tages, and write out the resulting list.			*/
X/****************************************************************/
X
Xstatic	void	write_result()
X  {
X  int	 i;
X
X  if (name_ordr)				/* Sort table by name? */
X    qsort(descs,nfuncs,sizeof(fdesc),fnam_comp);/* Sort in name order */
X  else
X    if (!addr_ordr)
X      qsort(descs,nfuncs,sizeof(fdesc),fhit_comp);/* Sort in freq order */
X
X  if ((file = fopen(outname,"w")) == NULL)	/* Possible if command did */
X    error(4, msgs[10], outname);		/* something like chmod -w */
X
X  for (i = 0; i < nfuncs; i++)			/* Add up total hit counts */
X    {
X    tot_hits += (double) ((descs + i)->hits);	/* Add upp total hits */
X    if ((descs + i)->typ & wrt_msk)
X      dsp_hits += (double) ((descs + i)->hits);	/* Add upp displayed hits */
X    if ((descs + i)->typ & USRTYP)
X      usr_hits += (double) ((descs + i)->hits);	/* Add up user hits */
X    } /* for */
X  if (tot_hits == 0.0)				/* Avoid div by 0.0 */
X    tot_hits = 1.0;
X  if (dsp_hits == 0.0)				/* Avoid div by 0.0 */
X    {
X    if (!tell_psp)
X      error(9,msgs[15]);
X    else
X      dsp_hits = 1.0;
X    } /* if */
X  if (usr_hits == 0.0)				/* Avoid div by 0.0 */
X    usr_hits = 1.0;
X
X  fprintf(file, "Function name          Addr      Total    Disp    User\n\n");
X  for (i = 0; i < nfuncs; i++)
X    {
X    if (((descs + i)->hits == 0) &&		/* Don't show 0 hit funcs */
X	    !(list_nulls || tell_psp))
X      continue;
X    if (wrt_msk & (descs +i)->typ)
X      {
X      if ((descs + i)->name[0] == 1)		/* Reconvert fixes done */
X	(descs + i)->name[0] = '_';		/* when reading the map */
X      if ((descs + i)->name[1] == 0x7f)
X	(descs + i)->name[1] = '_';
X      fprintf(file, "%-20s  %05lx     %6.2f  %6.2f",
X	(descs + i)->name, (descs + i)->addr,
X	100.0 * ((descs + i)->hits / tot_hits),
X	100.0 * ((descs + i)->hits / dsp_hits));
X      if ((descs + i)->typ & USRTYP)		/* Only usrfuncs get col 3 */
X	fprintf(file,"  %6.2f", 100.0 * ((descs + i)->hits / usr_hits));
X      fprintf(file,"\n");
X      } /* if */
X    } /* for */
X  fprintf(file, "\nStatistics based on %6.0f hits\n", tot_hits);
X  fclose(file);
X  file = (FILE *) NULL;
X  } /* write_result */
X
X/****************************************************************/
X/* Fadr_comp() compares addresses of two functions. If address	*/
X/* values are the same, name decides.				*/
X/****************************************************************/
X
Xstatic	int	fadr_comp(f1, f2)
X  fdesc	*f1, *f2;
X  {
X  if (f1->addr > f2->addr)
X    return(1);
X  if (f1->addr < f2->addr)
X    return(-1);
X  return(fnam_comp(f1,f2));
X  } /* fadr_comp */
X
X/****************************************************************/
X/* Fhit_comp() compares hit counts of two function table	*/
X/* entries. If counts are the same, name decides.		*/
X/****************************************************************/
X
Xstatic	int	fhit_comp(f1, f2)
X  fdesc	*f1, *f2;
X  {
X  if (f1->hits > f2->hits)
X    return(-1);
X  if (f1->hits < f2->hits)
X    return(1);
X  return(fnam_comp(f1,f2));
X  } /* fhit_comp */
X
X/****************************************************************/
X/* Fnam_comp() compares names of two function table entries.	*/
X/****************************************************************/
X
Xstatic	int	fnam_comp(f1, f2)
X  fdesc	*f1, *f2;
X  {
X  return (stricmp(f1->name, f2->name));
X  } /* fnam_comp */
X
X/****************************************************************/
X/* Usage() displays a usage menu.				*/
X/****************************************************************/
X
Xstatic	void	usage()
X  {
X  fprintf(stderr,
X "Usage: profile [-adin0] [-#<n>] [-m<map>] [-o<out>] <cmd> [<args>...]\n");
X  fprintf(stderr,
X "       -a       Sort output by adress, not by frequency\n");
X  fprintf(stderr,
X "       -d       Include DOS and BIOS areas in profiling\n");
X  fprintf(stderr,
X "       -i       Include intrinsic functions (starting with `__', or\n");
X  fprintf(stderr,
X "                containing the characters `@' or '$') in profiling\n");
X  fprintf(stderr,
X "       -n       Sort output by name, not by frequency\n");
X  fprintf(stderr,
X "       -0       List also functions that had zero frequency\n");
X  fprintf(stderr,
X "       -#<n>    Execute profiled command <n> times (default 1)\n");
X  fprintf(stderr,
X "       -m<map>  Use file <map> as linker map info (default <cmd>.MAP)\n");
X  fprintf(stderr,
X "       -o<out>  Put output result in file <out> (default <cmd>.PRF)\n");
X  fprintf(stderr,
X "       <cmd>    Name of command to be profiled (including path)\n");
X  fprintf(stderr,
X "       <args>   Optional arguments to <cmd>\n");
X  } /* usage */
X
X/****************************************************************/
X/* Error()							*/
X/*								*/
X/* Print an error diagnosis and exit.				*/
X/****************************************************************/
X/*VARARGS*/
Xstatic	void error(ercode, ermsg, s1, s2, s3, s4)
X  int	 ercode;
X  char	*ermsg;
X  char	*s1, *s2, *s3, *s4;
X  {
X  if ((char *) old_tmr_handler != NULL)
X    SETVECT(TMRINT,old_tmr_handler);
X  if (freemem_pg)
X    FREEMEM(freemem_pg);
X  if (load_psp)
X    FREEMEM(load_psp);
X  if (ercode != 0)
X    {
X    fprintf(stderr, msgs[0]);
X    fprintf(stderr, ermsg, s1, s2, s3, s4);
X    } /* if */
X  if (ercode == 1)
X    usage();
X  if (file != (FILE *) NULL)
X    fclose(file);
X  exit(ercode);
X  } /* error */
X
X/****************************************************************/
X/* Brk_handler() catches CTRL-C interrupts.			*/
X/****************************************************************/
X
Xstatic	int	brk_handler()
X  {
X  CTRLBREAK(brk_handler);
X  error(255,"User abort\n");
X  return(0);					/* Actually not executed */
X  } /* brk_handler */
X
X/****************************************************************/
X/* Tmr_handler() is the interrupt handler that updates hit	*/
X/* counts. It must be fast.					*/
X/****************************************************************/
X
X#if TRC_2_0
Xstatic	void interrupt tmr_handler(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs)
X  unsigned bp,di,si,ds,es,dx,cx,bx,ax,ip,cs;
X#endif
X#if MSC_5_1
Xstatic	void interrupt tmr_handler(es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs)
X  unsigned es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs;
X#endif
X  {
X  long	addr;
X  int	lower, upper, middle;
X
X  addr = ((unsigned long)cs << 4) + (unsigned long) ip;
X  lower = 0;
X  upper = nfuncs - 1;
X
X  while (upper - lower > 1)
X    {
X    middle = (lower + upper) / 2;
X    if ((descs + middle)->addr <= addr)
X      lower = middle;
X    else
X      upper = middle;
X    } /* while */
X  (descs + lower)->hits++;
X  (*old_tmr_handler)();
X  } /* tmr_handler */
END_OF_profile.c
if test 23853 -ne `wc -c <profile.c`; then
    echo \"profile.c\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f profile.man -a "${1}" != "-c" ; then 
  echo Will not over-write existing file \"profile.man\"
else
echo Extracting - \"profile.man\"
sed "s/^X//" >profile.man <<'END_OF_profile.man'
XPROFILE(1)		MS-DOS Programmer's Manual		 PROFILE(1)
X
XNAME
X   profile - execution time statistics collector
X
XSYNOPSIS
X   profile [-adin0] [-#<n>] [-m<mapfile>] [-o<outfile>] <cmdline>
X
XDESCRIPTION
X   Profile(1) executes a DOS command and gathers information about where in
X   the program the execution time is spent. After the command has terminated,
X   profile(1) writes a report file where the data is presented.
X
X   Current implementations of profile(1) requires that the program under test
X   is linked with the MicroSoft LINK or the Borland TLINK linkers, and that a
X   standard format map file (as produced by the above linkers) was generated.
X   The link command for TLINK must contain the options '/l/c', and for the MS
X   LINK linker, the '/MAP' option must be specified.
X
X   Normally, profile(1) assumes a linker map file with the same name as the
X   executable file (including any pathes) but with the extension '.MAP' in-
X   stead of '.EXE'. By default the output file has the same name as the '.EXE'
X   file, but with extension '.PRF'. Both these conventions can be overridden.
X
X   The profiler will present it's output data summary based on the information
X   about public function names and addresses described by the linker map file.
X   If the map file is out of date, profiling results may be incorrect. Fur-
X   thermore, the profiler only displays public functions. One way to ensure
X   that interesting symbols in 'C' programs are public is to temporarily in-
X   sert the line
X
X   	#define	static
X
X   at the head of all interesting 'C' modules (this may in some cases cause
X   multiple definition errors in the link stage, if different functions and
X   variables have the same names in different modules).
X
X   In cases where lengthy execution of a command is needed to produce relia-
X   ble statistics, an option is available to make the profiled command run
X   multiple times.
X
X   Profile(1) will not process '.COM' files.
X
XINTERNAL FUNCTIONING
X   Profile(1) works by first reading the linker map file and collect all the
X   function name and address information in memory. It then find out where in
X   the physical memory the executable will be loaded, and adjusts table infor-
X   mation according to this. Next, it installs it's own handler function for
X   for the PC real-time clock interrupt, and loads and executes the program.
X   Each time a real-time clock interrupt occurs, the interrupt handler will
X   record in which function the CPU was executing. This continues until the
X   profiled program terminates. Profile(1) then uninstalls the interrupt
X   handler and formats and outputs the results.
X
X   All functions described in the map file are classified either as 'intrinsic
X   functions' or 'user functions', according to their names: intrinsic func-
X   tions are those whose names start with '__', or which contain the charac-
X   ters '@' or '$'. All other functions are considered user functions. Apart
X   from the functions described in the map file, other interesting memory
X   areas are also defined:
X
X   	'Low Mem' 	  from 0 to 0x3ff
X	'DOS'		  from 0x400 to the beginning od the profile itself
X	'Profiler'	  from the beginning to the end of the profiler
X	'EGA BIOS'	  from 0xc0000 to 0xc7fff
X	'Fixed Disk BIOS' from 0xc8000 to 0xeffff
X	'System ROM'	  from 0xf0000 to 0xfe000
X	'System BIOS'	  from 0xfe000 to 0xfffff
X
XOUTPUT LISTING FORMAT
X   By default, DOS areas and intrinsic functions will not be listed in the
X   output report (this can be overridden). Below is an excerpt of a typical
X   report:
X
X   Function name          Addr      Total    Disp    User
X
X   _fclose               41b1c       0.32   50.00   50.00
X   _daylight             4a41e       0.00    3.00    3.00
X   _edata                4a826       0.00    3.00    3.00
X   _end                  4ab60       0.00    3.00    3.00
X   _exit                 416fa       0.00    2.00    2.00
X   _flushall             41c02       0.00    2.00    2.00
X   _fopen                41c46       0.00    1.00    1.00
X   _fprintf              41c7e       0.00    0.50    0.50
X   _main                 402e0       0.00    0.50    0.50
X     ...                  ...         ...     ...     ...
X   _timezone             4a41a       0.00    0.40    0.40
X   _tzname               4a420       0.00    0/30    0.30
X   FIARQQ                50112       0.00    0.10    0.10
X   FICRQQ                41112       0.00    0.01    0.01
X
X   Statistics based on    309 hits
X
X   As can be seen, functions are sorted according to their frequency of occu-
X   rence, and secondary by their name. They can also be sorted primarily ac-
X   cording to name or execution address. The first column gives name of the
X   function, and the second it's address at this particular instance of execu-
X   tion. Next is the percentage of each function related to total time, i.e.
X   if one adds up all DOS, intrinsic and user function percentages in this
X   column, the result will be 100. If DOS and intrinsics are not listed, the
X   total will be less than 100%.
X
X   In the second column, the percentages relative to all DISPLAYED functions
X   are shown. In the third, the percentages relative to all USER functions
X   are listed. The two latter columns should always add up very closely to
X   100% (the sum may not be exact due to rounding errors).
X
X   Functions that did not get any hits are not displayed by default. However,
X   you can force them to appear in the output listing by giving the '-0'
X   option in the command line.
X
X   In the last line is shown the total number of real-time clock interrupts
X   that occured during profiling. This value should be as large as possible,
X   otherwise it means the statistics is based on too little input data. See
X   the '#' option below to make multiple runs of the same command to increase
X   the statistical reliability of the data.
X
X   In the example above, it can be seen that of the displayed functions, half
X   the time (50%) was spent in the _fclose function. Since _fclose is also
X   classified as a user function, the value is the same in the third column.
X   But only 0.32% of the total time was spent in _fclose, indicating that most
X   of the program's execution time is spent doing system calls into DOS or
X   other system resources. To find out, one could include DOS and intrinsic
X   functions in the output listing by using the '-d' and/or '-i' options.
X
XOPTIONS
X   -a		Sort the output list in order of execution address, and not
X   		by frequency.
X   -d		Include DOS and BIOS areas, and the profiler itslef, in the
X   		output list.
X   -i		Include intrinsic functions in the output list.
X   -n		Sort the output list in order of function name, and not by
X   		frequency.
X   -0		Normally functions that did not get any hits during execution
X   		are not listed in the output. The '-0' option causes them to
X		be listed.
X   -#<n>	Execute the profiled command <n> times to increase the relia-
X   		bility of the statistics (default 1).
X   -m<mapfile>	Read the linker map from file <mapfile>. The default is the
X   		same name as the executable, but with extension '.MAP'.
X   -o<outfile>	Write output data to file <outfile>. The default is the same
X   		name as the executable, but with extension '.OUT'.It is poss-
X		ible to write the data directly to 'CON' or to the printer
X		device.
X   <cmdline>	The normal command line for the profiled command. Note that
X   		profile(1) will not follow the DOS 'PATH' environment variable
X		to find the executable file. If you want to profile a command
X		that is in another directory, the explicit path to it must be
X		supplied. But the '.exe' extension should NOT be specified.
X
X		Example:
X
X		C> profile ..\tst\tester junk <cr>
X
X		will profile the 'tester.exe' program in directory /..\tst'.
X		Note that in this example, the map file is assumed to be in
X		the '..\tst' directory too, and the profiler output file will
X		also be written to that directory.
X
XSPECIAL OPTIONS, NOT NORMALLY USED
X   -?		Report the memory address of profile(1)'s own PSP and main()
X   		function. Used only during development of profile(1).
X   -$		Report the memory address of the first free memory (approxi-
X   		mately where the profiled command will be loaded). Used only
X		during development of profile(1). '-$' also causes more infor-
X		mation to be included in the output listing.
X
XDIAGNOSTICS
X    Illegal command line option: `x'
X    No command specified for profiling
X    Cannot find executable `x'
X    `x' is a directory
X    Cannot find linker map file `x'
X    Output file `x' is write-protected
X    Invalid map file format in `x'
X    Insufficient memory for function tables
X    Not enough function table slots
X    Cannot open output file `x'
X    Insufficient memory to execute command
X    Error executing command `x'
X    Invalid EXE file format in `x'
X    Program ran too fast - no hits!
X
XBUGS
X   The method for finding out the address where the profiled command will be
X   loaded is compiler specific (to the compiler used to compile profile(1)).
X
X   It involves giving the command
X
X   	C> profile -$ profile -? <cr>
X
X   These two (!) instances of profile will produce some special output. The
X   one which EXECUTES the other will show what memory address it thinks is the
X   lowest free one. The one which IS EXECUTED by the other will report the
X   address of it's own Program Segment Prefix and main() function. Naively,
X   the two values would be expected to be the same, but they are not.
X
X   The discrepancy should be added during the adjustment of function addresses
X   to actual execution values. This is a nuisance. The values may even be dif-
X   ferent if you change the source code using the same compiler, so if you mo-
X   dify profile(1) you should check the address of the real executing main()
X   function and the assumed address for it and check that the values agree
X   after each change to the sources.
X
X   It could be considered to have some kind of regular expression format, per-
X   haps read from a specification file, that tells what functions should be
X   included in or excluded from the output report.
X
X   Other bugs? No doubt!
X
XNOTES
X   The writing of this command was inspired by a profiler distributed on Use-
X   Net by Diomidis Spinellis. Although his profile is very well written and
X   performs as described, it has two shortcomings. One is that the profiled
X   program has to be modified with a call to a special function that is linked
X   into it. Second, and much more serious: if you break out of the profiled
X   program with CTRL-C, the interrupt handler will not be restored, and as
X   soon as the memory used for it is overwritten by another command, the PC
X   dies immediately.
X
X   This profile(1) does noit suffer from these problems (but possibly from
X   many others...). Although I have taken some ideas from Diomidis' version,
X   most of this one is original work. It is hereby placed in the public do-
X   main.
X
X   Author: Bjorn Larsson		uucp: bl@infovox.se
X   	   Ynglingagatan 5, IV
X	   S-113 47 Stockholm
X	   SWEDEN
END_OF_profile.man
if test 11010 -ne `wc -c <profile.man`; then
    echo \"profile.man\" unpacked with wrong size!
fi
# end of overwriting check
fi
if test -f README -a "${1}" != "-c" ; then 
  echo Will not over-write existing file \"README\"
else
echo Extracting - \"README\"
sed "s/^X//" >README <<'END_OF_README'
X			PROFILER READ-ME	
X			================
X
XThis software is an execution time profiler for programs developed
Xwith Turbo C and Microsoft C, or any other program that is linked
Xwith Microsoft or Turbo LINK. PROFILE will read the linker's output
Xmap file, and then run the executable program N times (default 1),
Xusing the timer interrupt to gather statistical data about where
Xexecution is mostly spent. After the executable has been run, the
Xdata is formatted in different ways and sent to a file.
X
XAs is mentioned in the MAN file, there is a constant (ADJ_CONST) in
Xthe source that is dependent on the compiler used. This is a correc-
Xtion constant to compensate the actual load address for the executing
Xprogram when it resides in primary memory. The supplied values work
Xfor MicroSoft C v.5.1, and for TurboC v.2.0. Probably other values
Xneed to be used for other compilers, and maybe also for other ver-
Xsions of DOS (the supplied values work for PCDOS 3.30). Unfortunately,
XI know of no way to know for certain where a program executed by
Xspawn..() will come down into memory. On the other hand PROFILE con-
Xtains two command line switches ('-?' and '-#') to check that the
Xconstant is correct. In other words, to install this you need a
Xlittle more than just compile it and go... it works well in my
Xinstallation. Though I'd pass it along - hopefully it is of use
Xto at least someone.
X
XA word of the program's heritage - I started to write it after
Xcollecting Diomidis Spinellis' profiler for UseNet. Although his
Xprofiler works as advertised (and is well written) I think my
Xversion is somewhat improved, as discussed in the MAN file. At
Xleast, it will handle CTRL-C in an orderly manner...
X
XTo compile, you need a UNIX compatible make (in other words,
XMicroSoft MAKE will NOT work). Anyway the makefiles are so simple
Xyou could just as well compile 'by hand'. Who knows, maybe some
Xof you can remove the need of AJD_CONST and post the result (or
Xat least mail it to me)?
X
X							Bjorn
END_OF_README
if test 1992 -ne `wc -c <README`; then
    echo \"README\" unpacked with wrong size!
fi
# end of overwriting check
fi
echo End of shell archive.
##  End of shell archive.
exit 0

 ====================== InfoVox = Speech Technology =======================
 Bjorn Larsson, INFOVOX AB      :      ...seismo!mcvax!kth!sunic!infovax!bl
 Box 2503                       :         bl@infovox.se
 S-171 02 Solna, Sweden         :         Phone (+46) 8 735 80 90