home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Simtel MSDOS 1992 September
/
Simtel20_Sept92.cdr
/
msdos
/
c
/
profil.arc
/
PROFILE.C
< prev
next >
Wrap
C/C++ Source or Header
|
1989-09-04
|
25KB
|
727 lines
/****************************************************************/
/* PROFILE.C */
/* */
/* Execution time profiler. Reads an executable and it's link */
/* map and produces an output file with hit counts for all the */
/* functions listed in the map file. Handles (by option selec- */
/* tion) both MicroSoft LINK and Borland TLINK map files. */
/* PROF only profiles '.EXE' files. */
/****************************************************************/
/* Command line: */
/* */
/* prof [-adin0$?] [-#<n>] [-m<mapfile>] [-o<outfile>] <cmd> */
/* */
/* -d Include DOS areas (DOS and BIOSes) in the profiling. */
/* -a Sort output table by address, not by frequency. */
/* -i Include intrinsic functions (name starting with '__, */
/* or containing '@' or '$')' in the profiling. */
/* -n Sort output table by name, not by frequency. */
/* -0 List also functions that got no hits. */
/* -#<n> */
/* Execute the profiled command <n> times to increase */
/* statistical confidence. */
/* -? Report the address of the own PSP and main() function. */
/* -$ Report the address of the top DOS aloccation. */
/* -m<mapfile> */
/* Read link map from file <mapfile> (default <cmd>.MAP). */
/* -o<outfile> */
/* Output profile table in file <outfile> (default */
/* <cmd.PRF>). */
/* <cmd> */
/* The normal command line for the profiled command. */
/****************************************************************/
/* Exit codes delivered by prof: */
/* */
/* 0: No error. */
/* 1: Illegal command line arguments. */
/* 2: Cannot open command binary. */
/* 3: Cannot open linker map file. */
/* 4: Cannot open output file. */
/* 5: Invalid map file format. */
/* 6: Insufficient memory. */
/* 7: Invalid EXE file format. */
/* 8: EXEC error */
/* 9: Program too fast - no hits. */
/****************************************************************/
/* Revised: */
/* 1.02: Doesn't show functions that were not hit, thus */
/* reducing the amount of output data: 890909 */
/* 1.01: Attempts to make load address more certain: 890906 */
/* 1.00: Functional: 890904 */
/****************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <io.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <process.h>
#include <dos.h>
#include <errno.h>
#if TRC_2_0
#include <alloc.h>
#endif
#if MSC_5_1
#include <malloc.h>
#include <signal.h>
#endif
/****************************************************************/
/* Adjuct constant for file load addresses. */
/* THIS IS COMPILER SPECIFIC AND MUST BE FOUND EMPIRICALLY! */
/****************************************************************/
#if TRC_2_0
#define ADJ_CONST 0L
#endif
#if MSC_5_1
#define ADJ_CONST 0x160L
#endif
/* Compiler-specific #defines for DOS access functions */
#if TRC_2_0
#define ENABLE() enable()
#define DISABLE() disable()
#define ALLMEM(s,p,r) (r = allocmem(0xffff,p))
#define ALLMEMERR(s,p) (allocmem(s,p) != -1)
#define FREEMEM(p) freemem(p)
#define CTRLBREAK(f) ctrlbrk(f)
#define SETVECT(i,f) setvect(i,f)
#define GETVECT(i) getvect(i)
#endif
#if MSC_5_1
#define ENABLE() _enable()
#define DISABLE() _disable()
#define ALLMEM(s,p,r) (_dos_allocmem(0xffff,&r))
#define ALLMEMERR(s,p) (_dos_allocmem(s,p) != 0)
#define FREEMEM(p) _dos_freemem(p)
#define CTRLBREAK(f) signal(SIGINT,f)
#define SETVECT(i,f) _dos_setvect(i,f)
#define GETVECT(i) _dos_getvect(i)
#endif
#define TMRINT 8 /* Timer hardware interrupt */
#define MAXFUNC 2000 /* Max 2000 functions... */
#define CMDSIZ 130 /* Cmd's command line size */
#define FNSIZ 130 /* Max file name size */
#define LNSIZ 130 /* Max map file line length */
#define STSIZ 35 /* Size of small strings */
#define ABSTYP 1 /* Table entry is absolute */
#define USRTYP 2 /* Table entry is USR func */
#define INRTYP 4 /* Table entry is INR type */
#define DOSTYP 8 /* Table entry is DOS type */
typedef struct /* Entry in funcion table */
{
unsigned long addr; /* Function start address */
unsigned long hits; /* Hit count */
char *name; /* Function name */
char typ; /* Function properties */
char dummy; /* For word-aligning */
} fdesc; /* Function descriptor */
static char exename[FNSIZ] = {0}; /* Prof'ed command's binary */
static char mapname[FNSIZ] = {0}; /* Prof'ed command's map */
static char outname[FNSIZ] = {0}; /* Prof output table file */
static FILE *file = NULL; /* For read/write of files */
static struct stat statinfo; /* Stat() buffer */
static fdesc *descs; /* Start of desc table */
static int exe_count = 1; /* # times to exec command */
static int nfuncs; /* Number of functions */
static char name_ordr = 0; /* If sorting address-wise */
static char addr_ordr = 0; /* If sorting adress.-wise */
static char tell_psp = 0; /* Tell load PSP address */
static char list_nulls = 0; /* IF listing no-hit funcs */
static char wrt_msk = USRTYP; /* Default only user funcs */
static double tot_hits = 0.0; /* Total function hits */
static double dsp_hits = 0.0; /* Total displayed hits */
static double usr_hits = 0.0; /* Total user pgm hits */
static char **cmdline; /* Command line array ptr */
static unsigned freemem_pg = 0; /* At beg of free memory */
static unsigned load_psp = 0; /* Paragr of load area */
static void (interrupt *old_tmr_handler)() = NULL; /* Original handler */
static char *msgs[] =
{
"profile: ", /* 0 */
"Illegal command line option: `-%s'\n", /* 1 */
"No command specified for profiling\n", /* 2 */
"Cannot find executable `%s'\n", /* 3 */
"`%s' is a directory\n", /* 4 */
"Cannot find linker map file `%s'\n", /* 5 */
"Output file `%s' is write-protected\n", /* 6 */
"Invalid map file format in `%s'", /* 7 */
"Insufficient memory for function tables\n", /* 8 */
"Not enough function table slots\n", /* 9 */
"Cannot open output file `%s'\n", /* 10 */
"Insufficient memory to execute command", /* 11 */
"Error executing command %s\n", /* 12 */
"Invalid EXE file format in `%s'\n", /* 13 */
"@(#)profile.c v.1.02 - 890909", /* 14 */
"Program ran too fast - no hits!\n" /* 15 */
} ; /* msgs */
static void get_cmdline(); /* Interpret command line */
static void set_filenames(); /* Fix definitive filenames */
static void check_files(); /* Check files are OK */
static void read_map(); /* Read map, build tables */
static void add_func(); /* add function to table */
static int blankline(); /* Check if line is blank */
static void adjust(); /* Set correct func addrs */
static void profile(); /* Do the actual profiling */
static void write_result(); /* Output result */
static int fadr_comp(); /* Compare func addresses */
static int fhit_comp(); /* Compare func hitcounts */
static int fnam_comp(); /* Compare func names */
static void usage(); /* Help routine */
static void error(); /* Error/Abort routine */
static int brk_handler(); /* SIGINT handler */
static void interrupt tmr_handler(); /* Timer interrupt handler */
/****************************************************************/
void main(narg, args)
int narg;
char *args[];
{
CTRLBREAK(brk_handler);
get_cmdline(narg, args);
set_filenames();
check_files();
read_map();
adjust();
profile();
write_result();
error(0,"");
} /* main */
/****************************************************************/
/* Get_cmdline() extracts all switches etc, gets the name of */
/* the command to be profiled, and sets up file names. It also */
/* builds the command line for the command to be profiled. */
/****************************************************************/
static void get_cmdline(narg, args)
int narg;
char *args[];
{
int i = 1;
char *p;
unsigned long m;
while ((i < narg) && (*args[i] == '-')) /* Extract switches */
{
args[i]++;
while (*args[i]) /* Get switches */
{
switch (*args[i])
{
case '#': if (sscanf(args[i]+1, "%d", &exe_count) != 1)
error(1, msgs[1], args[i]);
args[i] = " ";
break;
case '?': printf("Actual PSP is at absolute address 0x%05x0\n",
_psp);
p = (char *) main; /* Trick for MSC */
m = FP_SEG(p);
m <<= 4;
m += FP_OFF(p);
printf("Main() fnc is at absolute address 0x%06lx\n",
m);
error(0,"");
case '$': tell_psp = 1; /* Tell DOS alloc address */
break;
case 'a': name_ordr = 0; /* Sort table by freq */
addr_ordr = 1;
break;
case 'd': wrt_msk |= DOSTYP; /* Include DOS areas */
break;
case 'i': wrt_msk |= INRTYP; /* Include intrinsic funcs */
break;
case 'n': name_ordr = 1; /* Sort table by name */
addr_ordr = 0;
break;
case '0': list_nulls = 1; /* List no-hit funcs */
break;
case 'm': strcpy(mapname, args[i]+1); /* Map file name */
args[i] = " ";
break;
case 'o': strcpy(outname, args[i]+1); /* Output table file name */
args[i] = " ";
break;
default: error(1, msgs[1], args[i]);
} /* switch */
args[i]++;
} /* while */
i++;
} /* while */
if (i >= narg) /* Check there is a command */
error(1, msgs[2]);
strcpy(exename,args[i]);
cmdline = args + i;
} /* get_cmdline */
/****************************************************************/
/* Set_filenames() adjust names of needed file names based on */
/* defaults and options. */
/****************************************************************/
static void set_filenames()
{
if (!mapname[0]) /* Set default mapfile name */
{
strcpy(mapname, exename);
strcat(mapname, ".map");
} /* if */
if (!outname[0]) /* Set default outfile name */
{
strcpy(outname, exename);
strcat(outname, ".prf");
} /* if */
strcat(exename,".exe"); /* It's an 'EXE' file */
} /* set_filenames */
/****************************************************************/
/* Check_files() checks that all files are available, readable */
/* and writeable as required. */
/****************************************************************/
static void check_files()
{
if (stat(exename, &statinfo)) /* Check executable exists */
error(2, msgs[3], exename);
if (statinfo.st_mode & S_IFDIR)
error(2, msgs[4], exename);
if (stat(mapname, &statinfo)) /* Check mapfile exists */
error(3, msgs[5], mapname);
if (statinfo.st_mode & S_IFDIR)
error(3, msgs[4], mapname);
if (stat(outname, &statinfo)) /* Check outfile writeable */
return;
if (statinfo.st_mode & S_IFDIR)
error(4, msgs[4], outname);
if (!(statinfo.st_mode & S_IWRITE))
error(4, msgs[6], outname);
} /* check_files */
/****************************************************************/
/* Read_map() reads the map file into memory and builds the */
/* linked list of entries. */
/****************************************************************/
static void read_map()
{
char line[LNSIZ+1];
char str1[STSIZ],str2[STSIZ],str3[STSIZ],str4[STSIZ];
fdesc *p;
long ofs, seg;
if ((p = descs = calloc(MAXFUNC,sizeof(fdesc))) == NULL)
error(6, msgs[8]);
if ((file = fopen(mapname,"r")) == NULL) /* 'Impossible' */
error(3, msgs[5], mapname);
while (fgets(line, LNSIZ, file) != NULL) /* Find 'Add Pub by Val' */
{
if (
(sscanf(line, " %s %s %s %s ", str1, str2, str3, str4) == 4)
&&
(stricmp(str1, "Address") == 0)
&&
(stricmp(str2, "Publics") == 0)
&&
(strcmp(str3, "by") == 0)
&&
(strcmp(str4, "Value") == 0)
)
break;
} /* while */
if (feof(file))
error(5, msgs[7], mapname);
while (fgets(line, LNSIZ, file) != NULL) /* Find Non-blank line */
if (!blankline(line))
break;
if (feof(file))
error(5, msgs[7], mapname);
add_func(p++, "Low Mem", 0l, ABSTYP|DOSTYP); /* Make entry for low mem */
add_func(p++, "DOS", 0x400l, ABSTYP|DOSTYP); /* Make entry for low mem */
seg = _psp; /* Get profiler's psp */
add_func(p++,"Profiler",seg<<4,ABSTYP|DOSTYP);/* Make entry for prof */
nfuncs = 2;
do /* Process and read another */
{
if (blankline(line)) /* Blank line end of data */
break;
if (sscanf(line, " %lx:%lx Abs %s ", &seg, &ofs, str1) != 3)
if (sscanf(line, " %lx:%lx %s ", &seg, &ofs, str1) != 3)
error(5, msgs[7], mapname);
if (str1[0] == '_') /* Play with '_' for */
str1[0] = 1; /* alpha sorting */
if (str1[1] == '_') /* This is converted back */
str1[1] = 0x7f; /* on output */
if ( /* Intrinsic function */
((str1[0] == 1) && (str1[1] == 0x7f)) /* with '__' */
||
(strchr(str1,'@') != NULL) /* or with '@' */
||
(strchr(str1,'$') != NULL) /* or with '$' */
)
add_func(p++, str1, (seg << 4) + ofs, INRTYP);/* Make entry */
else /* User function */
add_func(p++, str1, (seg << 4) + ofs, USRTYP);/* Make entry */
nfuncs++;
if (nfuncs > (MAXFUNC - 10))
error(6, msgs[9]);
}
while (fgets(line, LNSIZ, file) != NULL);
add_func(p++,"EGA BIOS", 0xc0000l, ABSTYP|DOSTYP);
add_func(p++,"Fixed Disk BIOS", 0xc8000l, ABSTYP|DOSTYP);
add_func(p++,"System ROM", 0xf0000l, ABSTYP|DOSTYP);
add_func(p++,"System BIOS", 0xfe000l, ABSTYP|DOSTYP);
nfuncs += 4;
fclose(file);
file = (FILE *) NULL;
} /* read_map */
/****************************************************************/
/* Add_func() adds a function to the function table. */
/****************************************************************/
static void add_func(p, nam, addr, typ)
fdesc *p;
char *nam;
long addr;
char typ;
{
p->addr = addr;
p->hits = 0l;
if ((p->name = calloc(1,strlen(nam)+1)) == NULL)
error(6, msgs[8]);
strcpy(p->name, nam);
p->typ = typ;
} /* add_func */
/****************************************************************/
/* Blankline() returns 1 if the passed line is entirely blank. */
/****************************************************************/
static int blankline(s)
char *s;
{
while (*s)
{
if (!isspace(*s))
return(0);
s++;
} /* while */
return(1);
} /* blankline */
/****************************************************************/
/* Adjust() finds out where in memory the executable will be */
/* loaded, and adjust function addresses accordingly. Does this */
/* By allocating first a hole, then sys memory 1, and then sys */
/* memory 2. Sys memory 2 indicates first free address, and is */
/* immediately re-freed. The hole is also freed to function as */
/* work space for functions that will run before the actual */
/* execution of the profiled program. THIS IS TRICKY AND COM- */
/* PILER DEPENDENT... */
/****************************************************************/
static void adjust()
{
char *hole;
long adj;
int i;
int maxsz;
if ((hole = malloc(0x800)) == NULL) /* Fix workspace for others */
error(6, msgs[11]);
if (ALLMEMERR(0x20, &freemem_pg)) /* Grab small mem over it */
error(6, msgs[11]);
ALLMEM(0xffff, &load_psp, maxsz); /* See what max space is */
if (ALLMEMERR(maxsz, &load_psp)) /* Grab it to know address */
error(6, msgs[11]);
free (hole); /* Make workspace available */
adj = load_psp;
adj <<= 4;
adj += 0x100 + ADJ_CONST;
if (tell_psp) /* If display free start */
printf("Expecting PSP at absolute address 0x%06lx\n", adj-0x100L);
for (i = 0; i < nfuncs; i++) /* Add adj to func addr:s */
if (!((descs + i)->typ & ABSTYP)) /* Only relocatable ones */
(descs + i)->addr += adj;
qsort(descs,nfuncs,sizeof(fdesc),fadr_comp); /* Sort in address order */
} /* adjust */
/****************************************************************/
/* Profile() does the profiling. It finds out where the pro- */
/* filed command will be loaded, adjusts function addresses */
/* accordingly, starts timer interrupts, and executes the com- */
/* mand as a subshell. */
/****************************************************************/
static void profile()
{
int i = 0;
old_tmr_handler = GETVECT(TMRINT); /* Save old int vector */
SETVECT(TMRINT,tmr_handler); /* Start profiling */
DISABLE();
FREEMEM(load_psp); /* Free the load area */
load_psp = 0; /* To not free it at exit */
ENABLE();
while(i++ < exe_count)
{
fprintf(stderr, "%-3d ----- Executing %s\n", i, *cmdline);
if (spawnv(P_WAIT, *cmdline, cmdline) == -1)
{
switch(errno)
{
case ENOEXEC: error(7,msgs[13], exename);
break;
case ENOMEM: error(6,msgs[11]);
break;
default: error(8,msgs[12]);
} /* switch */
} /* if */
} /* switch */
SETVECT(TMRINT,old_tmr_handler);
(char *) old_tmr_handler = NULL;
DISABLE();
FREEMEM(freemem_pg);
freemem_pg = 0;
ENABLE();
fprintf(stderr, "--------- Executing completed\n");
} /* profile */
/****************************************************************/
/* Write_result() sorts the data, computes profiling percen- */
/* tages, and write out the resulting list. */
/****************************************************************/
static void write_result()
{
int i;
if (name_ordr) /* Sort table by name? */
qsort(descs,nfuncs,sizeof(fdesc),fnam_comp);/* Sort in name order */
else
if (!addr_ordr)
qsort(descs,nfuncs,sizeof(fdesc),fhit_comp);/* Sort in freq order */
if ((file = fopen(outname,"w")) == NULL) /* Possible if command did */
error(4, msgs[10], outname); /* something like chmod -w */
for (i = 0; i < nfuncs; i++) /* Add up total hit counts */
{
tot_hits += (double) ((descs + i)->hits); /* Add upp total hits */
if ((descs + i)->typ & wrt_msk)
dsp_hits += (double) ((descs + i)->hits); /* Add upp displayed hits */
if ((descs + i)->typ & USRTYP)
usr_hits += (double) ((descs + i)->hits); /* Add up user hits */
} /* for */
if (tot_hits == 0.0) /* Avoid div by 0.0 */
tot_hits = 1.0;
if (dsp_hits == 0.0) /* Avoid div by 0.0 */
{
if (!tell_psp)
error(9,msgs[15]);
else
dsp_hits = 1.0;
} /* if */
if (usr_hits == 0.0) /* Avoid div by 0.0 */
usr_hits = 1.0;
fprintf(file, "Function name Addr Total Disp User\n\n");
for (i = 0; i < nfuncs; i++)
{
if (((descs + i)->hits == 0) && /* Don't show 0 hit funcs */
!(list_nulls || tell_psp))
continue;
if (wrt_msk & (descs +i)->typ)
{
if ((descs + i)->name[0] == 1) /* Reconvert fixes done */
(descs + i)->name[0] = '_'; /* when reading the map */
if ((descs + i)->name[1] == 0x7f)
(descs + i)->name[1] = '_';
fprintf(file, "%-20s %05lx %6.2f %6.2f",
(descs + i)->name, (descs + i)->addr,
100.0 * ((descs + i)->hits / tot_hits),
100.0 * ((descs + i)->hits / dsp_hits));
if ((descs + i)->typ & USRTYP) /* Only usrfuncs get col 3 */
fprintf(file," %6.2f", 100.0 * ((descs + i)->hits / usr_hits));
fprintf(file,"\n");
} /* if */
} /* for */
fprintf(file, "\nStatistics based on %6.0f hits\n", tot_hits);
fclose(file);
file = (FILE *) NULL;
} /* write_result */
/****************************************************************/
/* Fadr_comp() compares addresses of two functions. If address */
/* values are the same, name decides. */
/****************************************************************/
static int fadr_comp(f1, f2)
fdesc *f1, *f2;
{
if (f1->addr > f2->addr)
return(1);
if (f1->addr < f2->addr)
return(-1);
return(fnam_comp(f1,f2));
} /* fadr_comp */
/****************************************************************/
/* Fhit_comp() compares hit counts of two function table */
/* entries. If counts are the same, name decides. */
/****************************************************************/
static int fhit_comp(f1, f2)
fdesc *f1, *f2;
{
if (f1->hits > f2->hits)
return(-1);
if (f1->hits < f2->hits)
return(1);
return(fnam_comp(f1,f2));
} /* fhit_comp */
/****************************************************************/
/* Fnam_comp() compares names of two function table entries. */
/****************************************************************/
static int fnam_comp(f1, f2)
fdesc *f1, *f2;
{
return (stricmp(f1->name, f2->name));
} /* fnam_comp */
/****************************************************************/
/* Usage() displays a usage menu. */
/****************************************************************/
static void usage()
{
fprintf(stderr,
"Usage: profile [-adin0] [-#<n>] [-m<map>] [-o<out>] <cmd> [<args>...]\n");
fprintf(stderr,
" -a Sort output by adress, not by frequency\n");
fprintf(stderr,
" -d Include DOS and BIOS areas in profiling\n");
fprintf(stderr,
" -i Include intrinsic functions (starting with `__', or\n");
fprintf(stderr,
" containing the characters `@' or '$') in profiling\n");
fprintf(stderr,
" -n Sort output by name, not by frequency\n");
fprintf(stderr,
" -0 List also functions that had zero frequency\n");
fprintf(stderr,
" -#<n> Execute profiled command <n> times (default 1)\n");
fprintf(stderr,
" -m<map> Use file <map> as linker map info (default <cmd>.MAP)\n");
fprintf(stderr,
" -o<out> Put output result in file <out> (default <cmd>.PRF)\n");
fprintf(stderr,
" <cmd> Name of command to be profiled (including path)\n");
fprintf(stderr,
" <args> Optional arguments to <cmd>\n");
} /* usage */
/****************************************************************/
/* Error() */
/* */
/* Print an error diagnosis and exit. */
/****************************************************************/
/*VARARGS*/
static void error(ercode, ermsg, s1, s2, s3, s4)
int ercode;
char *ermsg;
char *s1, *s2, *s3, *s4;
{
if ((char *) old_tmr_handler != NULL)
SETVECT(TMRINT,old_tmr_handler);
if (freemem_pg)
FREEMEM(freemem_pg);
if (load_psp)
FREEMEM(load_psp);
if (ercode != 0)
{
fprintf(stderr, msgs[0]);
fprintf(stderr, ermsg, s1, s2, s3, s4);
} /* if */
if (ercode == 1)
usage();
if (file != (FILE *) NULL)
fclose(file);
exit(ercode);
} /* error */
/****************************************************************/
/* Brk_handler() catches CTRL-C interrupts. */
/****************************************************************/
static int brk_handler()
{
CTRLBREAK(brk_handler);
error(255,"User abort\n");
return(0); /* Actually not executed */
} /* brk_handler */
/****************************************************************/
/* Tmr_handler() is the interrupt handler that updates hit */
/* counts. It must be fast. */
/****************************************************************/
#if TRC_2_0
static void interrupt tmr_handler(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs)
unsigned bp,di,si,ds,es,dx,cx,bx,ax,ip,cs;
#endif
#if MSC_5_1
static void interrupt tmr_handler(es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs)
unsigned es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs;
#endif
{
long addr;
int lower, upper, middle;
addr = ((unsigned long)cs << 4) + (unsigned long) ip;
lower = 0;
upper = nfuncs - 1;
while (upper - lower > 1)
{
middle = (lower + upper) / 2;
if ((descs + middle)->addr <= addr)
lower = middle;
else
upper = middle;
} /* while */
(descs + lower)->hits++;
(*old_tmr_handler)();
} /* tmr_handler */