v08i002: A C execution profiler for MS-DOS

Brandon S. Allbery - comp.sources.misc allbery at uunet.UU.NET
Sun Aug 20 10:43:41 AEST 1989


Posting-number: Volume 8, Issue 2
Submitted-by: diomidis at ecrcvax.UUCP ("Diomidis Spinellis")
Archive-name: prof.msc

[...so how about a Turbo C version?  ++bsa]

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	README
#	Makefile
#	prof.c
#	profprt.c
#	test.c
# This archive created: Mon Aug 14 16:46:48 1989
export PATH; PATH=/bin:$PATH
if test -f 'README'
then
	echo shar: will not over-write existing file "'README'"
else
sed 's/^X//' << \SHAR_EOF > 'README'
XIncluded here is the source for an execution profiling system that can
Xbe used with the Microsoft C or the Microsoft Quick C compiler.  It
Xgives the time percentage a program spends in different functions.  It
Xis an indispensable tool when trying to optimise a program.  The system
Xhas been tested with Microsoft C Version 5.00.
X
XIn order to use the profiler first create the object files sprof.obj,
Xlprof.obj, mprof.obj and cprof.obj by typing ``make''.  The makefile
Xsupplied expects a Un*x compatible linker, so if you are using the one
Xthat came with the compiler do the compilations by hand.
X
XCompile your program in such a way as to create a linker map file.  To
Xdo this link your program with the -Fm option of cl or the /MAP option
Xof the linker.  The appropriate [slmc]prof.obj module has to be linked
Xtogether with the rest of the program.  The first letter of the module
Xindicates the memory model in use.  The program should call the
Xfunction prof_start( argv[0] ) for versions of MS-DOS above or equal to
X3.00 or prof_start( .map file name ) for MS-DOS versions before 3.00 in
Xorder to start profiling. When the program finishes the profiler
Xautomatically produces a prof.out file that contains the names of all
Xpublic symbols and the number of hits for each one.
X
XYou can read the results directly from the prof.out file, or you can
Xsummarize them using profprt.  Profprt reads the prof.out file (or
Xanother file if specified) and produces a list of hits and percentages
Xfor the functions for which hits were recorded. If given a -h option it
Xalso produces a histogram of the relative timings.
X
XA small test program is included to check the profiler functioning. It
Xgenerally found the profiler results to be within 1% of the expected
Xresults on an 8MHz PC.
X
XThe profiler is all written in C utilizing the ability to create
Xinterrupt handlers in C. It finds the addresses of the functions from
Xthe linker map file.  It should not be very hard to modify the source
Xfor other compilers.  Keep in mind that functions declared as static
Xare not included in the map file, thus will not be profiled and plan
Xaccordingly.  One solution is to compile with -Dstatic= if the naming
Xscheme used allows it.  Pay attention to quantization errors and to
Xerrors due to incorrectly specified boundaries. Portions of the code
Xthat are executed with interrupts disabled will not be profiled.
X
XDiomidis D. Spinellis (dds at cc.ic.ac.uk)
XMyrsinis 1
XGR-145 62 Kifisia
XGREECE
SHAR_EOF
if test 2465 -ne "`wc -c < 'README'`"
then
	echo shar: error transmitting "'README'" '(should have been 2465 characters)'
fi
fi # end of overwriting check
if test -f 'Makefile'
then
	echo shar: will not over-write existing file "'Makefile'"
else
sed 's/^X//' << \SHAR_EOF > 'Makefile'
X#
X# Makefile for the profiler
X#
X# (C) Copyright 1988, 1989 Diomidis D. Spinellis. All rights reserved.
X# See the file profprt.c for distribution details.
X#
X# You need a Uni*x compatible make program to use this makefile. 
X# Microsoft make will probably not work.
X
X# Debugging flags
X#CFLAGS=-Od -W3 -qc -Zi -DDEBUG
X# Production flags
XCFLAGS=-Ox -W3
X
XCC=cl
X#CC=qcl
X
Xall: lprof.obj mprof.obj cprof.obj sprof.obj profprt.exe
X
Xtest.exe: test.obj sprof.obj
X	$(CC) -Fm $(CFLAGS) test.obj sprof.obj
X
Xlprof.obj: prof.c
X	$(CC) -AL $(CFLAGS) -c -Folprof.obj prof.c
X
Xcprof.obj: prof.c
X	$(CC) -AC $(CFLAGS) -c -Focprof.obj prof.c
X
Xmprof.obj: prof.c
X	$(CC) -AM $(CFLAGS) -c -Fomprof.obj prof.c
X
Xsprof.obj: prof.c
X	$(CC) -AS $(CFLAGS) -c -Fosprof.obj prof.c
X
Xtest.obj: test.c
X	$(CC) -AS -c test.c
X
Xprofprt.exe: profprt.c
X	$(CC) $(CFLAGS) profprt.c
X
Xtest: test.exe
X	test
X
Xinstall:
X	copy lprof.obj $$LIB
X	copy cprof.obj $$LIB
X	copy mprof.obj $$LIB
X	copy sprof.obj $$LIB
X
Xclean:
X	rm -f *.exe *.obj *.map prof.out
X
Xshar:
X	shar -pX -c README Makefile prof.c profprt.c test.c >prof.shar
SHAR_EOF
if test 1066 -ne "`wc -c < 'Makefile'`"
then
	echo shar: error transmitting "'Makefile'" '(should have been 1066 characters)'
fi
fi # end of overwriting check
if test -f 'prof.c'
then
	echo shar: will not over-write existing file "'prof.c'"
else
sed 's/^X//' << \SHAR_EOF > 'prof.c'
X/*
X * A C program profiler. 
X *
X * (C) Copyright 1988, 1989 Diomidis D. Spinellis. All rights reserved.
X * 
X * Redistribution and use in source and binary forms are permitted
X * provided that the above copyright notice and this paragraph are
X * duplicated in all such forms and that any documentation,
X * advertising materials, and other materials related to such
X * distribution and use acknowledge that the software was developed
X * by Diomidis Spinellis.
X * 
X * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
X * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
X * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
X *
X * Author: Diomidis D. Spinellis (dds at cc.ic.ac.uk)
X *	   Myrsinis 1
X *	   GR-145 62 Kifisia
X *	   GREECE
X *
X * $Header: PROF.C^v 1.1 88/11/20 17:33:16 dds Rel $
X *
X * $Log:	PROF.C^v $
X * Revision 1.1  88/11/20  17:33:16  dds
X * Initial revision
X * 
X */
X
X#include <stddef.h>
X#include <stdlib.h>
X#include <stdio.h>
X#include <string.h>
X#include <dos.h>
X
X/* Linker output maximum line length */
X#define LINELEN 129
X/* Linker output maximum symbol length */
X#define STRLEN	65
X
X/* Entries can be absolute or relocatable */
Xenum relocation { absolute, relocatable };
X
X/* Function prototypes */
Xstatic void add_proc(char * name, unsigned long addr, enum relocation rel);
Xstatic void adjust_proc(long main_offset);
Xstatic void install_int(void);
Xstatic int prof_end(void);
Xstatic void * xmalloc(size_t size);
Xstatic void * xrealloc(void * buffer, size_t size);
Xstatic char * strsave(char * string);
Xstatic void interrupt far timer_handler(int es,int ds,int di,int si,int bp,
X		int sp,int bx,int dx,int cx,int ax,int ip,int cs,int flags);
Xvoid main(int argc, char *argv[]);
X#ifdef DEBUG
Xstatic void dump_table(void);
Xstatic void test_search(void);
Xstatic void disp(long n);
X#endif
X
Xstatic char rcsid[] = "$Header: PROF.C^v 1.1 88/11/20 17:33:16 dds Rel $";
X
Xvoid 
Xprof_start(char * argv0)
X{
X	FILE *f;
X	static char fname[65];
X	static char line[LINELEN];
X	static char str1[STRLEN], str2[STRLEN], str3[STRLEN], str4[STRLEN], 
X		    str5[STRLEN];
X	enum {searchsegs, scansegs, searchprocs, scanprocs } state;
X	static char *state_errs[] = {
X		"start of segment definitions",
X		"ENDCODE segment definition",
X		"the ``Publics by Value'' line",
X		"address greater than ENDCODE was found"
X	};
X	unsigned int seg, off;
X	unsigned long endcode;
X	int linenum = 0;
X	unsigned long main_addr, addr;
X	long main_offset = -1;
X	void far * main_p;
X
X	/* Find the address of main to adjust everything else */
X	main_p = (void far *)main;
X	main_addr = ((unsigned long)FP_SEG(main_p) << 4) + 
X		     (unsigned long)FP_OFF(main_p);
X	#ifdef DEBUG
X	printf("main=%08lx\n", main_addr);
X	#endif
X
X	add_proc("DOS", 0l, absolute);
X	strcpy(fname, argv0);
X	strcpy(strrchr(fname, '.'), ".MAP");
X
X	if ((f = fopen(fname, "r")) == NULL) {
X		perror(fname);
X		exit(1);
X	}
X
X	state = searchsegs;
X	while (fgets(line, LINELEN, f)) {
X		linenum++;
X		switch (state) {
X		case searchsegs :
X			if (sscanf(line, " %s %s %s %s %s ", 
X				   str1, str2, str3, str4, str5) == 5 && 
X				   strcmp(str1, "Start") == 0 && 
X				   strcmp(str2, "Stop") == 0 && 
X				   strcmp(str3, "Length") == 0 && 
X				   strcmp(str4, "Name") == 0 && 
X				   strcmp(str5, "Class") == 0)
X				state = scansegs;
X			break;
X		case scansegs :
X			if (sscanf(line, " %lxH %*lxH %*lxH %*s %s ", 
X				   &endcode, str1) != 2) {
X				fprintf(stderr, 
X					"%s(%d) : Unable to parse line : %s\n", 
X					fname, linenum, line);
X				exit(1);
X			}
X			if (strcmp(str1, "ENDCODE") == 0)
X				state = searchprocs;
X			break;
X		case searchprocs :
X			if (sscanf(line, " %s %s %s %s ", str1, str2, str3, 
X				   str4) == 4 && 
X				   strcmp(str1, "Address") == 0 && 
X				   strcmp(str2, "Publics") == 0 && 
X				   strcmp(str3, "by") == 0 && 
X				   strcmp(str4, "Value") == 0)
X				state = scanprocs;
X			break;
X		case scanprocs :
X			if (*line == '\n' || sscanf(line, " %x:%x Abs %s ", 
X						    &seg, &off, str1) == 3)
X				break;
X			if (sscanf(line, " %x:%x %s ", &seg, &off, str1) != 3) {
X				fprintf(stderr, 
X					"%s(%d) : Unable to parse line : %s\n", 
X					fname, linenum, line);
X				exit(1);
X			}
X			addr = ((unsigned long)seg << 4) + (unsigned long)off;
X			if (strcmp(str1, "_main") == 0)
X				main_offset = addr;
X			add_proc(str1, addr + main_addr, relocatable);
X			if (addr > endcode) {
X				/*
X				 * Add here in ascending order any important
X				 * memory bounds. One idea would be to partition
X				 * the BIOS in tasks e.g. printer, screen etc.
X				 */
X				add_proc("UNKOWN", addr + main_addr + 1, 
X					  relocatable);
X				add_proc("EGA_BIOS", 0xc0000l, absolute);
X				add_proc("FDISK_BIOS", 0xc8000l, absolute);
X				add_proc("SYSTEM_ROM", 0xf0000l, absolute);
X				add_proc("SYSTEM_BIOS", 0xfe000l, absolute);
X				add_proc("OUTER_SPACE", (unsigned long)-1l, 
X					  absolute);
X				fclose(f);
X				if (main_offset == -1) {
X					fputs("_main address not found\n", 
X					      stderr);
X					exit(1);
X				}
X				adjust_proc(main_offset);
X				if (onexit(prof_end) == NULL) {
X					fputs("onexit failed\n", stderr);
X					exit(1);
X				}
X				#ifdef DEBUG
X				dump_table();
X				test_search();
X				#endif
X				install_int();
X				return ;
X			}
X		}
X	}
X	/* Something went wrong */
X	fprintf(stderr, "%s(%d) EOF reached before %s\n", fname, linenum, 
X		state_errs[state]);
X	exit(1);
X}
X
X/* The structure where procedures are kept */
Xstatic struct proc_data {
X	unsigned long addr ;			/* Procedure start address */
X	unsigned long count ;			/* Hit count set by interrupt */
X	char *name ;				/* Procedure name */
X	enum relocation rel ;			/* Relocation type */
X} *procs;
X
X/* Number of procedures and allocated memory */
Xstatic int procnum, procalloc;
X
X/* Size of memory allocation chunk */
X#define BLK	30
X
X/* Define a procedure */
Xstatic void 
Xadd_proc(char * name, unsigned long addr, enum relocation rel)
X{
X	if (procs == NULL) {
X		procs = xmalloc(sizeof(struct proc_data) * BLK);
X		procalloc = BLK;
X	}
X	procs[procnum].addr = addr;
X	procs[procnum].count = 0l;
X	procs[procnum].name = strsave(name);
X	procs[procnum].rel = rel;
X	procnum++;
X	if (procnum >= procalloc) {
X		procalloc += BLK;
X		procs = xrealloc(procs, sizeof(struct proc_data) * 
X				  procalloc);
X	}
X}
X
X/* 
X * Adjust downwards the memory allocated for procedure data storage 
X * and subtract main_offset.
X */
Xstatic void
Xadjust_proc(long main_offset)
X{
X	struct proc_data *pp;
X
X	xrealloc(procs, sizeof(struct proc_data) * procnum);
X	for (pp = procs ; pp < &procs[procnum] ; pp++)
X		if (pp->rel == relocatable)
X			pp->addr -= main_offset;
X}
X
X/* Timer interrupt (Do not use 0x1c since it is always called from BIOS) */
X#define TIMER_INT	8
X
X/* Old timer handler to chain to */
Xstatic void (interrupt far * old_timer_handler)(void);
X
X/* Disable timer handler and print the profiling results */
Xstatic int
Xprof_end(void)
X{
X	register i;
X	FILE *f;
X
X	_dos_setvect(TIMER_INT, old_timer_handler);
X	if ((f = fopen("prof.out", "w")) == NULL) {
X		perror("prof.out");
X		return 1;
X	}
X	for (i = 0 ; i < procnum ; i++)
X		fprintf(f, "%s %ld\n", procs[i].name, procs[i].count);
X	fclose(f);
X	return 0;
X}
X
X/* Allocate memory with error checking. */
Xstatic void * 
Xxmalloc(size_t size)
X{
X	void * p;
X
X	if ((p = malloc(size)) == NULL) {
X		fputs("Profiler : Out of memory\n", stderr);
X		exit(1);
X	}
X	return p;
X}
X
X/* Reallocate memory with error checking.  */
Xstatic void * 
Xxrealloc(void * buffer, size_t size)
X{
X	void * p;
X
X
X	if ((p = realloc(buffer, size)) == NULL) {
X		fputs("Profiler : Out of memory\n", stderr);
X		exit(1);
X	}
X	return p;
X}
X
X/* Save a string in allocated memory */
Xstatic char * 
Xstrsave(char * string)
X{
X	return strcpy(xmalloc(strlen(string) + 1), string);
X}
X
X/* The timer interrupt handler */
Xstatic void interrupt far 
Xtimer_handler(es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs,flags)
X{
X	long addr;
X	int lower, upper, middle;
X
X	addr = ((unsigned long)cs << 4) + (unsigned long)ip;
X
X	#ifdef DEBUG
X	disp(addr);
X	#endif
X	/* 
X	 * Precondition : 
X	 * { a[k] < a[k+1] & 0 <= k <= procnum & a[0] <= addr < a[procnum] }
X	 */
X	lower = 0;
X	upper = procnum - 2;
X	/*
X	 * Invariant :
X	 * { a[l] <= addr < a[u] }
X	 * Variant :
X	 * { u - l }
X	 */
X	while (upper - lower > 1) {
X		middle = (lower + upper) / 2;
X		/*
X		 * m = l + (u - l) / 2 = (u + l) / 2
X		 * m = l + (u - l) / 2 & u - l > 1 implies l < m < u as:
X		 * m = (u + l) / 2 < (u + u) / 2 = u implies m < u
X		 * m = l + (u - l) / 2 >= l + 1 implies m > l
X		 */
X		if (procs[middle].addr <= addr)
X			lower = middle;
X		else
X			upper = middle;
X	}
X	/*
X	 * Postcondition :
X	 * { a[f] <= addr < a[f + 1] } which can be expressed as:
X	 * { a[l] <= addr < a[u] & u = l + 1 }
X	 */
X	procs[lower].count++;
X	(*old_timer_handler)();
X	/* Silence warnings */
X	(void)(es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs,flags);
X}
X
X/* Install the interrupt driver */
Xstatic void
Xinstall_int(void)
X{
X	old_timer_handler = _dos_getvect(TIMER_INT);
X	_dos_setvect(TIMER_INT, timer_handler);
X}
X
X#ifdef DEBUG
X
X/* Very fast display of a number on the screen. (Define MDA for mono adapter) */
X
X#ifdef MDA
X#define REGEN_BASE 0xb0000000
X#else /* CGA */
X#define REGEN_BASE 0xb8000000
X#endif
X
Xstatic void
Xdisp(long n)
X{
X	register i;
X	char far * sb = (char far *)(REGEN_BASE + 20);
X
X	for (i = 0 ; i < 8 ; i++) {
X		*sb = "0123456789abcdef"[n % 16];
X		n /= 16;
X		sb -= 2;
X	}
X}
X
X/* Test the binary search algorithm */
Xstatic void
Xpr_name(long addr)
X{
X	int lower, upper, middle;
X
X	/* 
X	 * Precondition : 
X	 * { a[k] < a[k+1] & 0 <= k <= procnum & a[0] <= addr < a[procnum] }
X	 */
X	lower = 0;
X	upper = procnum - 2;
X	/*
X	 * Invariant :
X	 * { a[l] <= addr < a[u] }
X	 * Variant :
X	 * { u - l }
X	 */
X	while (upper - lower > 1) {
X		middle = (lower + upper) / 2;
X		/*
X		 * m = l + (u - l) / 2 = (u + l) / 2
X		 * m = l + (u - l) / 2 & u - l > 1 implies l < m < u as:
X		 * m = (u + l) / 2 < (u + u) / 2 = u implies m < u
X		 * m = l + (u - l) / 2 >= l + 1 implies m > l
X		 */
X		if (procs[middle].addr <= addr)
X			lower = middle;
X		else
X			upper = middle;
X		printf("%5d %5d %5d\n", lower, middle, upper);
X	}
X	/*
X	 * Postcondition :
X	 * { a[f] <= addr < a[f + 1] } which can be expressed as:
X	 * { a[l] <= addr < a[u] & u = l + 1 }
X	 */
X	puts(procs[lower].name);
X}
X
X/* Interact with the user testing the search algorithm */
Xstatic void
Xtest_search()
X{
X	char buff[80];
X	long addr;
X
X	puts("Enter -1 to finish");
X	do{
X		gets(buff);
X		sscanf(buff, " %lx ", &addr);
X		pr_name(addr);
X	} while (addr != -1l);
X}
X
X/* Dump the procedure table */
Xstatic void
Xdump_table()
X{
X	struct proc_data *pd;
X
X	for (pd = procs ; pd < &procs[procnum] ; pd++)
X		printf("%08lx    %s\n", pd->addr, pd->name);
X}
X#endif
SHAR_EOF
if test 10560 -ne "`wc -c < 'prof.c'`"
then
	echo shar: error transmitting "'prof.c'" '(should have been 10560 characters)'
fi
fi # end of overwriting check
if test -f 'profprt.c'
then
	echo shar: will not over-write existing file "'profprt.c'"
else
sed 's/^X//' << \SHAR_EOF > 'profprt.c'
X/*
X * Print an analysis of the profiler results.
X *
X * (C) Copyright 1988, 1989 Diomidis D. Spinellis. All rights reserved.
X * See the file profprt.c for distribution details.
X * 
X * The code contained herein is not optimal. It is given as a substitute
X * for a ten line awk script that had the same functionality.
X *
X * $Header: PROFPRT.C^v 1.1 88/11/20 17:36:12 dds Rel $
X *
X * $Log:	PROFPRT.C^v $
X * Revision 1.1  88/11/20  17:36:12  dds
X * Initial revision
X * 
X */
X
X#include <stddef.h>
X#include <stdlib.h>
X#include <stdio.h>
X
X
X/* Profiler output maximum line length */
X#define LINELEN 129
X
X/* Linker output maximum symbol length */
X#define STRLEN	65
X
Xchar *Argv0 ;
X
Xstatic char rcsid[] = "$Header: PROFPRT.C^v 1.1 88/11/20 17:36:12 dds Rel $" ;
X
Xstatic void usage(void) ;
X
X
Xstatic void
Xusage()
X{
X	fprintf(stderr, "Usage : %s [-h] [file]", Argv0);
X	exit(1);
X}
X
Xvoid
Xmain(int argc, char *argv[])
X{
X	FILE           *f;
X	long            t, total = 0, max = -1;
X	static char     line[LINELEN], str[STRLEN];
X	char           *fname = "prof.out";
X	int             histo = 0, fname_given = 0;
X	register        i;
X
X	Argv0 = argv[0];
X
X	while (argc > 1)
X		switch (*argv[1]) {
X		case '-':
X			if (argv[1][1] == 'h') {
X				histo++;
X				argc--;
X				argv++;
X			} else
X				usage();
X			break;
X		default:
X			if (fname_given)
X				usage();
X			else {
X				fname_given++;
X				fname = argv[1];
X				argc--;
X				argv++;
X			}
X			break;
X		}
X
X	if ((f = fopen(fname, "r")) == NULL) {
X		perror(fname);
X		exit(1);
X	}
X	for (i = 1; fgets(line, LINELEN, f); i++) {
X		if (sscanf(line, " %*s %ld ", &t) != 1) {
X			fprintf(stderr, "%s : Error in reading %s(%d)\n", 
X				Argv0, fname, i);
X			exit(1);
X		}
X		total += t;
X		if (t > max)
X			max = t;
X	}
X	if (total == 0) {
X		fprintf(stderr, "%s : No hits found\n", Argv0);
X		exit(1);
X	}
X	(void) rewind(f);
X	while (fgets(line, LINELEN, f)) {
X		(void) sscanf(line, " %s %ld ", str, &t);
X		if (t) {
X			printf("%-20s %5.2lf%% ", *str == '_' ? str + 1 : 
X			       str, (double) t / (double) total * 100.0);
X			if (histo)
X				for (i = 0; i < (int) ((double) t / 
X					    (double) max * 50); i++)
X					putchar('*');
X			putchar('\n');
X		}
X	}
X	exit(0);
X}
SHAR_EOF
if test 2156 -ne "`wc -c < 'profprt.c'`"
then
	echo shar: error transmitting "'profprt.c'" '(should have been 2156 characters)'
fi
fi # end of overwriting check
if test -f 'test.c'
then
	echo shar: will not over-write existing file "'test.c'"
else
sed 's/^X//' << \SHAR_EOF > 'test.c'
X/*
X * A quick and dirty test program. Just warms up the CPU.
X * Author: Diomidis D. Spinellis
X */
X
Xvolatile int q;
X
X#define proc(x,n) \
Xvoid \
Xx()\
X{\
X	long i;\
X	register j;\
X	for (i = 0; i < n; i++)\
X		for (j = 0; j < 15; j++)\
X			q = j;\
X}
X		
Xproc(a, 10000)
Xproc(b, 4000)
Xproc(c, 20000)
Xproc(d, 6000)
X
Xvoid
Xmain(int argc, char *argv[])
X{
X	prof_start(argv[0]);
X
X	a();
X	b();
X	c();
X	d();
X	a();
X	a();
X}
SHAR_EOF
if test 401 -ne "`wc -c < 'test.c'`"
then
	echo shar: error transmitting "'test.c'" '(should have been 401 characters)'
fi
fi # end of overwriting check
#	End of shell archive
exit 0



More information about the Comp.sources.misc mailing list