Sherlock - A Programmers helper. Introduction Trying to debug an application is usually a mystery. Most of the time, the debugging is at the developers computer where source code is available and source level debuggers are able to quickly and easily locate problems. At this point it is easy to find problems and correct them. At this point, there is little challenge to the mystery. Unfortunately, after a program has gone out the door, it is inevitable that a problem occurs at the end user's site. Although we programmers might be able to reproduce the problem if an exact description is given, sometimes we cannot due to one reason or another. If a trace of what the program is currently doing at the customers site was available, then the developer would have a better idea of what went wrong and how to fix the problem. Giving the user access to the original source may not be desirable, and while reporting the linear address in OS/2 2.0 may make the end user feel good, it is useless to the developer since the machine configurations may be different and therefore will have different addresses. This is were programmers have a real mystery. Unfortunately, usually with too many the number of pieces missing to allow the mystery to be solved. This introduces a need into the developers market. This need is to allow any end user with minimal instructions to provide the type of debugging information that the developer really needs. Sherlock is a program to aid fellow programmers in this remote diagnosis of program problems. This is done through an interfaceless debugger. This debugger will load the desired executable for debugging, start the program and then continue until an exception occurs. When an exception occurs, the entire program state is dumped. Each thread will have the current register thread dumped and then attempt to trace the stack back to the root node of the thread. Installation Sherlock may be installed into any directory that is in you PATH statement. It is suggested that you create a new directory to store the files and add this directory to your PATH statement in CONFIG.SYS. If you have debugging DLLs, they MUST be installed in the same directory as Sherlock. Sherlock will search for the debugging DLLs only in the directory where Sherlock is stored. For example: C: Make Drive C current. MKDIR C:\SHERLOCK Create the Sherlock directory. CHDIR C:\SHERLOCK Change to the Sherlock directory. COPY A:*.EXE *.EXE Copy the executable. COPY A:*.DLL *.DLL Copy the debugging DLLs. E CONFIG.SYS Add C:\SHERLOCK to the PATH statement. Usage Usage of SHERLOCK is simple, even for most end users. The target program is started as it would normally be started with the addition of listing Sherlock ahead of the target application. For example, if you wish to test the program xyz.exe with the parameters a b. Normal: xyz a b With Sherlock: Sherlock xyz a b Sherlock supports only one command line argument -L which will specify the name of a new output file. The name of the output file is specified immediately after the L. For example: Sherlock -LCatch.log Catch.exe What will I get out of Sherlock? What Sherlock will generate is a log file of what the operator of a debugger would see. Sherlock will try to load debugging information for each module which is loaded. Sherlock's ability to decode debugging information is based upon loading a DLL to support each different debugging information format. Sherlock is capable of supporting many different debugging formats at the same time. Each module which is loaded by the target program will be sent to the support DLLs to ask whether it supports the debugging format of the module. If it says yes, no other support DLLs will be ask whether they support the debugging format of the module. Currently, only the following formats are supported: SYM files created by mapsym. Microsoft C V6.0 debugging format IBM C-Set/2 Version 1.0 and 2.x VisualAge is not supported currently. Sample Output Sherlock will produce a log file with all of the output from Sherlock. The log file is named SHERLOCK.LOG or whatever you specify on Sherlock's command line. This file is placed into the current directory. Each execution of Sherlock will overwrite any existing log file. The output will be as described below. Sherlock will log all exceptions that occur within the application to the log file. Note that there are many types of exceptions. Some are normal or diagnostic such as guard page exceptions. Other exceptions are fatal, but can be recovered from. Access Violations while usually fatal can be recovered from. For example, C-Set/2 (Copyright IBM) can insert an exception handler which if not overridden will print a register state dump. If overridden, it is possible to recover from the exception and continue program execution. Sherlock will log ALL exceptions to the log file until the program has terminated. Each exception will be dumped as a set of blocks of the format: Exception block Register block Traceback from current function to first function. Code dump For the descriptions of the different blocks below, the following program will be used: /* ** Main entrance routine. */ #define INCL_DOSDATETIME #include int main(int argc, char **argv); int main(int argc, char **argv) { DosOpen((PSZ) 0, /* pszFileName */ (PHFILE) 0, /* pHf */ (PULONG) 0, /* pulAction */ 0, /* cbFile */ 0, /* ulAttribute */ 0, /* fsOpenFlags */ 0, /* fsOpenMode */ (PEAOP2) 0); /* peaop2 */ return 0; } This program will cause a GP fault somewhere within OS/2 since a number of NULL pointers is being passed in. Note that the program will compile correctly without a complaint from the compiler. Exception Block The "Exception type" block will list the type of problem that was hit and where. (e.g. - Except#: c0000005 Access Violation). Some of the information in this block is gibberish if you have not worked with OS/2's exception mechanism. Some of the information is gibberish even if you do work with OS/2's exception mechanism. (Please refer to the BSEXCPT.H file from the OS/2 Toolkit for details as to what the Except # parameters mean.) The text printed to the right of the 'Except #" is the description as defined by IBM. Note that you and others might define other exception numbers. These might be used as signals to your application to signal some event. For this exception number, an access violation occurred. The exception address is the location where the error was detected. This is usually the INSTRUCTION POINTER when the exception occurred. For this exception type, there are two exception parameters. The first for access violation is the address that caused the problem. Zero would be a NULL pointer. How that pointer was obtained cannot be found from this block. It could have been a direct memory reference, or an indirect reference through one of the index registers in the CPU. The module name while useful, is also misleading It is NOT the name of the module that actually had the fault, but the name of the program that was executing. Note that in this case, there is no debugging information available where the exception actually occurred, so there is no function information available. Exception type 1 at 1a050179 Except #: c0000005 Access violation Flags: 00000000 Next Rec: 00000000 Except Addr: 1a050179 Num Parms: 2 Except 0: 00000000 Except 1: ffffffff Module: D:\SHERLOCK\CATCH\CATCH.EXE Size: 18778 Timestamp:Mon Dec 6 21:27:20 1993 Lo Function: UNKNOWN Hi Function: UNKNOWN Exception in thread: 1 ID of the thread where the exception occurred. Register block Each thread of execution within the executing program will have its register set dumped. The entire register set is dumped. If you have the assembler source code for the section of code where the exception occurs, then you can relate the registers to what the program was trying to accomplish. Additionally, the segment register information is dumped. These registers may be useful if you examine the limits. For example, if a reference using the 'FS' register is made at offset of 0x32, then an exception will occur. By checking FSLim, then you would see that you have indexed too far into that segment. For the test case given above, you can use the information from the Exception Block to possibly gather additional information. For example, we know from above that the address 0 was causing a problem. IF it was from a register index, then it would have to be from either the EBX, ESI or EDI registers. Without more information, you are still stuck. You do not know which register is causing the problem, or if a register is causing a problem. Pid: 000000f0 Tid: 00000001 Cmd: 0 Value: 00716668 Addr: 0002294c Buffer: 00037fd4 Len: 00000024 Index: 00000000 MTE: 0000030c EAX: 00172a80 EBX: 00000000 ECX: 00060010 EDX: 00060007 ESP: 0002291c EBP: 00022ae4 ESI: 00000000 EDI: 00000000 EIP: 0000c0a0 EFLAGS: 00002206 Carry Parity Aux Zero Sign Trap IntE Dir Flow IOPL Nested Resume NC PE 0 NE 0 0 1 DN NO 2 0 0 CSLim 1c000000 CSBase: 00000000 CSAcc: df CSAttr: d0 CS:005b DSLim 1c000000 DSBase: 00000000 DSAcc: f3 DSAttr: d0 DS:0053 ESLim 1c000000 ESBase: 00000000 ESAcc: f3 ESAttr: d0 ES:0053 FSLim 00000031 FSBase: 00050030 FSAcc: f3 FSAttr: 00 FS:150b GSLim 00000000 GSBase: 00000000 GSAcc: 00 GSAttr: 00 GS:0000 SSLim 1c000000 SSBase: 00000000 SSAcc: f3 SSAttr: d0 SS:0053 Traceback from current function to first function invoked The traceback is the block that answers the question of how you got to where the problem was detected. Sherlock takes the EBP, EIP of the current thread given by the register dump and tracks through the stack to where the program starts. At each EBP location found, Sherlock will dump any location information that is available. Sherlock assumes that EBP is used to save the Stack Frame base on every function call. If this is not the case, then Sherlock may skip over functions in its trace. Sherlock also assumes that EBP is pushed onto the stack immediately after the function has been called. If this is not the case, then Sherlock may become confused and give erratic results. Sherlock will follow the EBP back to the beginning of the program. Each block contains the EBP/EIP. These are the Stack Frame pointer and the EIP of the routine. The EIP is then translated by Sherlock into the Base Offset/ Relative Offset/ Object length/Object/ Module. These can be used to translate into source locations if a map file of the module is known. The Object is the Object or Segment in the linker's map file. The Relative Offset is the offset within the Object. By checking function addresses or line number information within the map file you can translate the Relative Offset into a function. For unsupported debugging modules, a Hi and Lo function marker are also given. These may be helpful or may not since these are based upon the exported functions of a module. If a module only contains only a few functions exported, then the function markers will most likely be useless. EBP: 00022ae4EIP: 1a02c0a0 Base: 1a020000Rel: 0000c0a0Len: 00010000 Object: 00000002 Module: C:\OS2\DLL\DOSCALL1.DLL Lo Function: DOSREAD Hi Function: DOSCOPY EBP: 00022b14EIP: 1a02a958 Base: 1a020000Rel: 0000a958Len: 00010000 Object: 00000002 Module: C:\OS2\DLL\DOSCALL1.DLL Lo Function: DOSREAD Hi Function: DOSCOPY The Function/Source/Line are the location where the program was trying to execute for the first block. In later blocks, this is the location of where the prior function (earlier in the listing) was called from. This entry shows the result of using one of the debugging support DLLs. The function, source module and line number are therefore accessible and dumped. EBP: 00022b3cEIP: 00010023 Base: 00010000Rel: 00000023Len: 00010000 Object: 00000001 Module: C:\WORK\DEBUG\CATCH.EXE Function: main Source: CATCH.C Line: 9 This entry shows the same module as the above entry, but the area where the stack traced back to does not have debugging information, so the bounding functions are supplied as markers. If the source code can be found, the source code will be listed after these blocks. If there is no debugging information available, then a disassembly of the code will occur. This disassembly will be where ever the EIP for the block is located. EBP: 00022b58EIP: 00010596 Base: 00010000Rel: 00000596Len: 00010000 Object: 00000001 Module: C:\WORK\DEBUG\CATCH.EXE Lo Function: _RunExitList Hi Function: UNKNOWN EIP: 1a02c834, DLL: C:\OS2\DLL\DOSCALL1.DLL Func: DOS32R3EXCEPTIONDISPATCHER 1a02c834 PUSH EBP 1a02c835 MOV EBP,ESP 1a02c837 SUB ESP,0x00000100 1a02c83d PUSH EDI 1a02c83e PUSH ESI 1a02c83f MOV EAX,DWORD PTR [EBP 0xc] 1a02c842 CMP DWORD PTR [EAX],0xc0010002 1a02c848 JE $ 0x08 1a02c84a CMP DWORD PTR [EAX],0xc0010003 1a02c850 JNE $ 0x0c 1a02c852 MOV DWORD PTR [EBP 0],0x00000000 Current Limitations The current release has the current limitations: 1) Only the program started will be monitored. Any subprocesses started by the program will not be monitored. 2) The distributed version does not have the debugging DLLs. 3) The first release has an annoying flashing effect. I am still working to find why this is happening. Hopefully the next version will have this fixed. 4) Crossing 16-32 bit function calls is not currently supported for the stack trace. 5) Tracing of 16 bit stacks is difficult and may be incorrect for code with near calls in the trace. Copyright and Legal Information Sherlock is Copyrighted (c) 1992 - 1995 by Harfmann Software. Adding new Interfaces Sherlock is designed to look in the startup directory where Sherlock has been installed to find support DLLs. Any file with a DLL extension is loaded and examined for two functions to be exported by name called: isKnownModule linkPriority Link Priority Link priority is used to determine the order used to try and resolve symbolic information. When Sherlock first starts, it tries to load every DLL in the Sherlock directory assuming that it is a support DLL for Sherlock. It will then invoke the linkPriority function to build an internal linked list of support DLLs. /* ** Answer the linkage priority. ** Return 1 for insert in front of list ** (first crack at linking) ** Return 0 for add to end of list. ** (last crack at linking) */ int _System linkPriority(void) { return 1; } When Sherlock tries to load a module, it iterates through each support DLL in the linked list to try and determine whether the debugging format of the module is known to that support DLL. For example, the SYM file support is given 0 priority and HLL (C Set++) is given 1 priority to allow the HLL format to take precedence over the SYM format. Supporting a Debugging Format When a module is loaded, Sherlock iterates through all of the support DLLs to determine if any of them can find the debugging information for the module. If the module does understand the format, it returns true, otherwise it returns false. This is done with the isKnownModule function shown below: /* ** Answer whether the module named is a HLL module. ** If so, set the function pointers and return true. */ int _System isKnownModule(DebugModule *module, int (* _System DispatchCommand) (int command), DebugBuffer *buffer) Where: module - Pointer to a structure that will be used to reference the module for as long as the module is loaded. All elements except AuxData and the function pointers should be considered static and should not be changed. AuxData and the function pointers should be filled in if the module understands the debugging format of the module. DispatchCommand - Function pointer to be used by the support DLL if it needs to use the DosDebug API. buffer - Pointer to the buffer used by DosDebug(). typedef struct _DebugModule { void *nextModule; // Internal - DO NOT MODIFY char *name; // Name of file as returned by DosQueryModuleName void *AuxData; // Spare pointer for support DLL usage void *ViewData; // Internal - DO NOT MODIFY time_t fTimestamp; // Time stamp of the module ULONG fileSize; // File size of the module ULONG MTE; // Module handle ULONG typeFlags; // Module flags return by DosQueryAppType /* ** Module cleanup. Free any support structures. */ void (* _System FreeModule)( struct _DebugModule *module); /* Module handle */ /* ** Source functions. */ int (* _System FindSource)( /* 1 found / 0 - not found */ struct _DebugModule *module, /* Module handle */ ULONG eipOffset,/* EIP for function to find */ char *funcName, /* Buffer for function name */ char *sourceName, /* Buffer for source code */ ULONG *lineNum);/* Pointer to line number */ ULONG (* _System FindSourceLine)( /* Return offset of file/line*/ struct _DebugModule *module, /* Module handle */ int line, /* Line to find */ char *fileName);/* File name */ ULONG (* _System FindFuncAddr) ( /* Return offset of function */ struct _DebugModule *module, /* Module handle */ char *name); /* Function name. */ /* ** Variable functions. */ int (* _System GetName)( /* State return value from above */ struct _DebugModule *module, /* Module handle */ State *state, /* State information to retrieve. ** Contains name of variable. */ State *state2); /* If !NULL: state2 is element of ** structure given in state. */ int (* _System GetArray)( /* State return value from above */ struct _DebugModule *module, /* Module handle */ State *state, /* Name of array */ State *state2); /* Index of element to retrieve */ int (* _System GetNumMembers)( /* Return # of elements 0 if not a structure */ struct _DebugModule *module, /* Module handle */ State *state); /* Variable to query and program state */ /* ** Get the name of the index'th structure element */ int (* _System GetMemberIndex)( /* State return value from above */ struct _DebugModule *module, /* Module handle */ State *state, /* Program state */ int MemberIndex,/* Index of element to retrieve */ char *name); /* Buffer to return name into */ } DebugModule; The FreeModule and FindSource function pointers must be filled in to support the current functionality of Sherlock. The other function pointers may be used in future versions.