home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
GEMini Atari
/
GEMini_Atari_CD-ROM_Walnut_Creek_December_1993.iso
/
zip
/
mint
/
mntlib16.lzh
/
MNTLIB16
/
GMON.C
< prev
next >
Wrap
C/C++ Source or Header
|
1993-08-03
|
18KB
|
673 lines
/*
* monitor(3), mcount() and profil(2) clones for gcc-Tos library
*
* Note: these routines need tuning up. they are very space
* inefficient. the implementation is totally biased towards support
* for gprof rather than prof (does anyone use prof anymore? why?)
*
* ++jrb bammi@dsrgsun.ces.cwru.edu
*/
#include <stddef.h>
#include <memory.h>
#include <unistd.h>
#include <fcntl.h>
#include <assert.h>
#include <osbind.h>
#include <basepage.h>
#include <sysvars.h>
#include <xbra.h>
/* gmon header */
struct gm_header {
void *low; /* low pc */
void *high; /* hi pc */
unsigned long nbytes; /* bytes in header + histo size */
};
typedef unsigned short CHUNK; /* type of each histogram entry */
struct gm_call { /* gm call record */
void *from; /* the caller */
void *to; /* the called function (callee) */
unsigned long ncalls; /* # of calls from FROM to TO */
};
#define GMON_FILE "gmon.out" /* name of GMON file */
/* format of gmon file
* gm_header
* ((gm_header.nbytes - sizeof(gm_header))/sizeof(CHUNK)) histo entries
* gm_call records upto EOF
*/
/* histogram variables and defines */
#define HIST_SCALE 2 /* text space scaled into size/HIST_SCALE CHUNKS */
#define HIST_SHIFT 1 /* HIST_SCALE == 2 ** HIST_SHIFT (assumption) */
/* 1 <= HIST_SHIFT <= 8 (assumption) */
static CHUNK *hist_buffer; /* histogram buffer */
static unsigned long hist_size; /* size of histogram in bytes */
/* call graph variables and defines */
typedef struct { /* a to chain element */
void *selfpc; /* the callee's pc */
unsigned long count; /* number of times called */
unsigned short link; /* link to next in chain (an index) */
} tostruct ;
tostruct *tos; /* pool of to chain elements */
unsigned short *froms; /* called from hash chain heads (an index) */
/* inherent assumption: typeof froms == typeof CHUNK, otherwise
change code in monstartup() */
#define MINARCS 64 /* min # of to's, a rand() # */
#define ARCDENSITY 2 /* scaling of to's (as a % of textsize) */
#define HASHFRACTION 1 /* scaling of froms over textsize.
note this is very memory wasteful,
but the alternatives are worse
beacuse of two reasons:
- increase compute requirements(in mcount)
- bsr, followed by bsr will loose!
the coding of mcount below almost
assumes that HASHFRACTION==1
anyone else have some brilliant ideas?
*/
#define HASH_SHIFT 0 /* HASHFRACTION = 2 ** HASH_SHIFT (assumption) */
/* housekeeping variables */
static long profiling; /* profiling flag */
static unsigned long textsize; /* size of profiled text area */
static unsigned short tolimit; /* max to == 65534, min == MINARCS */
static unsigned short toalloc; /* next free to record index */
static void *s_lowpc; /* low pc rounded down to multiples
of histo density =
(CHUNK size * HIST_SCALE)
(assumption: its mult of 2)
*/
#define USL(X) ((unsigned long)(X)) /* cast X to unsigned long */
/* round X down to last multiple of Y */ /* see assumption above */
#define ROUNDDOWN(X,Y) ( USL(X) & (~(USL((Y)-1))) )
/* round X up to next multiple of Y */
#define ROUNDUP(X,Y) ( USL((X)+((Y)-1)) & (~(USL((Y)-1))) )
/* functions */
#if __STDC__
#define _P(x) x
#else
#define _P(x)
#endif
void monstartup _P((void *lowpc, void *highpc));
void monitor _P((void *lowpc, void *highpc, void *buffer,
unsigned long bufsize, unsigned int nfunc));
void moncontrol _P((long flag));
void _mcleanup _P((void));
int profil _P((void *buff, unsigned long bufsiz, unsigned long offset,
int shift));
static void tick _P((void));
static void term _P((void));
static void install_handlers _P((void));
static void remove_handlers _P((void));
static void unlink_handler _P((xbra_struct *me, int exc));
static void build_graph _P((void *caller, void *callee));
#undef _P
/*
* allocate space for histogram and call graph given the sampling
* range. call monitor to start up profiling.
*/
void monstartup(lowpc, highpc)
void *lowpc, *highpc;
{
unsigned long monsize; /* size of hist buffer + gm_header rounded */
void *buf; /* hist + gm_header space */
unsigned long i;
assert(USL(lowpc) < USL(highpc));
#if 0 /* dont define: screws up gmon because of reloc assumptions */
s_lowpc = lowpc = (void *)
(ROUNDDOWN(USL(lowpc), sizeof(CHUNK)<<HIST_SHIFT ));
#else
s_lowpc = lowpc;
#endif
highpc = (void *)
(ROUNDUP(USL(highpc), sizeof(CHUNK)<<HIST_SHIFT ));
textsize = USL(highpc) - USL(lowpc);
/* allocate histogram buffer + gm_header buffer */
monsize = (textsize >> HIST_SHIFT) * sizeof(CHUNK) +
sizeof(struct gm_header);
monsize = ROUNDUP(monsize, sizeof(short));
if((buf = (CHUNK *)malloc(monsize)) == (CHUNK *)0)
{
Cconws("monitor: No memory for histogram buffer\r\n");
froms = (unsigned short *)0;
tos = (tostruct *)0;
return;
}
/* allocate space for graph data structs */
i = (textsize>>HASH_SHIFT) * sizeof(*froms);
i = ROUNDUP(i, sizeof(long));
if((froms = (unsigned short *)malloc(i)) == (unsigned short *)0)
{
Cconws("monitor: No memory for FROMs\r\n");
free(buf);
tos = (tostruct *)0;
return;
}
bzero(froms, i);
i = textsize * ARCDENSITY / 100;
if( i < MINARCS)
i = MINARCS;
else if ( i > 65534)
i = 65534;
tolimit = (unsigned short)i;
i = ROUNDUP(i*sizeof(tostruct), sizeof(long));
if((tos = (tostruct *)malloc(i)) == (tostruct *)0)
{
Cconws("monitor: No memory for TOs pool\r\n");
free(froms);
free(buf);
froms = (unsigned short *)0;
return;
}
bzero(tos, i);
toalloc = 0; /* index of next available element in TOs pool */
monitor(lowpc, highpc, buf, monsize, (unsigned int)tolimit);
}
/*
* monitor(3) interface to profil(2)
* last arg is silly and not used
*/
void monitor(lowpc, highpc, buffer, bufsize, nfunc)
void *lowpc, *highpc, *buffer;
unsigned long bufsize;
unsigned int nfunc;
{
struct gm_header *hdr;
if(lowpc == 0)
{ /* finished */
moncontrol(0L);
_mcleanup();
return;
}
s_lowpc = lowpc; /* in case user is calling */
/* initialize gm_header */
hdr = (struct gm_header *)buffer;
hdr->low = lowpc;
hdr->high = highpc;
hdr->nbytes = bufsize;
hist_size = bufsize - sizeof(struct gm_header); /* sizof hist buffer */
hist_buffer = (CHUNK *)(USL(buffer) + sizeof(struct gm_header));
/* integ. check, (user can call monitor) */
if((hist_size == 0) ||
(hist_size <
(((USL(highpc) - USL(lowpc))>>HIST_SHIFT)*sizeof(CHUNK))) )
{
return;
}
/* note: difference in scaling semantics from unix */
moncontrol(1L); /* begin */
}
/*
* control profiling
*/
void moncontrol(flag)
long flag;
{
if(flag)
{ /* start */
profil(hist_buffer, hist_size, (unsigned long)s_lowpc, HIST_SHIFT);
profiling = 0;
}
else
{
/* stop */
profil((void *)0, 0L, 0L, 0);
profiling = 3;
}
}
/*
* mcount
* called as a part of the entry prologue of a profiled function.
* the function that calls mcount is the CALLEE, and the function
* that called the CALLEE is the CALLER. mcount grabs the
* address of the CALLEE and the address of the CALLER off the
* stack, and then calls build_graph that incrementally
* constructs the call graphs in the FROMs and TOs structures,
* keeping track of the number of times the CALLER called CALLEE.
* on entry the stack look like:
*
* sp-> | ret address of CALLEE |
* |--------------------------|
* . CALLEE's locals .
* .__________________________.
* CALLEEs fp-> | CALLERS saved fp |
* |--------------------------|
* | ret address of CALLER |
* |--------------------------|
*
* Note:
* -this is true becuase -fomit-frame-pointer and -pg are
* incompatible flags (gcc will say so if you try)
*
* -on the 68k, the address of a long count location is passed in a0
* we dont use this, it was a convention for the old prof stuff.
*/
__asm__("\
.text; .even
.globl mcount /* note: no `_' */
mcount:
movl sp@,d0 /* CALLEE's address */
movl d0,sp@-
movl a6@(4),d0 /* CALLERs address */
movl d0,sp@-
jbsr _build_graph /* build_graph(caller, callee) */
addqw #8,sp
rts
");
/*
* build_graph
* incrementally build the call graph. at each call the CALLER
* and CALLEE are specified. this function builds an arc from
* CALLER to CALLEE, or increments the arc count if it already
* exists.
* the graph is maintianed in the structures FROMs and TOs. each
* entry in FROMs is the head of a chain of records of all
* functions called from FROM. The CALLERs address is hashed
* into FROMs
*/
static void build_graph(caller, callee)
void *caller, *callee;
{
unsigned short *fromp; /* hashed ptr into froms */
tostruct *top; /* current hash chain element */
unsigned short ti; /* index of current chain element */
tostruct *last; /* previous element */
if(profiling)
return; /* out if we are not profiling or this is a recursive call */
profiling++;
/* hash callee, to a pointer into FROMs */
fromp = (unsigned short *)(USL(caller) - USL(s_lowpc)); /* lowpc orig */
if(USL(fromp) > textsize)
{ /* not within profiled text area */
profiling--;
return;
}
/* scale to an index */
fromp = (unsigned short *)(USL(fromp) >> (HASH_SHIFT + sizeof(*froms)));
fromp = &froms[((long)fromp)]; /* hash bucket pointer */
ti = *fromp; /* head of the chain */
if(ti == 0)
{ /* head is null, first time in the bucket, start a new chain */
if((ti = ++toalloc) >= tolimit) /* allocate an element from tos pool */
{ /* ran out */
profiling = 3; /* give up profiling */
return;
}
*fromp = ti;
top = &tos[ti];
top->selfpc = callee;
top->count = 1;
top->link = 0;
profiling--;
return;
}
/* otherwise search the chain */
for(last = top = &tos[ti]; top->link != 0; last = top,
top = &tos[top->link])
{
if(top->selfpc == callee)
{ /* found it */
top->count++; /* increment call count */
if(top == last)
{ /* at the head of the chain already */
profiling--;
return;
}
/* otherwise bring it to the head */
ti = last->link;
last->link = top->link;
top->link = *fromp;
*fromp = ti;
profiling--;
return;
}
}
/* not found */
if((ti = ++toalloc) >= tolimit) /* allocate an element from tos pool */
{ /* ran out */
profiling = 3; /* give up profiling */
return;
}
/* put it at head of the chain */
top = &tos[ti];
top->count = 1;
top->selfpc = callee;
top->link = *fromp;
*fromp = ti;
profiling--;
}
/*
* _mcleanup
* dump out the gmon file
*/
void _mcleanup()
{
int fd;
unsigned long i;
unsigned short j;
unsigned long frompc;
struct gm_call arc;
if((fd = open(GMON_FILE, O_WRONLY|O_CREAT|O_TRUNC, 0644)) < 0)
{
Cconws(GMON_FILE); Cconws(": error opening\r\n");
return;
}
/* dump the header + histogram */
if(_write(fd, (void *)(USL(hist_buffer) - sizeof(struct gm_header)),
hist_size + sizeof(struct gm_header)) !=
(hist_size + sizeof(struct gm_header)) )
{
Cconws(GMON_FILE); Cconws(": error writing\r\n");
close(fd); return;
}
/* dump the call graph */
for( i = 0; i < (textsize >> (HASH_SHIFT + sizeof(*froms))); i++)
{
if(froms[i] != 0)
{
frompc = USL(s_lowpc) + ( i << (HASH_SHIFT + sizeof(*froms)));
for(j = froms[i]; j != 0; j = tos[j].link)
{
arc.from = (void *)frompc;
arc.to = tos[j].selfpc;
arc.ncalls = tos[j].count;
if(_write(fd, &arc, sizeof(arc)) != sizeof(arc))
{
Cconws(GMON_FILE); Cconws(": error writing\r\n");
close(fd); return;
}
}
}
}
close(fd);
}
#ifdef _USE_TIMER_C_
static unsigned short countdown;
#endif
static short installed = 0; /* reset to 0 before exit */
static unsigned short *bufr;
static unsigned long maxidx;
static unsigned long off;
static unsigned long shift_val;
static xbra_struct tick_xbra = _XBRA_INIT(tick);
static xbra_struct term_xbra = _XBRA_INIT(term);
extern BASEPAGE *_base;
static BASEPAGE **act_pd;
static BASEPAGE *my_base;
/*
* profil
* record pc every N ms into buffer
* index into buffer == (pc - offset) >> shift
* (note difference in scaling semantics)
* turned off by shift == 0
* ineffective by bufsiz == 0
*
* on the St, we hook into the Timer C interrupt, and record
* every 4'th tick (20 ms).
* this method was chosen over user Timer A, so that applications
* that use the timer can be profiled.
* vbl was not considered because we dont have the flexibility
* of changing the time resolution, and because its frequency is
* screen rez dependent (plus its harder to get at the user pc!)
*
* xbra protocol to hook in/out handlers. we hook into the terminate
* vector independent of the rest of the library to make sure we
* unhook before process termination. this is also necessary because
* the user can call up these routines independent of gcrt0
*/
int profil(buff, bufsiz, offset, shift)
void *buff;
unsigned long bufsiz, offset;
int shift;
{
if(shift == 0)
{
if(installed)
remove_handlers();
installed = 0;
return 0;
}
/* set the params atomically */
Jdisint(5);
#ifdef _USE_TIMER_C_
countdown = 4;
#endif
bufr = (unsigned short *)buff;
maxidx = bufsiz>>1; /* max index into short array */
off = offset;
shift_val = shift;
if(!installed)
{
installed = 1;
install_handlers();
}
Jenabint(5);
return 0;
}
#ifdef _USE_TIMER_C_
/*
* tick handler
* if countdown = 0, record pc
*/
__asm__ ("\
.text; .even
_tick:
subqw #1,_countdown
jne 1f
movw #4,_countdown
moveml d0-d1/a0,sp@-
movl sp@(14),d0 /* get user pc from exception frame */
subl _off,d0
jcs 2f /* branch if below */
movl _shift_val,d1 /* shift it */
lsrl d1,d0
cmpl _maxidx:l,d0 /* compare with max index */
jhi 2f /* branch if out of range */
lsll #1,d0 /* word index */
movl _bufr,a0
addl d0,a0 /* incr hist word */
addqw #1,a0@
2:
moveml sp@+,d0-d1/a0
1:
movl _tick_xbra+8,sp@-
rts ");
#else
/*
* tick handler
* in etv_timer timer handoff vector chain (called every 4th tick)
* stack at this point:
* <exception frame user pc, sr> 2
* <saved d0-d7/a0-a6> 60
* <timer calibration .w> 2
* <return address to timer C intr routine> 4
* ---
* 68 (offset to user pc)
*/
__asm__ ("\
.text; .even
_tick:
movl sp@(68),d0 /* get user pc from exception frame */
subl _off,d0
jcs 1f /* branch if below */
movl _shift_val,d1 /* shift it */
lsrl d1,d0
cmpl _maxidx:l,d0 /* compare with max index */
jhi 1f /* branch if out of range */
lsll #1,d0 /* word index */
movl _bufr,a0
addl d0,a0 /* incr hist word */
addqw #1,a0@
1:
movl _tick_xbra+8,sp@- /* call next handler in chain */
rts ");
#endif
/*
* terminate vector
*/
static void term()
{
/* validate process id */
if(_base != *act_pd)
{
__asm__ volatile("\
unlk a6
jmp %0@"
:
: "a"(term_xbra.next));
}
if(installed)
remove_handlers();
/* go on to the next guy */
__asm__ volatile("\
unlk a6
jmp %0@"
:
: "a"(term_xbra.next));
}
/*
* install tick and terminate handlers at the head of the xbra chains
* coding thanks to edgar roeder
*/
static void install_handlers()
{
long *sysbase;
sysbase = (long *) get_sysvar((void *) _sysbase);
switch(sysbase[6])
{
case 0x11201985L:
case 0x02061986L:
case 0x04241986L:
act_pd = (BASEPAGE **) 0x602C;
break;
default:
act_pd = (BASEPAGE **) sysbase[10];
}
#ifdef _USE_TIMER_C_
tick_xbra.next = (xptr) Setexc(276>>2, _XBRA_VEC(tick_xbra));
#else
tick_xbra.next = (xptr) Setexc(0x100, _XBRA_VEC(tick_xbra));
#endif
term_xbra.next = (xptr) Setexc(0x102, _XBRA_VEC(term_xbra));
my_base = _base;
}
/*
* unlink a handler in a xbra friendly manner from the exc chain
*/
static void unlink_handler(me, exc)
xbra_struct *me;
int exc;
{
xbra_struct *this, *prev;
long save_ssp;
this = (xbra_struct *) /* get head of chain */
((unsigned long)Setexc(exc, -1L) - offsetof(xbra_struct, jump));
if(this == me)
{ /* at the head, just unlink */
Setexc(exc, me->next);
return;
}
/* otherwise find me in the chain and unlink */
save_ssp = Super(0L);
for(prev = this; this && (this != me); prev = this, this = this->next)
{
/* validate the xbra */
if(this->xbra_magic != _XBRA_MAGIC)
{ /* shame on you */
Super(save_ssp);
Setexc(exc, me->next); /* nuke it, otherwise it may call ME */
return; /* after i am deinstalled */
}
}
if(this == me)
{ /* unlink me from middle of the chain */
prev->next = this->next;
Super(save_ssp);
return;
}
/* we are screwed */
Super(save_ssp);
Cconws("\r\nwhat the fuck!\r\n\n");
}
static void remove_handlers()
{
/* first validate pid */
if(_base == *act_pd)
{
if(_base != my_base) /* in vfork()ed parallel addr space */
_base -= 2;
else
{
/* do i need to Super and raise IPL here ?? */
#ifdef _USE_TIMER_C_
unlink_handler(&tick_xbra, 276>>2);
#else
unlink_handler(&tick_xbra, 0x100);
#endif
unlink_handler(&term_xbra, 0x102);
installed = 0;
}
}
}