2023年1月

简介

CLRDBG_NOTIFICATION_EXCEPTION_CODE,值为0x0x04242420。此异常在.CLR 4.0的启动路径期间触发,是CLR4.0版本初始化调试服务时向调试器发送消息通知。个人认为这应该只在附加调试器时抛出。

详细说明

这实际上是一个未记录的异常(CLRDBG_NOTIFICATION_exception_CODE),它显然是对4.0clr中托管调试器使用的IPC协议的一个补充。忽略它应该是完全安全的。

此特定异常并不表示发生了错误,而是表示CLR正在尝试向调试器发送消息。最有可能的情况是,您看到的异常对应于运行时启动事件,这是一条消息,指示CLR已初始化到足以让启蒙调试器开始与其交互。如果VS启用了托管调试,它将理解该消息并通知调试人员在将来继续生成这些消息。当然,聪明的调试器不会将它们显示为原始异常,它将解码它们的含义,而是在内部进行更新,例如“加载了新的应用程序域”或“反射发出刚刚在内存中创建了一个新类型”。或者,如果调试器没有托管代码,它将无法识别此异常的任何特殊情况没有特别回应。如果没有响应,CLR假设调试器没有为托管代码提供服务,并且不应该发送任何进一步的消息(异常)。

异常填充结构

ExceptionAddress: 762819b2 (KERNELBASE!RaiseException+0x00000062)//抛出地址
ExceptionCode: 04242420//异常代码
ExceptionFlags: 00000000
NumberParameters: 3//参数个数,一般为3个
   Parameter[0]: 31415927//是个常量,不知什么意思
   Parameter[1]: 721a0000//CLR4.0 clr.dll模块加载基址
   Parameter[2]: 00bef768//struct DebuggerIPCEvent const *调试器IPC事件指针

因为CLR是一个托管环境,所以运行时中有几个组件需要在执行任何代码之前初始化。本文将介绍EE(执行引擎)启动程序,并详细检查初始化过程。68只是一个粗略的指南,它取决于您使用的运行时版本、启用了哪些功能以及其他一些东西。

样例代码

假设你有一个最简单的C#程序,在CLR将“Hello World”输出到控制台之前会发生什么?

usingSystem;namespaceConsoleApplication
{
public classProgram
{
public static void Main(string[] args)
{
Console.WriteLine(
"Hello World!");
}
}
}

WOW64(Windows-On-Windows 64bit)是X64 Windows操作系统的一个子系统,为32位应用程序提供运行环境。类似的还有WOW32子系统,负责在32位Windows系统上运行16位应用程序。

WoW64存在的原因还要从CPU的发展上开始说,X86指令集是一个指令集架构家族,最初在Intel 8086处理器中引入,开始它主要用于16位系统。从Intel 386处理器发布开始升级为32位,并且32位指令集一直保持了很久。了解32位系统的都知道32位CPU将内存空间限制到了4G(单一用户模式进程至少是这样)。随着RAM的越来越大,4G限制就成了瓶颈,系统无法使用更大的内存空间。于是2001年Intel发布了64位的IA64架构,它是一个全新的架构,架构设计非常清晰,比老的X86架构要更好。对于软件来说兼容性很重要,但是IA64处理器无法运行X86代码,这样问题就很严重了,已有的软件无法在新的CPU上运行。于是在2003年AMD发布了AMD64架构,它是对X86架构的增量更新,用于添加64位支持。这种架构的X64处理器可以执行X86代码,所以用户可以在X64处理器上运行现有的程序和操作系统。直到今天,X86和X64依旧是个人计算机和笔记本电脑使用CPU的主流。

如下为X64和X86指令,与X86指令相对应,X64指令需要增加额外的前缀字节(REX Prefix)表示使用64位寄存器。由于指针等数据大小翻倍,所以结构体中指针偏移大小也可能会增加。

1
2
3
4
5
// X86指令
0x8B, 0x52, 0x0C, // mov edx, dword ptr [edx+0Ch]

// X64指令
0x48, 0x8B, 0x52, 0x18, // mov rdx, qword ptr [rdx+18h]

一些指令在X86和X64上编码一致,比如短跳转。区分两种指令比较通用的方法是看指令是否携带了REX前缀字节(REX前缀字节用于表示使用64位寄存器或使用64位操作数)。REX前缀字节会覆盖一部分现存X86指令,因此在执行一块代码时需要告知X64处理器按照X86还是X64来解析指令。到底是怎么告知CPU要将代码按照X86解析还是按照X64解析呢?下面看Intel的白皮书给出的关于IA-32e如何区分兼容模式和64位模式。


图1.64位模式中的代码段描述符

简介

EXCEPTION_HIJACK,值为0xe0434f4e。意思是CLR线程劫持异常。异常劫持是CLR在挂起线程进行垃圾收集的过程中抛出的。它的抛出是为了帮助停止后恢复执行。它定义在..\clr\src\inc\corexcep.h头文件里,如下:

#define EXCEPTION_HIJACK  0xe0434f4e    // 0xe0000000 | 'COM'+1

详细说明

在现实开发中,经常会出现多个线程同时访问托管堆的情况,或至少会有多个线程同时操作堆中的对象。一个线程引发垃圾回收时,其它线程绝对不能访问任何线程,因为垃圾回收器可能移动这些对象,更改它们的内存位置。CLR想要进行垃圾回收时,会立即挂起执行托管代码中的所有线程,正在执行非托管代码的线程不会挂起。然后,CLR检查每个线程的指令指针,判断线程指向到哪里。接着,指令指针与JIT生成的表进行比较,判断线程正在执行什么代码。

如果线程的指令指针恰好在一个表中标记好的偏移位置,就说明该线程抵达了一个安全点。线程可在安全点安全地挂起,直至垃圾回收结束。如果线程指令指针不在表中标记的偏移位置,则表明该线程不在安全点,CLR也就不会开始垃圾回收。在这种情况下,CLR就会劫持该线程。也就是说,CLR会修改该线程栈,使该线程指向一个CLR内部的一个特殊函数。然后,线程恢复执行。当前的方法执行完后,他就会执行这个特殊函数,这个特殊函数会将该线程安全地挂起。然而,线程有时长时间执行当前所在方法。所以,当线程恢复执行后,大约有250毫秒的时间尝试劫持线程。过了这个时间,CLR会再次挂起线程,并检查该线程的指令指针。如果线程已抵达一个安全点,垃圾回收就可以开始了。但是,如果线程还没有抵达一个安全点,CLR就检查是否调用了另一个方法。如果是,CLR再一次修改线程栈,以便从最近执行的一个方法返回之后劫持线程。然后,CLR恢复线程,进行下一次劫持尝试。所有线程都抵达安全点或被劫持之后,垃圾回收才能使用。垃圾回收完之后,所有线程都会恢复,应用程序继续运行,被劫持的线程返回最初调用它们的方法。

异常结构填充

ExceptionAddress: 762819b2 (KERNELBASE!RaiseException+0x00000062)
ExceptionCode: e0434f4e//异常代码
ExceptionFlags: 00000000
NumberParameters: 0//没有附加参数信息