Generating a Minidump

Last time, I demonstrated a way to automatically generate a stack crawl to help you debug errors in your application. However, I also mentioned that stack crawls are usually not enough information by themselves. In this post, I am going to cover how to create a file with more complete debugging information. Not only will it contain stack crawls, but it will contain local and global variable information, thread and process information, and even application memory. All of this information will be collected into a single minidump file, which you can submit automatically.

The API that we want to design should be able to write out a minidump on demand, without requiring the application to crash, or altering the application in any noticeable fashion. It should capture the state of the application as though a snapshot were taken — as if you broke into it with a debugger.

The magic for generating one of these minidump files is in the MiniDumpWriteDump API from the DbgHelp library. This single call will write out various pieces of application state to a file; but it requires a bit of help to ensure you’re only writing out information you actually need.

At its core, the MiniDumpWriteDump API requires a few pieces of information: what process to write debugging information for, what file to write the data to, what exception information to associate with the debugging scenario, and what filters to apply to the data. None of these pieces of information are difficult to obtain, but it requires an understanding of the problem domain to perform properly.

The first thing to understand about the API is the DumpType parameter — this is a logical OR’ing of MINIDUMP_TYPE enumeration values. The general rule of thumb is: the more stuff you OR in, the larger the dump file will be. However, this isn’t strictly true because 1) some of the flags you can OR in will filter data out out the resulting dump file (making it smaller), and 2) you can optionally specify a callback to further filter the results. MSDN does a pretty good job of describing what information is included or excluded for each of the flags.

One of the more confusing things about this call is the ExceptionParam parameter. MSDN claims that you can set this parameter to NULL to exclude exception information from the resulting dump file, but then goes on to tell you if you do so, the call stack may be wrong. It also tells you that you can use a worker thread to perform the work. So what’s really going on here? To understand this better, you need to understand that minidumps are usually something that come out of application crashes. And application crashes are usually a result of hitting the SEH unhandled exception filter at the base of a thread’s call stack. Because of this, the MiniDumpWriteDump API has a handy way for you to include exception information a la the ExceptionPointers field of the MINIDUMP_EXCEPTION_INFORMATION structure. You can assign the results of GetExceptionInfo into that field, and that exception information can be retained in the minidump.

However, due to the fact that our API can be called at any point in time, we cannot rely on crashes or exceptions being the cause of the minidump. So while it would be possible to use SEH to create the context we require to get a valid stack crawl, that would also mean we have to generate an exception and include that information in the minidump. Instead, we will make use of a worker thread to perform the operation. But this comes with its own considerations and questions. The first thing to remember is that we need to wait for the minidump to complete before allowing our API to continue. Failing to do this would mean that we’re dumping some random callstack and not what the user expected. But beyond that, it raises an interesting question: what should be done with other threads in the application? Do we want to allow them to continue working while the minidump is being written? Or do we want to stop them from running until the dump has completed? Unfortunately, the answer isn’t concrete. If we stop all of the threads by suspending them, then we’re altering the state of the application which is a pretty big debugging no-no. What’s more, by stopping them, we lose information in our minidump about whether the threads were suspended or not, since all of the threads will show up as suspended. However, it can still be beneficial to suspend the threads because it’s possible the caller has detected a threading issue and wants to debug it. So we’re going to have to push this problem off on our API and allow it to perform either way.

The next part of MiniDumpWriteDump of interest to us is the concept of user streams. At first blush, these appear to be ancillary streams of data that can be associated with the minidump. However, they’re not! MSDN does a terrible job of calling this fact out. The minidump itself is comprised of directories of information, and those directories contain these documented stream types. So if you attempt to implement your own stream from the list of MINIDUMP_STREAM_TYPE enumerations (for instance, the MiscInfoStream), then you will get a warning in WinDbg about ignoring a duplicate stream. In Visual Studio, the duplicate stream is silently ignored. The only streams which aren’t duplicated automatically seem to be the two comment stream types. What MSDN fails to call out is that the use of these user streams is for user-defined data, whose type is greater than LastReservedStream. You define your own stream types, and their format. No debugger understands this information automatically — so this option is really only interesting if you wish to write your own debugger extension.

So for this API, the user streams will be ignored and we can simply pass NULL.

The final part of the call which requires discussion is the callback filter. This is a function that the minidump writer will call, allowing you to filter the contents of the dump file. The callback is passed an input parameter that denotes the type of information to be filtered, and you can modify the output parameter to exclude specific pieces of information from the dump file. This is an important mechanism to us for two reasons: 1) we want to allow the caller to control the size of the dump files, and 2) we need to be able to filter out our helper thread so the user doesn’t see it. The first point is important because there are times when the caller may only need 10k of information to solve a particular bug, but may need 100MB of information to solve another. The second point is important simply so we don’t expose the implementation details of our API to the caller.

After all of that lead up, we’re finally ready to start looking at some code. The public parts of our API, with the boring bits elided, will look something like this:

class MiniDump {
public:
	typedef enum InfoLevel {
		kInfoLevelSmall,
		kInfoLevelMedium,
		kInfoLevelLarge
	} InfoLevel;

	bool Create( const wchar_t *inPath, 
				InfoLevel inLevel = kInfoLevelMedium, 
				bool inSuspendOtherThreads = false ) const;
};

This suits our need nicely — it allows the user to control where the dump file is written, how much information to output, and whether to suspend background threads or not.

The implementation of the Create function itself isn’t too interesting — it spawns a worker thread to do most of the work, and optionally suspends background threads.

bool MiniDump::Create( const wchar_t *inPath, InfoLevel inLevel, bool inSuspendOtherThreads ) const {
	if (!inPath)	return false;

	// Set up the list of parameters we want to pass to the newly-created thread.  Note
	// that we create the thread in a suspended state!
	struct MiniDumpParams param = { inPath, inLevel, this };
	DWORD threadId = 0;
	HANDLE hThread = ::CreateThread( NULL, 0, MinidumpWriter, &param, CREATE_SUSPENDED, &threadId );

	if (hThread) {
		// Having created the thread successfully, we need to put all of the other 
		// threads in the process into a suspended state, making sure not to suspend
		// our newly-created thread.  We do this because we want this function to
		// behave as a snapshot, and that means other threads should not continue
		// to perform work while we're creating the minidump.
		if (inSuspendOtherThreads) {
			EnumerateThreads( ::SuspendThread, threadId );
		}

		// Now we can resume our worker thread
		::ResumeThread( hThread );

		// Wait for the thread to finish working, without allowing the current
		// thread to continue working.  This ensures that the current thread won't
		// do anything interesting while we're writing the debug information out.  
		// This also means that the minidump will show this as the current callstack.
		::WaitForSingleObject( hThread, INFINITE );

		// The thread exit code tells us whether we were able to create the minidump
		DWORD code = 0;
		::GetExitCodeThread( hThread, &code );
		::CloseHandle( hThread );

		// If we suspended other threads, now is the time to wake them up
		if (inSuspendOtherThreads) {
			EnumerateThreads( ::ResumeThread, threadId );
		}

		return code != 0;
	}
	return false;
}

The MinidumpWriter thread entrypoint is where most of the interesting work happens. Ignoring the uninteresting bits, it looks like:

int type = MiniDumpNormal;
switch (p->level) {
	case kInfoLevelSmall: {
		type |= MiniDumpWithIndirectlyReferencedMemory |
				MiniDumpScanMemory;
	} break;
	case kInfoLevelMedium: {
		type |= MiniDumpWithDataSegs |
				MiniDumpWithPrivateReadWriteMemory |
				MiniDumpWithHandleData |
				MiniDumpWithFullMemoryInfo | 
				MiniDumpWithThreadInfo |
				MiniDumpWithUnloadedModules;
	} break;
	case kInfoLevelLarge: {
		type |= MiniDumpWithDataSegs |
				MiniDumpWithPrivateReadWriteMemory |
				MiniDumpWithHandleData |
				MiniDumpWithFullMemory |
				MiniDumpWithFullMemoryInfo | 
				MiniDumpWithThreadInfo |
				MiniDumpWithUnloadedModules |
				MiniDumpWithProcessThreadData;
	} break;
}

// Set up the callback to be called by the minidump writer.  This allows us to
// filter out information that we may not care about.
MINIDUMP_CALLBACK_INFORMATION callback = { 0 };
struct MinidumpCallbackParam info = { p->level, p->dump, ::GetCurrentThreadId() };
callback.CallbackParam = &info;
callback.CallbackRoutine = MinidumpFilter;

// After all that, we can write out the minidump
BOOL bRet = ::MiniDumpWriteDump( ::GetCurrentProcess(), ::GetCurrentProcessId(), hFile,
		(MINIDUMP_TYPE)type, NULL, NULL, &callback );

Most of the method is comprised of code to determine what flags to set for the DumpType parameter. As you can see, each dump type sets up its own set of flags for what information to dump. What you can’t see is how that information is filtered by the callback. Which brings us to the last interesting piece of the puzzle: the filter itself.

switch (inInput->CallbackType) {
	case IncludeModuleCallback:	
	case ThreadCallback:
	case ThreadExCallback:		return TRUE;
	case CancelCallback:		return FALSE;

	case IncludeThreadCallback: {
		// We don't want to include information about the minidump writing 
		// thread, as that's not of interest to the caller
		if (inInput->IncludeThread.ThreadId == param->thread)	return FALSE;
		return TRUE;
	} break;

	case MemoryCallback: {
		// Small and medium sized dumps don't need full memory access
		if (kInfoLevelSmall == infoLevel ||
			kInfoLevelMedium == infoLevel)	return FALSE;
		return TRUE;
	} break;

	case ModuleCallback: {
		if (kInfoLevelSmall == infoLevel) {
			// When creating a small dump file, we filter out any modules that
			// aren't being directly referenced.
			if (!(outOutput->ModuleWriteFlags & ModuleReferencedByMemory)) {
				outOutput->ModuleWriteFlags &= ~ModuleWriteModule;
			}
		} else if (kInfoLevelMedium == infoLevel) {
			// When creating a medium-sized dump file, we filter out any module
			// data segments if they're not part of our core module list.  This
			// helps reduce the size of the dump file by quite a bit.
			if (outOutput->ModuleWriteFlags & ModuleWriteDataSeg) {
				if (!param->dump->IsDataSegmentNeeded( inInput->Module.FullPath )) {
					outOutput->ModuleWriteFlags &= ~ModuleWriteDataSeg;
				}
			}
		}

		return TRUE;
	} break;
}

You’ll notice that when we determine whether a thread should be included in the dump or not, we look for our worker thread and return FALSE if we find it. This ensures that the worker thread is not written out to the dump file. Also, only large dump files get full memory access. Finally, when determining what module information to include, we ensure that only directly referenced modules are included for small memory dumps, and only modules we care about are included for medium memory dumps. The IsDataSegmentNeeded helper method will return true only for kernel modules and the process itself.

The beauty of this API is in the simplicity for the caller — they merely specify where the information should be written out to, and how much information to provide. Once the file has been written out, it is trivial to write a helper method to automatically generate a bug report and submit the data. Now the only question becomes: when would you use this API?

Personally, I use this approach for reporting on failed assertions. When you’ve got an assertion failing in your code, it means that one of your assumptions which couldn’t possibly be false… is false. This would be a great opportunity for you to write out the application state so you can determine why your assertion isn’t holding up.

One thing about this API that bothers me is the fact that we can lose thread state information. If the user says to suspend all background threads, then our dump file will show all background threads as being suspended. However, it’s possible that the user is trying to debug a threading issue where a background thread has stomped on a particular piece of memory. Unfortunately, there’s no way to handle this automatically, short of rewriting MiniDumpWriteDump ourselves.

tl;dr: I’ve posted the complete code examples on my svn repository, which you can access here.

This entry was posted in Win32 and tagged , . Bookmark the permalink.

22 Responses to Generating a Minidump

  1. Svetlana Furman says:

    I’ve used you sample code for creating medium size dump file. Works great! Thank you.
    But… if I enable gflags page heap for my application, the dump file is still generated, but it is not readable by MSVS or WinDbg. Any suggestions, hints?

    Command I use for enabling page heap:
    gflags /p /enable c:\svn\bin\myApp.exe /full

    Thanks in advance,
    Svetlana

  2. Aaron Ballman says:

    That’s really odd — I just did a very, very simple test and it doesn’t seem to matter whether I have gflags on or off.

    int main( void ) {
    MiniDump dump;
    dump.Create( L”F:\\Aaron Ballman\\Desktop\\dump.dmp” );
    return 0;
    }

    Either way VS 2010 is able to open the dump file up. So there must be something different between the ways we’re testing it. Can you give me some example code that demonstrates the problem on your end and I’ll try it on mine?

  3. Svetlana Furman says:

    (sorry for double posting – I wasn’t sure the first one worked)
    I haven’t changed anything in your code.
    I think it might be depends on the version of DbgHelp.dll or maybe because I’m building with MSVS 2008?
    Today I’ve downloaded newest version of Debuging tools for windows 7 and did build with that DbgHelp.dll, but got same results. I think I have VS 2010 I’ll try to use it, maybe at least open dump file. Can you tell me what version of DbgHelp.dll you are using? Also I see that Debuging tools for windows has installed several DbgHelp.lib files, I’m actually not sure which one to use. Actually, if you can add some information in your post on how to redistribute DbgHelp.dll it will be helpful. Right now I have about 12 files of that dll on my machine… and not quite sure what to make of it.

    Thanks for your help,
    Svetlana

  4. Aaron Ballman says:

    No worries, I removed the duplicate. :-)

    I’ve used whatever version of DbgHelp happens to be on the machine, not any specific one. You can tell which one you’re using by checking in the debugger. Stop at a breakpoint and look at the loaded modules. Mine is:

    dbghelp.dll C:\Windows\System32\dbghelp.dll N/A N/A Cannot find or open the PDB file 8 6.01.7601.17514 11/20/2010 7:57 AM 6A6C0000-6A7AB000 test.dmp: Native

    If you’d like, you can also email me the dump file you generate and I’ll see if I can open it. Perhaps it’s something specific to your debugging setup (though that seems like a stretch to me).

    As for redistributing the debug help libraries, it does come pre-installed on all versions of Windows that I’m aware of. It’s usually not the latest version though (sometimes it can be rather ancient). The only distribution mechanism I’ve heard of it the debugging tools package itself. You’d have to check into the license associated with it to see whether you can redistribute subpackages or not (I’m honestly not certain).

  5. Svetlana Furman says:

    I did a little more experiments and It seems like the dump file is good is I just create it in main ( as in your code), but if I create it in catch block it is corrupted.
    In the sample below dump1.dmp is ok, but dump_catch.dmp is corrupt.
    int main( void ) {
    MiniDump dump;
    dump.Create( L”c:\\Temp\\dump1.dmp” );
    try{
    char *p = (char*)malloc(2);
    throw EXCEPTION_BREAKPOINT;
    strcpy(p, 11111111111111111111111111111111111111111111111111111111111″);
    }catch(DWORD){
    MiniDump dump;
    dump.Create( L”c:\\Temp\\dump_catch.dmp” );
    }
    return 0;
    }
    I’ll try то send corrupt dump to your email. It’s size is 2MB

  6. Arun M G says:

    This is actually not a comment about the article, but a doubt raised while i tried to analyse a dump file using WinDbg. Dump file is created when my application is getting hang.

    I am creating the application in MFC without giving debug information. When analysing the dump, no useful information about the crash is found in the WinDbg. But surprisingly, exact source code is where exception occurred is displayed when creating the application with degug information.
    Please let me know, why this information is not displayed in the first case.
    I am actually expecting the same result since the appliaction i have created is not supposed to deliver with Debug information.
    I hope, you can easily solve the problem.
    I am expecting a reply from you.

    Thanks in advance,
    Arun MG

  7. Svetlana says:

    I did a little more experiments and It seems like the dump file is good is I just create it in main ( as in your code), but if I create it in catch block it is corrupted.
    In the sample below dump1.dmp is ok, but dump_catch.dmp is corrupt.
    int main( void ) {
    MiniDump dump;
    dump.Create( L”c:\\Temp\\dump1.dmp” );
    try{
    char *p = (char*)malloc(2);
    throw EXCEPTION_BREAKPOINT;
    strcpy(p, 11111111111111111111111111111111111111111111111111111111111″);
    }catch(DWORD){
    MiniDump dump;
    dump.Create( L”c:\\Temp\\dump_catch.dmp” );
    }
    return 0;
    }
    I’ll try то send corrupt dump to your email. It’s size is 2MB

  8. Aaron Ballman says:

    Arun MG — without debug information, the debugger doesn’t know what to map back to. With debug information, the debugger has a chance to locate the source (if it’s local, or stored on a source server with source indexed PDBs) because that information is stored with the PDB file. So it’s expected that the behavior will be different between the situations.

  9. Aaron Ballman says:

    Svetlana — I am able to reproduce the issue you are seeing. I’ll look into it as I have the chance.

  10. Aaron Ballman says:

    Svetlana — I think this problem only happens with your example when you attempt to write out both dump files. When you comment out the first dump (the non-catch one), then the second dump seems to always succeed for me. I don’t claim this is the root cause, but perhaps the issue doesn’t happen in practice because you don’t write multiple dump files from the same function?

  11. Aviv Hurvitz says:

    I can confirm that this method helped me get a dump that I otherwise couldn’t in the naive single-threaded way.

    Please note there is small bug in the code, with an extra semicolon in this line after MiniDumpWithUnloadedModules;

    type |= MiniDumpWithDataSegs |
    [ …]
    MiniDumpWithUnloadedModules;
    MiniDumpWithProcessThreadData;

  12. Aaron Ballman says:

    Whoops! Thank you for pointing out that typo — I’ve corrected it now. :-)

  13. Michael L says:

    Can this class be used with

    SetUnhandledExceptionFilter(OurCrashHandler);

    .. or what would be the preferred way to do that with Qt GUI application ?

    It seems when exception occurs, my program hangs and no dump file is created.

  14. Aaron Ballman says:

    @Michael — I don’t see why it couldn’t be used from an unhandled exception filter. Are you certain your crash handler is being called? Note that some exceptions will not get caught by using SetUnhandledExceptionFilter, but will get caught by using AddVectoredExceptionHandler.

  15. Peter Hackett says:

    Thank you so much for this code. I just tried it and it worked exactly how
    I was expecting. I love the “gift economy”

    I found this site after I tried to just call MiniDumpWriteDump to “save state”
    and as you suggested, Visual Studio just says “Huh?”

    I also discovered the extra semicolon that Aviv mentioned above. *

    As is often the case for me, I discovered it while reformatting the code to
    the “no line shall be longer than 80 characters” standard “enforced” by
    by the fact that I started programming in 1927 :-) and the *awesome* best
    terminal in the universe at that time was an Ann Arbor Ambassador (Wikipedia it)
    that had *60* lines and 80 columns. (Most terminals at the time had just 24 lines(!))

    * Perhaps http://code.aaronballman.com/minidumper/MiniDump.cpp
    needs to be update(?)

  16. Peter Hackett says:

    Minor correction:

    Wikipedia doesn’t have an article for the Ambassador. I was thinking of another
    classic: the ADM-3A (Used by Bill Joy at UC Berkeley to develop Vi)
    http://en.wikipedia.org/wiki/ADM-3A

    I know you’re all dying for info on the Ambassador, so:
    http://terminals.classiccmp.org/wiki/index.php/Ann_Arbor_Ambassador

  17. Aaron Ballman says:

    @Peter — oh, great point about forgetting to update the code in the repository! I’ve fixed the bug there as well. :-)

  18. Clement Wang says:

    This article is very useful to me then I made a test. My code was shown below.
    char* ptr = NULL;
    char* buf = “12345678990”;
    try
    {
    CopyMemory(ptr, buf, 20);
    }
    catch(…)
    {
    MyDump.Create(L”C:\\Temp\\dump_catch.dmp”);
    }

    I expected to have an ERROR_CODE: (NTSTATUS) 0xC0000005 in dump file, but I got ExceptionCode:80000003. Could you help me solving this problem?

    Many thanks.

    Clement Wang

  19. Aaron Ballman says:

    @Clement — did you run your code under the debugger? If so, that might explain why you are getting 80000003, as that’s a “break instruction exception”.

  20. Clement Wang says:

    Thank you for your prompt reply. I ran my code under vs2005. I think that is why I got 80000003. Now I got another problem. When I open the dump file, I got error message “Failure when opening dump file…HRESULT 0x80004005”. Am I missing any steps ?

    Many thanks

  21. @Clement — I’m not certain if you are missing steps or not. That HRESULT is the generic “something failed but we won’t tell you what” code, so it’s impossible for me to really know what may be the problem.

  22. IInspectable says:

    Since this code has been online for almost 5 years now, I feel like I need to point out, that it should not be used in its current form. I have written an answer on Stack Overflow (http://stackoverflow.com/a/41433641/1889329) addressing the most severe issues.

    Regarding @MichaelL’s post: Yes, this code can be called from an unhandled exception filter. But it produces a largely useless call stack. And it can deadlock. Details are explained in the Stack Overflow Q&A.

Leave a Reply

Your email address will not be published. Required fields are marked *