/*

(C) Copyright 1988 Diomidis D. Spinellis. All rights reserved.

This profiler is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY.  No author or distributor accepts responsibility
to anyone for the consequences of using it or for whether it serves
any particular purpose or works at all, unless he says so in writing.
Refer to the profiler General Public License for full details.

Everyone is granted permission to copy, modify and redistribute
the profiler, but only under the conditions described in the
profiler General Public License.  A copy of this license is
supposed to have been given to you along with the profiler so you can
know your rights and responsibilities.  It should be in a file named
COPYING.  Among other things, the copyright notice and this notice
must be preserved on all copies.

In other words, go ahead and share the profiler, but don't try to stop
anyone else from sharing it farther.  Help stamp out software
hoarding!  

*/

/*
 * A C program profiler. 
 *
 * (C) Copyright 1988 Diomidis D. Spinellis. All rights reserved.
 *
 * $Header: PROF.C^v 1.1 88/11/20 17:33:16 dds Rel $
 *
 * $Log:	PROF.C^v $
 * Revision 1.1  88/11/20  17:33:16  dds
 * Initial revision
 * 
 *
 */

#include <stddef.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <dos.h>

/* Linker output maximum line length */
#define LINELEN 129
/* Linker output maximum symbol length */
#define STRLEN	65

/* Entries can be absolute or relocatable */
enum relocation { absolute, relocatable } ;

/* Function prototypes */
static void add_proc( char * name, unsigned long addr, enum relocation rel ) ;
static void adjust_proc( long main_offset ) ;
static void install_int( void ) ;
static int prof_end( void ) ;
static void * xmalloc( size_t size ) ;
static void * xrealloc( void * buffer, size_t size ) ;
static char * strsave( char * string ) ;
static void interrupt far timer_handler(int es,int ds,int di,int si,int bp,int sp,int bx,int dx,int cx,int ax,int ip,int cs,int flags) ;
void main( int argc, char *argv[] ) ;
#ifdef DEBUG
static void dump_table( void ) ;
static void test_search( void ) ;
static void disp(long n) ;
#endif

static char rcsid[] = "$Header: PROF.C^v 1.1 88/11/20 17:33:16 dds Rel $" ;

void 
prof_start( char * argv0 )
{
	FILE *f ;
	static char fname[65] ;
	static char line[LINELEN] ;
	static char str1[STRLEN], str2[STRLEN], str3[STRLEN], str4[STRLEN], str5[STRLEN] ;
	enum {searchsegs, scansegs, searchprocs, scanprocs } state ;
	static char *state_errs[] = {
		"start of segment definitions",
		"ENDCODE segment definition",
		"the ``Publics by Value'' line",
		"address greater than ENDCODE was found"
	} ;
	unsigned int seg, off ;
	unsigned long endcode ;
	int linenum = 0 ;
	unsigned long main_addr, addr;
	long main_offset = -1 ;
	void far * main_p ;

	/* Find the address of main to adjust everything else */
	main_p = (void far * )main ;
	main_addr = ( (unsigned long)FP_SEG( main_p ) << 4 ) + (unsigned long)FP_OFF( main_p ) ;
	#ifdef DEBUG
	printf("main=%08lx\n", main_addr ) ;
	#endif

	add_proc("DOS", 0l, absolute ) ;
	strcpy(fname, argv0 ) ;
	strcpy( strrchr( fname, '.' ), ".MAP" ) ;

	if( ( f = fopen( fname, "r" ) ) == NULL ){
		perror( fname ) ;
		exit( 1 ) ;
	}

	state = searchsegs ;
	while( fgets(line, LINELEN, f ) ){
		linenum++ ;
		switch( state ){
		case searchsegs :
			if( sscanf(line, " %s %s %s %s %s ", str1, str2, str3, str4, str5 ) == 5 && strcmp(str1, "Start" ) == 0 && strcmp( str2, "Stop" ) == 0 && strcmp( str3, "Length" ) == 0 && strcmp( str4, "Name" ) == 0 && strcmp( str5, "Class" ) == 0 )
				state = scansegs ;
				break ;
		case scansegs :
			if( sscanf(line, " %lxH %*lxH %*lxH %*s %s ", &endcode, str1 ) != 2 ){
				fprintf(stderr, "%s(%d) : Unable to parse line : %s\n", fname, linenum, line ) ;
				exit( 1 ) ;
			}
			if( strcmp( str1, "ENDCODE" ) == 0 )
				state = searchprocs ;
		case searchprocs :
			if( sscanf(line, " %s %s %s %s ", str1, str2, str3, str4) == 4 && strcmp(str1, "Address" ) == 0 && strcmp( str2, "Publics" ) == 0 && strcmp( str3, "by" ) == 0 && strcmp( str4, "Value" ) == 0 )
				state = scanprocs ;
				break ;
		case scanprocs :
			if( *line == '\n' || sscanf(line, " %x:%x Abs %s ", &seg, &off, str1 ) == 3 )
				break ;
			if( sscanf(line, " %x:%x %s ", &seg, &off, str1 ) != 3 ){
				fprintf(stderr, "%s(%d) : Unable to parse line : %s\n", fname, linenum, line ) ;
				exit( 1 ) ;
			}
			addr = ((unsigned long)seg << 4 ) + (unsigned long)off ;
			if( strcmp( str1, "_main" ) == 0 )
				main_offset = addr ;
			add_proc( str1, addr + main_addr, relocatable ) ;
			if( addr > endcode ){
				/*
				 * Add here in ascending order any important
				 * memory bounds. One idea would be to partition
				 * the BIOS in tasks e.g. printer, screen etc.
				 */
				add_proc( "UNKOWN", addr + main_addr + 1, relocatable ) ;
				add_proc( "EGA_BIOS", 0xc0000l, absolute ) ;
				add_proc( "FDISK_BIOS", 0xc8000l, absolute ) ;
				add_proc( "SYSTEM_ROM", 0xf0000l, absolute ) ;
				add_proc( "SYSTEM_BIOS", 0xfe000l, absolute ) ;
				add_proc( "OUTER_SPACE", (unsigned long)-1l, absolute ) ;
				fclose( f ) ;
				if( main_offset == -1 ){
					fputs("_main address not found\n", stderr ) ;
					exit(1) ;
				}
				adjust_proc( main_offset ) ;
				if( onexit( prof_end ) == NULL ){
					fputs("onexit failed\n", stderr ) ;
					exit( 1 ) ;
				}
				#ifdef DEBUG
				dump_table() ;
				test_search() ;
				#endif
				install_int() ;
				return  ;
			}
		}
	}
	/* Something went wrong */
	fprintf(stderr, "%s(%d) EOF reached before %s\n", fname, linenum, state_errs[state] ) ;
	exit( 1 ) ;
}

/* The structure where procedures are kept */
static struct proc_data {
	unsigned long addr ;			/* Procedure start address */
	unsigned long count ;			/* Hit count set by interrupt */
	char *name ;				/* Procedure name */
	enum relocation rel ;			/* Relocation type */
} *procs ;

/* Number of procedures and allocated memory */
static int procnum, procalloc ;

/* Size of memory allocation chunk */
#define BLK	30

/* Define a procedure */
static void 
add_proc( char * name, unsigned long addr, enum relocation rel )
{
	if( procs == NULL ){
		procs = xmalloc( sizeof( struct proc_data ) * BLK ) ;
		procalloc = BLK ;
	}
	procs[procnum].addr = addr ;
	procs[procnum].count = 0l ;
	procs[procnum].name = strsave( name ) ;
	procs[procnum].rel = rel ;
	procnum++ ;
	if( procnum >= procalloc ){
		procalloc += BLK ;
		procs = xrealloc( procs, sizeof( struct proc_data ) * procalloc ) ;
	}
}

/* 
 * Adjust downwards the memory allocated for procedure data storage 
 * and subtract main_offset.
 */
static void
adjust_proc( long main_offset )
{
	struct proc_data *pp ;

	xrealloc( procs, sizeof( struct proc_data ) * procnum ) ;
	for( pp = procs ; pp < &procs[procnum] ; pp++ )
		if( pp->rel == relocatable )
			pp->addr -= main_offset ;
}

/* Timer interrupt (Do not use 0x1c since it is always called from BIOS) */
#define TIMER_INT	8

/* Old timer handler to chain to */
static void (interrupt far * old_timer_handler)( void ) ;

/* Disable timer handler and print the profiling results */
static int
prof_end( void )
{
	register i ;
	FILE *f ;

	_dos_setvect( TIMER_INT, old_timer_handler ) ;
	if( ( f = fopen( "prof.out", "w" ) ) == NULL ){
		perror( "prof.out" ) ;
		return 1 ;
	}
	for( i = 0 ; i < procnum ; i++ )
		fprintf(f, "%s %ld\n", procs[i].name, procs[i].count ) ;
	fclose( f ) ;
	return 0 ;
}

/* Allocate memory with error checking. */
static void * 
xmalloc( size_t size )
{
	void * p ;

	if( ( p = malloc( size ) ) == NULL ){
		fputs("Profiler : Out of memory\n", stderr ) ;
		exit( 1 ) ;
	}
	return p ;
}

/* Reallocate memory with error checking.  */
static void * 
xrealloc( void * buffer, size_t size )
{
	void * p ;


	if( ( p = realloc( buffer, size ) ) == NULL ){
		fputs("Profiler : Out of memory\n", stderr ) ;
		exit( 1 ) ;
	}
	return p ;
}

/* Save a string in allocated memory */
static char * 
strsave( char * string )
{
	return strcpy( xmalloc( strlen( string ) + 1 ), string ) ;
}

/* The timer interrupt handler */
static void interrupt far 
timer_handler(es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs,flags)
{
	long addr ;
	int lower, upper, middle ;

	addr = ( (unsigned long)cs << 4 ) + (unsigned long)ip ;

	#ifdef DEBUG
	disp( addr ) ;
	#endif
	/* 
	 * Precondition : 
	 * { a[k] < a[k+1] & 0 <= k <= procnum & a[0] <= addr < a[procnum] }
	 */
	lower = 0 ;
	upper = procnum - 2 ;
	/*
	 * Invariant :
	 * { a[l] <= addr < a[u] }
	 * Variant :
	 * { u - l }
	 */
	while( upper - lower > 1 ){
		middle = ( lower + upper ) / 2 ;
		/*
		 * m = l + ( u - l ) / 2 = ( u + l ) / 2
		 * m = l + ( u - l ) / 2 & u - l > 1 implies l < m < u as:
		 * m = ( u + l ) / 2 < ( u + u ) / 2 = u implies m < u
		 * m = l + ( u - l ) / 2 >= l + 1 implies m > l
		 */
		if( procs[middle].addr <= addr )
			lower = middle ;
		else
			upper = middle ;
	}
	/*
	 * Postcondition :
	 * { a[f] <= addr < a[f + 1] } which can be expressed as:
	 * { a[l] <= addr < a[u] & u = l + 1 }
	 */
	procs[lower].count++ ;
	(*old_timer_handler)() ;
	/* Silence warnings */
	(void)(es,ds,di,si,bp,sp,bx,dx,cx,ax,ip,cs,flags) ;
}

/* Install the interrupt driver */
static void
install_int( void )
{
	old_timer_handler = _dos_getvect( TIMER_INT ) ;
	_dos_setvect( TIMER_INT, timer_handler ) ;
}

#ifdef DEBUG

#ifdef MDA
#define REGEN_BASE 0xb0000000
#else /* CGA */
#define REGEN_BASE 0xb8000000
#endif

/* Very fast display of a number on the screen. (Define MDA for mono adapter) */
static void
disp(long n)
{
	register i ;
	char far * sb = (char far *)(REGEN_BASE + 20 ) ;

	for( i = 0 ; i < 8 ; i++ ){
		*sb = "0123456789abcdef"[n % 16] ;
		n /= 16 ;
		sb -= 2 ;
	}
}

/* Test the binary search algorithm */
static void
pr_name( long addr )
{
	int lower, upper, middle ;

	/* 
	 * Precondition : 
	 * { a[k] < a[k+1] & 0 <= k <= procnum & a[0] <= addr < a[procnum] }
	 */
	lower = 0 ;
	upper = procnum - 2 ;
	/*
	 * Invariant :
	 * { a[l] <= addr < a[u] }
	 * Variant :
	 * { u - l }
	 */
	while( upper - lower > 1 ){
		middle = ( lower + upper ) / 2 ;
		/*
		 * m = l + ( u - l ) / 2 = ( u + l ) / 2
		 * m = l + ( u - l ) / 2 & u - l > 1 implies l < m < u as:
		 * m = ( u + l ) / 2 < ( u + u ) / 2 = u implies m < u
		 * m = l + ( u - l ) / 2 >= l + 1 implies m > l
		 */
		if( procs[middle].addr <= addr )
			lower = middle ;
		else
			upper = middle ;
		printf("%5d %5d %5d\n", lower, middle, upper ) ;
	}
	/*
	 * Postcondition :
	 * { a[f] <= addr < a[f + 1] } which can be expressed as:
	 * { a[l] <= addr < a[u] & u = l + 1 }
	 */
	puts( procs[lower].name ) ;
}

/* Interact with the user testing the search algorithm */
static void
test_search()
{
	char buff[80] ;
	long addr ;

	puts("Enter -1 to finish") ;
	do{
		gets(buff) ;
		sscanf(buff, " %lx ", &addr ) ;
		pr_name( addr ) ;
	} while( addr != -1l ) ;
}

/* Dump the procedure table */
static void
dump_table()
{
	struct proc_data *pd ;

	for( pd = procs ; pd < &procs[procnum] ; pd++ )
		printf("%08lx    %s\n", pd->addr, pd->name ) ;
}
#endif

