您的位置:首页 > 其它

另一种WinDbg插件编写方法-Debugger Engine Extension

2007-11-14 15:36 633 查看
导读:
  来源:http://flier_lu.blogone.net/?id=2178387
  作者:Flier Lu
  主页:http://www.nsfocus.com
  在仔细阅读上期月刊中 scz 的《MSDN系列(11)--给SoftICE写插件》一文后,忍不住自己动手试试 WinDbg 插件的编写,呵呵。不过我选择的是与 scz 不同的另一种 WinDbg 插件编写方法。
  WinDbg 最新版本的 sdkhelp 目录下有一个 debugext.chm 文件,里面有很详细的 WinDbg 插件编写文档。其中提到 WinDbg 支持两种类型的插件:DbgEng 扩展和 WdbgExts 扩展。前者是使用在 dbgeng.h 中定义的针对 Debugger Engine API 的调试扩展;后者则是使用在 wdbgexts.h 中定义的专门针对 WinDbg 的调试扩展。小四文章中使用的就是后者的接口,较为简明,也可以被 SoftIce 很好支持;我则选择前一种插件类型,功能更强大,而且可以被除 WinDbg 外的其他支持 Debugger Engine API 的工具,如 Visual Studio.NET 支持。
  与 WdbgExts 类型扩展插件类似,DbgEng 类型扩展插件必须实现一个初始化回调函数:
  HRESULT CALLBACK DebugExtensionInitialize(OUT PULONG Version, OUT PULONG Flags);
  此函数在使用 .load 命令载入插件时被调用,返回插件的版本信息。如
  const int EXTS_VERSION_MAJOR = 1;
  const int EXTS_VERSION_MINOR = 0;
  extern "C" HRESULT CALLBACK DebugExtensionInitialize(OUT PULONG Version, OUT PULONG Flags)
  {
  *Version = DEBUG_EXTENSION_VERSION(EXTS_VERSION_MAJOR, EXTS_VERSION_MINOR);
  *Flags = 0;
  return S_OK;
  }
  定义插件回调函数时,必须使用 extern "C" 指定此函数的函数名使用与 C 兼容的命名格式,并建立一个 .def 文件定义入口名字,如
  LIBRARY ClrExts
  EXPORTS
  DebugExtensionInitialize
  DebugExtensionUninitialize
  DebugExtensionNotify
  KnownStructOutput
  help
  showcontext
  这里建立一个新的 DbgEng 类型插件 ClrExts 完成对 CLR 调试支持扩展功能,并导出四个标准回调函数。除 DebugExtensionInitialize 必须有以外,其他三个回调函数是可选的。
  void CALLBACK DebugExtensionNotify(IN ULONG Notify, IN ULONG64 Argument);
  DebugExtensionNotify 函数在调试会话的状态转换的时候被调用,以通知插件调整自己的状态。Notify 参数可以有四个值:
  DEBUG_NOTIFY_SESSION_ACTIVE: 调试会话被激活
  DEBUG_NOTIFY_SESSION_INACTIVE: 没有被激活的调试会话
  DEBUG_NOTIFY_SESSION_ACCESSIBLE: 调试会话被中断并可访问
  DEBUG_NOTIFY_SESSION_INACCESSIBLE: 调试会话恢复执行并不能访问
  调试会话的概念,表示是否正在调试一个进程中;而根据调试状态,中断目标程序运行并由调试器获得控制权时,调试会话被中断并可访问(DEBUG_NOTIFY_SESSION_ACCESSIBLE)。
  调试插件可以通过跟踪这几个状态的改变,调整自己对目标调试进程的控制方法。
  void CALLBACK DebugExtensionUninitialize(void);
  DebugExtensionUninitialize函数则是在插件被 .unload 命令卸载的时候被调用。
  HRESULT CALLBACK KnownStructOutput(IN ULONG Flag, IN ULONG64 Address, IN PSTR StructName, OUT PSTR Buffer, IN OUT PULONG BufferSize);
  最后一个 KnownStructOutput 回调函数较少被用到,用于提供此调试插件支持打印的结构列表,并可打印指定地址的指定结构内容。
  与 WdbgExts 类型插件不太一样,DbgEng 类型插件的调试接口可以通过 DebugCreate 函数,自行获取其 COM 接口
  HRESULT DebugCreate(IN REFIID InterfaceId, OUT PVOID *Interface);
  也可以通过插件命令的参数获得。插件的通用命令接口如下:
  HRESULT CALLBACK (* PDEBUG_EXTENSION_CALL)(IN IDebugClient *Client, IN OPTIONAL PCSTR Args);
  第一个参数 Client 就是调试接口,另外一个则是命令的参数字符串。
  可以使用一个简单的包装类 CDebugClient 对 IDebugClient 接口就行包装,其构造函数自动获取调试接口
  class CDebugClient
  {
  private:
  HRESULT m_hr;
  CComPtr m_spDebugClient;
  CComQIPtr m_spDebugControl;
  WINDBG_EXTENSION_APIS32 m_extensionApis;
  public:
  CDebugClient(void);
  };
  CDebugClient::CDebugClient(void)
  {
  m_hr = DebugCreate(__uuidof(IDebugClient), (PVOID *)&m_spDebugClient);
  if(SUCCEEDED(m_hr))
  {
  m_spDebugControl = m_spDebugClient;
  m_extensionApis.nSize = sizeof(m_extensionApis);
  m_hr = m_spDebugControl->GetWindbgExtensionApis32(&m_extensionApis);
  }
  }
  DebugCreate 函数构造一个新的 IDebugClient 接口实例,并放入 ATL 接口包装类 CComPtr 的对象 m_spDebugClient 中,并可从此接口查询获取 IDebugControl 接口实例。IDebugControl::GetWindbgExtensionApis32 则可以获取与 WdbgExts 类型插件兼容的调试接口函数集。不过我们后面将看到,DbgEng 的相应接口,比 WinDbg 的传统函数集功能要强大得多。
  对于插件命令的入口直接给出的 IDebugClient 实例,则可以省去构造过程,如
  CDebugClient::CDebugClient(IDebugClient *dbg)
  : m_outLevel(olDefault), m_hr(S_OK), m_spDebugClient(dbg)
  {
  if(dbg)
  {
  m_spDebugControl = m_spDebugClient;
  m_extensionApis.nSize = sizeof(m_extensionApis);
  m_hr = m_spDebugControl->GetWindbgExtensionApis32(&m_extensionApis);
  }
  }
  在了解了调试接口的创建和包装方法后,可以建立第一个插件命令,help,显示一个帮助字符串给调试器
  extern "C" HRESULT CALLBACK help(IN IDebugClient *Client, IN OPTIONAL PCSTR Args)
  {
  UNREFERENCED_PARAMETER(Args);
  CDebugClient DebugClient(Client);
  if(FAILED(DebugClient.getLastHResult())) return DebugClient.getLastHResult();
  DebugClient.Info("Help for %s "
  "help - Show this help ", EXTS_NAME);
  return DebugClient.getLastHResult();
  }
  UNREFERENCED_PARAMETER 是一个宏,用于显式引用一次不会用到的函数参数,避免编译器警告;
  然后使用命令参数构造 CDebugClient 实例,并判断其构造过程是否有效;
  接着调用 CDebugClient 封装的 Info 函数打印一堆帮助字符串;
  最后返回 DebugClient 的最后调用状态。
  函数逻辑非常简单,就不罗嗦了,下面看看对字符串的输出
  enum OutputLevel
  {
  olAll,
  olDebug,
  olInfo,
  olWarning,
  olError,
  #ifdef _DEBUG
  olDefault = olAll
  #else
  olDefault = olInfo
  #endif
  };
  class CDebugClient
  {
  private:
  OutputLevel m_outLevel;
  };
  首先定义了5个缺省的输出级别,所有、调试、信息、警告和错误;然后定义调试接口的信息显示级别。
  class CDebugClient
  {
  public:
  void OutputString(OutputLevel lvl, const char *fmt, va_list args) const;
  void OutputString(OutputLevel lvl, const char *fmt, ...) const;
  void DoLog(OutputLevel level, const char *fmt, va_list args) const
  {
  if(m_outLevel <= level) OutputString(level, fmt, args);
  }
  #define DEF_LOG_LEVEL(name) void name(const char *fmt, ...) const
  {
  va_list args;
  va_start(args, fmt);
  DoLog(ol ## name, fmt, args);
  va_end(args);
  }
  DEF_LOG_LEVEL(Debug);
  DEF_LOG_LEVEL(Info);
  DEF_LOG_LEVEL(Warning);
  DEF_LOG_LEVEL(Error);
  };
  实际的信息输出放在 OutputString 函数中完成,而 DoLog 则根据当前调试接口的信息级别判断是否需要输出信息。并使用 DEF_LOG_LEVEL 宏定义四种常用的信息输出函数。
  void CDebugClient::OutputString(OutputLevel lvl, const char *fmt, va_list args) const
  {
  #if 1
  static ULONG OutputMask[] = {
  0,
  DEBUG_OUTPUT_VERBOSE,
  DEBUG_OUTPUT_NORMAL,
  DEBUG_OUTPUT_WARNING,
  DEBUG_OUTPUT_ERROR
  };
  m_spDebugControl->OutputVaList(OutputMask[lvl], fmt, args);
  #else
  std::string str;
  str.resize(_vscprintf(fmt, args)+1, 0);
  _vsnprintf(const_cast(str.c_str()), str.size(), fmt, args);
  m_extensionApis.lpOutputRoutine(str.c_str());
  #endif
  }
  void CDebugClient::OutputString(OutputLevel lvl, const char *fmt, ...) const
  {
  va_list args;
  va_start(args, fmt);
  OutputString(lvl, fmt, args);
  va_end(args);
  }
  OutputString 可以通过 IDebugControl 的 OutputVaList 方法输出,也可以通过传统的 WdbgExts 调试接口的 lpOutputRoutine 函数输出。前者的优点是可以根据信息输出级别,设定相应的输出掩码。如 olDebug 对应于 DEBUG_OUTPUT_VERBOSE,此类型信息只有在 WinDbg 打开了 Verbose 模式(菜单 View/Verbose Output)时才会显示,非常适合对插件就行调试跟踪。
  在了解了调试接口函数的大致使用流程后,接着编写一个有实际意义的功能,也就是 scz 文章中的 showcontext 函数,代码如下:
  #define OFFSETOF(TYPE, MEMBER) ((size_t)&((TYPE)0)->MEMBER)
  extern "C" HRESULT CALLBACK showcontext(IN IDebugClient *Client, IN OPTIONAL PCSTR Args)
  {
  CDebugClient DebugClient(Client);
  if(FAILED(DebugClient.getLastHResult())) return DebugClient.getLastHResult();
  DebugClient.Debug("%s: call externsion function showcontext with arguments - %s ", EXTS_NAME, Args);
  std::string buf;
  DWORD dwSize = OFFSETOF(PCONTEXT, ExtendedRegisters);
  buf.resize(dwSize);
  DWORD dwAddress = DebugClient.Evaluate(Args);
  DebugClient.Debug("%s: get expression "%s" 's value %x ", EXTS_NAME, Args, dwAddress);
  if(DebugClient.ReadMemory(dwAddress, buf) == dwSize)
  {
  PCONTEXT pCtxt = (PCONTEXT)buf.c_str();
  DebugClient.Info("EAX=%08X EBX=%08X ECX=%08X EDX=%08X ESI=%08X "
  "EDI=%08X EBP=%08X ESP=%08X EIP=%08X EFLAGS=%08X "

本文转自
http://www.sec119.com/bbs/simple/index.php?t1188.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: