Minidumps


Some of you might be wondering why I went to the trouble to write and continue to support the EXCEPTION_POINTERS manipulation code in the CrashHandler library because you've heard about or used a thing called minidumps. The main reason is that there are many companies out there using my code in their applications and I didn't want to break compatibility. However, the minidump capabilities are so amazingly cool, I'm sure that many folks will rip out the existing code inside their crash handlers and simply replace it with a call to the minidump-creation functions as soon as they can.

I've already talked about how to read minidump files with both Microsoft Visual Studio .NET and WinDBG in Chapter 7 and Chapter 8, respectively. What I want to turn to now is how you can create your own minidumps right from your own code. After the symbol server technology and Visual Studio .NET itself, I think that minidumps API is the second greatest thing Microsoft has released for native developers in the last couple of years! However, there are a few quirks with creating your own, so I want to show you how to get the very best minidumps possible so that you can solve your bugs much faster.

The MiniDumpWriteDump API

DBGHELP.DLL contains the MiniDumpWriteDump function, which does all the work. DBGHELP.DLL version 5.1 or later contains the function. This means that the versions that come with stock Microsoft Windows 2000 (up through Service Pack 3) are earlier versions and don't export MiniDumpWriteDump. Additionally, versions of DBGHELP.DLL earlier than version 6.0 had a bug in MiniDumpWriteDump that caused a deadlock when you called it to write a minidump from the current process. Fortunately, those versions were distributed only with Debugging Tools for Windows, so they should not be on users' machines. To ensure life is good for your application, your best bet is to include DBGHELP.DLL 6.1.17.1 or later with your application and install it into your application's directory because it's now fully redistributable. Do not install it into the %SYSTEMROOT%\System32 directory. DBGHELP.DLL is a part of Debugging Tools for Windows (that is, WinDBG), which is included with the sample files for this book. To get the latest version of DBGHELP.DLL, go to http://www.microsoft.com/ddk/debugging/ and download Debugging Tools for Windows. After you install everything, you can extract DBGHELP.DLL from the Debugging Tools for Windows directory.

The next snippet of code is the prototype for MiniDumpWriteDump. The parameters are generally self-explanatory, but I want to discuss a few highlights. The first parameter, the handle to the process, must have read and query rights. Since many of our programs are not running with full Administrator rights, you might need to do the security dance to ensure you have the rights to call MiniDumpWriteDump if you're doing impersonation or other cross user/rights programming. However, in all the different types of applications I've used MiniDumpWriteDump in, I've never needed to do any security work on the process handle.

BOOL MiniDumpWriteDump ( HANDLE                            hProcess       ,                          DWORD                             ProcessId      ,                          HANDLE                            hFile          ,                          MINIDUMP_TYPE                     DumpType       ,                          PMINIDUMP_EXCEPTION_INFORMATION   ExceptionParam ,                          PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,                          PMINIDUMP_CALLBACK_INFORMATION    CallbackParam ); 

The fourth parameter is the type of dump you want to write. This enumeration seems to be updated with every release of Debugging Tools for Windows, so make sure you do a custom installation of Debugging Tools for Windows and install the SDK components to get the latest DBGHELP.H header file. What's not clear from the documentation is that you can OR together the various MINIDUMP_TYPE enumeration flags to request that additional bits of information be written to the dump. No matter what you do, you'll always want to ensure MiniDumpWithHandleData is included so that you can get the handle information.

If you're working with secure applications or have customers that are extremely concerned about protecting data, MiniDumpWriteDump can expose information in the dump that you shouldn't see. To protect users, Microsoft introduced two flags with DBGHELP.DLL 6.1.17.1 and later versions: MiniDumpFilterMemory and MiniDumpFilterModulePaths. The former removes private data unnecessary for stack walking, and the latter filters pathnames for usernames and important directory names. Although useful, MiniDumpFilterModulePaths can make finding the modules in a minidump harder.

The final parameter of interest is the fifth, ExceptionParam. You'll set this parameter to a pointer to an EXCEPTION_POINTERS structure to get the crash information added to the minidump. For the final two parameters, you'll almost always pass NULL. However, if you do want to consider writing custom information—the key program state, user preferences, object lists, or anything else your heart desires—you can plop information into the UserStreamParam parameter and get it written to the minidump file. It'll be up to you to read the user streams out with MiniDumpReadDumpStream, but the good news is that you're limited only to your imagination regarding what you want to have in a minidump.

Pacifying MiniDumpWriteDump

When I first looked at MiniDumpWriteDump, I immediately realized that I needed a wrapper function around it to accomplish two things: hiding the grunge of GetProcAddress because I wanted to ensure my code ran on a stock copy of Windows 2000; and avoiding having to open the file before every call to MiniDumpWriteDump. After I'd done the first version of my simple wrapper, I realized I was never going to do more than set the ExceptionParam parameter to point to the EXCEPTION_POINTERS structure I was processing in a crash. My minidump function for writing dumps in your crash handler function is CreateCurrentProcessCrashDump. I also added a function, IsMiniDumpFunctionAvailable, that returns TRUE when MiniDumpWriteDump is available in the address space. You can see both functions in the BugslayerUtil MINIDUMP.CPP file.

Everything was going along well until one day, I decided I wanted a function that would snap out a minidump at any point during program execution, not just when I crashed. I was working on a server application and we wanted to be able to snap out the minidump when a specific event was signaled externally to the application. That way we could look at application states after the fact without attaching a debugger to the machine. Alas, the minidumps created by MiniDumpWriteDump weren't always readable.

WinDBG always reported what looked like a bogus call stack in those snapped minidumps. Visual Studio .NET did a better job but sometimes reported weird stack walks even though I had perfect symbols all around. After a little bit of head scratching, it dawned on me what was going on. MiniDumpWriteDump was writing the call stack for the thread that was writing the dump, starting deep in the bowels of MiniDumpWriteDump itself. Even though I had perfect symbols, walking back into my code was proving very difficult.

Since I was snapping out a dump file and not responding to a crash, I was a little stumped about how to proceed. Any dump files I wrote as part of a crash were perfectly formed and readable by both debuggers. Of course, to get WinDBG to read a true crash dump file, I had to issue the .ecxr;kp commands to get the exception record set and to look at the stack. That gave me the idea to set up the MINIDUMP_EXCEPTION_INFORMATION structure and fake the same information as a crash so that I could get a minidump file with good call stacks.

The whole key to setting up the MINIDUMP_EXCEPTION_INFORMATION structure is getting the CONTEXT (register) information correct so that the debuggers think the fake crash looks like a real one. After much trial and error, I came up with the SnapCurrentProcessMiniDump function. Now snapping a minidump at any time will always walk the stack. You might want to examine the code in Listing 13-5 because the way it works is a little interesting.

Listing 13-5: SnapCurrentProcessMiniDump and friends from MINIDUMP.CPP

start example
 // The following are snippets from MINIDUMP.CPP so you can see // how SnapCurrentProcessMiniDump works.     // The distances (in bytes) from a return address to the call // instruction for near and far calls. These are used in the // CalculateBeginningOfCallInstruction function. #define k_CALLNEARBACK  5 #define k_CALLFARBACK   6     // The common prolog for the naked functions, // SnapCurrentProcessMiniDumpA and SnapCurrentProcessMiniDumpW. #define SNAPPROLOG(Cntx)                                               \ __asm PUSH  EBP                   /* Save EBP explictly.            */ \ __asm MOV   EBP , ESP             /* Move the stack.                */ \ __asm SUB   ESP , __LOCAL_SIZE    /* Space for the local variables. */ \ /* Copy over all the easy current registers values. */                 \ __asm MOV   Cntx.Eax , EAX                                             \ __asm MOV   Cntx.Ebx , EBX                                             \ __asm MOV   Cntx.Ecx , ECX                                             \ __asm MOV   Cntx.Edx , EDX                                             \ __asm MOV   Cntx.Edi , EDI                                             \ __asm MOV   Cntx.Esi , ESI                                             \ /* Zero put the whole EAX register and just copy the segments into  */ \ /* the lower word. This avoids leaving the upper word uninitialized */ \ /* as the context segment registers are really 32-bit values.       */ \ __asm XOR   EAX , EAX                                                  \ __asm MOV   AX , GS                                                    \ __asm MOV   Cntx.SegGs , EAX                                           \ __asm MOV   AX , FS                                                    \ __asm MOV   Cntx.SegFs , EAX                                           \ __asm MOV   AX , ES                                                    \ __asm MOV   Cntx.SegEs , EAX                                           \ __asm MOV   AX , DS                                                    \ __asm MOV   Cntx.SegDs , EAX                                           \ __asm MOV   AX , CS                                                    \ __asm MOV   Cntx.SegCs , EAX                                           \ __asm MOV   AX , SS                                                    \ __asm MOV   Cntx.SegSs , EAX                                           \ /* Get the previous EBP value. */                                      \ __asm MOV  EAX , DWORD PTR [EBP]                                       \ __asm MOV  Cntx.Ebp , EAX                                              \ /* Get the previous ESP value. */                                      \ __asm MOV  EAX , EBP                                                   \ /* Two DWORDs up from EBP is the previous stack address. */            \ __asm ADD  EAX , 8                                                     \ __asm MOV  Cntx.Esp , EAX                                              \ /* Save changed registers. */                                          \ __asm PUSH ESI                                                         \ __asm PUSH EDI                                                         \ __asm PUSH EBX                                                         \ __asm PUSH ECX                                                         \ __asm PUSH EDX      // The common epilog for the naked functions, // SnapCurrentProcessMiniDumpA and SnapCurrentProcessMiniDumpW. #define SNAPEPILOG(eRetVal)                                            \ __asm POP     EDX             /* Restore saved registers.  */          \ __asm POP     ECX                                                      \ __asm POP     EBX                                                      \ __asm POP     EDI                                                      \ __asm POP     ESI                                                      \ __asm MOV     EAX , eRetVal   /* Set the return value.      */         \ __asm MOV     ESP , EBP       /* Restore the stack pointer. */         \ __asm POP     EBP             /* Restore the frame pointer. */         \ __asm RET                     /* Return to caller.          */     BSUMDRET CommonSnapCurrentProcessMiniDump ( MINIDUMP_TYPE eType      ,                                             LPCWSTR       szDumpName ,                                             PCONTEXT      pCtx        ) {     // Assume the best.     BSUMDRET eRet = eDUMP_SUCCEEDED ;         // Have I even tried to get the exported MiniDumpWriteDump function     // yet?     if ( ( NULL == g_pfnMDWD ) && ( eINVALID_ERROR == g_eIMDALastError))     {         if ( FALSE == IsMiniDumpFunctionAvailable ( ) )         {             eRet = g_eIMDALastError ;         }     }     // If the MiniDumpWriteDump function pointer is NULL, I'm done.     if ( NULL == g_pfnMDWD )     {         eRet = g_eIMDALastError ;     }          if ( eDUMP_SUCCEEDED == eRet )     {         // Armed with the context at the time of the call to this         // function, I can now look to actually writing the dump. To         // make everything work, I need to make it look like an         // exception happened. Hence, all this work to fill out the         // MINIDUMP_EXCEPTION_INFORMATION structure.                              EXCEPTION_RECORD stExRec ;         EXCEPTION_POINTERS stExpPtrs ;         MINIDUMP_EXCEPTION_INFORMATION stExInfo ;             // Zero out all the individual values.         ZeroMemory ( &stExRec , sizeof ( EXCEPTION_RECORD )) ;         ZeroMemory ( &stExpPtrs , sizeof ( EXCEPTION_POINTERS ) ) ;         ZeroMemory ( &stExInfo ,sizeof(MINIDUMP_EXCEPTION_INFORMATION));             // Set the exception address to the start of the CALL         // instruction. Interestingly, I found I didn't have to set the         // exception code. When you open up a .DMP file created         // with this code in VS.NET, you'll see the exception code         // reported as:         // 0x00000000: The operation completed successfully.                              // warning C4312: 'type cast' : conversion from 'DWORD'         // to 'PVOID' of greater size         #pragma warning ( disable : 4312 )         stExRec.ExceptionAddress = (PVOID)(pCtx->Eip) ;         #pragma warning ( default : 4312 )             // Set the exception pointers.         stExpPtrs.ContextRecord = pCtx ;         stExpPtrs.ExceptionRecord = &stExRec ;             // Finally, set up the exception info structure.         stExInfo.ThreadId = GetCurrentThreadId ( ) ;         stExInfo.ClientPointers = TRUE ;         stExInfo.ExceptionPointers = &stExpPtrs ;             // Create the file to write.         HANDLE hFile = CreateFile ( szDumpName                   ,                                     GENERIC_READ | GENERIC_WRITE ,                                     FILE_SHARE_READ              ,                                     NULL                         ,                                     CREATE_ALWAYS                ,                                     FILE_ATTRIBUTE_NORMAL        ,                                     NULL                          ) ;         ASSERT ( INVALID_HANDLE_VALUE != hFile ) ;         if ( INVALID_HANDLE_VALUE != hFile )         {             // Do the dump file.             BOOL bRetVal = g_pfnMDWD ( GetCurrentProcess ( )   ,                                        GetCurrentProcessId ( ) ,                                        hFile                   ,                                        eType                   ,                                        &stExInfo               ,                                        NULL                    ,                                        NULL                     ) ;             ASSERT ( TRUE == bRetVal ) ;             if ( TRUE == bRetVal )             {                 eRet = eDUMP_SUCCEEDED ;             }             else             {                 eRet = eMINIDUMPWRITEDUMP_FAILED ;             }             // Close the file.             VERIFY ( CloseHandle ( hFile ) ) ;         }         else         {             eRet = eOPEN_DUMP_FAILED ;         }     }     return ( eRet ) ; }     BSUMDRET __declspec ( naked )             SnapCurrentProcessMiniDumpW ( MINIDUMP_TYPE eType      ,                                           LPCWSTR       szDumpName  )                                            {     // Where the registers coming into this function are stored.     CONTEXT stInitialCtx ;     // Where the final registers are stored.     CONTEXT stFinalCtx ;     // The return value.     BSUMDRET    eRet ;     // Boolean return value local.     BOOL        bRetVal ;         // Do the prolog.     SNAPPROLOG ( stInitialCtx ) ;         eRet = eDUMP_SUCCEEDED ;         // Check the string parameter.     ASSERT ( FALSE == IsBadStringPtr ( szDumpName , MAX_PATH ) ) ;     if ( TRUE == IsBadStringPtr ( szDumpName , MAX_PATH ) )     {         eRet = eBAD_PARAM ;     }          if ( eDUMP_SUCCEEDED == eRet )     {         // Zero out the final context structure.         ZeroMemory ( &stFinalCtx , sizeof ( CONTEXT ) ) ;                      // Indicate I want everything in the context.         stFinalCtx.ContextFlags = CONTEXT_FULL                 |                                     CONTEXT_CONTROL            |                                     CONTEXT_DEBUG_REGISTERS    |                                     CONTEXT_EXTENDED_REGISTERS |                                     CONTEXT_FLOATING_POINT       ;                                                 // Get all the groovy context registers and such for this          // thread.         bRetVal = GetThreadContext ( GetCurrentThread ( ) ,&stFinalCtx);         ASSERT ( TRUE == bRetVal ) ;         if ( TRUE == bRetVal )         {             COPYKEYCONTEXTREGISTERS ( stFinalCtx , stInitialCtx ) ;                          // Get the return address and hunt down the call instruction             // that got us into this function. All the rest of the             // registers are set up before the call so I'll ensure the             // instruction pointer is set that way too.             UINT_PTR dwRetAddr = (UINT_PTR)_ReturnAddress ( ) ;             bRetVal = CalculateBeginningOfCallInstruction ( dwRetAddr );             ASSERT ( TRUE == bRetVal ) ;             if ( TRUE == bRetVal )             {                 // Set the instruction pointer to the beginning of the                 // call instruction.                 stFinalCtx.Eip = (DWORD)dwRetAddr ;                     // Call the common function that does the actual write.                 eRet = CommonSnapCurrentProcessMiniDump ( eType       ,                                                           szDumpName  ,                                                           &stFinalCtx );             }             else             {                 eRet = eGETTHREADCONTEXT_FAILED ;             }         }     }     // Do the epilog.     SNAPEPILOG ( eRet ) ; }     // I had to pull this out of SnapCurrentProcessMiniDumpA/W as it's naked // so can't use SEH. BOOL CalculateBeginningOfCallInstruction ( UINT_PTR & dwRetAddr ) {     BOOL bRet = TRUE ;     // Protect everything inside exception handling. I need to be extra     // careful here as I'm reading up the stack and could possibly bump     // off the top. As I don't want SnapCurrentProcessMiniDump whacking     // the application when you call it, I've got to eat any possible     // exception I could run into here.     __try     {         BYTE * pBytes = (BYTE*)dwRetAddr ;             if ( 0xE8 == *(pBytes - k_CALLNEARBACK) )         {             dwRetAddr -= k_CALLNEARBACK ;         }         else if ( 0xFF == *(pBytes - k_CALLFARBACK) )         {             dwRetAddr -= k_CALLFARBACK ;         }         else         {             bRet = FALSE ;         }     }     __except ( EXCEPTION_EXECUTE_HANDLER )     {         bRet = FALSE ;     }     return ( bRet ) ; }
end example

My first challenge was figuring out where I should grab the registers and what value I should use for the exception address. I finally decided that the best thing to do would be to use the exact registers as they came into my SnapCurrentProcessMiniDump function because they were the users' registers at the time they called my function. To cleanly get registers coming into a function, I had to get a crack at the registers before the compiler-generated code whacked them, so I used the naked calling convention.

What I ended up doing was utilizing a CONTEXT structure on the stack and writing the inline assembly language to copy the registers into the appropriate structure fields. I caused the first bug myself because I copied the 16-bit segment registers into their CONTEXT member fields and didn't realize that the structure still utilized 32-bit values for those members. Consequently, I left garbage in the upper world. Copying the segment registers in the EAX register and using that value to fill the structure worked just fine. The only real work necessary was to get the EBP and ESP values because I had to use those to build my local variables, but getting them was no big deal. For ESP and EBP, I copied the values as they were at the time SnapCurrentProcessMiniDump was called. The SNAPPROLOG macro in Listing 13-5 shows the prolog, and SNAPEPILOG handles the epilog code necessary for naked calling convention functions. The one CONTEXT register value not filled out is the EIP register, which takes a little more work.

My initial thought was that the general registers I captured would be enough, but I was unsure whether the debuggers needed other values, such as the floating point values or extended registers, to properly display the user's information. Therefore, I thought it was safe enough to call GetThreadContext on the executing thread to get these other values. Since my code didn't touch any of those registers, I was going to get the actual values at the time of the call. Of course, I made the call with a second CONTEXT structure so that I wouldn't overwrite my carefully saved values. Once I had the registers retrieved by GetThreadContext, I copied the specific values I saved at the beginning of the function over the retrieved values.

At this point I had the register state correct when the call to SnapCurrentProcessMiniDump occurred. My original thought was to put the return address onto the stack as the EIP register as well as the exception address. Accessing the return address is now easy because Microsoft documented the _ReturnAddress intrinsic function. _ReturnAddress will give you the address anywhere in the function. To use _ReturnAddress, you'll need to add the following two lines to your code so that the compiler doesn't complain the function is undefined. I like to put these lines in my precompiled header file so that they are globally accessible.

extern "C" void * _ReturnAddress ( void ) ; #pragma intrinsic ( _ReturnAddress )

Because I had the rest of the registers set as they were before the call, very few people would have noticed the difference if I had simply used the return address as the EIP and exception address. Given that I can be quite anal-retentive, I went ahead and looked a few bytes back from the return address for the 0xE8 and 0xFF opcodes, which are the near CALL instruction and the far CALL instruction, respectively. That way the register set is completely aligned and appears at the instance of the call. You can see the work of determining the appropriate CALL type in CalculateBeginningOfCallInstruction.

The rest of the work after getting the register squared away is simply setting up the MINIDUMP_EXCEPTION_INFORMATION structure, opening the file handle, and calling MiniDumpWriteDump. You can see all this in action in the CommonSnapCurrentProcessMiniDump function in Listing 13-5.

Opening up a file created with SnapCurrentProcessMiniDump is just like opening any other minidump. The only difference is that the debugger will report the exception number as zero and show the instruction pointer on the CALL instruction itself. Now you have no excuse for not snapping minidumps all the time. Set up a background thread that waits on an event, and when that even is signaled from an external program, call SnapCurrentProcessMiniDump in your thread to get perfect dumps all along the execution of your program.




Debugging Applications for Microsoft. NET and Microsoft Windows
Debugging Applications for MicrosoftВ® .NET and Microsoft WindowsВ® (Pro-Developer)
ISBN: 0735615365
EAN: 2147483647
Year: 2003
Pages: 177
Authors: John Robbins

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net