编写分析扩展插件以扩展 !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 函数。
注意 元数据文件的最后一行必须以换行符结尾。
框架示例
下面是一个可以用作起点的框架示例。
生成一个名为 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; }
创建包含以下条目的名为 MyAnalyzer.alz 的元数据文件。
PluginId MyPlugin DebuggeeClass Kernel BugCheckCode 0xE2
注意 元数据文件的最后一行必须以换行符结尾。
在主机和目标计算机之间建立内核模式调试会话。
在主计算机上,将 MyAnalyzer.dll 和 MyAnalyzer.alz 放在文件夹 c:\MyAnalyzer 中。
在主计算机上的调试器中,输入以下命令。
.extpath+ c:\MyAnalyzer
。崩溃
.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 接口, pAnalysis 是 IDebugFailureAnalysis2 接口。 前面的主干示例中的代码演示如何获取另外两个接口指针。 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 对象具有属于特定分析会话的 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);