home *** CD-ROM | disk | FTP | other *** search
- /*
- LogToFile.c
- © 1992 Rock Ridge Enterprises. All Rights Reserved.
-
- Requires Think C 5.0x
-
- To use this file:
- 1a) Turn on the profiling and stack frame options for your entire
- project from the Edit/Options/Debugging dialog.
-
- 1b) Or, if you prefer, add the following line to your files:
- #pragma options( profile, force_frame )
- Place it within a function to profile just that function,
- within a file for just that file, or in a header file.
-
- 2) Call LogInit at program initialization. For example:
- LogInit( NULL, true, false, true );
-
- 3) Call LogDInit at program de-initialization. For example:
- LogDInit();
-
- 4) You can call SetLogState( true ) or SetLogState( false ) to
- turn logging on and off dynamically.
- */
-
- #include <string.h>
- #include <files.h>
- #include <errors.h>
- #include <memory.h>
- #include <pascal.h>
-
- #include "LogToFile.h"
-
- #define kMaxLogDepth 100 // maximum 100 calls deep
-
- // create Think C text files as output
- #define kThinkCreatorID 'KAHL'
- #define kTextFileType 'TEXT'
-
- // strings written to output file
- #define kTabString ((void*) "\t" )
- #define kReturnString ((void*) "\r" )
- #define kOpenBraceString ((void*) "{")
- #define kCloseBraceString ((void*) "}")
- #define kSimpleExitString " {}" // exiting a function that called nobody
-
- // write this string the top of every log file
- #define kStringForNewFile ((void*) "" )
-
- // write this string between program runs when appending to the log file
- #define kStringForReusedFile ((void*) "\r\r*****************************\r")
-
- // the largest buffer we'll need (+ a little extra)
- #define kMaxStringLength ( kMaxLogDepth + 100 )
-
- // the default output file is on the root of the boot disk
- #define kBootVol -1
- #define kRootDir 2L
- #define kDefaultPName "\PLog File.txt"
-
- // don't profile our profile handler
- #pragma options( !profile )
-
- /*
- Permanent storage for this file
- */
- static short gLogFileID = 0;
- static short gLogFileVol = -1; // vRefNum of log file's disk
- static Boolean gAlwaysFlush = false; // true = call FlushVol after every write
- static Boolean gLogActive = false; // true = logging is active
- static FSSpec gDefaultFileLoc = { kBootVol, kRootDir, kDefaultPName };
-
- /*
- Info on the calling chain
- */
- static long gDepth = 0; // how many calls deep are we?
- static void *gStack[ kMaxLogDepth ]; // the return addresses
- static Boolean gNeedBraces[ kMaxLogDepth ];// are braces needed for this function?
-
- /*
- Internal routine prototypes
- */
- void _profile_( void *pFuncName );
- void StringPtoC( void *source, void *target );
- OSErr WriteOpenBrace( short howDeep );
- OSErr WriteFunctionEntry( void *pString );
- OSErr WriteFunctionExit( void );
- OSErr WriteOpenBrace( short howDeep );
-
-
- /**********************************
- LogInit - Call at program start
- fileLoc - set to NULL or a valid FSSpec for the output file
- deleteOld - true => truncate old log file
- alwaysFlush - true => call FlushVol a lot (better log file if you crash)
- startLogging - true => turn logging on, false => don't log yet
- **********************************/
- OSErr LogInit( FSSpec *fileLoc, Boolean deleteOld, Boolean alwaysFlush, Boolean startLogging )
- {
- OSErr err = noErr;
- Boolean createdFile = false;
-
- if ( !fileLoc )
- fileLoc = &gDefaultFileLoc; // use default if user doesn't specify one
-
- if ( !gLogFileID ) // in case user calls init twice
- {
- /*
- Create the file & open the data fork for writing.
- */
- err = FSpCreate( fileLoc, kThinkCreatorID, kTextFileType, 0 );
- if ( !err )
- createdFile = true;
-
- err = FSpOpenDF( fileLoc, fsRdWrPerm, &gLogFileID );
- if ( err ) goto DONE;
- }
-
- /*
- Clear out the file if the user requests it.
- */
- if ( deleteOld )
- {
- err = SetEOF( gLogFileID, 0L );
- if ( err ) goto DONE;
- }
-
- /*
- Append to the file
- */
- err = SetFPos( gLogFileID, fsFromLEOF, 0 );
- if ( err ) goto DONE;
-
- /*
- Setup the globals and write '****' to the file.
- */
- gAlwaysFlush = alwaysFlush;
- gLogActive = startLogging;
- gLogFileVol = fileLoc->vRefNum;
-
- // write a header to the file
- if ( deleteOld || createdFile )
- err = WriteLogString( kStringForNewFile );
- else
- err = WriteLogString( kStringForReusedFile );
-
- DONE:
- if ( err )
- LogDInit();
-
- return( err );
- }
-
- /**********************************
- LogDInit - Call at program exit
- **********************************/
- OSErr LogDInit( void )
- {
- OSErr err;
-
- if ( !gLogFileID )
- return( openErr );
-
- /*
- Close the file and flush the data to the disk.
- */
- err = FSClose( gLogFileID );
- if ( !err )
- err = FlushVol( NULL, gLogFileVol );
-
- /*
- Clear out the globals so we know we're not active.
- */
- gLogFileID = 0;
- gLogActive = false;
-
- return( err );
- }
-
- /**********************************
- SetLogState - Call to start or restart logging.
- Returns previous state.
- **********************************/
- Boolean SetLogState( Boolean startLogging )
- {
- Boolean result;
-
- result = gLogActive;
- gLogActive = startLogging;
-
- return( result );
- }
-
- /**********************************
- SetLogFlush - Call to start or restart flushing.
- Returns previous state.
- **********************************/
- Boolean SetLogFlush( Boolean startFlushing )
- {
- Boolean result;
-
- result = gAlwaysFlush;
- gAlwaysFlush = startFlushing;
-
- return( result );
- }
-
- /**********************************
- ClearLogFile - Call to zero out the log file
- **********************************/
- OSErr ClearLogFile( void )
- {
- OSErr err = noErr;
- OSErr err2;
-
- if ( !gLogFileID )
- return( openErr );
-
- err = SetEOF( gLogFileID, 0L );
-
- if ( gAlwaysFlush )
- {
- err2 = FlushVol( NULL, gLogFileVol );
- if ( !err )
- err = err2; // return the first error to occur
- }
-
- return( err );
- }
-
- /**********************************
- WriteLogString - Write a C string to the log file
- **********************************/
- OSErr WriteLogString( void *cString )
- {
- OSErr err = noErr;
- OSErr err2;
- long numBytes;
-
- if ( !gLogFileID )
- return( openErr );
-
- /*
- Write the data to the file
- */
- numBytes = strlen( cString );
- err = FSWrite( gLogFileID, &numBytes, cString );
-
- /*
- Flush the volume if we always flush
- */
- if ( gAlwaysFlush )
- {
- err2 = FlushVol( NULL, gLogFileVol );
- if ( !err )
- err = err2;
- }
-
- return( err );
- }
-
- /**********************************
- StringPtoC - Convert a pascal string to a c string
- **********************************/
- static void StringPtoC( void *source, void *target )
- {
- BlockMove( source, target, 1 + *( (unsigned char*)source ) );
- PtoCstr( target );
- }
-
-
- /**********************************
- WriteFunctionEntry - called by _profile_ whenever a function is entered
-
- The following string is written to the output:
- <CR> + <TABS> + FunctionName
- Where <CR> is a carriage return and <TABS> indicates 1 tab per depth.
- **********************************/
- static OSErr WriteFunctionEntry( void *pString )
- {
- Str255 cString;
- unsigned char theString[ kMaxStringLength ];
- long count;
- OSErr err;
-
- StringPtoC( pString, cString ); // convert func name to c string
-
- strcpy( (void*)theString, kReturnString ); // start with a carriage return
-
- for ( count=0; count<gDepth; count++ ) // 1 tab for each level
- strcat( (void*)theString, kTabString );
-
- strcat( (void*)theString, (void*)cString ); // the function name
-
- err = WriteLogString( theString ); // write the string
- return( err );
- }
-
- /**********************************
- WriteFunctionExit - called whenever a function is exited by our exit handler
-
- If this function called another function, write the following string:
- <CR> + <TABS> + }
- Otherwise, write:
- {}
- Where <CR> is a carriage return and <TABS> indicates 1 tab per depth.
- **********************************/
- static OSErr WriteFunctionExit( void )
- {
- OSErr err = noErr;
- long count;
- unsigned char theString[ kMaxStringLength ];
-
- if ( gNeedBraces[ gDepth ] )
- {
- strcpy( (void*)theString, kReturnString ); // start with a carriage return
- for ( count=0; count<gDepth; count++ ) // indent 1 tab for each level
- strcat( (void*)theString, kTabString );
- strcat( (void*)theString, kCloseBraceString );// then a close-brace
- }
- else
- {
- strcpy( (void*)theString, kSimpleExitString ); // just write "exit"
- }
-
- err = WriteLogString( theString ); // write the string
- return( err );
- }
-
- /**********************************
- WriteOpenBrace - adds some tabs and an open brace to the output
-
- The following string is written to the output:
- <CR> + <TABS> + {
- Where <CR> is a carriage return and <TABS> indicates 1 tab per depth.
- **********************************/
- static OSErr WriteOpenBrace( short howDeep )
- {
- OSErr err;
- unsigned char theString[ kMaxStringLength ];
-
- strcpy( (void*)theString, kReturnString ); // start with a return
- while( howDeep-- > 0 ) // 1 tab per level deep
- strcat( (void*)theString, kTabString );
- strcat( (void*)theString, kOpenBraceString );// add an open-brace
-
- err = WriteLogString( theString );
- return( err );
- }
-
- /**********************************
- _profile_ - Called every time a function is entered
-
- This more complicated version does the following:
- 1) Prints the function name to the file in an indented list
- 2) Saves the return address of the caller's caller into gStack[]
- 3) Modifies the stack so that the caller returns to our code
- and not to its caller.
- 4) Prints exit info when the function is exited and then jumps
- to the correct return address.
- **********************************/
- void _profile_( void *pFuncName )
- {
- OSErr err;
-
- if ( !gLogFileID ) return; // output file not opened
- if ( !gLogActive ) return; // logging is off
- if ( gDepth >= kMaxLogDepth ) return; // we're too deep
-
- /*
- We have to put an open brace in the output if the parent function
- hasn't called any other functions until now.
- */
- if ( gDepth > 0 )
- if ( !gNeedBraces[gDepth-1] )
- {
- gNeedBraces[gDepth-1] = true;
- err = WriteOpenBrace( gDepth-1 );
- if ( err ) return;
- }
-
- err = WriteFunctionEntry( pFuncName ); // write the function name
- if ( err ) return;
-
- gNeedBraces[ gDepth ] = false;
-
-
- /*
- Save the return address that the caller will return to.
- Modify the stack so that the caller will return to us instead.
- */
- asm
- {
- ; gStack[ gDepth ] = return address where caller will return to
-
- ; A1 = &gStack[ gDepth ]
- lea.l gStack, A1
- move.l gDepth, D0
- lsl.l #2, D0
- adda.l D0, A1
-
- move.l (A6), A0 ; A0 = A6 from previous call
- move.l 4(A0), (A1) ; gStack[ gDepth ] = ret Addr
-
- ; Change the return address on the stack to point to our code
- lea @FuncExit, A1
- move.l A1, 4(A0)
-
- addq.l #1, gDepth ; we're one level deeper now
-
- ; return to caller
- unlk A6
- rts
- }
-
- /*
- This code is executed when a profiled function exits
- */
- FuncExit:
- asm
- {
- move.l D0, -(SP) ; save return value onto stack
- subq.l #1, gDepth ; we're one level more shallow
-
- jsr WriteFunctionExit ; write exit info to the file
-
- ; get the real return address from our array and jump to it
-
- ; A0 = &gStack[ gDepth ]
- lea.l gStack, A0
- move.l gDepth, D0
- lsl.l #2, D0
- adda.l D0, A0
-
- move.l (SP)+, D0 ; restore return value
- move.l (A0), A0 ; A0=real return address
- jmp (A0) ; jump to real return address
- }
- }
-
-