分享
 
 
 

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

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

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

在上一节中简单介绍了 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...

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有