Нарушение прав доступа в MiniDumpWriteDump при вызове вне процесса

Документация по функции MiniDumpWriteDump говорится, что

MiniDumpWriteDump должен вызываться из отдельного процесса, если это вообще возможно, а не из целевого процесса, который выводится.

Поэтому я написал небольшую программу обработки сбоев MFC, которая делает именно это. Я последовал совету в этот так ответ Хансом Пассантом, то есть я передаю значение указателя исключения из программы, вызывающей сбой, в программу-обработчик сбоя, даже если указатель исключения является недопустимым в контексте программы-обработчика сбоя. Это хорошо работает, когда я запускаю тесты в отладочной сборке, но когда я переключаюсь на сборку релиза, программа-обработчик аварийно завершает работу с нарушением доступа, которое происходит внутри MiniDumpWriteDump функция.

Я в тупике. Почему это должно работать в отладочных сборках, а не в сборках релиза? Это сводит с ума, потому что нарушения доступа часто являются индикаторами для доступа к недействительным указателям, и указатель исключения, который я получаю в программе обработки сбоев, действительно недействителен — но с другой стороны, мне говорят, что это не должно иметь значения, что MiniDumpWriteDump интерпретирует указатель в контексте сбойного процесса (откуда произошел указатель).

Есть идеи, что я могу делать не так?

Об одном замечании: В своем ответе Ханс предлагает решение, в котором процесс сторожевого устройства предварительно запускается, затем засыпает и просыпается, когда он запускается процессом сбоя. Мое решение несколько иное: я просто запускаю программу-обработчик сбоя в тот момент, когда происходит сбой, затем я передаю необходимую информацию из программы сбоя в программу-обработчик сбоя через аргументы командной строки. Я дважды проверил правильность передаваемой информации, в частности указатель исключения.

1

Решение

Я боролся с подобной проблемой, и теперь, наконец, заметил, что случилось.

Документация MSDN о MINIDUMP_EXCEPTION_INFORMATION утверждает, что ClientPointers поле должно быть TRUE если ExceptionPointers адрес из целевого процесса, а не из локального процесса.

После правильной установки этого поля, я могу просто передать ThreadId а также ExceptionPointers из процесса сбоя, заполните их в MINIDUMP_EXCEPTION_INFORMATION в процессе записи дампа, и он работает отлично.

0

Другие решения

Я изменил свой подход так, чтобы окончательное решение выглядело так, как было предложено Хансом Пассантом: процесс сторожевого таймера запускается предварительно, затем переходит в спящий режим и просыпается, когда он запускается процессом сбоя. Процесс сбоя делает глубокую копию 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), чтобы перетасовать информацию из одного процесса к следующему. (Уникальные) имена события и отображенного в памяти файла определяются основным процессом и передаются в сторожевой процесс через аргументы командной строки.

0

По вопросам рекламы [email protected]