CLR 调试接口的架构与应用 [3] 调试事件

架构

在上一节中简单介绍了 CLR 调试器的框架结构,其中提到 CLR 调试环境同时支持 Native 和 Managed 两种模式的调试事件。这一节将从整体上对调试事件做一个概括性的介绍。

首先看看 CLR 通过 ICorDebugManagedCallback 回调接口提供的 Managed 调试事件。这部分的调试事件可以大致分为被动调试事件和主动调试事件:前者由 CLR 在调试程序时自动引发被动调试事件,如创建一个新的线程;后者由调试器通过 CLR 的其他调试接口,控制 CLR 调试环境完成某种调试任务,并在适当的时候引发主动调试事件,如断点和表达式计算。

就被动调试事件来说,基本上对应于 CLR 载入运行程序的若干个步骤

首先是动态环境的建立,分为进程、AppDomain和线程三级,并分别有对应的建立和退出调试事件:

以下为引用:

interface ICorDebugManagedCallback : IUnknown
{
//...
HRESULT CreateProcess([in] ICorDebugProcess *pProcess);
HRESULT ExitProcess([in] ICorDebugProcess *pProcess);

HRESULT CreateAppDomain([in] ICorDebugProcess *pProcess,
[in] ICorDebugAppDomain *pAppDomain);
HRESULT ExitAppDomain([in] ICorDebugProcess *pProcess,
[in] ICorDebugAppDomain *pAppDomain);

HRESULT CreateThread([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *thread);
HRESULT ExitThread([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *thread);

HRESULT NameChange([in] ICorDebugAppDomain *pAppDomain,
[in] 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([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugAssembly *pAssembly);
HRESULT UnloadAssembly([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugAssembly *pAssembly);

HRESULT LoadModule([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugModule *pModule);
HRESULT UnloadModule([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugModule *pModule);

HRESULT LoadClass([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugClass *c);
HRESULT UnloadClass([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugClass *c);
//...
};

在 CLR 中,Assembly 很大程度上是一个逻辑上的聚合体,真正落实到实现上的更多的是其 Module。一个 Assembly 在载入时,可以只是保护相关 Manifest 和 Metadata,真正的代码和数据完全可以存放在不同地点的多个 Module 中。因此,在 Managed 调试事件中,明确分离了 Assembly 和 Module 的生命周期。

然后就是对 IL 代码中特殊指令和功能的支持用调试事件:

以下为引用:

interface ICorDebugManagedCallback : IUnknown
{
//...
HRESULT Break([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *thread);

HRESULT Exception([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *pThread,
[in] BOOL unhandled);

HRESULT DebuggerError([in] ICorDebugProcess *pProcess,
[in] HRESULT errorHR,
[in] DWORD errorCode);

HRESULT LogMessage([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *pThread,
[in] LONG lLevel,
[in] WCHAR *pLogSwitchName,
[in] WCHAR *pMessage);

HRESULT LogSwitch([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *pThread,
[in] LONG lLevel,
[in] ULONG ulReason,
[in] WCHAR *pLogSwitchName,
[in] WCHAR *pParentName);

HRESULT ControlCTrap([in] ICorDebugProcess *pProcess);

HRESULT UpdateModuleSymbols([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugModule *pModule,
[in] 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([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *pThread,
[in] ICorDebugBreakpoint *pBreakpoint);
HRESULT BreakpointSetError([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *pThread,
[in] ICorDebugBreakpoint *pBreakpoint,
[in] DWORD dwError);

HRESULT StepComplete([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *pThread,
[in] ICorDebugStepper *pStepper,
[in] CorDebugStepReason reason);

HRESULT EvalComplete([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *pThread,
[in] ICorDebugEval *pEval);
HRESULT EvalException([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *pThread,
[in] ICorDebugEval *pEval);

HRESULT EditAndContinueRemap([in] ICorDebugAppDomain *pAppDomain,
[in] ICorDebugThread *pThread,
[in] ICorDebugFunction *pFunction,
[in] 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)
ManagedEventHandler.LoadModule(e:windowsmicrosoft.net rameworkv1.1.4322mscorlib.dll @ DefaultDomain)

ManagedEventHandler.NameChange(AppDomain=cordbg)

ManagedEventHandler.CreateThread(3944 @ cordbg)

ManagedEventHandler.LoadAssembly(F:StudyDotNetDebuggercordbginDebugcordbg.exe @ cordbg)
ManagedEventHandler.LoadModule(F:StudyDotNetDebuggercordbginDebugcordbg.exe @ cordbg)

ManagedEventHandler.NameChange(AppDomain=cordbg.exe)

ManagedEventHandler.LoadAssembly(e:windowsassemblygacsystem.0.5000.0__b77a5c561934e089system.dll @ cordbg.exe)
ManagedEventHandler.LoadModule(e:windowsassemblygacsystem.0.5000.0__b77a5c561934e089system.dll @ cordbg.exe)

ManagedEventHandler.CreateThread(2964 @ cordbg.exe)

ManagedEventHandler.UnloadModule(F:StudyDotNetDebuggercordbginDebugcordbg.exe @ cordbg.exe)
ManagedEventHandler.UnloadAssembly(F:StudyDotNetDebuggercordbginDebugcordbg.exe @ cordbg.exe)

ManagedEventHandler.UnloadModule(e:windowsassemblygacsystem.0.5000.0__b77a5c561934e089system.dll @ cordbg.exe)
ManagedEventHandler.UnloadAssembly(e:windowsassemblygacsystem.0.5000.0__b77a5c561934e089system.dll @ cordbg.exe)

ManagedEventHandler.UnloadModule(e:windowsmicrosoft.net rameworkv1.1.4322mscorlib.dll @ cordbg.exe)
ManagedEventHandler.UnloadAssembly(e:windowsmicrosoft.net rameworkv1.1.4322mscorlib.dll @ cordbg.exe)

ManagedEventHandler.ExitAppDomain(cordbg.exe @ 3636)
ManagedEventHandler.ExitThread(3944 @ cordbg.exe)
ManagedEventHandler.ExitProcess(3636)

可以看到 CLR 首先构造进程和 AppDomain;然后将系统执行所需的 mscorlib.dll 载入;接着将要执行的 Assembly 和缺省 Module 载入;并分析其外部应用(system.dll),载入之;建立一个新的 Managed Thread 执行之;最后卸载相关 Module 和 Assembly,并退出环境。

在打印调试事件信息时值得注意的是很多调试接口都提供了类似的函数从 Unmanaged 环境中获取字符串或整数,如

以下为引用:

interface ICorDebugAppDomain : ICorDebugController
{
HRESULT GetName([in] ULONG32 cchName,
[out] ULONG32 *pcchName,
[out, size_is(cchName),
length_is(*pcchName)] WCHAR szName[]);
};

interface ICorDebugAssembly : IUnknown
{
HRESULT GetName([in] ULONG32 cchName,
[out] ULONG32 *pcchName,
[out, size_is(cchName),
length_is(*pcchName)] WCHAR szName[]);
};

因此在实现上可以将之抽象为一个 delegate,以便共享基于尝试策略的数据获取算法,如

以下为引用:

public class CorObject
{
protected delegate void GetStrFunc(uint cchName, out uint pcchName, IntPtr szName);

protected string GetString(GetStrFunc func, uint bufSize)
{
uint size = bufSize;

IntPtr szName = Marshal.AllocHGlobal((int)size);

func(size, out size, szName);

if(size > bufSize)
{
szName = Marshal.ReAllocHGlobal(szName, new IntPtr(size));

func(size, out size, szName);
}

string name = Marshal.PtrToStringUni(szName, (int)size-1);

Marshal.FreeHGlobal(szName);

return name;
}

protected string GetString(GetStrFunc func)
{
return GetString(func, 256);
}
}

这里使用 Marshal 对 Native 内存的直接操作,避免编写 unsafe 代码。使用的时候可以很简单地使用

以下为引用:

public class CorAssembly : CorObject
{
private ICorDebugAssembly _asm;

public CorAssembly(ICorDebugAssembly asm)
{
_asm = asm;
}

public string Name
{
get
{
return GetString(new GetStrFunc(_asm.GetName));
}
}
}

等到 CLR 2.0 支持泛型编程后,实现将更加方便。 :P

这一小节,从整体上大致分析了 Managed 调试事件的分类和相关功能。具体的使用将在以后的文章中结合实际情况有针对性的介绍。至于 Win32 API 调试事件,介绍的资料就比较多了,这里就不在罗嗦,有兴趣进一步研究的朋友可以参考我以前的一个系列文章。

Win32 调试接口设计与实现浅析 [2] 调试事件

下一节将介绍 CLR 调试接口中断点如何实现和使用。

to be continue...

时间: 2024-11-03 21:59:31

CLR 调试接口的架构与应用 [3] 调试事件的相关文章

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

架构 如 Don Box 在<.NET本质论 第1卷:公共语言运行库>一书的第10章中介绍, CLR 调试框架是一个由 CLR 提供的,面向工具开发商的,支持调试功能的最小功能集.与 JVM 的 JDI (Java Debug Interface)不同,CLR 调试框架不仅仅关注于虚拟机一级的调试,同时也提供了 Native 一级调试的统一接口.使得现有工具开发商能够以最小代价移植并支持 CLR 调试功能.而对 CLR 调试更高层次或更细粒度的支持,则是由前面提到的 Profiling API

Win32调试接口设计与实现浅析

所谓调试器实际上是一个很宽泛的概念,凡是能够以某种形式监控其他程序执行过程的程序,都可以泛称为调试器.在Windows平台上,根据调试器的实现原理大概可以将之分为三类:内核态调试器.用户态调试器和伪代码调试器. 内核态调试器直接工作在操作系统内核一级,在硬件与操作系统之间针对系统核心或驱动进行调试,常见的有SoftICE.WinDbg.WDEB386和i386KD等等:用户态调试器则通过操作系统提供的调试接口,在操作系统和用户态程序之间针对用户态程序进行调试,常见的有各种开发环境如VC/Delp

was集群下基于接口分布式架构和开发经验谈

   某b项目是我首次采用was环境下架构和开发的手机wap应用,尽管做到了该项目的主程,但对此项目的全面构件依然有不清楚的地方,因此在这里我只能简单的谈谈开发中遇到的问题怎么处理和应对办法.          记得第一天接触这个项目时,只记得些案例代码(不知道那些是对的,那些是错的)似曾相识,但不懂如何动手写下第一个helloword,因其中的基于接口开发的ejb的架构以前根本就没接触过.好了,没办法,于是只有硬着头皮去尝试第一个基于接口开发的ejb的第一个查询方法(呵呵最简单了吧).因为一切

对 ASP.NET 应用程序启动调试应如何设置(包括远程调试)

asp.net|程序|asp.net 对 ASP.NET 应用程序启动调试应如何设置(包括远程调试) 请根据你的调试类型与操作系统选择,以下步骤请勿颠倒(不行的话,从头开始设置) PS:因本人撰写本稿时,用的是Win 2003 .Win2000 繁体版 + Visual Studio.Net 2003 英文版,故抓下来的图与说明不大一样,但这些"属性"的位置是不变的 一.本地调试 A. Windows 2000 操作系统 1.打开VS.Net工具选项 → 项目Web设置 → Web服务

编绎调试HotSpot JVM及在Eclipse里调试

编绎整个OpenJDK要很久,而且有很多东西是不需要的.研究HotSpot的话,其实只要下HotSpot部分的代码就可以了. 下面简单记录下编绎调试HotSpot一些步骤. 一.编绎 进入hotsopt的make目录下: cd code/cpp/openjdk/hotspot/make/ 用make help可以看到有很多有用的信息.当然查看Makefile文件,里面也有很多有用的注释. make help会输出当前的一些环境变量的设置,如果不对,自然编绎不过去. 设置环境变量: unset J

怎么在代码里判断当前是调试运行(F5)还是不调试直接运行(Ctrl+F5)呢?

问题描述 怎么在代码里判断当前是调试运行(F5)还是不调试直接运行(Ctrl+F5)呢? 解决方案 解决方案二:一般用System.Diagnostics.Debug.Write来输出调试信息就行了,发布版本不会插入这些语句解决方案三:我需要判断到底是不是调试执行的,有没有这样的方法呢?谢谢了~~~引用1楼jinjazz的回复: 一般用System.Diagnostics.Debug.Write来输出调试信息就行了,发布版本不会插入这些语句 解决方案四:只能判断release和debug把

字符串-C++小程序,调试,在我这个基础上调试

问题描述 C++小程序,调试,在我这个基础上调试 任意输入两个字符串(如"Jiangsu China"和"hello!") 并存放在a.b两个数组中.把较短的字符串放在a数组, 较长的字符串放在b数组,并输出.#include using namespace std; void swap(char a[], char b[], int n) { char ch; char k=0; for (int k = 0; k < n; k++) ch = a[k];

微信公众号开发系列教程一(调试环境部署续:vs远程调试)

原文:微信公众号开发系列教程一(调试环境部署续:vs远程调试) 目录 C#微信公众号开发系列教程一(调试环境部署) C#微信公众号开发系列教程一(调试环境部署续:vs远程调试) C#微信公众号开发系列教程二(新手接入指南)   前几天决定写个微信公众平台开发系列,在发布第一篇博文后,收到了很多园友的反馈和建议,在这里感谢大家的支持,我会坚持写完这个系列,希望能帮助更多的小伙伴.特别要感谢下@ZIP,是他的一个提醒才有了这篇博文.也希望更多的小伙伴能把你的想法反馈给我. 上一篇中主要介绍的是使用花

求改正-求调试,因为是新手不会调试,求各位大神帮忙看看哪里错了

问题描述 求调试,因为是新手不会调试,求各位大神帮忙看看哪里错了 有两个红色的报错,怎么改 报错显示 解决方案 这两个字再字符串里,怎么会报错,而且你的下方提示是 0 errors 解决方案二: 你的where中有条件吗,没有不用写,要不会sql语句出错 解决方案三: 去掉where就可以,或者给where添加查询条件. 解决方案四: 加where什么鬼. 解决方案五: 请把where条件补全