分享
 
 
 

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

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

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

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
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- 王朝網路 版權所有