Windows API Profile Logger LOGGER by BobDay 08/23/92 1.0 General Information 2.0 How to use Logger 3.0 Debugging Scenarios with Logger 4.0 How Logger Works 5.0 Source code for Logger 1.0 General Information LOGGER is a tool that records all API calls and parameters that a Windows application program makes. It records these calls in the order that they are made by the application. 2.0 How to use Logger 1. Copy LOGGER onto your machine: net use p: \\popcorn\public copy p:\bobday\logger\z*.dll . copy p:\bobday\logger\z*.dll .\z*.exe (see description below) copy p:\bobday\logger\logger.* . copy p:\bobday\logger\apfcnvrt.exe . Some applications (WinWord 1.1, Excel 2.1) require that z*.dll have the extension of .EXE, to match those of KERNEL,USER, and GDI. I usually copy these things into the current directory, put they could also be put anywhere accessable by the PATH environment variable. 2. Run APFCNVRT on the application: APFCNVRT WIN myapp.exe APFCNVRT WIN mydll.dll Run APFCNVRT with WIN as the 1st parameter and the filename as the second for all .exe's and .dll's that you wish logged. 3. Create a LOGGER section in WIN.INI (C:\NT\WINDOWS\WIN.INI) The section should have two values: [LOGGER] dbgport=0 mousehack=0 "dbgport" is used to control where logging output should go. 0 = log to disk (into a file called OUTPUT.LOG) 1 = log to debug port (via OutputDebugString) 2 = log to nowhere (no logging) This is useful, see #5 below The default value is 0. "mousehack" is used in conjunction with SGA and Win 3.0/Win 3.1. It should be set to 0. The default value is 1, so you MUST set it to 0. If you do not have it set to 0 and you attempt to run in the WOW environment, LOGGER will GP Fault. 4. Run the application as normal. It will run slower due to logging, and will create two output files: OUTPUT.LOG and OUTPUT.DAT. OUTPUT.LOG is a log of all of the API call made by the application. OUTPUT.DAT is any large data that would not fit onto nice log lines. OUTPUT.DAT is binary data, whereas OUTPUT.LOG is readable. Both files are created in the current directory. 5. You can change the location where logging output is going while the application is running. You can use this to selectively log just a portion of the application run. a. Start with "dbgport=2" in your logger section of WIN.INI b. Start the application and get it to a nice paused position where you want to start logging from. c. Break into the debugger and edit the symbol "_cDbg" (command for debugger). Samples: (WDEB386) (NTSD) eb _cDbg 1 !bde.es _cDbg Symbol is #0397:061E eb 397:61E 1 ^ ^ | | Both of these samples change the debugging port to 1 (output to the debugger). d. Make the application perform the section which you wish to log. e. Break into the debugger and set the debug port back to 2. When you are done, you will have output which happened only during the time when debug port was not 2. 6. Run APFCNVRT on the application to disconnect LOGGER: APFCNVRT UNDO myapp.exe APFCNVRT UNDO mydll.dll Run APFCNVRT with UNDO as the 1st parameter and the filename as the second for all .exe's and .dll's that you did in step 2. You may delete the .exe's and .dll's copied in the 1st step. 3.0 Debugging Scenarios with LOGGER If your application dies mysteriously, or GP's during certain operations: Do a stack trace and see if the bug is obvious. Otherwise, connect logger and set the "dbgport" option to 1. Run the application and watch until the application dies. Examine the last API executed to determine whether valid parameters are being passed. Possibly run the application again, inserting breakpoints to determine what caused the application or the system to become confused. A good starting breakpoint would be at the API that is performed last. If you application runs fine, but has errors (things don't work right) doing certain operations: Run the application, watching the debugger as it runs, look for error messages from WOW32 at log level 2. These are failures. If the error is not obvious from that process, then connect logger and set the "dbgport" option to 0. Run the application and make it perform the operations that are wrong. Then exit the application and examine the OUTPUT.LOG file for APIs which are failing. Try to narrow down the search by doing as little as possible with the application besides the part that is malfunctioning. Also, look through the OUTPUT.LOG file for WM_KEYDOWN, WM_KEYUP messages or WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN, etc. messages. Find those messages that you know you did just before the operation that is malfunctioning. This should narrow down the search quite a bit. You may also want to create a log of the application running on Win 3.1 (remember to edit your \windows\win.ini file too). Comparing the two OUTPUT.LOG files will give you an idea about what is occuring differently under WOW vs. under Win 3.1. 4.0 How LOGGER works LOGGER consists of 3 main pieces: 1. APFCNVRT.EXE 2. Thunk DLLs - ZSER, ZERNEL, ZDI 3. LOGGER.DLL LOGGER and its associated pieces make up a mechanism that can be used to record all of the API calls made by a Windows application. Logger does this by installing a set of thunks in-between the application and the real Windows APIs. The thunks record the API name and information into a log and make the real API call for the application. APFCNVRT is a tool that edits the application program to use the thunks, rather than the real Windows APIs. It does this by editing the imported names table in the application program's .exe header. It replaces the API in GDI.EXE with equivalent thunks in the ZDI.DLL. It replaces the API in USER.EXE with those in ZSER.DLL, and KERNEL.EXE with ZERNEL.DLL. Suppose, for example, an application is using the API RectInRegion. That API is really know in the system as "GDI.181", an API in GDI.EXE whose ordinal number is 181. Using APFCNVRT, we change the application to reference "ZDI.181" instead, where "ZDI.181" happens to be an exported function from the ZDI.DLL whose ordinal number is 181. In this case, "ZDI.181" is a function called zRectInRegion, which is the thunk for the RectInRegion API. All of the thunk DLLs (ZSER.DLL, ZERNEL.DLL, and ZDI.DLL) are filled with these thunks. For API FiddleFoo, there will be a function zFiddleFoo in one of the thunk DLLs. The letter z was prepended to all of the thunk function names so that they can easily be distinguished from the real function, yet the real functions name is still known. Each of the thunk functions perform a well ordered process: 1. Receive all of the parameters from the application program in the same manner that real API would (C/Pascal calling convention). 2. Log the API as having been called and all of the input parameters. 3. Perform necessary parameter subsitutions. 4. Perform the real API. 5. Log the API as having returned, the return value and all of the output parameters 6. Return the return value to the application in the same manner that the real API would (C/Pascal calling convention). In this manner, there is some information logged before the API call is made, and some information logged after the API call has returned. This is helpful in discovering whether the system is failing during the API call, or after it. The parameter substitution step is necessary so that we can catch functions which have callbacks. For example, before calling the EnumFonts API, we replace the applications supplied callback address with our own callback address. The function at this address will log the parameters which are passed to the application in the same way that the API thunks do. There are callback functions for all of the APIs that need them: Window Procs for RegisterClass, Font Procs for EnumFonts, Hook Procs for SetWindowsHook, etc. The callback functions all look the same: 1. Receive all of the parameters (C/Pascal). 2. Log the callback as having been started and all of the input parameters. 3. Make the real callback to the applications originally supplied callback address. 4. Log the callback as having been finished and all of the output parameters. 5. Return the return value to the system in the same manner (C/Pascal). All of these logging calls go through the Logging DLL, LOGGER.DLL. LOGGER.DLL has 2 entry points, LogIn, and LogOut. The only difference between LogIn and LogOut is that LogIn is used for logging input parameters and LogOut is used for logging output parameters. Both functions take a variable number of parameters, but operate differently than the standard C function printf. A sample call to LogIn would look like this: LogIn( "APICALL:RectVisible HDC+LPRECT+", param1, param2 ); Instead of having %d's, %s's, %x's in the format string, LogIn and LogOut take the type names. Each parameter is logged according to its type. The "+" character is a seperator between parameters and there must be a trailing one. The line which gets logged from this LogIn call would look like this: 01|APICALL:RectVisible 2056 { 0002 0003 0195 018f } The "{ 0002 0003 0195 018f }" is the logging of the rectangle pointed to by the LPRECT parameter, param2. LOGGER knows the type LPRECT and knows that if there is a valid pointer passed, it should dump it as a RECT structure. Similar operations are performed for other parameters, knowing their parameter type. 5.0 Source code for Logger Source code for Logger is on \\brillig\ntct\slm\src\sga\logger and \\brillig\ntct\slm\src\wrapper\win31x