故障转储分析

并非所有的错误都能在发布前发现,这意味着并非所有引发异常的错误在所难免。 幸运的是,Microsoft 在平台 SDK 中提供了一个功能,可帮助开发人员收集用户发现的异常信息。 MiniDumpWriteDump 函数将必要的崩溃转储信息写入文件中,而不保存整个进程空间。 这种故障转储信息文件称为小型转储。 这篇技术文章介绍了如何编写和使用小型转储。

编写小型转储

编写小型转储的基本选项如下:

  • 不执行任何操作。 每当程序引发未处理的异常时,Windows 就会自动生成小型转储。 从 Windows XP 开始就可自动生成小型转储。 如果用户允许,小型转储将通过 Windows 错误报告 (WER) 发送给 Microsoft,而不是开发人员。 开发人员可通过 Windows 桌面应用程序访问这些小型转储。

    使用 WER 需要:

    • 开发人员使用 Authenticode 对其应用程序进行签名
    • 应用程序的每个可执行文件和 DLL 都有有效的 VERSIONINFO 资源

    如果对未处理的异常执行自定义例程,强烈建议在异常处理程序中使用 ReportFault 函数,以便同时向 WER 发送自动小型转储。 ReportFault 函数可处理连接小型转储并将其发送到 WER 的所有问题。 不向 WER 发送小型转储会违反 Games for Windows 的要求。

    有关 WER 的详细信息,请参阅 Windows 错误报告

  • 使用 Microsoft Visual Studio Team System 中的产品。 在“调试”菜单上,单击“保存转储方式”以保存转储的副本。 使用本地保存的转储数据只能用于内部测试和调试。

  • 将代码添加到项目。 添加 MiniDumpWriteDump 函数和相应的异常处理代码,以保存小型转储并直接发送给开发人员。 本文将演示如何执行该选项。 但是请注意,MiniDumpWriteDump 目前不能与托管代码一起使用,并且仅适用于 Windows XP、Windows Vista 和 Windows 7。

线程安全性

MiniDumpWriteDump 是 DBGHELP 库的一部分。 此库不是线程安全的,因此任何使用 MiniDumpWriteDump 的程序都应在尝试调用 MiniDumpWriteDump 之前同步所有线程。

使用代码编写小型转储

实际执行过程简单明了。 下面是一个如何使用 MiniDumpWriteDump 的简单示例。

#include <dbghelp.h>
#include <shellapi.h>
#include <shlobj.h>

int GenerateDump(EXCEPTION_POINTERS* pExceptionPointers)
{
    BOOL bMiniDumpSuccessful;
    WCHAR szPath[MAX_PATH]; 
    WCHAR szFileName[MAX_PATH]; 
    WCHAR* szAppName = L"AppName";
    WCHAR* szVersion = L"v1.0";
    DWORD dwBufferSize = MAX_PATH;
    HANDLE hDumpFile;
    SYSTEMTIME stLocalTime;
    MINIDUMP_EXCEPTION_INFORMATION ExpParam;

    GetLocalTime( &stLocalTime );
    GetTempPath( dwBufferSize, szPath );

    StringCchPrintf( szFileName, MAX_PATH, L"%s%s", szPath, szAppName );
    CreateDirectory( szFileName, NULL );

    StringCchPrintf( szFileName, MAX_PATH, L"%s%s\\%s-%04d%02d%02d-%02d%02d%02d-%ld-%ld.dmp", 
               szPath, szAppName, szVersion, 
               stLocalTime.wYear, stLocalTime.wMonth, stLocalTime.wDay, 
               stLocalTime.wHour, stLocalTime.wMinute, stLocalTime.wSecond, 
               GetCurrentProcessId(), GetCurrentThreadId());
    hDumpFile = CreateFile(szFileName, GENERIC_READ|GENERIC_WRITE, 
                FILE_SHARE_WRITE|FILE_SHARE_READ, 0, CREATE_ALWAYS, 0, 0);

    ExpParam.ThreadId = GetCurrentThreadId();
    ExpParam.ExceptionPointers = pExceptionPointers;
    ExpParam.ClientPointers = TRUE;

    bMiniDumpSuccessful = MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), 
                    hDumpFile, MiniDumpWithDataSegs, &ExpParam, NULL, NULL);

    return EXCEPTION_EXECUTE_HANDLER;
}


void SomeFunction()
{
    __try
    {
        int *pBadPtr = NULL;
        *pBadPtr = 0;
    }
    __except(GenerateDump(GetExceptionInformation()))
    {
    }
}

此示例演示了 MiniDumpWriteDump 的基本用法,以及调用它所需的最低限度信息。 转储文件的名称由开发人员自行决定;但为了避免文件名冲突,建议根据应用程序名称和版本号、进程和线程 ID 以及日期和时间生成文件名。 这也有助于按应用程序和版本对小型转储进行分组。 开发人员可自行决定使用多少信息来区分小型转储文件名。

需要注意的是,上例中的路径名是通过调用 GetTempPath 函数来检索临时文件指定目录的路径生成的。 即使是权限最低的用户帐户也能使用该目录,而且还能防止小型转储在不再需要后占用硬盘空间。

果在日常生成过程中对产品进行存档,也要确保包含生成的符号,以便在必要时调试旧版本的产品。 还需要采取措施,在生成符号时保持编译器的全面优化。 具体方法是在开发环境中打开项目属性,并执行以下操作以获得发布配置:

  1. 在项目属性页面左侧,单击 C/C++。 默认情况下,这将显示“常规”设置。 在项目属性页的右侧,将“调试信息格式”设置为“程序数据库 (/Zi)”。
  2. 在属性页面的左侧,展开“链接器”,然后单击“调试”。 在属性页面右侧,将“生成调试信息”设置为“是 (/DEBUG)”。
  3. 单击“优化”,并将“引用”设置为“消除未引用数据 (/OPT:REF)”。
  4. 将“启用 COMDAT 折叠”设置为“删除冗余 COMDAT (/OPT:ICF)”。

有关详细信息,请参阅 MINIDUMP_EXCEPTION_INFORMATION 结构和 MiniDumpWriteDump 函数。

使用 Dumpchk.exe

Dumpchk.exe 是一个命令行实用工具,可用于验证是否已正确创建转储文件。 如果 Dumpchk.exe 生成错误,则说明转储文件已损坏,无法进行分析。 有关使用 Dumpchk.exe 的信息,请参阅如何使用 Dumpchk.exe 检查内存转储文件

Dumpchk.exe 包含在 Windows XP 产品 CD 中,可以通过运行 Windows XP 产品 CD 上 Support\Tools\ 文件夹中的 Setup.exe 安装到 System Drive\Program Files\Support Tools\ 中。 你还可以从 Windows 硬件开发人员中心上的 Windows 调试工具下载并安装调试工具,以获得最新版本的 Dumpchk.exe。

分析小型转储

打开小型转储进行分析与创建小型转储一样简单。

分析小型转储

  1. 打开 Visual Studio。
  2. 在“文件”菜单上,单击“打开项目”
  3. 将“文件类型”设置为“转储文件”,导航到转储文件,选择该文件,然后单击“打开”。
  4. 运行调试程序。

调试器将创建一个模拟进程。 模拟进程将在导致崩溃的指令处停止。

使用 Microsoft 公共符号服务器

要获取驱动程序或系统级崩溃的堆栈,可能需要将 Visual Studio 配置为指向 Microsoft 公共符号服务器。

设置 Microsoft 符号服务器的路径

  1. 在“调试”菜单上,单击“选项”。
  2. 在“选项”对话框中,打开“调试”节点,然后单击“符号”。
  3. 确保未选择仅在手动加载符号时搜索上述位置,除非想在调试时手动加载符号。
  4. 如果在远程符号服务器上使用符号,则可以指定一个本地目录,将符号复制到该目录,从而提高性能。 为此,请输入从符号服务器缓存符号到此目录的路径。 要连接到 Microsoft 公共符号服务器,则需要启用此设置。 请注意,如果在远程计算机上调试程序,缓存目录指的是远程计算机上的一个目录。
  5. 单击“确定”。
  6. 由于你使用的是 Microsoft 公共符号服务器,因此会出现“最终用户许可协议”对话框。 单击“是”接受协议并将符号下载到本地缓存。

使用 WinDbg 调试小型转储

还可以使用 WinDbg(Windows 调试工具中的一个调试器)来调试小型转储。 使用 WinDbg,无需使用 Visual Studio 即可进行调试。 要下载 Windows 调试工具,请参阅 Windows 硬件开发人员中心上的 Windows 调试工具

安装 Windows 调试工具后,必须在 WinDbg 中输入符号路径。

在 WinDbg 中输入符号路径

  1. 在“文件”菜单上,单击“符号路径”。

  2. 在“符号搜索路径”窗口中,输入以下内容:

    "srv\*c:\\cache\*https://msdl.microsoft.com/download/symbols;"

将复制保护工具与小型转储配合使用

开发人员还需要注意他们的复制保护方案可能对小型转储产生的影响。 大多数复制保护方案都有自己的反框架工具,开发人员需要学习如何通过 MiniDumpWriteDump 来使用这些工具。

总结

在产品发布后,MiniDumpWriteDump 函数可以成为收集和解决错误的极其有用的工具。 通过编写使用 MiniDumpWriteDump 的自定义异常处理程序,开发人员可以自定义信息收集并改进调试过程。 该函数非常灵活,可用于任何基于 C++ 的项目,应被视为任何项目稳定性流程的一部分。