问题
The documentation of the MiniDumpWriteDump function states that
MiniDumpWriteDump
should be called from a separate process if at all possible, rather than from within the target process being dumped.
So I wrote a small MFC crash handler program that does just that. I followed the advice in this SO answer by Hans Passant, i.e. I am passing the value of the exception pointer from the crashing program to the crash handler program even though the exception pointer is not valid in the context of the crash handler program. This works well when I run tests in a debug build, but when I switch to a release build the crash handler program crashes, with an access violation that occurs inside the MiniDumpWriteDump
function.
I am stumped. Why should this work in debug builds, but not in release builds? It's maddening because access violations often are indicators for accessing invalid pointers, and the exception pointer I am receiving in the crash handler program is indeed invalid - but on the other hand I am told that this should not matter, that MiniDumpWriteDump
is interpreting the pointer in the context of the crashing process (from where the pointer originated).
Any ideas what I could be doing wrong?
On a sidenote: In his answer, Hans proposes a solution where the watchdog process is pre-launched, then goes to sleep and wakes up when it is triggeredd by the crashing process. My solution is slightly different: I am simply launching the crash handler program at the time when the crash occurs, then I pass the necessary information from the crashing program to the crash handler program via command line arguments. I double-checked that the information being passed is correct, specifically the exception pointer.
回答1:
I struggled with a similar issue, and have now finally noticed what was wrong.
The MSDN documentation of MINIDUMP_EXCEPTION_INFORMATION states that the ClientPointers
field must be TRUE
if the ExceptionPointers
address is from the target process instead of the local process.
After setting this field correctly, I can simply pass ThreadId
and ExceptionPointers
from the crashing process, fill them into a MINIDUMP_EXCEPTION_INFORMATION
in the dump-writing process, and it works perfectly.
回答2:
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.
来源:https://stackoverflow.com/questions/32691914/access-violation-in-minidumpwritedump-when-invoked-out-of-process