home *** CD-ROM | disk | FTP | other *** search
/ MacTech 1 to 12 / MacTech-vol-1-12.toast / Source / MacTech® Magazine / Volume 08 - 1992 / 08.07 Nov⁄Dec 92 / Function Logging / Code / LogToFile.c < prev    next >
Encoding:
C/C++ Source or Header  |  1992-08-11  |  11.0 KB  |  441 lines  |  [TEXT/KAHL]

  1. /*
  2.     LogToFile.c
  3.     © 1992 Rock Ridge Enterprises. All Rights Reserved.
  4.  
  5.     Requires Think C 5.0x
  6.  
  7.     To use this file:
  8.     1a)    Turn on the profiling and stack frame options for your entire 
  9.         project from the Edit/Options/Debugging dialog.
  10.     
  11.     1b) Or, if you prefer, add the following line to your files:
  12.             #pragma options( profile, force_frame )
  13.         Place it within a function to profile just that function,
  14.         within a file for just that file, or in a header file.
  15.  
  16.     2)    Call LogInit at program initialization. For example:
  17.             LogInit( NULL, true, false, true );
  18.             
  19.     3)    Call LogDInit at program de-initialization. For example:
  20.             LogDInit();
  21.  
  22.     4)    You can call SetLogState( true ) or SetLogState( false ) to
  23.         turn logging on and off dynamically.
  24. */
  25.  
  26. #include <string.h>
  27. #include <files.h>
  28. #include <errors.h>
  29. #include <memory.h>
  30. #include <pascal.h>
  31.  
  32. #include "LogToFile.h"
  33.  
  34. #define kMaxLogDepth 100        // maximum 100 calls deep
  35.  
  36.     // create Think C text files as output
  37. #define kThinkCreatorID        'KAHL'
  38. #define kTextFileType        'TEXT'
  39.  
  40.     // strings written to output file
  41. #define kTabString            ((void*) "\t" )
  42. #define kReturnString        ((void*) "\r" )
  43. #define kOpenBraceString    ((void*) "{")
  44. #define kCloseBraceString    ((void*) "}")
  45. #define kSimpleExitString    " {}"            // exiting a function that called nobody
  46.  
  47.     // write this string the top of every log file
  48. #define kStringForNewFile        ((void*) "" )
  49.  
  50.     // write this string between program runs when appending to the log file
  51. #define kStringForReusedFile    ((void*) "\r\r*****************************\r")
  52.  
  53.     // the largest buffer we'll need (+ a little extra)
  54. #define kMaxStringLength    ( kMaxLogDepth + 100 )
  55.  
  56.     // the default output file is on the root of the boot disk
  57. #define kBootVol            -1
  58. #define kRootDir            2L
  59. #define kDefaultPName        "\PLog File.txt"
  60.  
  61.     // don't profile our profile handler
  62. #pragma options( !profile )
  63.  
  64. /*
  65.     Permanent storage for this file
  66. */
  67. static short     gLogFileID = 0;    
  68. static short    gLogFileVol = -1;            // vRefNum of log file's disk
  69. static Boolean    gAlwaysFlush = false;        // true = call FlushVol after every write
  70. static Boolean    gLogActive = false;            // true = logging is active
  71. static FSSpec    gDefaultFileLoc = { kBootVol, kRootDir, kDefaultPName };
  72.  
  73. /*
  74.     Info on the calling chain
  75. */
  76. static long     gDepth = 0;                    // how many calls deep are we?
  77. static void     *gStack[ kMaxLogDepth ];    // the return addresses
  78. static Boolean     gNeedBraces[ kMaxLogDepth ];// are braces needed for this function?
  79.  
  80. /*
  81.     Internal routine prototypes
  82. */
  83. void     _profile_( void *pFuncName );
  84. void     StringPtoC( void *source, void *target );
  85. OSErr     WriteOpenBrace( short howDeep );
  86. OSErr     WriteFunctionEntry( void *pString );
  87. OSErr     WriteFunctionExit( void );
  88. OSErr     WriteOpenBrace( short howDeep );
  89.  
  90.  
  91. /**********************************
  92.     LogInit - Call at program start
  93.         fileLoc            - set to NULL or a valid FSSpec for the output file
  94.         deleteOld        - true => truncate old log file
  95.         alwaysFlush        - true => call FlushVol a lot (better log file if you crash)
  96.         startLogging    - true => turn logging on, false => don't log yet
  97. **********************************/
  98. OSErr LogInit( FSSpec *fileLoc, Boolean deleteOld, Boolean alwaysFlush, Boolean startLogging )
  99. {
  100.     OSErr        err = noErr;
  101.     Boolean        createdFile = false;
  102.     
  103.     if ( !fileLoc )
  104.         fileLoc = &gDefaultFileLoc;        // use default if user doesn't specify one
  105.  
  106.     if ( !gLogFileID )                    // in case user calls init twice
  107.     {
  108.         /*
  109.             Create the file & open the data fork for writing.
  110.         */
  111.         err = FSpCreate( fileLoc, kThinkCreatorID, kTextFileType, 0 );
  112.         if ( !err )
  113.             createdFile = true;
  114.         
  115.         err = FSpOpenDF( fileLoc, fsRdWrPerm, &gLogFileID );
  116.         if ( err ) goto DONE;
  117.     }
  118.  
  119.     /*
  120.         Clear out the file if the user requests it.
  121.     */
  122.     if ( deleteOld )
  123.     {
  124.         err = SetEOF( gLogFileID, 0L );
  125.         if ( err ) goto DONE;
  126.     }
  127.  
  128.     /*
  129.         Append to the file
  130.     */
  131.     err = SetFPos( gLogFileID, fsFromLEOF, 0 );
  132.     if ( err ) goto DONE;
  133.  
  134.     /*
  135.         Setup the globals and write '****' to the file.
  136.     */
  137.     gAlwaysFlush = alwaysFlush;
  138.     gLogActive = startLogging;
  139.     gLogFileVol = fileLoc->vRefNum;
  140.     
  141.         // write a header to the file
  142.     if ( deleteOld || createdFile )
  143.         err = WriteLogString( kStringForNewFile );
  144.     else
  145.         err = WriteLogString( kStringForReusedFile );
  146.     
  147.     DONE:
  148.     if ( err )
  149.         LogDInit();
  150.  
  151.     return( err );
  152. }
  153.  
  154. /**********************************
  155.     LogDInit - Call at program exit
  156. **********************************/
  157. OSErr LogDInit( void )
  158. {
  159.     OSErr        err;
  160.  
  161.     if ( !gLogFileID )
  162.         return( openErr );
  163.     
  164.     /*
  165.         Close the file and flush the data to the disk.
  166.     */
  167.     err = FSClose( gLogFileID );
  168.     if ( !err )
  169.         err = FlushVol( NULL, gLogFileVol );
  170.     
  171.     /*
  172.         Clear out the globals so we know we're not active.
  173.     */
  174.     gLogFileID = 0;
  175.     gLogActive = false;
  176.  
  177.     return( err );
  178. }
  179.  
  180. /**********************************
  181.     SetLogState - Call to start or restart logging. 
  182.         Returns previous state.
  183. **********************************/
  184. Boolean SetLogState( Boolean startLogging )
  185. {
  186.     Boolean        result;
  187.     
  188.     result = gLogActive;
  189.     gLogActive = startLogging;
  190.  
  191.     return( result );
  192. }
  193.  
  194. /**********************************
  195.     SetLogFlush - Call to start or restart flushing. 
  196.         Returns previous state.
  197. **********************************/
  198. Boolean SetLogFlush( Boolean startFlushing )
  199. {
  200.     Boolean        result;
  201.     
  202.     result = gAlwaysFlush;
  203.     gAlwaysFlush = startFlushing;
  204.  
  205.     return( result );
  206. }
  207.  
  208. /**********************************
  209.     ClearLogFile - Call to zero out the log file
  210. **********************************/
  211. OSErr ClearLogFile( void )
  212. {
  213.     OSErr        err = noErr;
  214.     OSErr        err2;
  215.     
  216.     if ( !gLogFileID )
  217.         return( openErr );
  218.     
  219.     err = SetEOF( gLogFileID, 0L );
  220.  
  221.     if ( gAlwaysFlush )
  222.     {
  223.         err2 = FlushVol( NULL, gLogFileVol );
  224.         if ( !err )
  225.             err = err2;                // return the first error to occur
  226.     }
  227.  
  228.     return( err );
  229. }
  230.  
  231. /**********************************
  232.     WriteLogString - Write a C string to the log file
  233. **********************************/
  234. OSErr WriteLogString( void *cString )
  235. {
  236.     OSErr        err = noErr;
  237.     OSErr        err2;
  238.     long        numBytes;
  239.     
  240.     if ( !gLogFileID )
  241.         return( openErr );
  242.  
  243.     /*
  244.         Write the data to the file
  245.     */
  246.     numBytes = strlen( cString );
  247.     err = FSWrite( gLogFileID, &numBytes, cString );
  248.     
  249.     /*
  250.         Flush the volume if we always flush
  251.     */
  252.     if ( gAlwaysFlush )
  253.     {
  254.         err2 = FlushVol( NULL, gLogFileVol );
  255.         if ( !err )
  256.             err = err2;
  257.     }
  258.  
  259.     return( err );
  260. }
  261.  
  262. /**********************************
  263.     StringPtoC - Convert a pascal string to a c string
  264. **********************************/
  265. static void StringPtoC( void *source, void *target )
  266. {
  267.     BlockMove( source, target, 1 + *( (unsigned char*)source ) );
  268.     PtoCstr( target );
  269. }
  270.  
  271.  
  272. /**********************************
  273.     WriteFunctionEntry - called by _profile_ whenever a function is entered
  274.  
  275.         The following string is written to the output:
  276.             <CR> + <TABS> + FunctionName
  277.         Where <CR> is a carriage return and <TABS> indicates 1 tab per depth.
  278. **********************************/
  279. static OSErr WriteFunctionEntry( void *pString )
  280. {
  281.     Str255            cString;
  282.     unsigned char    theString[ kMaxStringLength ];
  283.     long            count;
  284.     OSErr            err;
  285.  
  286.     StringPtoC( pString, cString );                // convert func name to c string        
  287.  
  288.     strcpy( (void*)theString, kReturnString );    // start with a carriage return
  289.     
  290.     for ( count=0; count<gDepth; count++ )        // 1 tab for each level
  291.         strcat( (void*)theString, kTabString );
  292.         
  293.     strcat( (void*)theString, (void*)cString );    // the function name
  294.  
  295.     err = WriteLogString( theString );            // write the string
  296.     return( err );
  297. }
  298.  
  299. /**********************************
  300.     WriteFunctionExit - called whenever a function is exited by our exit handler
  301.  
  302.         If this function called another function, write the following string:
  303.             <CR> + <TABS> + }
  304.         Otherwise, write:
  305.             {}
  306.         Where <CR> is a carriage return and <TABS> indicates 1 tab per depth.
  307. **********************************/
  308. static OSErr WriteFunctionExit( void )
  309. {
  310.     OSErr            err = noErr;
  311.     long            count;
  312.     unsigned char    theString[ kMaxStringLength ];
  313.     
  314.     if ( gNeedBraces[ gDepth ] )
  315.     {
  316.         strcpy( (void*)theString, kReturnString );    // start with a carriage return
  317.         for ( count=0; count<gDepth; count++ )        // indent 1 tab for each level
  318.             strcat( (void*)theString, kTabString );
  319.         strcat( (void*)theString, kCloseBraceString );// then a close-brace
  320.     }
  321.     else
  322.     {
  323.         strcpy( (void*)theString, kSimpleExitString );    // just write "exit"
  324.     }
  325.  
  326.     err = WriteLogString( theString );                // write the string
  327.     return( err );
  328. }
  329.  
  330. /**********************************
  331.     WriteOpenBrace - adds some tabs and an open brace to the output
  332.  
  333.         The following string is written to the output:
  334.             <CR> + <TABS> + {
  335.         Where <CR> is a carriage return and <TABS> indicates 1 tab per depth.
  336. **********************************/
  337. static OSErr WriteOpenBrace( short howDeep )
  338. {
  339.     OSErr            err;
  340.     unsigned char    theString[ kMaxStringLength ];
  341.     
  342.     strcpy( (void*)theString, kReturnString );    // start with a return
  343.     while( howDeep-- > 0 )                        // 1 tab per level deep
  344.         strcat( (void*)theString, kTabString );
  345.     strcat( (void*)theString, kOpenBraceString );// add an open-brace
  346.  
  347.     err = WriteLogString( theString );
  348.     return( err );
  349. }
  350.  
  351. /**********************************
  352.     _profile_  -  Called every time a function is entered
  353.     
  354.     This more complicated version does the following:
  355.         1) Prints the function name to the file in an indented list
  356.         2) Saves the return address of the caller's caller into gStack[]
  357.         3) Modifies the stack so that the caller returns to our code
  358.            and not to its caller.
  359.         4) Prints exit info when the function is exited and then jumps
  360.            to the correct return address.
  361. **********************************/
  362. void _profile_( void *pFuncName )
  363. {
  364.     OSErr        err;
  365.  
  366.     if ( !gLogFileID ) return;                // output file not opened
  367.     if ( !gLogActive ) return;                // logging is off
  368.     if ( gDepth >= kMaxLogDepth ) return;    // we're too deep
  369.  
  370.     /*
  371.         We have to put an open brace in the output if the parent function
  372.         hasn't called any other functions until now.
  373.     */
  374.     if ( gDepth > 0 )
  375.         if ( !gNeedBraces[gDepth-1] )
  376.         {
  377.             gNeedBraces[gDepth-1] = true;
  378.             err = WriteOpenBrace( gDepth-1 );
  379.             if ( err ) return;
  380.         }
  381.  
  382.     err = WriteFunctionEntry( pFuncName );        // write the function name
  383.     if ( err ) return;
  384.  
  385.     gNeedBraces[ gDepth ] = false;
  386.     
  387.  
  388.     /*
  389.         Save the return address that the caller will return to.
  390.         Modify the stack so that the caller will return to us instead.
  391.     */
  392.     asm
  393.     {
  394.             ; gStack[ gDepth ] = return address where caller will return to
  395.  
  396.             ; A1 = &gStack[ gDepth ]
  397.         lea.l        gStack, A1
  398.         move.l        gDepth, D0
  399.         lsl.l        #2, D0
  400.         adda.l        D0, A1
  401.         
  402.         move.l        (A6), A0                ; A0 = A6 from previous call
  403.         move.l        4(A0), (A1)                ; gStack[ gDepth ] = ret Addr
  404.         
  405.             ; Change the return address on the stack to point to our code
  406.         lea            @FuncExit, A1
  407.         move.l        A1, 4(A0)
  408.  
  409.         addq.l        #1, gDepth                ; we're one level deeper now
  410.         
  411.             ; return to caller
  412.         unlk        A6
  413.         rts
  414.     }
  415.     
  416.     /*
  417.         This code is executed when a profiled function exits
  418.     */
  419.     FuncExit:
  420.     asm
  421.     {
  422.         move.l        D0, -(SP)                ; save return value onto stack
  423.         subq.l        #1, gDepth                ; we're one level more shallow
  424.  
  425.         jsr            WriteFunctionExit        ; write exit info to the file
  426.  
  427.             ; get the real return address from our array and jump to it
  428.     
  429.             ; A0 = &gStack[ gDepth ]
  430.         lea.l        gStack, A0
  431.         move.l        gDepth, D0
  432.         lsl.l        #2, D0
  433.         adda.l        D0, A0
  434.         
  435.         move.l        (SP)+, D0                ; restore return value
  436.         move.l        (A0), A0                ; A0=real return address
  437.         jmp            (A0)                    ; jump to real return address
  438.     }
  439. }
  440.  
  441.