作为windbg调试器扩展的第三篇,这篇文章将结合代码,梳理调试器扩展的骨架脉络。
先附上代码片段:
#include "dbgexts.h"
extern "C" HRESULT CALLBACK
DebugExtensionInitialize(PULONG Version, PULONG Flags) {
*Version = DEBUG_EXTENSION_VERSION(2, 11);
*Flags = 0; // Reserved for future use.
return S_OK;
}
extern "C" void CALLBACK
DebugExtensionNotify(ULONG Notify, ULONG64 Argument) {
UNREFERENCED_PARAMETER(Argument);
switch (Notify) {
// A debugging session is active. The session may not necessarily be suspended.
case DEBUG_NOTIFY_SESSION_ACTIVE:
break;
// No debugging session is active.
case DEBUG_NOTIFY_SESSION_INACTIVE:
break;
// The debugging session has suspended and is now accessible.
case DEBUG_NOTIFY_SESSION_ACCESSIBLE:
break;
// The debugging session has started running and is now inaccessible.
case DEBUG_NOTIFY_SESSION_INACCESSIBLE:
break;
}
return;
}
extern "C" void CALLBACK
DebugExtensionUninitialize(void) {
return;
}
1.入口和出口
DebugExtensionInitialize和DebugExtensionUnitialize是每个调试器扩展必须导出给windbg的接口,二者缺一不可。DebugExtensionInitialize接口在调试器扩展首次被windbg加载时被调用,这个接口除了负责做初始化相关的工作,没什么特别的作用。唯一需要注意的点是:必须用DEBUG_EXTENSION_VERSION宏初始化该调试器扩展的版本号,当运行.chain命令时,windbg会列出该调试器扩展的版本号。前面示例代码段中设置版本号为2.11,让我们看下运行.chain命令后的输出:
0:004> .chain
Extension DLL search Path:
C:\WinDDK\7600.16385.1\Debuggers\sdk\samples\exts\objchk_win7_x86\i386\dbgexts.dll ..., API 2.11.0, ...
[path: C:\WinDDK\7600.16385.1\Debuggers\sdk\samples\exts\objchk_win7_x86\i386\dbgexts.dll]
2.调试器通知事件
众所周知,调试状态至少可以分为两种:a.调试器连续运行状态(如用户键入F5运行);b.调试器中断,接受用户输入状态(调试器遇到断点,中断到交互界面)。windbg为调试器扩展提供了4种状态以及通知扩展关于状态转换的接口,这个接口就是前面代码中的DebugExtensionNotify。状态码DEBUG_NOTIFY_SESSION_ACCESSIBLE代表windbg遇到断点或者被手动中断而进入到交互界面;DEBUG_NOTIFY_SESSION_INACCESSIBLE代表用户按下F5使windbg恢复运行。
3.windbg导出的调试接口
以一个向windbg输出HelloWorld的调试接口为例,我们来一点点介绍windbg导出的接口:
HRESULT CALLBACK
helloworld(PDEBUG_CLIENT pDebugClient, PCSTR args) {
UNREFERENCED_PARAMETER(args);
IDebugControl* pDebugControl;
if (SUCCEEDED(pDebugClient->QueryInterface(__uuidof(IDebugControl),
(void **)&pDebugControl))) {
pDebugControl->Output(DEBUG_OUTPUT_NORMAL, "Hello World!\n");
pDebugControl->Release();
}
return S_OK;
}
需要强调的是,调试器扩展的每个命令----也就是Dll中每个导出函数----必须小写,否则这个扩展命令会有各种奇怪的问题(至少在编写内核调试扩展时是这样)。 每个导出函数的接口形式固定为:HRESULT CALLBACK
ExtensionCommandName(PDEBUG_CLIENT , PCSTR );
其中参数1是指当前调试终端,简单的说就是用户输入的来源和命令输出的目标,就像Linux下的Terminal终端工具,Linux把这种用来显示和接收用户输入的交互端口定义为终端。对应到windbg上的功能就是Command输入/输出窗口和,如下图红框所示:参数2是指跟在扩展命令后的参数:以!process扩展命令为例,我们可能会输入!process 0 0来显示当前系统中所有的进程。键入该命令后会调用KdExt.dll中的Process函数,该函数的参数args接受到的字符串就是"0 0"。
说完了参数,我们再来聊聊函数体内部。在导出函数内部会看到大量诸如以下COM形式的语句:
IDebugClient *DebugClient;
PDEBUG_CONTROL DebugControl;
...
if ((Hr = DebugClient->QueryInterface(__uuidof(IDebugControl),
(void **)&DebugControl)) == S_OK)
只能说,这只是类似COM接口,实际只是C++多态。以IDebugControl为例,查看MSDN可知,windbg导出的IDebugControl类已经超过5个版本(IDebugControl~IDebugControl5,IDebugControl是基类,IDebugControlN继承自这个类),每个版本的windbg用的IDebugControl类可能都不同。MS使用多态性,平滑的解决了代码在不同版本windbg之间的兼容性问题。代码首先定义了一个IDebugControl基类指针,并通过QueryInterface获得当前版本windbg使用的IDebugControl子类。最后通过基类IDebugControl中的虚函数指针,来调用IDebugControl子类实现的各个函数(我获得的IDebugControl对象,其实际类是IDebugControl5)。如使用函数IDebugControl->Execute("kb");来执行windbg的栈回溯功能。在调试器扩展中,我们可以通过不断的调用QueryInterface接口获得不同功能的对象,最终实现反汇编/解析符号/设置断点等基本的调试功能,当然这部分内容不是本文重点,需要读者自己查阅MSDN相关章节。