故障转储分析

并非所有 bug 都可以在发布之前找到,这意味着并非所有引发异常的 bug 都可以在发布之前找到。 幸运的是,Microsoft 在平台 SDK 中包含了一个函数,可帮助开发人员收集有关用户发现的异常的信息。 MiniDumpWriteDump 函数在不节省整个进程空间的情况下,将必要的故障转储信息写入文件。 此故障转储信息文件称为小型转储。 此技术文章提供有关如何编写和使用小型转储的信息。

编写小型转储

用于编写小型转储的基本选项如下所示:

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

    使用 WER 需要:

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

    如果为未经处理的异常实现自定义例程,则强烈建议在异常处理程序中使用 ReportFault 函数,同时将自动小型转储发送到 WER。 ReportFault 函数处理连接到小型转储并将其发送到 WER 的所有问题。 不向 WER 发送最小转数违反了 Windows 游戏的要求。

    有关 WER 工作原理的详细信息,请参阅Windows 错误报告的工作原理。 有关注册详细信息的说明,请参阅 MSDN ISV 区域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++”。 默认情况下,这会显示 “常规 ”设置。 在项目属性页的右侧,将 “调试信息格式” 设置为 Program Database (/Zi)
  2. 在属性页左侧,展开 “链接器”,然后单击“ 调试”。 在属性页右侧,将 “生成调试信息 ”设置为 “ (/DEBUG)
  3. 单击“ 优化”,并将 引用 设置为 Eliminate Unreferenced Data (/OPT:REF)
  4. “启用 COMDAT 折叠 ”设置为 (/OPT:ICF) 删除冗余 COMDAT

MSDN 提供了有关 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 Hardware Developer Central 上的 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 Hardware Developer Central 上的 Windows调试工具

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

在 WinDbg 中输入符号路径

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

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

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

将 Copy-Protection Tools 与 Minidumps 配合使用

开发人员还需要了解其复制保护方案如何影响小型转储。 大多数复制保护方案都有自己的解构工具,开发人员需要了解如何将这些工具与 MiniDumpWriteDump 配合使用。

总结

MiniDumpWriteDump 函数在产品发布后收集和解决 bug 方面非常有用。 编写使用 MiniDumpWriteDump 的自定义异常处理程序允许开发人员自定义信息集合并改进调试过程。 函数足够灵活,可用于任何基于 C++ 的项目,应将其视为任何项目的稳定性过程的一部分。