this is a extra element for clear the floated element
CLR调试接口的架构与应用之调试框架
  • 12/31
  • 2008
优化性能 | Windows 1070 次查看
  如 Don Box 在《.NET本质论 第1卷:公共语言运行库》一书的第10章中介绍, CLR 调试框架是一个由 CLR 提供的,面向工具开发商的,支持调试功能的最小功能集。与 JVM 的 JDI (Java Debug Interface)不同,CLR 调试框架不仅仅关注于虚拟机一级的调试,同时也提供了 Native 一级调试的统一接口。使得现有工具开发商能够以最小代价移植并支持 CLR 调试功能。而对 CLR 调试更高层次或更细粒度的支持,则是由前面提到的 Profiling API 完成。

  CLR 调试接口主要通过 mscordbi.dll 提供的 ICorDebug 接口,让调试器通过进程内或进程外方式,对被调试 CLR 进行监控。而 ICorDebug 接口可以通过 .NET Framework SDK 中 includecordebug.idl 或 includecordebug.h 直接使用。对 C#/Delphi 也可以直接 reference/import 在 SDK 的 lib 目录下的 cordebug.tlb 类型库,获得调用包装类。下面示例将都使用 C# 作为描述语言。

  在使用时,可以直接获取ICorDebug接口,并调用其Initialize/Terminate方法进行初始化和析构操作,框架代码如下:

  以下为引用:

  using CORDBLib;

  namespace cordbg

  {

  public class Debugger : IDisposable

  {

  private ICorDebug _dbg;

  public void Run()

  {

  _dbg = new CorDebugClass();

  try

  {

  _dbg.Initialize();

  // 构造调试环境

  // 处理调试事件

  }

  finally

  {

  _dbg.Terminate();

  }

  }

  ...

  }

  [MTAThread]

  static void Main(string[] args)

  {

  using(Debugger dbg = new Debugger())

  {

  dbg.Run();

  }

  }

  }

  注意 CLR 调试环境必须在 MTA 的线程套间上下文(Thread Apartment Context)中运行,因此必须将入口函数的 STAThread 属性改成 MTAThread,否则会在调试接口调用回调函数时出现异常。对应于 COM 中的 CoInitializeEx(NULL, COINIT_MULTITHREADED) 调用。

  在创建了 ICorDebug 调试接口后,需要针对托管和非托管调试事件,提供调试事件回调接口。可以将实现了调试事件接口 ICorDebugManagedCallback/ICorDebugUnmanagedCallback 的实例,使用 ICorDebug 接口的 SetManagedHandler/SetUnmanagedHandler 方法,挂接到调试系统上,在适当的时候由调试系统回调,通知调试器有调试事件发生。实现上可以通过 ManagedEventHandler/UnmanagedEventHandler 两个单独的类,抽象出对托管和非托管调试事件的处理机制,将之挂接到调试器上,如:

  以下为引用:

  namespace cordbg

  {

  public class DebugEventHandler

  {

  protected Debugger _dbg;

  public DebugEventHandler(Debugger dbg)

  {

  this._dbg = dbg;

  }

  }

  public class ManagedEventHandler : DebugEventHandler, ICorDebugManagedCallback

  {

  public ManagedEventHandler(Debugger dbg) : base(dbg)

  {

  }

  // 实现 ICorDebugManagedCallback 接口

  }

  public class UnmanagedEventHandler : DebugEventHandler, ICorDebugUnmanagedCallback

  {

  public UnmanagedEventHandler(Debugger dbg) : base(dbg)

  {

  }

  // 实现 ICorDebugUnmanagedCallback 接口

  }

  public class Debugger : IDisposable

  {

  public void Run()

  {

  //...

  _dbg.SetManagedHandler(new ManagedEventHandler(this));

  _dbg.SetUnmanagedHandler(new UnmanagedEventHandler(this));

  //...

  }

  }

  }

  在准备好了调试事件处理器后,就可以根据需要,创建或者附加到目标调试进程上。ICorDebug 提供了 CreateProcess 方法对 Win32 API 中 CreateProcess 函数进行了包装。

  以下为引用:

  public abstract interface ICorDebug

  {

  public abstract new void CreateProcess (

  string lpApplicationName,

  string lpCommandLine,

  _SECURITY_ATTRIBUTES lpProcessAttributes,

  _SECURITY_ATTRIBUTES lpThreadAttributes,

  int bInheritHandles,

  uint dwCreationFlags,

  IntPtr lpEnvironment,

  System.String lpCurrentDirectory,

  uint lpStartupInfo,

  uint lpProcessInformation,

  CorDebugCreateProcessFlags debuggingFlags,

  ICorDebugProcess ppProcess)

  }

  BOOL CreateProcess(

  LPCTSTR lpApplicationName,

  LPTSTR lpCommandLine,

  LPSECURITY_ATTRIBUTES lpProcessAttributes,

  LPSECURITY_ATTRIBUTES lpThreadAttributes,

  BOOL bInheritHandles,

  DWORD dwCreationFlags,

  LPVOID lpEnvironment,

  LPCTSTR lpCurrentDirectory,

  LPSTARTUPINFO lpStartupInfo,

  LPPROCESS_INFORMATION lpProcessInformation

  );

  可以看到这两个函数的参数基本上是一一对应的,只不过ICorDebug.CreateProcess函数多了一个输入debuggingFlags参数指定调试标志和一个输出ppProcess参数返回创建进程的控制接口。

  两个 _SECURITY_ATTRIBUTES 类型的安全属性,一般来说可以设置为空,使用缺省设置。

  以下为引用:

  _SECURITY_ATTRIBUTES sa = new _SECURITY_ATTRIBUTES();

  sa.nLength = (uint)Marshal.SizeOf(sa);

  sa.bInheritHandle = Win32.BOOL.FALSE;

  sa.lpSecurityDescriptor = IntPtr.Zero;

  值得注意的是 dwCreationFlags 指定了创建进程是否支持 Native 模式的调试,也就是前面 SetUnmanagedHandler 方法调用的接口是否起作用。可以根据情况如命令行选项决定是否支持 Native 调试模式,如

  以下为引用:

  namespace Win32

  {

  public struct CreationFlag

  {

  public const uint DEBUG_PROCESS

  = 0x00000001;

  public const uint DEBUG_ONLY_THIS_PROCESS

  = 0x00000002;

  public const uint CREATE_SUSPENDED

  = 0x00000004;

  public const uint DETACHED_PROCESS

  = 0x00000008;

  public const uint CREATE_NEW_CONSOLE

  = 0x00000010;

  public const uint NORMAL_PRIORITY_CLASS

  = 0x00000020;

  public const uint IDLE_PRIORITY_CLASS

  = 0x00000040;

  public const uint HIGH_PRIORITY_CLASS

  = 0x00000080;

  public const uint REALTIME_PRIORITY_CLASS

  = 0x00000100;

  public const uint CREATE_NEW_PROCESS_GROUP

  = 0x00000200;

  public const uint CREATE_UNICODE_ENVIRONMENT

  = 0x00000400;

  public const uint CREATE_SEPARATE_WOW_VDM

  = 0x00000800;

  public const uint CREATE_SHARED_WOW_VDM

  = 0x00001000;

  public const uint CREATE_FORCEDOS

  = 0x00002000;

  public const uint BELOW_NORMAL_PRIORITY_CLASS = 0x00004000;

  public const uint ABOVE_NORMAL_PRIORITY_CLASS = 0x00008000;

  public const uint CREATE_BREAKAWAY_FROM_JOB

  = 0x01000000;

  }

  }

  namespace cordbg

  {

  public class Debugger : IDisposable

  {

  private void Run()

  {

  //...

  uint dwCreationFlag = CreationFlag.CREATE_NEW_CONSOLE;

  if(Options.NativeMode)

  {

  dwCreationFlag |= CreationFlag.DEBUG_ONLY_THIS_PROCESS | CreationFlag.DEBUG_PROCESS;

  }

  //...

  }

  }

  }

  比较麻烦的是指定启动参数的 lpStartupInfo 参数和返回进程信息的 lpProcessInformation 参数。C# 在导入 cordebug.tlb 类型库时,都没有处理这两个类型,必须自己定义之:

  以下为引用:

  [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)]

  public struct STARTUPINFO

  {

  public uint

  cb;

  public string

  lpReserved;

  public string

  lpDesktop;

  public string

  lpTitle;

  public uint

  dwX;

  public uint

  dwY;