CLR 调试接口的架构与应用 [2] 调试框架

原创 2004年07月07日 20:00:00

http://www.blogcn.com/user8/flier_lu/index.html?id=1894812


    如 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;
   public uint    dwXSize;
   public uint    dwYSize;
   public uint    dwXCountChars;
   public uint    dwYCountChars;
   public uint    dwFillAttribute;
   public uint    dwFlags;
   public ushort  wShowWindow;
   public ushort  cbReserved2;
   public IntPtr  lpReserved2;
   public IntPtr  hStdInput;
   public IntPtr  hStdOutput;
   public IntPtr  hStdError;
 }

 [StructLayout(LayoutKind.Sequential)]
 public struct PROCESS_INFORMATION
 {
   public IntPtr hProcess;
   public IntPtr hThread;
   public uint   dwProcessId;
   public uint   dwThreadId;
 }
 



     使用的时候则需要先在堆栈中构造此结构的值类型对象,然后通过 unsafe 形式指针,或者 Marshal 手工处理将之转换为地址。这里为了避免使用较为 dirty 的 unsafe 方式,通过 Marshal.AllocHGlobal 分配全局内存;然后调用 Marshal.StructureToPtr 将结构复制到内存中;调用 CreateProcess 时使用此内存的地址;调用返回后使用 Marshal.PtrToStructure 从内存中获得结构的内容;最后调用 Marshal.FreeHGlobal 释放全局内存。简要代码如下:
 
以下为引用:

 //...

 STARTUPINFO si = new STARTUPINFO(); // 构造时所有字段已清零

 si.cb = (uint)Marshal.SizeOf(si);

 PROCESS_INFORMATION pi = new PROCESS_INFORMATION();

 IntPtr ppi = Marshal.AllocHGlobal(Marshal.SizeOf(pi)),
        psi = Marshal.AllocHGlobal(Marshal.SizeOf(si));

 Marshal.StructureToPtr(si, psi, true);
 Marshal.StructureToPtr(pi, ppi, true);

 _dbg.CreateProcess(Options.FileInfo.FullName, Options.CommandLine,
   ref sa, ref sa, BOOL.FALSE, dwCreationFlag, IntPtr.Zero,
   Options.CurrentDirectory, (uint)psi.ToInt32(), (uint)ppi.ToInt32(),
   CorDebugCreateProcessFlags.DEBUG_NO_SPECIAL_OPTIONS, out _proc);

 pi = (PROCESS_INFORMATION)Marshal.PtrToStructure(ppi, typeof(PROCESS_INFORMATION));

 Marshal.FreeHGlobal(ppi);
 Marshal.FreeHGlobal(psi);

 Native.CloseHandle(pi.hProcess);

 //...
 



     而将调试器附加到现有进程上则相对简单得多,接口方法如下:
 
以下为引用:

 public abstract interface ICorDebug
 {
   public abstract new void DebugActiveProcess(uint id, int win32Attach, ICorDebugProcess ppProcess)
 }

 BOOL DebugActiveProcess(
   DWORD dwProcessId
 );
 



     与 Win32 API 的 DebugActiveProcess 相比,ICorDebug.DebugActiveProcess 增加的 win32Attach 指定是否允许 Native 模式调试,ppProcess 返回目标调试进程的控制接口。

     以上简要介绍了 CLR 调试接口在使用时如何构造调试环境,以及对调试目标进程的创建和附加的方法。下一节将整体上对托管和非托管的各种调试事件做一个介绍,然后再针对不同的调试功能开始详细介绍。

《数据结构》链表程序调试示例--有一定代表性请看看

下面的一个同学程序,编译和链接均没问题,只是运行
  • u013847155
  • u013847155
  • 2014年11月03日 23:37
  • 1035

SQL Server实现CLR步骤及其需要配置注意的问题(转)

介绍 我们一起来做个示例,在.NET中新建一个类,并在这个类里新建一个方法,然后在SQL Server中调用这个方法。按照微软所述,通过宿主 Microsoft .NET Framework 2....
  • ljh56789
  • ljh56789
  • 2014年08月06日 09:12
  • 4893

使用Fiddler调试你的移动应用后台api接口

我们在做移动app(android,ios,wp等)时,由于需要和服务器接口进行数据交互,为了每次调试后台接口,得需要单独根据开发库打一个版本的app用于测试,其实不用这么麻烦,直接使用正式服务器地址...
  • coolcaosj
  • coolcaosj
  • 2014年09月26日 18:19
  • 5898

C#调用CLR C++ DLL异常分析

前言在使用C#调用CLR C++ DLL开发程序完成后在本机上运行正常(本机为Win8.1 64位系统),在将生成的程序复制到客户机电脑上时(Win7 32位操作系统),遇到了如下所示提示: ...
  • kwfly
  • kwfly
  • 2016年04月07日 21:00
  • 1375

自己实现Struts2(六)实现参数拦截器并调试框架

上一章自己实现Struts2(五)实现StrutsPrepareAndExecuteFilter我把Struts2的核心拦截器StrutsPrepareAndExecuteFilter实现好了,现在其...
  • TimHeath
  • TimHeath
  • 2017年03月26日 20:29
  • 742

android登陆接口调试

最近项目要开始调API,于是自己写了个关于登陆界面调试的Demo,为了保护项目,接口文档里面的内容都是被我改过的,不涉及任何项目内容。当然,代码在运行成功后,上传至博客前,相应内容我也根据改过后的文档...
  • u010093630
  • u010093630
  • 2013年12月20日 17:05
  • 2599

Android,iso接口调试-抓包调试接口

准备工具 cmd 用于查看本机IPfiddler 抓包工具,fiddler应该算windows是最流行的,但是太丑了charles 和fiddler相比,要好看一点,所以用这个做例子...
  • phyooos
  • phyooos
  • 2016年12月06日 16:34
  • 553

调试WebService接口的小工具-storm

调试WebService接口的小工具-storm 记得在url后面加上 ?wsdl,就可以调试wsdl包含的接口函数了。 Storm快速上手:  添加一个WebService(如下图一所示)...
  • sunxiaopengsun
  • sunxiaopengsun
  • 2017年03月03日 12:06
  • 3232

微信开发实战(2)—微信公众平台接口调试工具

微信公众平台为公众号开发者提供了网页版的接口调试工具,开发者可以直接在网页中调用对应的接口,比如获取access_token接口,创建菜单接口,发送消息接口 等等。 先看一下界面,访问: ...
  • quasimodo_es
  • quasimodo_es
  • 2016年10月30日 20:07
  • 3640

C# webservice服务跟踪调试方法

1.新建网站,添加服务,并创建服务。 2.打开internet 信息服务管理器,添加网站,映射到创建的服务所在网站的目录。 3.打开服务所在网站的解决方案,进行配置。  1) 设置启动选项 选...
  • smile00_0
  • smile00_0
  • 2017年05月26日 08:55
  • 2167
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:CLR 调试接口的架构与应用 [2] 调试框架
举报原因:
原因补充:

(最多只允许输入30个字)