编写分析扩展插件以扩展 !analyze

可以通过编写分析扩展插件来扩展 !analyze 调试器命令的功能。 通过提供分析扩展插件,你可以以特定于自己的组件或应用程序的方式参与 bug 检查或异常的分析。

编写分析扩展插件时,还编写一个元数据文件,用于描述你希望调用插件的情况。 当 !analyze 运行时,它会查找、加载并运行相应的分析扩展插件。

若要编写分析扩展插件并使其可用于 !analyze,请执行以下步骤。

  • 创建导出 _EFN_Analyze 函数的 DLL。
  • 创建一个元数据文件,该文件的名称与 DLL 相同,扩展名为 .alz。 例如,如果 DLL 命名为MyAnalyzer.dll,则必须将元数据文件命名为 MyAnalyzer.alz。 有关如何创建元数据文件的信息,请参阅 用于分析扩展的元数据文件。 将元数据文件放置在 DLL 所在的同一目录中。
  • 在调试器中,使用 .extpath 命令将目录添加到扩展文件路径。 例如,如果 DLL 和元数据文件位于名为 c:\MyAnalyzer 的文件夹,请输入命令 .extpath+ c:\MyAnalyzer

当 !analyze 命令在调试器中运行时,分析引擎将查找扩展名为 .alz 的元数据文件的扩展文件路径。 分析引擎读取元数据文件,以确定应加载哪些分析扩展插件。 例如,假设分析引擎正在运行以响应 Bug 检查0xA IRQL_NOT_LESS_OR_EQUAL,并读取名为 MyAnalyzer.alz 的元数据文件,其中包含以下条目。

PluginId       MyPlugin
DebuggeeClass  Kernel
BugCheckCode   0xA
BugCheckCode   0xE2

该条目 BugCheckCode 0x0A 指定此插件希望参与 Bug 检查0xA的分析,因此分析引擎将加载MyAnalyzer.dll (,它必须与 MyAnalyzer.alz) 位于同一目录中,并调用其 _EFN_Analyze 函数。

注意 元数据文件的最后一行必须以换行符结尾。

框架示例

下面是一个框架示例,你可以将其用作起点。

  1. 生成一个名为MyAnalyzer.dll的 DLL,用于导出此处所示 的_EFN_Analyze 函数。

    #include <windows.h>
    #define KDEXT_64BIT
    #include <wdbgexts.h>
    #include <dbgeng.h>
    #include <extsfns.h>
    
    extern "C" __declspec(dllexport) HRESULT _EFN_Analyze(_In_ PDEBUG_CLIENT4 Client, 
       _In_ FA_EXTENSION_PLUGIN_PHASE CallPhase, _In_ PDEBUG_FAILURE_ANALYSIS2 pAnalysis)
    { 
       HRESULT hr = E_FAIL;
    
       PDEBUG_CONTROL pControl = NULL;
       hr = Client->QueryInterface(__uuidof(IDebugControl), (void**)&pControl);
    
       if(S_OK == hr && NULL != pControl)
       {
          IDebugFAEntryTags* pTags = NULL;
          pAnalysis->GetDebugFATagControl(&pTags);
    
          if(NULL != pTags)
          {
             if(FA_PLUGIN_INITILIZATION == CallPhase)
             { 
                pControl->Output(DEBUG_OUTPUT_NORMAL, "My analyzer: initialization\n");  
             }
             else if(FA_PLUGIN_STACK_ANALYSIS == CallPhase)
             {
                pControl->Output(DEBUG_OUTPUT_NORMAL, "My analyzer: stack analysis\n"); 
             }
             else if(FA_PLUGIN_PRE_BUCKETING == CallPhase)
             {
                pControl->Output(DEBUG_OUTPUT_NORMAL, "My analyzer: prebucketing\n");
             }
             else if(FA_PLUGIN_POST_BUCKETING == CallPhase)
             {
                pControl->Output(DEBUG_OUTPUT_NORMAL, "My analyzer: post bucketing\n");    
                FA_ENTRY_TYPE entryType = pTags->GetType(DEBUG_FLR_BUGCHECK_CODE);       
                pControl->Output(DEBUG_OUTPUT_NORMAL, "The data type for the DEBUG_FLR_BUGCHECK_CODE tag is 0x%x.\n\n", entryType);
             }
          }
    
          pControl->Release();
       }
       return hr;
    }
    
  2. 创建包含以下条目的名为 MyAnalyzer.alz 的元数据文件。

    PluginId      MyPlugin
    DebuggeeClass Kernel
    BugCheckCode  0xE2
    

    注意 元数据文件的最后一行必须以换行符结尾。

  3. 在主机和目标计算机之间建立内核模式调试会话。

  4. 在主计算机上,将 MyAnalyzer.dll 和 MyAnalyzer.alz 放在文件夹 c:\MyAnalyzer 中。

  5. 在主计算机上,在调试器中输入这些命令。

    .extpath+ c:\MyAnalyzer

    .crash

  6. .crash 命令在目标计算机上生成 Bug 检查0xE2 MANUALLY_INITIATED_CRASH,这会导致主机计算机上的调试器中断。 bug 检查分析引擎 (在主机上的调试器中运行,) 读取 MyAnalyzer.alz,并看到MyAnalyzer.dll能够参与分析 bug 检查0xE2。 因此,分析引擎加载MyAnalyzer.dll并调用其 _EFN_Analyze 函数。

    验证是否在调试器中看到类似于以下内容的输出。

    *                        Bugcheck Analysis                                    *
    *                                                                             *
    *******************************************************************************
    
    Use !analyze -v to get detailed debugging information.
    
    BugCheck E2, {0, 0, 0, 0}
    
    My analyzer: initialization
    My analyzer: stack analysis
    My analyzer: prebucketing
    My analyzer: post bucketing
    The data type for the DEBUG_FLR_BUGCHECK_CODE tag is 0x1.
    

前面的调试器输出显示分析引擎调用 _EFN_Analyze 函数四次:一次用于分析的每个阶段。 分析引擎传递 _EFN_Analyze 函数两个接口指针。 客户端IDebugClient4 接口, pAnalysisIDebugFailureAnalysis2 接口。 前面的框架示例中的代码演示如何获取另外两个接口指针。 Client->QueryInterface 获取 IDebugControl 接口,并 pAnalysis->GetDebugFATagControl 获取 IDebugFAEntryTags 接口。

故障分析条目、标记和数据类型

分析引擎创建 DebugFailureAnalysis 对象来组织与特定代码故障相关的数据。 DebugFailureAnalysis 对象包含故障分析条目集合, (FA 条目) ,每个条目都由FA_ENTRY结构表示。 分析扩展插件使用 IDebugFailureAnalysis2 接口来访问此 FA 条目集合。 每个 FA 条目都有一个标记,用于标识条目包含的信息类型。 例如,FA 条目可能具有标记 DEBUG_FLR_BUGCHECK_CODE,它告诉我们该条目包含 bug 检查代码。 标记是 extsfns.h) 中定义的 DEBUG_FLR_PARAM_TYPE 枚举 (中的值,也称为 FA_TAG 枚举。

typedef enum _DEBUG_FLR_PARAM_TYPE {
    ...
    DEBUG_FLR_BUGCHECK_CODE,
    ...
    DEBUG_FLR_BUILD_VERSION_STRING,
    ...
} DEBUG_FLR_PARAM_TYPE;

typedef DEBUG_FLR_PARAM_TYPE FA_TAG;

大多数 FA 条目 都有关联的数据块。 FA_ENTRY结构的 DataSize 成员包含数据块的大小。 某些 FA 条目没有关联的数据块;所有信息都由标记传达。 在这些情况下, DataSize 成员的值为 0。

typedef struct _FA_ENTRY
{
    FA_TAG Tag;
    USHORT FullSize;
    USHORT DataSize;
} FA_ENTRY, *PFA_ENTRY;

每个标记都有一组属性:例如名称、说明和数据类型。 DebugFailureAnalysis 对象与 DebugFailureAnalysisTags 对象相关联,该对象包含标记属性的集合。 下图说明了此关联。

diagram that shows the analysis engine, a debugfailureanalysis object, and a debugfailureanalysistags object.

DebugFailureAnalysis 对象包含属于特定分析会话的 FA 条目集合。 关联的 DebugFailureAnalysisTags 对象具有一组标记属性,该集合仅包含同一分析会话使用的标记。 如上图所示,分析引擎具有一个全局标记表,该表包含一组有限信息,这些标记通常可供分析会话使用。

分析会话使用的大多数标记通常是标准标记;也就是说,标记是 FA_TAG 枚举中的值。 但是,分析扩展插件可以创建自定义标记。 分析扩展插件可以将 FA 条目 添加到 DebugFailureAnalysis 对象,并为该条目指定自定义标记。 在这种情况下,自定义标记的属性将添加到关联的 DebugFailureAnalysisTags 对象的标记属性集合中。

可以通过 IDebugFAEntry 标记接口访问 DebugFailureAnalysisTags 。 若要获取指向 IDebugFAEntry 接口的指针,请调用 IDebugFailureAnalysis2 接口的 GetDebugFATagControl 方法。

每个标记都有一个数据类型属性,你可以检查以确定故障分析条目中的数据的数据类型。 数据类型由 FA_ENTRY_TYPE 枚举中的值表示。

以下代码行获取 DEBUG_FLR_BUILD_VERSION_STRING 标记的数据类型。 在这种情况下,数据类型 DEBUG_FA_ENTRY_ANSI_STRING。 在代码中, pAnalysis 是指向 IDebugFailureAnalysis2 接口的指针。

IDebugFAEntryTags* pTags = pAnalysis->GetDebugFATagControl(&pTags);

if(NULL != pTags)
{
   FA_ENTRY_TYPE entryType = pTags->GetType(DEBUG_FLR_BUILD_VERSION_STRING);
}

如果失败分析条目没有数据块,则关联的标记的数据类型 DEBUG_FA_ENTRY_NO_TYPE

回想一下, DebugFailureAnalysis 对象具有 FA 条目的集合。 若要检查集合中的所有 FA 条目,请使用 NextEntry 方法。 以下示例演示如何循环访问 FA 条目的整个集合。 假设 pAnalysis 是指向 IDebugFailureAnalysis2 接口的指针。 请注意,我们通过将 NULL 传递给 NextEntry 来获取第一个条目。

PFA_ENTRY entry = pAnalysis->NextEntry(NULL);

while(NULL != entry)
{
   // Do something with the entry

   entry = pAnalysis->NextEntry(entry);
}

标记可以具有名称和说明。 在以下代码中, pAnalysis 是指向 IDebugFailureAnalysis 接口的指针, pControl 是指向 IDebugControl 接口的指针, pTags 是指向 IDebugFAEntryTags 接口的指针。 该代码演示如何使用 GetProperties 方法获取与 FA 条目关联的标记的名称和说明。

#define MAX_NAME_LENGTH 64
#define MAX_DESCRIPTION_LENGTH 512

CHAR name[MAX_NAME_LENGTH] = {0};
ULONG nameSize = MAX_NAME_LENGTH;
CHAR desc[MAX_DESCRIPTION_LENGTH] = {0};
ULONG descSize = MAX_DESCRIPTION_LENGTH;
                  
PFA_ENTRY pEntry = pAnalysis->NextEntry(NULL); 
pTags->GetProperties(pEntry->Tag, name, &nameSize, desc, &descSize, NULL);
pControl->Output(DEBUG_OUTPUT_NORMAL, "The name is %s\n", name);
pControl->Output(DEBUG_OUTPUT_NORMAL, "The description is %s\n", desc);

编写自定义分析调试器扩展

_EFN_Analyze

分析扩展插件的元数据文件

IDebugFailureAnalysis2

IDebugFAEntryTags