- 浏览: 3052964 次
- 性别:
- 来自: 海外
文章分类
- 全部博客 (430)
- Programming Languages (23)
- Compiler (20)
- Virtual Machine (57)
- Garbage Collection (4)
- HotSpot VM (26)
- Mono (2)
- SSCLI Rotor (1)
- Harmony (0)
- DLR (19)
- Ruby (28)
- C# (38)
- F# (3)
- Haskell (0)
- Scheme (1)
- Regular Expression (5)
- Python (4)
- ECMAScript (2)
- JavaScript (18)
- ActionScript (7)
- Squirrel (2)
- C (6)
- C++ (10)
- D (2)
- .NET (13)
- Java (86)
- Scala (1)
- Groovy (3)
- Optimization (6)
- Data Structure and Algorithm (3)
- Books (4)
- WPF (1)
- Game Engines (7)
- 吉里吉里 (12)
- UML (1)
- Reverse Engineering (11)
- NSIS (4)
- Utilities (3)
- Design Patterns (1)
- Visual Studio (9)
- Windows 7 (3)
- x86 Assembler (1)
- Android (2)
- School Assignment / Test (6)
- Anti-virus (1)
- REST (1)
- Profiling (1)
- misc (39)
- NetOA (12)
- rant (6)
- anime (5)
- Links (12)
- CLR (7)
- GC (1)
- OpenJDK (2)
- JVM (4)
- KVM (0)
- Rhino (1)
- LINQ (2)
- JScript (0)
- Nashorn (0)
- Dalvik (1)
- DTrace (0)
- LLVM (0)
- MSIL (0)
最新评论
-
mldxs:
虽然很多还是看不懂,写的很好!
虚拟机随谈(一):解释器,树遍历解释器,基于栈与基于寄存器,大杂烩 -
HanyuKing:
Java的多维数组 -
funnyone:
Java 8的default method与method resolution -
ljs_nogard:
Xamarin workbook - .Net Core 中不 ...
LINQ的恶搞…… -
txm119161336:
allocatestlye1 顺序为 // Fields o ...
最近做的两次Java/JVM分享的概要
(Disclaimer:如果需要转载请先与我联系。
作者:RednaxelaFX -> rednaxelafx.iteye.com)
系列文章:
要让CLR挂掉的话……
要让CLR挂掉的话(第二弹)……
同前一篇的后半段一样,以下内容都是以PC上的32位x86的CLR、版本2.0.50727.3082,以及Windows XP SP3为前提的讨论。
前一篇提到了通过改变委托中的指针来改变实际的调用目标。修改委托实例中的_target、_methodPtr、_methodPtrAux这三个成员,都能够改变跳转目标;特别是后两个,它们的类型是IntPtr,可以构造出任意数值的指针设置进去,那样就可以跳转到任意目标了。
但只能指定目标地址,却不能随意控制目标里的代码,显然还不够好玩。如果要跳转的目标是托管方法,那构造一个正常的委托就够了。如果能在不使用P/Invoke也不使用unsafe code的条件下在C#程序里执行一小块自定义的native code就好玩多了。先前的两篇日志(这里和这里)我提到在内存里生成native code并执行并不是件难事。那么在CLR上的C#也能做到么?
===========================================================================
要玩怎样的代码?
如果能执行任意native code的话,不得不说可玩的东西就多了。例如说把整个调用栈给乱搅一通、把SEH链全都破坏掉;或者……嗯还是别想那么可怕的玩法了,我还不想把自己的系统弄垮。等什么时候我再装个用了就扔的虚拟机镜像再试可怕的玩法……
还是跟前面一样,写段类似HelloWorld的代码,往标准输出流写句话,表明“可以做”就算了。想用System.Console.WriteLine()会有点不爽,因为它要接收CLR对象(的引用)为参数,而我不想费事去在自己的native code里去找出System.String的type token、调用CORINFO_HELP_NEWSFAST创建新实例、调用构造器之类的一大堆麻烦事。我就想把一个C风格的字符串输出而已。那么,就不用.NET标准库里的方法了,干脆直接用Win32 API,简单省事,WriteConsole()函数正好够用。调用Win32函数时也懒得通过P/Invoke,而是在native code里直接call过去。
要用Win32 API,首先得确保需要的DLL已经被加载到当前进程中。CLR为了自身的正常运行,本来就需要加载很多模块。可以看看一个HelloWorld式的托管程序都加载些什么模块进来。
除了这个exe本身之外,可以看到加载进来的模块有:
光是一个HelloWorld就加载了这么多DLL,可以用的函数那就多了 XD
要调用的WriteConsoleA()函数位于Kernel32.dll中,在列表里可以找到,没问题。其实就没什么Win32程序是不加载Kernel32.dll的吧 =v=
怎么获取Win32 API的函数地址呢?如果是在C里,那不用管函数地址,引入windows.h或相关头文件后正常用那些函数就行。要不然LoadLibrary()得到模块句柄然后GetProcAddress()得到函数地址也行。前者在C#里固然是不行(即使用P/Invoke然后用个委托指向包装函数,得到的地址也是stub的地址而不是底下实际目标函数的地址);后者有自举困难——LoadLibrary()的地址从哪儿来?
所以干脆ad-hoc点,既然是玩嘛就先别那么麻烦,先弄出能演示的版本再说。用别的办法找到需要用的函数的地址,然后硬编码到我们生成的native code里就算了。因为DLL有默认的加载地址,只要一个进程里加载的DLL没有地址冲突,它们所在的位置就是可预测的,其中函数的位置也是可预测的。当然,我是在XP上玩,在Vista之后有ASLR,地址就不好预测了……但如果按照下文的方法保存生成的native code的话,程序在遇到ASLR前先就被DEP干掉了。
OK,废话那么多,我想达到的效果要是用C直接写会是怎样呢?
很简单对吧?基本对应的机器码和汇编,后面还会用到:
我准备把要输出的字符串紧接在代码后面。注意这里用了之前介绍过的一个技巧,通过call指令来获取当前IP(指令指针)的值,并由此计算出要输出的字符串的地址。其实不用这个技巧也可以的,毕竟我已经知道代码的起始地址了。不过玩嘛,呵呵~
我用call r32指令而不是call imm32指令来调用那两个Win32函数,是因为前者的r32里装的是虚拟内存的绝对地址,而后者的imm32里装的是相对下一条指令的偏移量;我懒得在生成代码的时候去计算偏移量所以用前者了。
另外还要注意,Win32 API的calling convention是WINAPI,也就是__stdcall,是由被调用方来清理栈的。
其实要让CLR无声无息的就停掉,在native code里调用TerminateProcess()应该也行。这次的例子还是专于HelloWorld好了 =v=
===========================================================================
如何存放生成的native code?
之前用C来演示运行时操纵代码的时候,是把native code“生成”到malloc出来的一块堆空间上的。在C#里我要怎么找到可写可执行的一块放代码的空间呢?
在前一篇的后半段中我介绍了改变委托的_target成员也可以改变最终被调用的目标。留意到_target的类型是System.Object,也就是说任何对象都可以放在里面。如果构造一个委托实例时,它是指向静态方法的,那么它的_methodPtr成员就会指向那个特定的stub:(stub会根据委托类型的参数个数不同而不同,下面是接收一个参数的委托的情况)
这个stub完全不理会_target到底是什么类型的,直接从偏移量0x10的地方取出一个DWORD,然后就间接跳转过去了。正常情况下_target指向委托自身,那么在偏移量0x10的地方就是_methodPtrAux成员,整个逻辑就是对的。那要是狸猫换太子,放点什么别的东西进去当作_target呢?
C#里,引用类型的默认内存布局是LayoutKind.Auto,值类型的默认内存布局是LayoutKind.Sequential,而我们现在需要的是在一个确定的偏移量保持跳转目标的地址。给类型指定LayoutKind.Explicit可以达到目的,不过其实有更简单的办法,连特殊类型都不需要声明——直接用数组就行了嘛。数组里的元素肯定是按顺序保存的。
CLR里,一个最简单不过的int[]在内存中的布局如下:(括号中数字表示距离数组起始地址的偏移量)
而一个委托实例的开头部分在内存中的布局是:
那么只要用一个int[](或者uint[]),在下标为2的地方放一个数字,然后把该数组设为某个委托的_target,那就……嘿嘿。
在Windows XP上,DEP还没有对所有程序默认开启,所以基本上在堆上申请到的空间都是可写可执行的。CLR里的托管数组都在堆上分配空间,可以把native code“生成”到数组里保存着。不过Vista和Windows 7上DEP默认对所有程序都启用,而不通过VirtualAlloc()或者VirtualProtect()就没办法申请到可写可执行的空间,所以下面的办法在这些新的系统上运行会看到AccessViolationException。
结合我们需要在特定偏移量保存伪装的_methodPtrAux的需要,我们需要构造的int[]或者uint[]数组应该像这样:
其中,在下标为2的地方放置“假的_methodPtrAux”;该值应该等于下标为3的地址,好让委托调用到“生成”的native code。
于是又有问题了:我们该如何得到数组的地址?
===========================================================================
如何获得对象的地址?
可能会有人想到用对象的hashcode来找出对象的地址。让我们笼统的分析一下其合理性。
Java的java.lang.Object和.NET的System.Object都支持获取对象的hashcode。如果一个对象在“活着”的时候不会被移动,则其起始地址不会发生改变;对象间不应该有交叠,所以用对象地址直接作为hashcode是一种很直观的实现。事实上Android里的Dalvik虚拟机就是这样实现Object.hashCode()的,详细可查看Dalvik源码的vm/native/java_lang_Object.c中的Dalvik_java_lang_Object_hashCode()和InternalNative.c中的dvmGetObjectHashCode()。
注意到Dalvik的GC是典型的标记-清除(mark-and-sweep)式,不会移动堆中的对象。
也可以留意一下Apache Harmony里的其中一种hashcode计算方式,第16页:Design a Product-Ready JVM for Apache Harmony
也有一些JVM的实现会选择以对象地址为源通过位移、异或等运算来计算hashcode,这种情况下要从hashcode反推回来原本的地址就有点困难了。
Mono在使用不移动对象的GC时,采用的hashcode算法来自Thomas Wang,Address Based Hash Function
其算法实现是:
那要是GC会移动对象呢,例如说采用了拷贝式收集器或者压缩式收集器的话?一个办法是可以拿对象第一次被分配的地址为hashcode的源,另一个办法是干脆无视对象的地址,用别的办法来得到能够区分对象身份的值;还有些别的办法是上面两种的混合。Xiao-Feng在Object hashcode implementation一帖中描述了Apache Harmony实现hashcode的三种方案,可以参考阅读一下。
CLR中System.Object.GetHashCode()并不返回对象的地址,所以很可惜不能从这里挖出点指针来玩玩。
---------------------------------------------------------------------------
然后可能会有人想到像C那样用union来骗过类型系统。
在C里,要直接把float类型的值的底层表示“看作”int,光靠类型转换是不行的,因为编译器会在转换的地方插入类似inttofloat()的函数,把底层表示从单精度浮点数格式改变为二的补码整数格式。以前专门的浮点数运算器不普及时,程序员经常自己用整数运算去模拟浮点数运算,需要在不改变底层表示的前提下操作。怎么办呢?像上面那样用union就可以做到。通过union,任意等宽度的类型间都可以做不改变底层表示的转换,跟后来C++的reinterpret_cast作用一样。
CLR里的引用用指针的形式来实现,而指针以直接记录地址的形式来实现。(注意:引用、指针和地址是三个在不同抽象层次上的相关概念,不应该把它们看作同一抽象层次上的概念。)
虽然C#中没有reinterpret_cast,但有模拟C的union的结构:显式指定内存布局的struct。如果可以在C#里实现一个union,把IntPtr值和Object引用保存在同一位置,不就可以提取到对象的地址了吗?于是:
很可惜CLR已经预料到这种玩法,不允许值类型与引用类型的域交叠。上面的代码可以通过编译,但在加载Reinterpreter类型时会出错:
此路不通 =x=|||
---------------------------------------------------------------------------
其实.NET标准库里有System.Runtime.InteropServices.GCHandle这么个值类型。它的作用主要体现在托管代码与native code的互操作中,以避免需要用到的托管对象在native code执行过程中意外被GC回收。GCHandle也可以把对象“定”(pin)住,以避免native code访问对象的过程中对象地址发生改变。
GCHandle有个AddrOfPinnedObject()方法,正好可以提供对象的地址;使用GCHandle还附带了保证对象不被回收的功能,对这帖想要玩的代码是正合适。
创建GCHandle时用到的Alloc()方法要求调用它的程序集有SecurityPermissionFlag.UnmanagedCode权限。不过即便用了它,我也没有在代码中显式使用unsafe code,满足我的要求。
要用GCHandle定住一个对象的话,创建GCHandle时会对一些类型的对象给予特别待遇,包括:
1、System.String
2、元素为原始类型的数组
3、元素为值类型且Blittable的数组
4、其它Blittable类型
任何不在上述范围内的对象obj,传给GCHandle.Alloc(obj, GCHandleType.Pinned)的话,会引发异常:
换成一个字符串就没问题:
GCHandle.Alloc()文档中对此有提及。我差点看漏了以为文档没说……
有趣的是,GCHandle对类型的挑剔还不止于此。GCHandle.AddrOfPinnedObject()方法乍一看像是返回对象自身的起始地址,实际不然,返回的是对象的“数据区”的起始地址。对System.String来说,“数据区”就是实际存放字符的char数组(CLR的String实现把char数组融合到String里了,没有单独的“char数组成员”。忽略ObjHeader,跳过MethodTable指针和其它成员,例如m_arrayLength、m_stringLength,跳到m_firstChar也就是融合后char数组的开始);而对数组来说,“数据区”就是实际存放值的区域(忽略ObjHeader,跳过MethodTable指针和Length);对其它Blittable类型来说,“数据区”就是Object里MethodTable指针之后的部分。
===========================================================================
完事俱备,只欠开工写代码来实现前面讨论的内容。
先上代码:
执行结果是:(再次提醒:在Vista或者Windows 7上运行会遇到AccessViolationException)
Good!成功的让CLR执行了一段我们指定的native code,在标准输出流上显示了"Greetings from generated code!\n",而且没有显式使用P/Invoke或者unsafe code。为了演示从native code返回后CLR仍在正常运行,所以通过Console.WriteLine()再输出了一行"Greetings from managed code!"。
前文基本上已经把代码的原理解释得差不多了(吧?),所以这边就不再详细解释。
fakeDelegate数组里的native code/string那段可能不太直观,其实那就是前面给出的x86机器码以及"Greetings from generated code!\n"。需要注意的是因为x86的字节序(endian)是little-endian,低位字节在前;而本例中用的数组元素类型是uint,是4字节的整型,所以以4字节为单位,其中的顺序是“反”的。
就以第一个数字0x6AEC8B55为例,它在内存中是0x55 0x8B 0xEC 0x6A这4个字节,其中头3个字节就是这两条指令:
这样应该就好理解了吧?
===========================================================================
回顾标题,“要让CLR挂掉的话”,上面的例子都还没让CLR挂掉,似乎有点不够意思。其实真要让CLR连最后的防护措施都挂掉、连异常都抓不到,那还挺难的。但我们可以很轻松的做出些例子,观察一下平时难得一见的异常。
(在家实验的同学们千万注意了:要自行尝试引发错误的话,一定要小心,不要在有重要资料的系统上试。任意篡改代码或者栈上/堆上的数据,实际会引发什么后果很难预料。发生什么糟糕后果责任要自己承担的哦~)
如果构造这样的一段代码:
把它放到上面的C#例子里:
执行,我们会看到什么呢?
把构造的代码改为
(也就是native code那段的数字是0xC318C483)
执行结果是:
stack trace是:
EIP停下的位置是:
这个地址是Windows上很典型的栈地址。EIP居然跑到这个地方来,把数据当成指令执行了一句“HLT”指令……
HLT是一个Ring 0指令,用户模式的应用程序没办法是用这条指令,所以试图执行它引发了错误。
SEHException这种泛泛的异常少见吧?成功的搞出了一个没有映射到有具体含义的CLR异常,呵呵 ^ ^
P.S. 这帖告诉我们,类型安全的真面目就是:一旦你开始玩裸指针、玩union、玩goto,啥类型安全都是浮云……
P.P.S. 查了文档,Silverlight 3里的GCHandle.Alloc和GCHandle.AddrOfPinnedObject果然是SecurityCritical的,从用户代码里无法调用它。然而Type.GetMethod和Type.GetField之类还是Transparent的,还是有搞头——至少在Moonlight上 XD
P.P.P.S. 呜,但是FieldInfo.SetValue只能用来设置可访问的域的值……又没搞头了
嗯,没错。而且即便出来严重错误,在多数时候也突破不了最后防线——异常处理和log/core dump……
下次得找个JVM来试试看了 =v=
夸张了吧 一个进程再怎么乱飞指针 也不会把操作系统都弄死吧
弄死就罢了,如果凑巧在执行一个ret前,栈顶的值是删除文件的函数地址、格式化分区的函数地址之类,再凑巧栈顶下面的数据指向C盘啊什么的……不确定的事情很难保证安全,至少我是不敢乱试 =v=|||
夸张了吧 一个进程再怎么乱飞指针 也不会把操作系统都弄死吧
作者:RednaxelaFX -> rednaxelafx.iteye.com)
系列文章:
要让CLR挂掉的话……
要让CLR挂掉的话(第二弹)……
同前一篇的后半段一样,以下内容都是以PC上的32位x86的CLR、版本2.0.50727.3082,以及Windows XP SP3为前提的讨论。
前一篇提到了通过改变委托中的指针来改变实际的调用目标。修改委托实例中的_target、_methodPtr、_methodPtrAux这三个成员,都能够改变跳转目标;特别是后两个,它们的类型是IntPtr,可以构造出任意数值的指针设置进去,那样就可以跳转到任意目标了。
但只能指定目标地址,却不能随意控制目标里的代码,显然还不够好玩。如果要跳转的目标是托管方法,那构造一个正常的委托就够了。如果能在不使用P/Invoke也不使用unsafe code的条件下在C#程序里执行一小块自定义的native code就好玩多了。先前的两篇日志(这里和这里)我提到在内存里生成native code并执行并不是件难事。那么在CLR上的C#也能做到么?
===========================================================================
要玩怎样的代码?
如果能执行任意native code的话,不得不说可玩的东西就多了。例如说把整个调用栈给乱搅一通、把SEH链全都破坏掉;或者……嗯还是别想那么可怕的玩法了,我还不想把自己的系统弄垮。等什么时候我再装个用了就扔的虚拟机镜像再试可怕的玩法……
还是跟前面一样,写段类似HelloWorld的代码,往标准输出流写句话,表明“可以做”就算了。想用System.Console.WriteLine()会有点不爽,因为它要接收CLR对象(的引用)为参数,而我不想费事去在自己的native code里去找出System.String的type token、调用CORINFO_HELP_NEWSFAST创建新实例、调用构造器之类的一大堆麻烦事。我就想把一个C风格的字符串输出而已。那么,就不用.NET标准库里的方法了,干脆直接用Win32 API,简单省事,WriteConsole()函数正好够用。调用Win32函数时也懒得通过P/Invoke,而是在native code里直接call过去。
要用Win32 API,首先得确保需要的DLL已经被加载到当前进程中。CLR为了自身的正常运行,本来就需要加载很多模块。可以看看一个HelloWorld式的托管程序都加载些什么模块进来。
using System; static class Program { static void Main(string[] args) { // block the program so that we could easily attach a debugger var name = Console.ReadLine(); Console.WriteLine("Greet me, {0}", name); } }
除了这个exe本身之外,可以看到加载进来的模块有:
引用
ADVAPI32
COMCTL32
COMCTL_1
GDI32
IMM32
KERNEL32
LPK
MSCOREE
MSCORJIT
MSCORLIB
MSCORWKS
MSVCR80
MSVCRT
OLE32
RPCRT4
SECUR32
SHELL32
SHLWAPI32
USER32
USP10
COMCTL32
COMCTL_1
GDI32
IMM32
KERNEL32
LPK
MSCOREE
MSCORJIT
MSCORLIB
MSCORWKS
MSVCR80
MSVCRT
OLE32
RPCRT4
SECUR32
SHELL32
SHLWAPI32
USER32
USP10
光是一个HelloWorld就加载了这么多DLL,可以用的函数那就多了 XD
要调用的WriteConsoleA()函数位于Kernel32.dll中,在列表里可以找到,没问题。其实就没什么Win32程序是不加载Kernel32.dll的吧 =v=
怎么获取Win32 API的函数地址呢?如果是在C里,那不用管函数地址,引入windows.h或相关头文件后正常用那些函数就行。要不然LoadLibrary()得到模块句柄然后GetProcAddress()得到函数地址也行。前者在C#里固然是不行(即使用P/Invoke然后用个委托指向包装函数,得到的地址也是stub的地址而不是底下实际目标函数的地址);后者有自举困难——LoadLibrary()的地址从哪儿来?
所以干脆ad-hoc点,既然是玩嘛就先别那么麻烦,先弄出能演示的版本再说。用别的办法找到需要用的函数的地址,然后硬编码到我们生成的native code里就算了。因为DLL有默认的加载地址,只要一个进程里加载的DLL没有地址冲突,它们所在的位置就是可预测的,其中函数的位置也是可预测的。当然,我是在XP上玩,在Vista之后有ASLR,地址就不好预测了……但如果按照下文的方法保存生成的native code的话,程序在遇到ASLR前先就被DEP干掉了。
OK,废话那么多,我想达到的效果要是用C直接写会是怎样呢?
#include <windows.h> int main() { HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); WriteConsole(hStdout, "Greetings from generated code!\n", 31, NULL, NULL); return 0; }
很简单对吧?基本对应的机器码和汇编,后面还会用到:
55 push ebp 8BEC mov ebp,esp 6A F5 push -0B ; /DevType = STD_OUTPUT_HANDLE B8 D92F817C mov eax,KERNEL32.GetStdHandle ; | FFD0 call eax ; \GetStdHandle 6A 00 push 0 ; /pReserved = NULL 6A 00 push 0 ; |pWritten = NULL 6A 1F push 1F ; |CharsToWrite = 1F (31.) E8 00000000 call <&next_instruction> ; | 830424 10 add dword ptr ss:[esp],10 ; |Buffer 50 push eax ; |hConsole BA 5DCC817C mov edx,KERNEL32.WriteConsoleA ; | FFD2 call edx ; \WriteConsoleA 8BE5 mov esp,ebp 5D pop ebp C3 ret
我准备把要输出的字符串紧接在代码后面。注意这里用了之前介绍过的一个技巧,通过call指令来获取当前IP(指令指针)的值,并由此计算出要输出的字符串的地址。其实不用这个技巧也可以的,毕竟我已经知道代码的起始地址了。不过玩嘛,呵呵~
我用call r32指令而不是call imm32指令来调用那两个Win32函数,是因为前者的r32里装的是虚拟内存的绝对地址,而后者的imm32里装的是相对下一条指令的偏移量;我懒得在生成代码的时候去计算偏移量所以用前者了。
另外还要注意,Win32 API的calling convention是WINAPI,也就是__stdcall,是由被调用方来清理栈的。
其实要让CLR无声无息的就停掉,在native code里调用TerminateProcess()应该也行。这次的例子还是专于HelloWorld好了 =v=
===========================================================================
如何存放生成的native code?
之前用C来演示运行时操纵代码的时候,是把native code“生成”到malloc出来的一块堆空间上的。在C#里我要怎么找到可写可执行的一块放代码的空间呢?
在前一篇的后半段中我介绍了改变委托的_target成员也可以改变最终被调用的目标。留意到_target的类型是System.Object,也就是说任何对象都可以放在里面。如果构造一个委托实例时,它是指向静态方法的,那么它的_methodPtr成员就会指向那个特定的stub:(stub会根据委托类型的参数个数不同而不同,下面是接收一个参数的委托的情况)
mov eax,ecx ; 把第一参数(_target)复制到EAX mov ecx,edx ; 把原本的第二参数变为第一参数 add eax,10h ; 把_target._methodPtrAux的地址设到EAX jmp dword ptr [eax] ; 间接调用EAX,也就是调用_target._methodPtrAux
这个stub完全不理会_target到底是什么类型的,直接从偏移量0x10的地方取出一个DWORD,然后就间接跳转过去了。正常情况下_target指向委托自身,那么在偏移量0x10的地方就是_methodPtrAux成员,整个逻辑就是对的。那要是狸猫换太子,放点什么别的东西进去当作_target呢?
C#里,引用类型的默认内存布局是LayoutKind.Auto,值类型的默认内存布局是LayoutKind.Sequential,而我们现在需要的是在一个确定的偏移量保持跳转目标的地址。给类型指定LayoutKind.Explicit可以达到目的,不过其实有更简单的办法,连特殊类型都不需要声明——直接用数组就行了嘛。数组里的元素肯定是按顺序保存的。
CLR里,一个最简单不过的int[]在内存中的布局如下:(括号中数字表示距离数组起始地址的偏移量)
----------------------- | SyncBlk索引 | (-4) ----------------------- | 指向MethodTable的指针 | (+0) ----------------------- | 数组长度 Length | (+4) ----------------------- | 下标为0的元素 | (+8+4*0) ----------------------- | 下标为1的元素 | (+8+4*1) ----------------------- | ... | ----------------------- | 下标为n的元素 | (+8+4*n) ----------------------- | ... | -----------------------
而一个委托实例的开头部分在内存中的布局是:
----------------------- | SyncBlk索引 | (-4) ----------------------- | 指向MethodTable的指针 | (+0) ----------------------- | _target | (+4) ----------------------- | _methodBase | (+8) ----------------------- | _methodPtr | (+12) ----------------------- | _methodPtrAux | (+16) ----------------------- | _invocationList | (+20) ----------------------- | _invocationCount | (+24) -----------------------
那么只要用一个int[](或者uint[]),在下标为2的地方放一个数字,然后把该数组设为某个委托的_target,那就……嘿嘿。
在Windows XP上,DEP还没有对所有程序默认开启,所以基本上在堆上申请到的空间都是可写可执行的。CLR里的托管数组都在堆上分配空间,可以把native code“生成”到数组里保存着。不过Vista和Windows 7上DEP默认对所有程序都启用,而不通过VirtualAlloc()或者VirtualProtect()就没办法申请到可写可执行的空间,所以下面的办法在这些新的系统上运行会看到AccessViolationException。
结合我们需要在特定偏移量保存伪装的_methodPtrAux的需要,我们需要构造的int[]或者uint[]数组应该像这样:
----------------------- | SyncBlk索引 | (-4) ----------------------- | 指向MethodTable的指针 | (+0) ----------------------- | 数组长度 Length | (+4) ----------------------- | 0 | (+8+4*0==8,下标为0) ----------------------- | 0 | (+8+4*1==12,下标为1) ----------------------- | 假的_methodPtrAux | (+8+4*2==16,下标为2) ----------------------- | 生成的native code | (+8+4*3==20 ...) | ... | -----------------------
其中,在下标为2的地方放置“假的_methodPtrAux”;该值应该等于下标为3的地址,好让委托调用到“生成”的native code。
于是又有问题了:我们该如何得到数组的地址?
===========================================================================
如何获得对象的地址?
可能会有人想到用对象的hashcode来找出对象的地址。让我们笼统的分析一下其合理性。
Java的java.lang.Object和.NET的System.Object都支持获取对象的hashcode。如果一个对象在“活着”的时候不会被移动,则其起始地址不会发生改变;对象间不应该有交叠,所以用对象地址直接作为hashcode是一种很直观的实现。事实上Android里的Dalvik虚拟机就是这样实现Object.hashCode()的,详细可查看Dalvik源码的vm/native/java_lang_Object.c中的Dalvik_java_lang_Object_hashCode()和InternalNative.c中的dvmGetObjectHashCode()。
/* * Return the hash code for the specified object. */ u4 dvmGetObjectHashCode(Object* obj) { return (u4) obj; }
注意到Dalvik的GC是典型的标记-清除(mark-and-sweep)式,不会移动堆中的对象。
也可以留意一下Apache Harmony里的其中一种hashcode计算方式,第16页:Design a Product-Ready JVM for Apache Harmony
也有一些JVM的实现会选择以对象地址为源通过位移、异或等运算来计算hashcode,这种情况下要从hashcode反推回来原本的地址就有点困难了。
Mono在使用不移动对象的GC时,采用的hashcode算法来自Thomas Wang,Address Based Hash Function
其算法实现是:
uint32 address_hash(char* addr) { register uint32 key; key = (uint32) addr; return (key >> 3) * 2654435761; }
那要是GC会移动对象呢,例如说采用了拷贝式收集器或者压缩式收集器的话?一个办法是可以拿对象第一次被分配的地址为hashcode的源,另一个办法是干脆无视对象的地址,用别的办法来得到能够区分对象身份的值;还有些别的办法是上面两种的混合。Xiao-Feng在Object hashcode implementation一帖中描述了Apache Harmony实现hashcode的三种方案,可以参考阅读一下。
CLR中System.Object.GetHashCode()并不返回对象的地址,所以很可惜不能从这里挖出点指针来玩玩。
---------------------------------------------------------------------------
然后可能会有人想到像C那样用union来骗过类型系统。
#include <stdio.h> typedef union tagMyUnion { int i; float f; } MyUnion; int main() { MyUnion u; int i; u.f = 2.0f; // 0x40000000 i = u.i; // 0x40000000 u.i = i + (1 << 23); // 0x40800000 printf("%f\n", u.f); // 4.000000 printf("%d, 0x%08x\n", i, i); // 1073741824, 0x40000000 printf("%d, 0x%08x\n", (int)u.f, (int)u.f); // 4, 0x00000004 return 0; }
在C里,要直接把float类型的值的底层表示“看作”int,光靠类型转换是不行的,因为编译器会在转换的地方插入类似inttofloat()的函数,把底层表示从单精度浮点数格式改变为二的补码整数格式。以前专门的浮点数运算器不普及时,程序员经常自己用整数运算去模拟浮点数运算,需要在不改变底层表示的前提下操作。怎么办呢?像上面那样用union就可以做到。通过union,任意等宽度的类型间都可以做不改变底层表示的转换,跟后来C++的reinterpret_cast作用一样。
CLR里的引用用指针的形式来实现,而指针以直接记录地址的形式来实现。(注意:引用、指针和地址是三个在不同抽象层次上的相关概念,不应该把它们看作同一抽象层次上的概念。)
虽然C#中没有reinterpret_cast,但有模拟C的union的结构:显式指定内存布局的struct。如果可以在C#里实现一个union,把IntPtr值和Object引用保存在同一位置,不就可以提取到对象的地址了吗?于是:
using System; using System.Runtime.InteropServices; namespace TestCLR2Crash { [StructLayout( LayoutKind.Explicit )] struct Reinterpreter { [FieldOffset( 0 )] IntPtr _pointer; [FieldOffset( 0 )] object _target; public IntPtr Cast( object obj ) { _target = obj; return _pointer; } } static class Program { static void Main( string[ ] args ) { var reinterpreter = new Reinterpreter( ); var arr = new[ ] { 1, 2, 3 }; var ptr = reinterpreter.Cast( arr ); Console.WriteLine( ptr.ToString( "X" ) ); } } }
很可惜CLR已经预料到这种玩法,不允许值类型与引用类型的域交叠。上面的代码可以通过编译,但在加载Reinterpreter类型时会出错:
引用
Unhandled Exception: System.TypeLoadException: Could not load type 'TestCLR2Crash.Reinterpreter' from assembly 'TestCLR2Crash, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' because it contains an object field at offset 0 that is incorrectly aligned or overlapped by a non-object field.
at TestCLR2Crash.Program.Main(String[] args)
at TestCLR2Crash.Program.Main(String[] args)
此路不通 =x=|||
---------------------------------------------------------------------------
其实.NET标准库里有System.Runtime.InteropServices.GCHandle这么个值类型。它的作用主要体现在托管代码与native code的互操作中,以避免需要用到的托管对象在native code执行过程中意外被GC回收。GCHandle也可以把对象“定”(pin)住,以避免native code访问对象的过程中对象地址发生改变。
GCHandle有个AddrOfPinnedObject()方法,正好可以提供对象的地址;使用GCHandle还附带了保证对象不被回收的功能,对这帖想要玩的代码是正合适。
创建GCHandle时用到的Alloc()方法要求调用它的程序集有SecurityPermissionFlag.UnmanagedCode权限。不过即便用了它,我也没有在代码中显式使用unsafe code,满足我的要求。
要用GCHandle定住一个对象的话,创建GCHandle时会对一些类型的对象给予特别待遇,包括:
1、System.String
2、元素为原始类型的数组
3、元素为值类型且Blittable的数组
4、其它Blittable类型
任何不在上述范围内的对象obj,传给GCHandle.Alloc(obj, GCHandleType.Pinned)的话,会引发异常:
using System; using System.Runtime.InteropServices; namespace TestCLR2Crash { static class Program { static void Main( string[ ] args ) { var o = new object( ); var handle = GCHandle.Alloc( o, GCHandleType.Pinned ); var addr = handle.AddrOfPinnedObject( ); handle.Free( ); } } }
引用
Unhandled Exception: System.ArgumentException: Object contains non-primitive or
non-blittable data.
at System.Runtime.InteropServices.GCHandle.InternalAlloc(Object value, GCHandleType type)
at TestCLR2Crash.Program.Main(String[] args) in F:\document\Visual Studio 2008\Projects\TestDev9\TestCLR2Crash\Program.cs:line 8
non-blittable data.
at System.Runtime.InteropServices.GCHandle.InternalAlloc(Object value, GCHandleType type)
at TestCLR2Crash.Program.Main(String[] args) in F:\document\Visual Studio 2008\Projects\TestDev9\TestCLR2Crash\Program.cs:line 8
换成一个字符串就没问题:
using System; using System.Runtime.InteropServices; namespace TestCLR2Crash { static class Program { static void Main( string[ ] args ) { var str = "check me"; var handle = GCHandle.Alloc( str, GCHandleType.Pinned ); var addr = handle.AddrOfPinnedObject( ); Console.WriteLine( "{0}, {1}", str, addr.ToInt32( ) ); handle.Free( ); } } }
GCHandle.Alloc()文档中对此有提及。我差点看漏了以为文档没说……
有趣的是,GCHandle对类型的挑剔还不止于此。GCHandle.AddrOfPinnedObject()方法乍一看像是返回对象自身的起始地址,实际不然,返回的是对象的“数据区”的起始地址。对System.String来说,“数据区”就是实际存放字符的char数组(CLR的String实现把char数组融合到String里了,没有单独的“char数组成员”。忽略ObjHeader,跳过MethodTable指针和其它成员,例如m_arrayLength、m_stringLength,跳到m_firstChar也就是融合后char数组的开始);而对数组来说,“数据区”就是实际存放值的区域(忽略ObjHeader,跳过MethodTable指针和Length);对其它Blittable类型来说,“数据区”就是Object里MethodTable指针之后的部分。
===========================================================================
完事俱备,只欠开工写代码来实现前面讨论的内容。
先上代码:
using System; using System.Reflection; using System.Runtime.InteropServices; namespace TestCLR2Crash { static void Main( string[ ] args ) { // declare a delegate that refers to a static method, // in this case it's a static method generated from the // anonymous delegate. Action action = delegate( ) { }; // "generate" code into an array of uint var fakeDelegate = new uint[ ] { // dummy values 0x00000000, 0x00000000, // fake _methodPtrAux 0x00000000, // native code/string 0x6AEC8B55, 0x2FD9B8F5, 0xD0FF7C81, 0x006A006A, 0x00E81F6A, 0x83000000, 0x50102404, 0x81CC5DBA, 0x8BD2FF7C, 0x47C35DE5, 0x74656572, 0x73676E69, 0x6F726620, 0x6567206D, 0x6172656E, 0x20646574, 0x65646F63, 0x00000A21 }; // fill in the fake _methodPtrAux, // make it point to the code region in fakeDelegate var handle = GCHandle.Alloc( fakeDelegate, GCHandleType.Pinned ); var addr = handle.AddrOfPinnedObject( ); const int sizeOfUInt32 = sizeof( uint ); // 4 const int indexOfCode = 3; fakeDelegate[ 2 ] = Convert.ToUInt32( addr.ToInt32( ) + sizeOfUInt32 * indexOfCode ); var targetInfo = typeof( Action ) .GetField( "_target", BindingFlags.NonPublic | BindingFlags.Instance ); targetInfo.SetValue( action, fakeDelegate ); action( ); // Greetings from generated code! Console.WriteLine( "Greetings from managed code!" ); handle.Free( ); } } }
执行结果是:(再次提醒:在Vista或者Windows 7上运行会遇到AccessViolationException)
引用
Greetings from generated code!
Greetings from managed code!
Greetings from managed code!
Good!成功的让CLR执行了一段我们指定的native code,在标准输出流上显示了"Greetings from generated code!\n",而且没有显式使用P/Invoke或者unsafe code。为了演示从native code返回后CLR仍在正常运行,所以通过Console.WriteLine()再输出了一行"Greetings from managed code!"。
前文基本上已经把代码的原理解释得差不多了(吧?),所以这边就不再详细解释。
fakeDelegate数组里的native code/string那段可能不太直观,其实那就是前面给出的x86机器码以及"Greetings from generated code!\n"。需要注意的是因为x86的字节序(endian)是little-endian,低位字节在前;而本例中用的数组元素类型是uint,是4字节的整型,所以以4字节为单位,其中的顺序是“反”的。
就以第一个数字0x6AEC8B55为例,它在内存中是0x55 0x8B 0xEC 0x6A这4个字节,其中头3个字节就是这两条指令:
55 push ebp 8BEC mov ebp,esp
这样应该就好理解了吧?
===========================================================================
回顾标题,“要让CLR挂掉的话”,上面的例子都还没让CLR挂掉,似乎有点不够意思。其实真要让CLR连最后的防护措施都挂掉、连异常都抓不到,那还挺难的。但我们可以很轻松的做出些例子,观察一下平时难得一见的异常。
(在家实验的同学们千万注意了:要自行尝试引发错误的话,一定要小心,不要在有重要资料的系统上试。任意篡改代码或者栈上/堆上的数据,实际会引发什么后果很难预料。发生什么糟糕后果责任要自己承担的哦~)
如果构造这样的一段代码:
83C4 08 add esp,8 C3 ret
把它放到上面的C#例子里:
using System; using System.Reflection; using System.Runtime.InteropServices; namespace TestCLR2Crash { static void Main( string[ ] args ) { // declare a delegate that refers to a static method, // in this case it's a static method generated from the // anonymous delegate. Action action = delegate( ) { }; // "generate" code into an array of uint var fakeDelegate = new uint[ ] { // dummy values 0x00000000, 0x00000000, // fake _methodPtrAux 0x00000000, // native code 0xC308C483 }; // fill in the fake _methodPtrAux, // make it point to the code region in fakeDelegate var handle = GCHandle.Alloc( fakeDelegate, GCHandleType.Pinned ); var addr = handle.AddrOfPinnedObject( ); const int sizeOfUInt32 = sizeof( uint ); // 4 const int indexOfCode = 3; fakeDelegate[ 2 ] = Convert.ToUInt32( addr.ToInt32( ) + sizeOfUInt32 * indexOfCode ); var targetInfo = typeof( Action ) .GetField( "_target", BindingFlags.NonPublic | BindingFlags.Instance ); targetInfo.SetValue( action, fakeDelegate ); action( ); handle.Free( ); } } }
执行,我们会看到什么呢?
引用
Process is terminated due to StackOverflowException.
把构造的代码改为
83C4 18 add esp,18h C3 ret
(也就是native code那段的数字是0xC318C483)
执行结果是:
引用
Unhandled Exception: System.Runtime.InteropServices.SEHException: External component has thrown an exception.
stack trace是:
引用
> 0012f4f9()
mscorwks.dll!_CallDescrWorker@20() + 0x33 bytes
mscorwks.dll!_CallDescrWorkerWithHandler@24() + 0x9f bytes
mscorwks.dll!MethodDesc::CallDescr() + 0x15a bytes
mscorwks.dll!MethodDesc::CallTargetWorker() + 0x1f bytes
mscorwks.dll!MethodDescCallSite::CallWithValueTypes() + 0x1a bytes
mscorwks.dll!ClassLoader::RunMain() - 0x39028 bytes
mscorwks.dll!Assembly::ExecuteMainMethod() + 0xa4 bytes
mscorwks.dll!SystemDomain::ExecuteMainMethod() + 0x416 bytes
mscorwks.dll!ExecuteEXE() + 0x49 bytes
mscorwks.dll!__CorExeMain@0() + 0x98 bytes
mscoree.dll!__CorExeMain@0() + 0x34 bytes
kernel32.dll!_BaseProcessStart@4() + 0x23 bytes
mscorwks.dll!_CallDescrWorker@20() + 0x33 bytes
mscorwks.dll!_CallDescrWorkerWithHandler@24() + 0x9f bytes
mscorwks.dll!MethodDesc::CallDescr() + 0x15a bytes
mscorwks.dll!MethodDesc::CallTargetWorker() + 0x1f bytes
mscorwks.dll!MethodDescCallSite::CallWithValueTypes() + 0x1a bytes
mscorwks.dll!ClassLoader::RunMain() - 0x39028 bytes
mscorwks.dll!Assembly::ExecuteMainMethod() + 0xa4 bytes
mscorwks.dll!SystemDomain::ExecuteMainMethod() + 0x416 bytes
mscorwks.dll!ExecuteEXE() + 0x49 bytes
mscorwks.dll!__CorExeMain@0() + 0x98 bytes
mscoree.dll!__CorExeMain@0() + 0x34 bytes
kernel32.dll!_BaseProcessStart@4() + 0x23 bytes
EIP停下的位置是:
0012F4F9 F4 hlt
这个地址是Windows上很典型的栈地址。EIP居然跑到这个地方来,把数据当成指令执行了一句“HLT”指令……
HLT是一个Ring 0指令,用户模式的应用程序没办法是用这条指令,所以试图执行它引发了错误。
SEHException这种泛泛的异常少见吧?成功的搞出了一个没有映射到有具体含义的CLR异常,呵呵 ^ ^
P.S. 这帖告诉我们,类型安全的真面目就是:一旦你开始玩裸指针、玩union、玩goto,啥类型安全都是浮云……
P.P.S. 查了文档,Silverlight 3里的GCHandle.Alloc和GCHandle.AddrOfPinnedObject果然是SecurityCritical的,从用户代码里无法调用它。然而Type.GetMethod和Type.GetField之类还是Transparent的,还是有搞头——至少在Moonlight上 XD
P.P.P.S. 呜,但是FieldInfo.SetValue只能用来设置可访问的域的值……又没搞头了
MSDN 写道
In Silverlight, only accessible fields can be set using reflection.
评论
6 楼
RednaxelaFX
2010-05-16
NyaRuRu的一个新帖,C# のコードに x86/x86-64 命令を直接組み込む,也有类似的东西,关注……他的方案显然比我的完善多了,至少用了VirtualAlloc来保证申请的空间有可执行权限,从而使代码能正确运行在Vista/Win7上。
他用了Marshal.GetDelegateForFunctionPointer,而没有用我这种hack,是正统派做法 XD
他用了Marshal.GetDelegateForFunctionPointer,而没有用我这种hack,是正统派做法 XD
5 楼
RednaxelaFX
2009-09-14
查了文档,Silverlight 3里的GCHandle.Alloc和GCHandle.AddrOfPinnedObject果然是SecurityCritical的,从用户代码里无法调用它。然而Type.GetMethod和Type.GetField之类还是Transparent的,还有搞头——至少在Moonlight上 XD
4 楼
RednaxelaFX
2009-09-04
night_stalker 写道
不搞 native code 几乎没办法弄挂它,managed code 确实显示了其安全性 ……
嗯,没错。而且即便出来严重错误,在多数时候也突破不了最后防线——异常处理和log/core dump……
下次得找个JVM来试试看了 =v=
3 楼
night_stalker
2009-09-04
不搞 native code 几乎没办法弄挂它,managed code 确实显示了其安全性 ……
2 楼
RednaxelaFX
2009-09-04
seen 写道
引用
(在家实验的同学们千万注意了:要自行尝试引发错误的话,一定要小心,不要在有重要资料的系统上试。任意篡改代码或者栈上/堆上的数据,实际会引发什么后果很难预料。发生什么糟糕后果责任要自己承担的哦~)
夸张了吧 一个进程再怎么乱飞指针 也不会把操作系统都弄死吧
弄死就罢了,如果凑巧在执行一个ret前,栈顶的值是删除文件的函数地址、格式化分区的函数地址之类,再凑巧栈顶下面的数据指向C盘啊什么的……不确定的事情很难保证安全,至少我是不敢乱试 =v=|||
1 楼
seen
2009-09-04
引用
(在家实验的同学们千万注意了:要自行尝试引发错误的话,一定要小心,不要在有重要资料的系统上试。任意篡改代码或者栈上/堆上的数据,实际会引发什么后果很难预料。发生什么糟糕后果责任要自己承担的哦~)
夸张了吧 一个进程再怎么乱飞指针 也不会把操作系统都弄死吧
发表评论
-
做菜与洗碗
2014-12-20 15:45 13410今天晚饭,老婆连着用了4个锅来做了两菜一汤。好吃 其中一道菜 ... -
The Prehistory of Java, HotSpot and Train
2014-06-02 08:18 0http://cs.gmu.edu/cne/itcore/vi ... -
MSJVM and Sun 1.0.x/1.1.x
2014-05-20 18:50 0当年的survey paper: http://www.sym ... -
Sun JDK1.4.2_28有TieredCompilation
2014-05-12 08:48 0原来以前Sun的JDK 1.4.2 update 28就已经有 ... -
IBM JVM notes (2014 ver)
2014-05-11 07:16 0Sovereign JIT http://publib.bou ... -
class data sharing by Apple
2014-03-28 05:17 0class data sharing is implement ... -
HotSpot Server VM与Server Class Machine
2014-02-18 13:21 0HotSpot VM历来有Client VM与Server V ... -
Java 8的lambda表达式在OpenJDK8中的实现
2014-02-04 12:08 0三月份JDK8就要发布首发了,现在JDK8 release c ... -
GC stack map与deopt stack map的异同
2014-01-08 09:56 0两者之间不并存在包含关系。它们有交集,但也各自有特别的地方。 ... -
HotSpot Server Compiler与data-flow analysis
2014-01-07 17:41 0http://en.wikipedia.org/wiki/Da ... -
基于LLVM实现VM的JIT的一些痛点
2014-01-07 17:25 0同事Philip Reames Sanjoy Das http ... -
tailcall notes
2013-12-27 07:42 0http://blogs.msdn.com/b/clrcode ... -
《自制编程语言》的一些笔记
2013-11-24 00:20 0http://kmaebashi.com/programmer ... -
字符串的一般封装方式的内存布局 (1): 元数据与字符串内容,整体还是分离?
2013-11-07 17:44 22409(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局 (0): 拿在手上的是什么
2013-11-04 18:22 21508(Disclaimer:未经许可请 ... -
字符串的一般封装方式的内存布局
2013-11-01 12:55 0(Disclaimer:未经许可请 ... -
关于string,内存布局,C++ std::string,CoW
2013-10-30 20:45 0(Disclaimer:未经许可请 ... -
Java的instanceof是如何实现的
2013-09-22 16:57 0Java语言规范,Java SE 7版 http://docs ... -
也谈类型: 数据, 类型, 标签
2013-08-18 01:59 0numeric tower http://en.wikiped ... -
oop、klass、handle的关系
2013-07-30 17:34 0oopDesc及其子类的实例 oop : oopDesc* ...
相关推荐
总的来说,SQL Server 2005与CLR的结合显著提升了SQL Server作为企业级数据库平台的能力,让开发者能够利用.NET的全部潜力来构建高性能、可扩展的数据库解决方案。这一改变不仅提高了开发效率,还促进了不同角色之间...
《框架设计 CLR Via C#(第二版)》是.NET开发者深入了解.NET Framework底层机制的重要参考资料,由权威专家编写,旨在帮助开发者提升对.NET平台的理解和应用能力。这本书详细讲解了CLR(Common Language Runtime)的...
### CLR via C# 第四版知识点解析 #### 一、书籍概述 《CLR via C#》是一本由著名软件架构师Jeffrey Richter撰写的、深入探讨C#语言与公共语言运行时(Common Language Runtime, CLR)之间关系的经典著作。本书自...
《CLR Via C# 第二版源代码》是著名IT专家Jeffrey Richter的作品,这本书深入解析了.NET框架的核心——公共语言运行时(Common Language Runtime, CLR)。通过C#语言,作者详细介绍了CLR的工作原理和机制,帮助...
《CLR via C# 第四版》是一本深入探讨.NET Framework公共语言运行库(Common Language Runtime, CLR)的经典著作,由知名技术专家Jeffrey Richter撰写。这本书是.NET开发者提升技能的重要资源,尤其对于想要深入了解...
第1章Visual C++ 2010 CLR字符串与正则表达式 第2章Visual C++ 2010 CLR集合 第3章Visual C++ 2010 CLR数据访问 第4章Visual C++ 2010 CLR 文件和注册表操作 第5章Visual C++ 2010 CLR使用GDI+绘图 第6章Visual C++ ...
【CLR编程】是一种基于.NET框架的编程模型,它全称为Common Language Runtime,是Microsoft .NET Framework的核心组成部分。CLR为各种编程语言提供了统一的运行环境,实现了语言间的互操作性。在CLR中,内存管理有着...
VS2017 创建 CLR 项目 VS2017 是一款功能强大且广泛使用的集成开发环境(IDE),CLR(Common Language Runtime)是微软公司推出的一个公共语言运行库,用于提供一个中立于语言的平台,以便于开发人员使用不同的编程...
《CLR via C# 第四版示例源码》是一份宝贵的学习资源,它包含了著名作者 Jeffrey Richter 在其经典著作《CLR via C#》第四版中提到的各种代码示例。这本书深入探讨了.NET Common Language Runtime (CLR) 的内部工作...
clr-via-csharp针对CLR和.NET Framework 4.0进行深入、全面的探讨,并结合...第Ⅰ部分介绍CLR基础,第Ⅱ部分解释如何设计类型,第Ⅲ部分介绍基本类型,第Ⅳ部分以实用特性为主题,第Ⅴ部分花大量篇幅重点介绍线程处理。
### 二、C#与CLR的关系 1. **托管代码**:C#是一种面向对象的编程语言,其编译后的中间语言(IL)代码需要通过CLR进行解释执行。 2. **IL到机器码转换**:当程序首次运行时,CLR会将IL代码即时编译(JIT)成针对特定...
杰里米·金会介绍如何利用CLR的特性来优化代码,例如避免不必要的类型转换、正确使用集合类、理解对象生命周期等,帮助开发者写出高性能的应用程序。 8. **程序集与部署** 理解程序集的结构和作用,以及.NET ...
首先,我们要理解什么是CLR。CLR是.NET Framework的核心,它是一个运行时环境,负责管理代码的执行,包括内存管理、类型安全、异常处理以及安全性等。它支持多种编程语言,使得开发者可以使用自己熟悉的语言(如C#、...
第 l 部分介绍 CLR 基础.分介 tlI 从本类贬.第 I ' V 部分以核心机制为毛脱,并结合实例介绍了如何利用它们进行第 【 I 部分解释如何设计类型,第 m 部第 V 部分吸点介绍线程处理。通过木书的阅读.读者. 11 以掌...
框架设计 CLR Via C# 第二版中文版 绝对清晰中文版,一共有6个包
《CLR via C#(第3版)》深入、全面探讨.NET Framework、CLR和多核编程,广泛讨论Framework Class Library(FCL)核心类型,对泛型和线程处理等深奥难懂的开发概念提供权威、实用的指导 自下而上,由浅入深掌握CLR和...
CLR via C# 中文版 第三版 (高清 PDF 全 带书签) 引用清清月儿说的话就是,此书不看,看遍千书也枉然。 地球人都知道此书的权威性。 .NET技术领域有两位世界级专家。 一位是Don Box。他以《Essential COM...
《CLR via C# 中文版 第三版》是周靖老师翻译的一本深入理解.NET Common Language Runtime (CLR) 的经典著作。这本书通过C#语言详细解释了.NET框架的核心组件,帮助开发者深入理解CLR的工作原理,从而提升编程技能和...
框架设计 CLR Via C# 中文第二版(第一部分),分成六卷不厚道,而且还要分!!,因为CSDN有上传大小限制,所以无奈的分成了两部分,
《VC2005编程实例:深入理解CLR》 Visual C++ 2005(简称VC2005)是微软公司推出的一款强大的C++集成开发环境,它集成了.NET Framework的Common Language Runtime(CLR),使得C++开发者能够利用.NET平台的优势进行...