Windbg扩展指令开发教程

之前我曾提供了导出stl map和set内容的windbg扩展指令库,并给出了使用方法。授人以鱼不如授人以渔,下面的文章将以开发一个导出CMap容器内容的windbg扩展指令cmap为例,介绍如果开发一个windbg的扩展指令库。


1. windbg扩展指令

Windbg自身提供丰富的扩展指令,使得它的调试功能十分强大。通常,我看到的以!开头的指令就是扩展指令,而这些扩展指令大多由windbg安装目录下winext、winxp等子目录中的DLL导出。
有时候我们也需要开发自己的扩展指令,用以方便地完成特殊的调试任务。例如,我们很可能需要导出一个存储了大量数据CMap容器的内容,开发一个自己的CMap导出指令会很方便地解决问题。
开发一个扩展指令,实质上就是要按照debug engine定义的开发规范,开发一个导出了扩展指令的扩展DLL。扩展DLL可以分为3类,在Windbg目录下sdk\sample中提供了对应的简单示例:
  • DbgEng extension DLLs。这种类型是基于dbgeng.h头文件提供的接口所开发的DLL并导出DbgEng 扩展指令。这类指令的实现过程中可以使用Debugger Engine的API并且也能使用WdbgExts API。(示例代码名称:exts)
  • EngExtCpp extension DLLs。 这种类型是基于engextcpp.h和dbgeng.h头文件中提供的接口开发的DLL。导出DbgEng 扩展指令。这类指令在实现过程中可以使用Debugger Engine的API,EngExtCpp 扩展框架提供的接口,同时也能使用WdbgExts API。(示例代码名称:extcpp)
  • WdbgExts extension DLLs。这种类型基于wdbgexts.h头文件提供的接口开发并导出DbgEng 扩展指令。指令开发中只能使用WdbgExts API。(示例代码名称:simplext)

2. EngExtCpp扩展指令开发

EngExtCpp扩展指令是按照C++的语法进行开发,下面的内容将主要介绍如何开发这种类型的指令。在windbg官方帮助文档中有名为EngExtCpp Extension Design Guide的专题对EngExtCpp扩展指令的开发做了一定的介绍,不一定适用,但是可以参考其中的基本概念。

2.1 创建完整的EngExtCpp工程

按照windbg官方帮助文档提供的开发方案,扩展插件需要在DDK环境编译,显然比较麻烦。而且按照它的方法,我用DDK没有成功编译过它所提供的示例代码。事实上,在VC环境中照样能够开发出扩展指令。下面就以在VS2008中开发一个导出CMap内容的 扩展指令cmap 为例,说明主要的过程。
1) 创建一个空白的win32 DLL工程。
2) 向工程添加 engextcpp.hpp和engextcpp.cpp两个现有文件,文件所在路径位于Windbg安装目录下 sdk\sample\inc。按照Windbg帮助文档的说明,以及官方示例代码的内容显示,原本我们只需要包含头文件 engextcpp.hpp,并连接engextcpp.lib静态库就能完成开发,但是事实上直接使用静态库在连接过程中会出现错误,并不能成功编译,所以我 选择了直接包含engextcpp.cpp源文件的方法,而不去连接engextcpp.lib
3) 向工程中添加一个新建的cpp文件,命名为mtlkit.cpp。这个文件的内容就是我们实现扩展指令 cmap 我们需要自己开发的代码,其内容如下:
//----------------------------------------------------------------------------
//
// extcpp.cpp
//
// EngExtCpp-style extension sample.
//
// Copyright (C) Microsoft Corporation, 2005.
//
//----------------------------------------------------------------------------

#include
#include
#include

using std::string;
//----------------------------------------------------------------------------
//
// Base extension class.
// Extensions derive from the provided ExtExtension class.
//
// The standard class name is "Extension". It can be
// overridden by providing an alternate definition of
// EXT_CLASS before including engextcpp.hpp.
//
//----------------------------------------------------------------------------

class EXT_CLASS : public ExtExtension
{
public:
 EXT_COMMAND_METHOD(cmap);
 //
private:
 void OutUnfold( ExtRemoteTyped& node, int nRecur, int nOrgRecur);
 void DumpCmap(ExtRemoteTyped& hashTable, INT_PTR hashTableSize);
};

// EXT_DECLARE_GLOBALS must be used to instantiate
// the framework's assumed globals.
EXT_DECLARE_GLOBALS();

void EXT_CLASS::DumpCmap(ExtRemoteTyped& hashTable, INT_PTR hashTableSize)
{
 for (LONG i=0; i  {
  ExtRemoteTyped tableElement = hashTable[i];
  while (tableElement.GetPtr() != 0)
  {
   ExtRemoteTyped key = tableElement.Field("key");
   ExtRemoteTyped value = tableElement.Field("value");

   Warn("== key======>\n");
   OutUnfold(key, 5, 5);
   Warn("\n== value=====>\n");
   OutUnfold(value, 5, 5);

   Out("\n");
   
   tableElement = tableElement.Field("pNext");
  }
 }
}

void EXT_CLASS::OutUnfold( ExtRemoteTyped& node, int nRecur, int nOrgRecur)
{
 CHAR Name[MAX_PATH];

 if (nRecur <= 0)
 {
  return;
 }

 if (nRecur == nOrgRecur)
 {
  string strType = node.GetTypeName();
  if (strType.find("class") != string::npos &&
   strType.find("*") != string::npos)
  {
   dprintf("%s : %s\n", node.GetTypeName(), node.GetSimpleValue());
  }
  else
  {
   dprintf("%s :\n", node.GetTypeName());
  }
 }

 for (int i=0; ;i++)
 {
  HRESULT Hr;
  ULONG Offset=0;
 
  Hr = m_Symbols2->GetFieldName(node.m_Typed.ModBase, node.m_Typed.TypeId, i, Name, MAX_PATH, NULL);
  if (Hr == S_OK)
  {
   m_Symbols2->GetFieldOffset(node.m_Typed.ModBase, node.m_Typed.TypeId, Name, &Offset);

   ExtRemoteTyped nodefield;
   try
   {
    nodefield = node.Field(Name);
    for(int i=0; i     {
     dprintf(" ");
    }
    dprintf("+%03lx %s %10s %s\n", Offset, Name, ":", nodefield.GetSimpleValue());
   
    OutUnfold(nodefield, nRecur-1, nOrgRecur);
   }
   catch (.../*ExtException e*/)
   {
    //dprintf("excp :%s\n", e.GetMessage());
    //dprintf("Exception OutUnfold \n");
   }
  }
  else if (Hr == E_INVALIDARG)
  {
   if (i == 0 && nRecur == nOrgRecur)
   {
    node.OutSimpleValue();
   }
   // All Fields done
   break;
  }
  else
  {
   dprintf("GetFieldName Failed %lx\n", Hr);
   break;
  }
 }
}

EXT_COMMAND(cmap,
   "\n-Description: \n"
   "- Dump the content of a specific CMap (VC9).\n"
   "-Usage:"
   " Directly use the name of a map variate, eg."
   " !cmap NameOfMapVar",
   "{;x;arg;argument}")
{
 ExtRemoteTyped mtlmap;

 //dprintf("raw:%s\n",GetRawArgStr());

 string strRawArgs = GetRawArgStr();
 mtlmap.Set(strRawArgs.c_str());

 mtlmap.OutFullValue();
 Out("\n");

 ExtRemoteTyped mapsize = mtlmap.Field("m_nCount");
 ExtRemoteTyped hashTable = mtlmap.Field("m_pHashTable");
 ExtRemoteTyped hashTableSize = mtlmap.Field("m_nHashTableSize");

 Out("\n----------------------------------\n");
 Warn("Map size: %s", mapsize.GetSimpleValue());
 Out("\n----------------------------------\n");
 Out("%s , %s",
  mtlmap.Field("m_pFreeList.key").GetTypeName(),
  mtlmap.Field("m_pFreeList.value").GetTypeName());
 Out("\n----------------------------------\n");
 //
 DumpCmap(hashTable, hashTableSize.GetLongPtr());
}

4) 添加一个新建的def文件,命名为mtlkit.def。这个文件定义扩展DLL中需要导出的接口,也就是支持的扩展指令名字。其内容如下:
;--------------------------------------------------------------------
; Copyright (c) 2005 Microsoft Corporation
;
;Module:
; extcpp.def
;--------------------------------------------------------------------

EXPORTS

;--------------------------------------------------------------------
; Core exports provided by the ExtCpp framework.
;--------------------------------------------------------------------

    DebugExtensionInitialize
    DebugExtensionUninitialize
    DebugExtensionNotify
    help

;--------------------------------------------------------------------
; Extension commands.
;--------------------------------------------------------------------

    cmap

5) 按照实际情况配置工程属性。
C++编译配置中,程序文件的包含路径中要添加上Windbg sdk的源码所在目录,也就是 engextcpp.hpp和engextcpp.cpp两个文件的所在目录。
连接配置中,添加模块定义文件名称mtlkit.def,以表明需要使用def文件。


6) 最后还要注意工程常规属性里面要选择多字节集和静态使用MFC库。当然,根据自己的需要这些设置是可以调整的。


7) 至此,一个完整的windbg EngExtCpp扩展DLL工程就建立起来了,只需要只需编译就能得到,一个名为mtlkit.dll的动态库文件。之后只要将mtlkit.dll拷贝到windbg安装目录下的winext子目录中,我们就能在windbg中使用cmap指令了。使用方法可以是!mtlkit.cmap,也可以先用.load mtlkit加载动态库,之后就能直接使用!cmap指令了。


最后需要注意的是,以上编译得到的是32位DLL,只能在32位的windbg上使用,64位windbg需要加载64位的扩展DLL才能正常使用。

2.2 EngExtCpp开发代码规则

下面以 mtlkit.cpp的内容为例,介绍 EngExtCpp扩展指令开发 基本的规则。

包含头文件
#include
要使用EngExtCpp框架所提供的接口,显然需要包含它提供的头文件。

类声明的规矩
class EXT_CLASS : public ExtExtension
{
public:
 EXT_COMMAND_METHOD(cmap);
 //
private:
 void OutUnfold( ExtRemoteTyped& node, int nRecur, int nOrgRecur);
 void DumpCmap(ExtRemoteTyped& hashTable, INT_PTR hashTableSize);
};

// EXT_DECLARE_GLOBALS must be used to instantiate
// the framework's assumed globals.
EXT_DECLARE_GLOBALS();
我们自定义的类通常会继承EngExtCpp框架所定义的 ExtExtension类,在这个类的内部将实现扩展指令的所有功能。自定义类的类名通常必须是EXT_CLASS,因为 EngExtCpp框架在实现时,声明了一些变量,而这些变量固定使用了 EXT_CLASS这样一个符号。可以参看EXT_DECLARE_GLOBALS()宏的定义。
类名EXT_CLASS实际上也是一个宏,定义在文件engextcpp.hpp中,具体的定义为:
#ifndef EXT_CLASS
#define EXT_CLASS Extension
#endif
也就是说,我们自定义类的真实名字默认情况下是Extension。如果我们需要改变真实类名,则需要在#include 之前,对EXT_CLASS进行#define操作,否则可能无效或报错。
自定义类中的函数声明同普通的C++类基本一致。唯一不同的是扩展指令必须用EXT_COMMAND_METHOD宏申明为public方法。
public:
 EXT_COMMAND_METHOD(cmap);
上面的代码,声明了一个叫做cmap的public方法,cmap方法是用EXT_COMMAND_METHOD宏声明的,因此它就是一个扩展指令。

指令函数实现的规矩
相应的,用EXT_COMMAND_METHOD宏声明的函数,其实现也不同于普通的C++类函数,实现时必须用EXT_COMMAND宏引导:
EXT_COMMAND(cmap,
   "\n-Description: \n"
   "- Dump the content of a specific CMap (VC9).\n"
   "-Usage:"
   " Directly use the name of a map variate, eg."
   " !cmap NameOfMapVar",
   "{;x;arg;argument}")
{
 ExtRemoteTyped mtlmap;

 //dprintf("raw:%s\n",GetRawArgStr());

 string strRawArgs = GetRawArgStr();
 mtlmap.Set(strRawArgs.c_str());

 mtlmap.OutFullValue();
 Out("\n");

 ExtRemoteTyped mapsize = mtlmap.Field("m_nCount");
 ExtRemoteTyped hashTable = mtlmap.Field("m_pHashTable");
 ExtRemoteTyped hashTableSize = mtlmap.Field("m_nHashTableSize");

 Out("\n----------------------------------\n");
 Warn("Map size: %s", mapsize.GetSimpleValue());
 Out("\n----------------------------------\n");
 Out("%s , %s",
  mtlmap.Field("m_pFreeList.key").GetTypeName(),
  mtlmap.Field("m_pFreeList.value").GetTypeName());
 Out("\n----------------------------------\n");
 //
 DumpCmap(hashTable, hashTableSize.GetLongPtr());
}
EXT_COMMAND宏的原型为:
EXT_COMMAND( _Name, _Desc, _Args );
_Name:指令的名称
_Desc:指令的描述信息
_Args:指令参数的描述信息
(指定windbg指令后面参数的属性,如参数是数字还是字符串等等,可以参考windbg帮助文档中的主题
Parsing Extension Arguments)

.def文件说明
def文件最后一行的cmap就是我们扩展指令的名字,同时也是自定义类中导出函数名字。必须把 cmap 添加到def文件中,声明cmap函数是mtlkit.dll中的一个导出函数才能在windbg中调用cmap指令。另外,def文件中help等4个导出函数的名称是由开发库默认导出的接口。

代码简单说明
OutUnfold函数:由于变量可能不是基本数据类型,而是复杂的类,在导出类的时候就需要按层次展开类中的结构。OutUnfold函数指定展开的层数,对数据内容做输出。
DumpCmap函数:根据CMap的存储结构,导出其中的key以及key对应的value。CMap实际上是一个hash表,具体的实现可以参考它的源码。
ExtRemoteTyped类: 一个由EngExtCpp框架提供的很方便的类,在调试器成功加载程序符号表的情况下,ExtRemoteTyped 可以根据符号信息对一段给定的内存做解析,使我们可以方便提取内存中数据的内容、数据类型等信息。具体情况可以参考windbg帮助文档的说明,也可以分析它的源码对它做进一步了解。
Out,Warn,dprintf:都是格式化输出函数,Out和Warn是由EngExtCpp对DbgEng输出接口的封装,dprintf是wdbgexts对DbgEng接口的封装。Out输出的信息是提示性的文字,Warn是警告性的文字,通过对Windbg的输出格式配置,可以设置不同类型文本显示不同的颜色。本代码中使用Warn函数仅仅是演示,没有其他更多含义。另外,需要了解更多的格式化输出函数,可以参考windbg的帮助文档,或者直接分析开发库的源码。

以上完整的工程文件可以在此处下载: http://download.csdn.net/detail/yichigo/9672868
为了避免windbg版本差异引起不必要的问题,我已经将一个windbg资源添加到了工程文件中,可在此基础上学习开发,并直接在其中使用开发出的扩展库。

3.参考文档

以下都是windbg官方帮助文档中的主题,对于学习windbg扩展指令开发都有帮助。
(1)Introduction
(2)Parsin g Extension Arguments
(3)EngExtCpp Extension Design Guide
(4)Using Debugger Extension Commands
(5)WdbgExts Extension Design Guide
(6)DbgEng Extension Design Guide

你可能感兴趣的:(Windbg扩展指令开发教程)