在进程外调用时,MiniDumpWriteDump中的访问冲突

时间:2015-09-21 09:39:34

标签: c++ windows crash-dumps minidump

The documentation of the MiniDumpWriteDump function表示

  如果可能的话,应该从一个单独的进程调用

MiniDumpWriteDump,而不是从被转储的目标进程中调用。{/ p>

所以我写了一个小的MFC崩溃处理程序,就是这样做的。我遵循Hans Passant在this SO answer中的建议,即我将异常指针的值从崩溃程序传递给崩溃处理程序,即使异常指针在崩溃处理程序的上下文中无效。当我在调试版本中运行测试时,这很有效,但是当我切换到版本构建时,崩溃处理程序崩溃,在MiniDumpWriteDump函数内发生访问冲突。

我很难过。为什么这应该在调试版本中工作,而不是在发布版本中?这很令人抓狂,因为访问违规通常是访问无效指针的指标,而我在崩溃处理程序中收到的异常指针确实无效 - 但另一方面,我被告知这应该无关紧要,MiniDumpWriteDump是在崩溃过程的上下文中解释指针(从指针发起的位置开始)。

任何想法我可能做错了什么?

旁观:在他的回答中,汉斯提出了一个解决方案,其中看门狗进程是预先启动的,然后进入睡眠状态并在崩溃过程触发时唤醒。我的解决方案略有不同:我只是在崩溃发生时启动崩溃处理程序,然后通过命令行参数将崩溃程序中的必要信息传递给崩溃处理程序。我仔细检查了传递的信息是否正确,特别是异常指针。

2 个答案:

答案 0 :(得分:0)

我在类似的问题上挣扎,现在终于发现了什么是错的。

MINIDUMP_EXCEPTION_INFORMATION的MSDN文档指出,如果ClientPointers地址来自目标进程而非本地进程,则TRUE字段必须为ExceptionPointers

正确设置此字段后,我可以简单地从崩溃过程中传递ThreadIdExceptionPointers,在转储写入过程中将它们填充到MINIDUMP_EXCEPTION_INFORMATION,并且它可以正常工作。

答案 1 :(得分:0)

我改变了方法,以便最终解决方案看起来像Hans Passant建议的那样:看门狗进程是预先启动的,然后进入睡眠状态并在崩溃过程触发时唤醒。崩溃过程会对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结构是保存此信息的候选者。此处未显示进程如何相互通信的方式。在我的例子中,我使用了Hans Passant提出的解决方案,该解决方案在问题开头链接:使用事件进行同步(CreateEvent + SetEvent)和内存映射文件(CreateFileMapping + MapViewOfFile)来从一个进程中混洗信息到下一个。事件的(唯一)名称和内存映射文件由主进程确定,并通过命令行参数传递给监视程序进程。