在进程外调用时 MiniDumpWriteDump 中的访问冲突
Access violation in MiniDumpWriteDump when invoked out-of-process
The documentation of the MiniDumpWriteDump function 表示
MiniDumpWriteDump
should be called from a separate process if at all possible, rather than from within the target process being dumped.
所以我编写了一个小型 MFC 崩溃处理程序来执行此操作。我遵循了 Hans Passant 在 this SO answer 中的建议,即我将异常指针的值从崩溃程序传递到崩溃处理程序,即使异常指针在崩溃处理程序的上下文中无效。当我 运行 在调试版本中进行测试时,这很有效,但是当我切换到发布版本时,崩溃处理程序崩溃,并在 MiniDumpWriteDump
函数内部发生访问冲突。
我很难过。为什么这应该在调试版本中工作,而不是在发布版本中?这令人抓狂,因为访问冲突通常是访问无效指针的指标,而我在崩溃处理程序中收到的异常指针确实无效——但另一方面我被告知这无关紧要,MiniDumpWriteDump
是在崩溃进程的上下文中解释指针(从指针起源的地方)。
知道我可能做错了什么吗?
旁注:在他的回答中,Hans 提出了一个解决方案,其中 watchdog 进程是预启动的,然后进入休眠状态,并在崩溃进程触发时唤醒。我的解决方案略有不同:我只是在崩溃发生时启动崩溃处理程序,然后通过命令行参数将崩溃程序的必要信息传递给崩溃处理程序。我仔细检查了传递的信息是否正确,特别是异常指针。
我曾遇到过类似的问题,现在终于发现问题出在哪里了。
MINIDUMP_EXCEPTION_INFORMATION 的 MSDN 文档指出,如果 ExceptionPointers
地址来自目标进程而不是本地进程,则 ClientPointers
字段必须是 TRUE
。
正确设置这个字段后,我可以简单地从崩溃过程中传递ThreadId
和ExceptionPointers
,在转储写入过程中将它们填充到MINIDUMP_EXCEPTION_INFORMATION
中,并且它有效完美。
我改变了我的方法,使最终解决方案看起来像 Hans Passant 建议的那样:看门狗进程是预启动的,然后进入休眠状态,并在崩溃进程触发时唤醒。崩溃进程对 EXCEPTION_POINTERS
结构进行深度复制,并将该信息传递给看门狗进程。
这是进行深拷贝的代码。正如问题评论中提到的,主要的 "problem" 是 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
结构是保存此信息的候选者。此外,此处未显示进程如何相互通信的方式。就我而言,我采用了 Hans Passant 提出的解决方案,该解决方案链接在问题的开头:Use an event for synchronization (CreateEvent + SetEvent) and a memory-mapped file (CreateFileMapping + MapViewOfFile) to shuffle the information from one process到下一个。事件和内存映射文件的(唯一)名称由主进程确定并通过命令行参数传递给看门狗进程。
The documentation of the MiniDumpWriteDump function 表示
MiniDumpWriteDump
should be called from a separate process if at all possible, rather than from within the target process being dumped.
所以我编写了一个小型 MFC 崩溃处理程序来执行此操作。我遵循了 Hans Passant 在 this SO answer 中的建议,即我将异常指针的值从崩溃程序传递到崩溃处理程序,即使异常指针在崩溃处理程序的上下文中无效。当我 运行 在调试版本中进行测试时,这很有效,但是当我切换到发布版本时,崩溃处理程序崩溃,并在 MiniDumpWriteDump
函数内部发生访问冲突。
我很难过。为什么这应该在调试版本中工作,而不是在发布版本中?这令人抓狂,因为访问冲突通常是访问无效指针的指标,而我在崩溃处理程序中收到的异常指针确实无效——但另一方面我被告知这无关紧要,MiniDumpWriteDump
是在崩溃进程的上下文中解释指针(从指针起源的地方)。
知道我可能做错了什么吗?
旁注:在他的回答中,Hans 提出了一个解决方案,其中 watchdog 进程是预启动的,然后进入休眠状态,并在崩溃进程触发时唤醒。我的解决方案略有不同:我只是在崩溃发生时启动崩溃处理程序,然后通过命令行参数将崩溃程序的必要信息传递给崩溃处理程序。我仔细检查了传递的信息是否正确,特别是异常指针。
我曾遇到过类似的问题,现在终于发现问题出在哪里了。
MINIDUMP_EXCEPTION_INFORMATION 的 MSDN 文档指出,如果 ExceptionPointers
地址来自目标进程而不是本地进程,则 ClientPointers
字段必须是 TRUE
。
正确设置这个字段后,我可以简单地从崩溃过程中传递ThreadId
和ExceptionPointers
,在转储写入过程中将它们填充到MINIDUMP_EXCEPTION_INFORMATION
中,并且它有效完美。
我改变了我的方法,使最终解决方案看起来像 Hans Passant 建议的那样:看门狗进程是预启动的,然后进入休眠状态,并在崩溃进程触发时唤醒。崩溃进程对 EXCEPTION_POINTERS
结构进行深度复制,并将该信息传递给看门狗进程。
这是进行深拷贝的代码。正如问题评论中提到的,主要的 "problem" 是 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
结构是保存此信息的候选者。此外,此处未显示进程如何相互通信的方式。就我而言,我采用了 Hans Passant 提出的解决方案,该解决方案链接在问题的开头:Use an event for synchronization (CreateEvent + SetEvent) and a memory-mapped file (CreateFileMapping + MapViewOfFile) to shuffle the information from one process到下一个。事件和内存映射文件的(唯一)名称由主进程确定并通过命令行参数传递给看门狗进程。