In Part 1
, I discussed the problems of debugging a program that has been put into production or shipped to a customer. On the developer's workstation, the program might work fine, but on the customer's system, there are random crashes. To fix the problem, you need to know where the program is crashing, and in Part 1 I discussed an approach to determining this.
In Part 2, I described a simple way to instrument the source code that would facilitate the analysis of stack dumps. But this technique has its own problems, and in this article I will describe my attempts to improve on this, and how I came to the perfect solution.
Deus Ex Minidump
After I wrote Part 2, I really thought this was as far as stack tracing could be taken, without resorting to a much larger exception handler footprint or other technology that might not work on Win9x systems.
I was aware of a new Visual Studio .Net technology called minidumps, which you could apparently load directly into the .Net debugger and see the call stack, but I could find very little information on how this was done. Not having Visual Studio .Net, it did not seem worth pursuing - even if I could generate a minidump, how could I look at it? Also, there was a question about whether Win9x systems could create a minidump.
Then two things happened. I found out that the dbghelp.dll - which contains the
MiniDumpWriteDump() API - could be redistributed. Second, I learned of a way to read the minidumps, without using Visual Studio .Net.
MiniDumpWriteDump() API is pretty easy to use, for what it does. Here is what MSDN says:
function writes user-mode minidump information to the specified file.BOOL MiniDumpWriteDump(
[in] Handle to the process for which the information is to be generated. This handle must have read and query access.
[in] Identifier of the process for which the information is to be generated.
[in] Handle to the file in which the information is to be written.
[in] Type of information to be generated. This parameter can be one of the values from the MINIDUMP_TYPE enumeration.
[in] Pointer to a MINIDUMP_EXCEPTION_INFORMATION structure describing the client exception that caused the minidump to be generated. If the value of this parameter is NULL, no exception information is included in the minidump file.
[in] Pointer to an array of MINIDUMP_USER_STREAM_INFORMATION structures. If the value of this parameter is NULL, no user-defined information is included in the minidump file.
[in] Pointer to a MINIDUMP_CALLBACK_INFORMATION structure that specifies a callback routine which is to receive extended minidump information. If the value of this parameter is NULL, no callbacks are performed.
If the function succeeds, the return value is TRUE; otherwise, the return value is FALSE.Remarks
The MiniDumpCallback function receives extended minidump information from MiniDumpWriteDump. It also provides a way for the caller to determine the granularity of information written to the minidump file, as the callback function can filter the default information.
MiniDumpWriteDump may not produce a valid stack trace for the calling thread. To work around this problem, you must capture the state of the calling thread before calling MiniDumpWriteDump and use it as the ExceptionParam parameter. One way to do this is to force an exception inside a __try/__except block and use the EXCEPTION_POINTERS information provided by GetExceptionInformation. Alternatively, you can call the function from a new worker thread and filter this worker thread from the dump.Requirements
Client: Included in Windows XP.
Server: Included in Windows Server 2003.
Redistributable: Requires dbghelp.dll on Windows 2000, Windows NT 4.0, and Windows Me/98/95.
Header: Declared in DbgHelp.h.
Library: Use Dbghelp.lib.
The one tricky parameter is the
DumpType. The only value for
DumpType that I could get to work on Win9x systems was
Theory Into Practice
In order to get set up to call
, we need to open a file for writing and pass its handle to
. In the newly revised ExceptionHandler.cpp
module, this is how we call
static void DumpMiniDump(HANDLE hFile, PEXCEPTION_POINTERS excpInfo)
if (excpInfo == NULL)
RaiseException(EXCEPTION_BREAKPOINT, 0, 0, NULL);
eInfo.ThreadId = GetCurrentThreadId();
eInfo.ExceptionPointers = excpInfo;
eInfo.ClientPointers = FALSE;
excpInfo ? &eInfo : NULL,
This new ExceptionHandler.cpp is incorporated into the XCrashReportTest demo program. When the program is run, you may choose different types of crashes:
When you click on one of the buttons, the program crashes and it generates a minidump file called CRASH.DMP Remember when I said that I learned of a way to read the minidumps, without using Visual Studio .Net? Here is how: use WinDbg, that is available for download from Microsoft Debugging Tools. WinDbg requires that the pdb file be in the same directory as the exe and the CRASH.DMP file. After starting WinDbg, go to File | Open Crash Dump and select the CRASH.DMP file. Next enter ALT+1 to switch to the command window, and type .ecxr and hit Enter. You will see:
In the lower window you will see that
Crash1 which called
Crash2 which called
CrashTestFunction, and the crash occurred at line 33 in crashtest.cpp.
We now have a way of getting the call stack and finding the crash location, without needing to instrument the source code. Downloading and learning to use WinDbg require a few minutes - OK, maybe more than a few if you have dialup. But it is only a one-time thing, and it seems worthwhile. The bottom line: exception handling and minidumps can be added to any existing MFC application by adding a few files, changing three build settings, and recompiling.
The only additional work for you is to save the pdb file, each time you make a new version.
I thought this was going to be the last article about crash reporting, but then I came across something so brilliant that I knew I had to incorporate it into my approach to handling exceptions and crash reporting. I will tell you all about it in Part 4.
Version 1.1 - 2003 October 19
This software is released into the public domain. You are free to use it in any way you like, except that you may not sell this source code. If you modify it or extend it, please to consider posting new code here for everyone to share. This software is provided "as is" with no expressed or implied warranty. I accept no liability for any damage or loss of business that this software may cause.