How to Get at the Exception Information When Using Minidumpwritedump Out-Of-Process

How do I get at the exception information when using MiniDumpWriteDump out-of-process?

You also need the MINIDUMP_EXCEPTION_INFORMATION.ThreadId value. The simplest way, and the way I made it work, is to use a memory-mapped file to transfer both the ThreadId and the ExceptionPointers. And a named event to wake up the watchdog. It doesn't matter that the pointer is not valid in the context of the watchdog process.

Use CreateFileMapping + MapViewOfFile in the watched process as part of its initialization, OpenFileMapping + MapViewOfFile in the watchdog. Your SetUnhandledExceptionFilter should then only call GetCurrentThreadId() and copy the tid and the pExcept to the memory mapped file view, call SetEvent() to wake up the watchdog and block forever until the watchdog terminates it.

Access violation in MiniDumpWriteDump when invoked out-of-process

I changed my approach so that the final solution looks like the one suggested by Hans Passant: The watchdog process is pre-launched, then goes to sleep and wakes up when it is triggeredd by the crashing process. The crashing process makes a deep-copy of the EXCEPTION_POINTERS structure and passes that information to the watchdog process.

Here's the code that makes the deep-copy. As mentioned in the comments to the question, the main "problem" is EXCEPTION_RECORD which is a linked-list of potentially unlimited size.

// The maximum number of nested exception that we can handle. The value we
// use for this constant is an arbitrarily chosen number that is, hopefully,
// sufficiently high to support all realistic and surrealistic scenarios.
//
// sizeof(CrashInfo) for a maximum of 1000 = ca. 80 KB
const int MaximumNumberOfNestedExceptions = 1000;

// Structure with information about the crash that we can pass to the
// watchdog process
struct CrashInfo
{
EXCEPTION_POINTERS exceptionPointers;
int numberOfExceptionRecords;
// Contiguous area of memory that can easily be processed by memcpy
EXCEPTION_RECORD exceptionRecords[MaximumNumberOfNestedExceptions];
CONTEXT contextRecord;
};

// The EXCEPTION_POINTERS parameter is the original exception pointer
// that we are going to deep-copy.
// The CrashInfo parameter receives the copy.
void FillCrashInfoWithExceptionPointers(CrashInfo& crashInfo, EXCEPTION_POINTERS* exceptionPointers)
{
// De-referencing creates a copy
crashInfo.exceptionPointers = *exceptionPointers;
crashInfo.contextRecord = *(exceptionPointers->ContextRecord);

int indexOfExceptionRecord = 0;
crashInfo.numberOfExceptionRecords = 0;
EXCEPTION_RECORD* exceptionRecord = exceptionPointers->ExceptionRecord;

while (exceptionRecord != 0)
{
if (indexOfExceptionRecord >= MaximumNumberOfNestedExceptions)
{
// Yikes, maximum number of nested exceptions reached
break;
}

// De-referencing creates a copy
crashInfo.exceptionRecords[indexOfExceptionRecord] = *exceptionRecord;

++indexOfExceptionRecord;
++crashInfo.numberOfExceptionRecords;
exceptionRecord = exceptionRecord->ExceptionRecord;
}
}

When we receive the CrashInfo structure in the watchdog process we now have a problem: The EXCEPTION_RECORD references are pointing to invalid memory addresses, i.e. to memory addresses that are valid only in the crashing process. The following function (which must be run in the watchdog process) fixes those references.

// The CrashInfo parameter is both in/out
void FixExceptionPointersInCrashInfo(CrashInfo& crashInfo)
{
crashInfo.exceptionPointers.ContextRecord = &(crashInfo.contextRecord);

for (int indexOfExceptionRecord = 0; indexOfExceptionRecord < crashInfo.numberOfExceptionRecords; ++indexOfExceptionRecord)
{
if (0 == indexOfExceptionRecord)
crashInfo.exceptionPointers.ExceptionRecord = &(crashInfo.exceptionRecords[indexOfExceptionRecord]);
else
crashInfo.exceptionRecords[indexOfExceptionRecord - 1].ExceptionRecord = &(crashInfo.exceptionRecords[indexOfExceptionRecord]);
}
}

We are now ready to pass &(crashInfo.exceptionPointers) to the MiniDumpWriteDump function.

Note: Obviously this is not a complete solution. You will probably want to pass more info from the crashing process to the watchdog process. The CrashInfo structure is the candidate to hold this information. Also the way how the processes communicate with each other is not shown here. In my case I went with the solution presented by Hans Passant which is linked at the beginning of the question: Use an event for synchronization (CreateEvent + SetEvent) and a memory-mapped file (CreateFileMapping + MapViewOfFile) to shuffle the information from one process to the next. The (unique) names of the event and the memory-mapped file are determined by the main process and passed to the watchdog process via command line arguments.

Trouble passing _EXCEPTION_POINTERS * using FileMapping

Right, you cannot dereference the pointer in another process, it is only valid in the crashed process. It is only good enough to pass to MiniDumpWriteDump(), MINIDUMP_EXCEPTION_INFORMATION.ExceptionPointers field. Technically you could use ReadProcessMemory() but doing so for a crashed process is unnecessarily risky. The simple solution is to add an extra field to your structure that stores the exception code and written by your exception filter.

mytest passdata ;
passdata.except = ExceptionInfo;
// Note: added field
passdata.ExceptionCode = ExceptionInfo->ExceptionRecord->ExceptionCode;
passdata.ThreadId = GetCurrentThreadId();
// etc..

Also avoid calling winapi functions like OpenFileMapping and MapViewOfFile, it is too risky. They tend to deadlock when the program crashed due to heap corruption of the process heap. A common reason to crash and a deadlock because the heap lock is still held. Just do this at program initialization. You don't need to bother cleaning up either, Windows takes care of it when your watchdog process terminates the crashed process after taking the minidump.

How do I get a meaningful stack-trace using MiniDumpWriteDump

I figured it out. Basically game.exe had its own MiniDumpWriteDump code that was triggering before my code. So the stack trace I was getting wasn't a trace of the error, it was a trace of game.exe doing its own MiniDump. I put more details up in the original post.

Thanks!

Stack information disappears when I add exception information to my minidump

Calling GetThreadContext with CONTEXT_FULL does not capture all the registers needed to get the stack trace, and the existence of the context prevents the debugger from using other information to get the call stack. Using CONTEXT_ALL instead gets enough information to recreate the call stack.

https://msdn.microsoft.com/en-us/magazine/hh580738.aspx was a helpful reference in figuring this out.

Getting information about where c++ exceptions are thrown inside of catch block?

This is possible with using SEH (structured exception handling). The point is that MSVC implements C++ exceptions via SEH. On the other hand the pure SEH is much more powerful and flexible.

That's what you should do. Instead of using pure C++ try/catch blocks like this:

try
{
DoSomething();
} catch(MyExc& exc)
{
// process the exception
}

You should wrap the inner code block DoSomething with the SEH block:

void DoSomething()
{
__try {
DoSomethingInner();
}
__except (DumpExc(GetExceptionInformation()), EXCEPTION_CONTINUE_SEARCH) {
// never get there
}
}

void DumpEx(EXCEPTION_POINTERS* pExc)
{
// Call MiniDumpWriteDump to produce the needed dump file
}

That is, inside the C++ try/catch block we place another raw SEH block, which only dumps all the exceptions without catching them.

See here for an example of using MiniDumpWriteDump.



Related Topics



Leave a reply



Submit