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.