Документация по функции MiniDumpWriteDump говорится, что
MiniDumpWriteDump
должен вызываться из отдельного процесса, если это вообще возможно, а не из целевого процесса, который выводится.
Поэтому я написал небольшую программу обработки сбоев MFC, которая делает именно это. Я последовал совету в этот так ответ Хансом Пассантом, то есть я передаю значение указателя исключения из программы, вызывающей сбой, в программу-обработчик сбоя, даже если указатель исключения является недопустимым в контексте программы-обработчика сбоя. Это хорошо работает, когда я запускаю тесты в отладочной сборке, но когда я переключаюсь на сборку релиза, программа-обработчик аварийно завершает работу с нарушением доступа, которое происходит внутри MiniDumpWriteDump
функция.
Я в тупике. Почему это должно работать в отладочных сборках, а не в сборках релиза? Это сводит с ума, потому что нарушения доступа часто являются индикаторами для доступа к недействительным указателям, и указатель исключения, который я получаю в программе обработки сбоев, действительно недействителен — но с другой стороны, мне говорят, что это не должно иметь значения, что MiniDumpWriteDump
интерпретирует указатель в контексте сбойного процесса (откуда произошел указатель).
Есть идеи, что я могу делать не так?
Об одном замечании: В своем ответе Ханс предлагает решение, в котором процесс сторожевого устройства предварительно запускается, затем засыпает и просыпается, когда он запускается процессом сбоя. Мое решение несколько иное: я просто запускаю программу-обработчик сбоя в тот момент, когда происходит сбой, затем я передаю необходимую информацию из программы сбоя в программу-обработчик сбоя через аргументы командной строки. Я дважды проверил правильность передаваемой информации, в частности указатель исключения.
Я боролся с подобной проблемой, и теперь, наконец, заметил, что случилось.
Документация MSDN о MINIDUMP_EXCEPTION_INFORMATION утверждает, что ClientPointers
поле должно быть TRUE
если ExceptionPointers
адрес из целевого процесса, а не из локального процесса.
После правильной установки этого поля, я могу просто передать ThreadId
а также ExceptionPointers
из процесса сбоя, заполните их в MINIDUMP_EXCEPTION_INFORMATION
в процессе записи дампа, и он работает отлично.
Я изменил свой подход так, чтобы окончательное решение выглядело так, как было предложено Хансом Пассантом: процесс сторожевого таймера запускается предварительно, затем переходит в спящий режим и просыпается, когда он запускается процессом сбоя. Процесс сбоя делает глубокую копию EXCEPTION_POINTERS
структура и передает эту информацию в сторожевой процесс.
Вот код, который делает глубокую копию. Как уже упоминалось в комментариях к вопросу, главная «проблема» EXCEPTION_RECORD
который является связанным списком потенциально неограниченного размера.
// 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;
}
}
Когда мы получим CrashInfo
Структура в сторожевом процессе у нас теперь есть проблема: EXCEPTION_RECORD
ссылки указывают на недействительные адреса памяти, то есть на адреса памяти, которые действительны только в процессе сбоя. Следующая функция (которая должна быть запущена в сторожевом процессе) исправляет эти ссылки.
// 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]);
}
}
Теперь мы готовы пройти &(crashInfo.exceptionPointers)
к MiniDumpWriteDump
функция.
Замечания: Очевидно, что это не полное решение. Вы, вероятно, захотите передать больше информации от процесса сбоя процессу наблюдения. CrashInfo
Структура является кандидатом на хранение этой информации. Также способ, которым процессы связываются друг с другом, не показан здесь. В моем случае я использовал решение, представленное Хансом Пассантом, которое связано в начале вопроса: используйте событие для синхронизации (CreateEvent + SetEvent) и файл с отображением в памяти (CreateFileMapping + MapViewOfFile), чтобы перетасовать информацию из одного процесса к следующему. (Уникальные) имена события и отображенного в памяти файла определяются основным процессом и передаются в сторожевой процесс через аргументы командной строки.