蓝信子 发表于 2008-12-24 11:59:58

CLR 调试接口的架构与应用之调试事件

  首先看看 CLR 通过 ICorDebugManagedCallback 回调接口提供的 Managed 调试事件。这部分的调试事件可以大致分为被动调试事件和主动调试事件:前者由 CLR 在调试程序时自动引发被动调试事件,如创建一个新的线程;后者由调试器通过 CLR 的其他调试接口,控制 CLR 调试环境完成某种调试任务,并在适当的时候引发主动调试事件,如断点和表达式计算。
  
  就被动调试事件来说,基本上对应于 CLR 载入运行程序的若干个步骤
  
  首先是动态环境的建立,分为进程、AppDomain和线程三级,并分别有对应的建立和退出调试事件:
   
  
  以下为引用:
  
   interface ICorDebugManagedCallback : IUnknown
   {
    //...
   HRESULT CreateProcess( ICorDebugProcess *pProcess);
   HRESULT ExitProcess( ICorDebugProcess *pProcess);
  
   HRESULT CreateAppDomain( ICorDebugProcess *pProcess,
             ICorDebugAppDomain *pAppDomain);
   HRESULT ExitAppDomain( ICorDebugProcess *pProcess,
            ICorDebugAppDomain *pAppDomain);
  
   HRESULT CreateThread( ICorDebugAppDomain *pAppDomain,
            ICorDebugThread *thread);
   HRESULT ExitThread( ICorDebugAppDomain *pAppDomain,
           ICorDebugThread *thread);
  
    HRESULT NameChange( ICorDebugAppDomain *pAppDomain,
       ICorDebugThread *pThread);
    //...
   };
  
  在 CLR 的实现上,实际上是存在有物理上的 Native Thread 和逻辑上的 Managed Thread 两个概念的。进程和 Native Thread 对应着操作系统提供的相关概念,而 AppDomain 和 Managed Thread 则对应着 CLR 内部的相关抽象。上面的线程相关调试事件,实际上是 Native Thread 第一次以 Managed Thread 身份执行 Managed Code 的时候被引发的。更完整的控制需要借助后面要提及的 Native Thread 的调试事件。
  
  此外 AppDomain 和 Managed Thread 在创建并开始运行后,都会根据情况改名,并调用 NameChange 调试事件,让调试器有机会更新界面显示上的相关信息。
  
  其次是静态 Metadata 的载入和解析工作,也分为Assembly, Module和Class三级,并分别有对应的建立和退出调试事件:
   
  以下为引用:
  
   interface ICorDebugManagedCallback : IUnknown
   {
    //...
   HRESULT LoadAssembly( ICorDebugAppDomain *pAppDomain,
            ICorDebugAssembly *pAssembly);
   HRESULT UnloadAssembly( ICorDebugAppDomain *pAppDomain,
             ICorDebugAssembly *pAssembly);
  
   HRESULT LoadModule( ICorDebugAppDomain *pAppDomain,
           ICorDebugModule *pModule);
   HRESULT UnloadModule( ICorDebugAppDomain *pAppDomain,
            ICorDebugModule *pModule);
  
   HRESULT LoadClass( ICorDebugAppDomain *pAppDomain,
           ICorDebugClass *c);
   HRESULT UnloadClass( ICorDebugAppDomain *pAppDomain,
           ICorDebugClass *c);
    //...
   };
  
  在 CLR 中,Assembly 很大程度上是一个逻辑上的聚合体,真正落实到实现上的更多的是其 Module。一个 Assembly 在载入时,可以只是保护相关 Manifest 和 Metadata,真正的代码和数据完全可以存放在不同地点的多个 Module 中。因此,在 Managed 调试事件中,明确分离了 Assembly 和 Module 的生命周期。
  
  然后就是对 IL 代码中特殊指令和功能的支持用调试事件:
   
  以下为引用:
  
   interface ICorDebugManagedCallback : IUnknown
   {
    //...
   HRESULT Break( ICorDebugAppDomain *pAppDomain,
         ICorDebugThread *thread);
  
   HRESULT Exception( ICorDebugAppDomain *pAppDomain,
           ICorDebugThread *pThread,
           BOOL unhandled);
  
   HRESULT DebuggerError( ICorDebugProcess *pProcess,
               HRESULT errorHR,
               DWORD errorCode);
  
    HRESULT LogMessage( ICorDebugAppDomain *pAppDomain,
              ICorDebugThread *pThread,
           LONG lLevel,
           WCHAR *pLogSwitchName,
           WCHAR *pMessage);
  
   HRESULT LogSwitch( ICorDebugAppDomain *pAppDomain,
             ICorDebugThread *pThread,
           LONG lLevel,
           ULONG ulReason,
           WCHAR *pLogSwitchName,
           WCHAR *pParentName);
  
    HRESULT ControlCTrap( ICorDebugProcess *pProcess);
  
   HRESULT UpdateModuleSymbols( ICorDebugAppDomain *pAppDomain,
                  ICorDebugModule *pModule,
                  IStream *pSymbolStream);
    //...
   };
  
  Break 事件在执行 IL 指令 Break 时被引发,可被用于实现特殊的断点等功能;
  Exception 事件在代码抛出异常时,以及异常未被处理时被引发,类似于 Win32 Debug API 中的异常事件。后面介绍调试器中对异常的处理方法时再详细介绍;
  DebuggerError 事件则是在调试系统处理 Win32 调试事件发生错误时被引发;
  LogMessage 和 LogSwitch 事件分别用于处理内部类 System.Diagnostics.Log 的相关功能,类似于 Win32 API 下 OutputDebugString 函数的功能,等有机会再单独写篇文章介绍相关内容;
  ControlCTrap 事件响应用户使用 Ctrl C 热键直接中断程序,等同于 Win32 API 下 SetConsoleCtrlHandler 函数的功能;
  UpdateModuleSymbols 事件在系统更新某个模块调试符号库的时候被引发,使调试器有机会同步状态。
  
  最后还省下几个主动调试事件,在调试器调用 CLR 调试接口相关功能被完成或异常时引发:
   
  以下为引用:
  
   interface ICorDebugManagedCallback : IUnknown
   {
    //...
   HRESULT Breakpoint( ICorDebugAppDomain *pAppDomain,
           ICorDebugThread *pThread,
           ICorDebugBreakpoint *pBreakpoint);
    HRESULT BreakpointSetError( ICorDebugAppDomain *pAppDomain,
                  ICorDebugThread *pThread,
                  ICorDebugBreakpoint *pBreakpoint,
                  DWORD dwError);
  
   HRESULT StepComplete( ICorDebugAppDomain *pAppDomain,
            ICorDebugThread *pThread,
            ICorDebugStepper *pStepper,
            CorDebugStepReason reason);
  
   HRESULT EvalComplete( ICorDebugAppDomain *pAppDomain,
               ICorDebugThread *pThread,
               ICorDebugEval *pEval);
   HRESULT EvalException( ICorDebugAppDomain *pAppDomain,
               ICorDebugThread *pThread,
               ICorDebugEval *pEval);
  
    HRESULT EditAndContinueRemap( ICorDebugAppDomain *pAppDomain,
                   ICorDebugThread *pThread,
                   ICorDebugFunction *pFunction,
                   BOOL fAccurate);
    //...
   };
  
  Breakpoint 和 BreakpointSetError 在断点被触发或设置断点失败时被调用,下一节介绍断点的实现时再详细讨论;
  StepComplete 则在调试环境因为某种原因完成了一次代码步进(step)时被调用,以后介绍单步跟踪等功能实现时再详细讨论;
  EvalComplete 和 EvalException 在表达式求值完成或失败时被调用,以后介绍调试环境当前信息获取时再详细讨论;
  EditAndContinueRemap 则用于实现调试时代码编辑功能,暂不涉及。
  
  下面是一个比较直观的实例,显示一个简单的 CLR 调试环境在运行一个普通 CLR 程序除非相关调试事件的顺序
   
  以下为引用:
  
   ManagedEventHandler.CreateProcess(3636)
   ManagedEventHandler.CreateAppDomain(DefaultDomain @ 3636)
  
   ManagedEventHandler.LoadAssembly(e:windowsmicrosoft.net rameworkv1.1.4322mscorlib.dll @ DefaultDomain)
    <
页: [1]
查看完整版本: CLR 调试接口的架构与应用之调试事件