编写分析扩展插件以扩展 !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

    。崩溃

  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) (也称为 FA_TAG 枚举)中定义的DEBUG_FLR_PARAM_TYPE枚举 ( 的值。

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 对象相关联,该对象包含标记属性的集合。 下图说明了此关联。

显示分析引擎、DebugFailureAnalysis 对象和 DebugFailureAnalysisTags 对象的示意图。

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