// // When __PROFILE is called, stack frame looks like this: // // sp+08 Caller's return address // sp+04 Address of function name string // sp Return address to start of function // // After we save registers and such, stack frame looks like this: // // sp+28 Caller's return address // sp+24 Address of function name string // sp+20 Return address to start of function // sp+16 saved A0 // sp+12 saved A1 // sp+08 saved D0 // sp+04 saved D1 // sp saved D2 // // Upon returning from __PROFILE, stack frame should look like this: // // sp Address of __PROFILEEND // // #include #include #include #include #include #pragma profile off #if defined(powerc) || defined(__powerc) extern void Profile_GetTime(UnsignedWide *time); #else #define Profile_GetTime(time) Microseconds(time) #endif #define THREAD_OFFSET 100 #define NAME_LENGTH 23 #define MAX_FUNCS 1000 #define MAX_THREADS 50 #define STACK_DEPTH 100 typedef struct { char name[NAME_LENGTH+1]; int calls; long address; UnsignedWide totalTime; } FunctionInfo; typedef struct { FunctionInfo *callerFunction; long returnAddress; } StackFrame; typedef struct { StackFrame stack[STACK_DEPTH]; StackFrame *stackPtr; FunctionInfo *currentFunction; UnsignedWide startTime; } ThreadInfo; static ThreadInfo gThread[MAX_THREADS]; static FunctionInfo gFunction[MAX_FUNCS]; static long gProfilerOn = false; static ThreadInfo *gCurrentThread; static FunctionInfo *gCallerFunction; static long gSaveArea[5]; static FunctionInfo *GetIndex(char *functionName:__A0, long address:__D0):__A0; static void StartTime(FunctionInfo *function:__A0, int call:__D0); static void StopTime(int index:__D0); static void GetThreadInfo(void); static void PushFrame(long returnAddress:__D0); static long PopFrame(void):__D0; static asm pascal void __PROFILEEND(void); #pragma parameter AddToWide(__A0, __A1) void AddToWide(UnsignedWide *a, UnsignedWide *b) = { 0x2011, // move.l 0(a1),d0 ;d0 = hi 0x2229, 0x0004, // move.l 4(a1),d1 ;d1 = lo 0x2410, // move.l 0(a0),d2 ;d2 = hi 0xD2A8, 0x0004, // add.l 4(a0),d1 ;add lo parts 0xD580, // addx.l d0,d2 ;add hi parts with carry 0x2082, // move.l d2,0(a0) ;store high result 0x2141, 0x0004 // move.l d1,4(a0) ;store low result }; #pragma parameter SubFromWide(__A1, __A0) void SubFromWide(UnsignedWide *a, UnsignedWide *b) = { 0x2011, // move.l 0(a1),d0 ;d0 = hi 0x2229, 0x0004, // move.l 4(a1),d1 ;d1 = lo 0x2410, // move.l 0(a0),d2 ;d2 = hi 0x92A8, 0x0004, // sub.l 4(a0),d1 ;add lo parts 0x9580, // subx.l d0,d2 ;add hi parts with carry 0x2082, // move.l d2,0(a0) ;store high result 0x2141, 0x0004 // move.l d1,4(a0) ;store low result }; pascal OSErr ProfilerInit(ProfilerCollectionMethod method, ProfilerTimeBase timeBase, short numFunctions, short stackDepth) { int i; // initialize the function table memset(gFunction, 0, sizeof(gFunction)); strcpy(gFunction[0].name, "Root_Function"); gFunction[0].address = -1; gFunction[0].calls = 1; // initialize the thread table memset(gThread, 0, sizeof(gThread)); for (i = 0; i < MAX_THREADS; i++) gThread[i].stackPtr = gThread[i].stack; Profile_GetTime(&gThread[0].startTime); gThread[0].currentFunction = gFunction; // make sure we're turned off gProfilerOn = false; return noErr; } pascal void ProfilerSetStatus(short on) { gProfilerOn = (on) ? true : false; } pascal OSErr ProfilerDump(StringPtr filename) { /* static char cfile[256]; FunctionInfo *function; double temp; FILE *f; int i; BlockMoveData(filename + 1, cfile, *filename); cfile[*filename] = 0; f = fopen(cfile, "w"); if (f) { for (i = 0, function = gFunction; i < MAX_FUNCS; i++, function++) if (function->address) { temp = ((double)function->totalTime.hi * 65536.0 * 65536.0) + (double)function->totalTime.lo; fprintf(f, "%-40s %15.0lf ticks, %15.2lf ticks/call, %7d calls\n", function->name, temp, temp/function->calls, function->calls); } fclose(f); } return noErr; */ } pascal void ProfilerTerm(void) { gProfilerOn = false; } static void StartTime(FunctionInfo *function:__A0, int call:__D0) { UnsignedWide thisTime, diff; FunctionInfo *last; // get the current time Profile_GetTime(&thisTime); // if there was another function being timed, update that timer last = gCurrentThread->currentFunction; if (last) { diff = gCurrentThread->startTime; SubFromWide(&thisTime, &diff); AddToWide(&last->totalTime, &diff); } // if we have a new function to track, reset the start time and increment the calling count if (function) { if (call) function->calls++; gCurrentThread->startTime = thisTime; } // update the current thread's function pointer gCurrentThread->currentFunction = function; } static FunctionInfo *GetIndex(char *functionName:__A0, long address:__D0):__A0 { FunctionInfo *function; register int i; // loop over all functions, looking for an address match or an empty space for (i = 0, function = gFunction; i < MAX_FUNCS; i++, function++) { long a = function->address; if (!a) break; else if (a == address) return function; } // if we have space for a new function, create an entry if (i < MAX_FUNCS) { strncpy(function->name, functionName, NAME_LENGTH); function->address = address; return function; } // return nil if nothing could be found return nil; } void GetThreadInfo(void) { // get a pointer to the current thread's info ThreadID thread; GetCurrentThread(&thread); gCurrentThread = gThread + thread - THREAD_OFFSET; } void PushFrame(long returnAddress:__D0) { // get a pointer to the next empty stack frame, and increment the stack pointer StackFrame *sp = gCurrentThread->stackPtr++; // save the current function and return address sp->callerFunction = gCurrentThread->currentFunction; sp->returnAddress = returnAddress; } long PopFrame(void):__D0 { // get a pointer to the next empty stack frame, and increment the stack pointer StackFrame *sp = --gCurrentThread->stackPtr; // save the current function and return address gCallerFunction = sp->callerFunction; return sp->returnAddress; } // When __PROFILE is called, stack frame looks like this: // // sp+0c Return space for Pascal function // sp+08 Caller's return address // sp+04 Address of function name string // sp Return address to start of function // // After we save registers and such, stack frame looks like this: // // sp+32 Return space for Pascal function // sp+28 Caller's return address // sp+24 Address of function name string // sp+20 Return address to start of function // sp+16 saved A0 // sp+12 saved A1 // sp+08 saved D0 // sp+04 saved D1 // sp saved D2 // // Upon returning from __PROFILE, stack frame should look like this: // // sp+04 Return space for Pascal function // sp Address of __PROFILEEND asm pascal void __PROFILE(char *functionName) { tst.l gProfilerOn // is the profiler on? bne.s @doProfile // if so, go do the work move.l (sp),4(sp) // otherwise, copy return address over parameter addq.l #4,sp // adjust the stack pointer accordingly rts // return @doProfile: _Debugger clr.l gProfilerOn // clear the profiler flag -- we are not reentrant movem.l a0/a1/d0-d2,-(sp) // save registers jsr GetThreadInfo // get the current thread info into a global move.l 28(sp),d0 // get the return address in d0 jsr PushFrame // and push it onto the stack move.l 24(sp),a0 // now get the function name move.l 20(sp),d0 // and the function address move.l d0,24(sp) // (copy the address onto the stack for later) jsr GetIndex // and return an index into our function table moveq.l #1,d0 // set the call flag jsr StartTime // start the timer for this baby (and stop the last one) lea __PROFILEEND,a0 // point a0 to our profile end routine move.l a0,28(sp) // store that in place of the real return address movem.l (sp)+,a0/a1/d0-d2 // restore the registers addq.l #4,sp // skip over stack space for the functionName parameter move.l #1,gProfilerOn // turn the profiler flag back on rts // and return } // sequence of events: // // call to __PROFILE // clears profilerOn flag to prevent re-entrancy // gets a pointer to the current thread info in gCurrentThread // saves the currentFunction pointer and the real return address as a stack frame // calculates a pointer to the new currentFunction // stops the timer for the previous currentFunction and starts it for the new one // replaces the real return address with a return into __PROFILEEND // resets profilerOn flag // returns to the beginning of the function // // return to __PROFILEEND // clears profilerOn flag to prevent re-entrancy // gets a pointer to the current thread info in gCurrentThread // pops the stack frame, returning real return address and pointer to caller function // saves real return address on the stack for returning // stops the timer for the previous function and starts it back up for the caller function // resets profilerOn flag // returns to the calling function // // When __PROFILEEND is called, stack frame looks like this: // // sp Return space for Pascal function // // Upon returning from __PROFILE, stack frame should look like this: // // sp Address of __PROFILEEND asm pascal void __PROFILEEND(void) { _Debugger clr.l gProfilerOn // clear the profiler flag -- we are not reentrant movem.l a0/d0,gSaveArea // save a0/d0 jsr GetThreadInfo // get the current thread info into a global jsr PopFrame // pop the frame, returning the real return address move.l d0,gSaveArea+8 // save return address in a safe place move.l gCallerFunction,a0 // get pointer to new current function clr.l d0 // clear the call flag jsr StartTime // now start the timer movem.l gSaveArea,a0/d0 // restore a0/d0 move.l gSaveArea+8,a1 // get return address in a1 move.l #1,gProfilerOn // turn the profiler flag back on jmp (a1) // return to the caller }