`
RednaxelaFX
  • 浏览: 3049195 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

要让CLR挂掉的话……

阅读更多
(Disclaimer:如果需要转载请先与我联系。
作者:RednaxelaFX -> rednaxelafx.iteye.com)

系列文章:
要让CLR挂掉的话……
要让CLR挂掉的话(第二弹)……

前几天跟浩飞老兄闲聊的时候,聊到说一个不知道什么地方在面试人的时候,如果面试者说自己精通Java,他们就出题考面试者如何让JVM挂掉。这种面试方式或许是比较激进,不过倒也可以考考别人对特定JVM的实现的认识。
于是在爆栈上有这么一帖:How do you crash a JVM?。跟帖中一些同学的观点一样,我也不认为爆栈或者爆堆能算得上是“crash”,因为JVM还能正确捕捉到错误,并且执行合适的异常处理。真正的“crash”应该是连正常的异常处理都没起作用,直接就出crash log了;要是能连出crash log的步骤都破坏掉那就更彻底了。
爆栈帖里有人建议说:
ralfs 写道
1. Use JNI and crash in the native code.
2. If no security manager is installed you can use reflection to crash the VM. This is VM specific, but normally a VM stores a bunch of pointers to native resources in private fields (e.g. a pointer to the native thread object is stored in a long field in java.lang.Thread). Just change them via reflection and the VM will crash sooner or later.
3. All VMs have bugs, so you just have to trigger one.

都是些有趣的建议……

对应到.NET上的话,
第一点基本上就映射到P/Invoke的使用了。如果被P/Invoke的native code里有非常糟糕的错误而且不使用SEH,那CLR什么办法也没有,只能让程序crash了。

第二点是关于操纵VM内部实现用到的指针。各种JVM实现里在不同位置暴露了一些指针(即便是Compressed Oops那也是指针),改变它们的值确实能达到crash的效果,虽然如果更进一步能它它们改成“有意义”的值的话就能更有效的操纵破坏的具体行为。
CLR里也有许多看起来很无辜的东西实际上是指针来的(注意我是说CLR不是CLI)。一个典型的例子是Type.TypeHandle属性,在CLR里它实际上就是指向类型的MethodTable的指针。通过它我们可以找到很多关于类型的“裸”信息。“裸”是指CLR内部的实现细节,本来不应该暴露出来的部分)。还有一个典型的例子是.NET的类型安全函数指针,委托。下面会看看委托的例子。
要操纵VM内部的指针,势必要通过反射去获取或设置一些私有变量的值。这种操作一般都会受到VM的安全管理器监管,在没有足够权限的情况下无法执行。所以其实也不算危险……不,应该说原本用native code的话就有这种危险了,用了VM并没有变得更危险。

第三点是说VM自身的实现有bug。嗯这种状况常有,像先前我就看到HotSpot的JIT有bug挂掉了。CLR小组也没少遇到内部发生内存管理错误的问题,组里有专人盯着这种问题在修。如果发现这样的bug并有意利用的话,也能有效让VM挂掉,甚至进一步做别的事情……呵呵

===========================================================================

.NET的委托,在不考虑多播(multicast)状况时,完成调用所需要的Delegate类上最关键的3个成员是_target、_methodPtr和_methodPtrAux。其中只有_target是以Delegate.Target属性的形式公开出来的。看看它们都有什么用:

_target:委托调用的目标对象。
.NET的委托是类型安全的,不但指在构造委托实例时会检查其类型与目标方法的signature是否匹配,也指委托能够捕获目标对象的引用,进而能够由其得到相关的类型和方法的元数据,以供执行引擎监管类型的安全性。
在CLR的实现中,_target可能有两种情况:
1、如果委托指向的方法是成员方法,那么_target就会是指向目标方法所属的对象实例的指针;
2、如果委托指向的是静态方法,或者是涉及native方法,那么_target会指向委托实例自身。
有趣的是,虽然指向静态方法时_target指向委托实例自身,但Delegate.Target却会返回null。

_methodPtr:委托调用的目标方法的指针。
这个是“函数指针”的真面目,跟C里的函数指针没什么两样。
它的值也分两大种情况:
1、如果委托指向的方法是成员方法,那么_methodPtr就可能指向一个JIT stub(假如创建委托时目标方法尚未被JIT),或者可能是直接指向目标方法JIT后的地址;
2、如果委托指向的方法是静态方法,那么_methodPtr指向的是一个stub,去掉原本调用时隐藏的第一个参数(_target),然后调用_methodPtrAux。这个stub是所有signature相同的委托共享的。
如果涉及native方法的话我还没弄清楚具体是什么状况 =v=

_methodPtrAux:委托调用的目标方法的第二个指针。
联系前两个成员的介绍,这个也不例外分两种情况:
1、如果委托指向的是成员方法,那么_methodPtrAux就是null(0)。Delegate.Target属性实际的实现是_methodPtrAux.IsNull() ? _target : null,可以看到目标是成员方法与否的影响。
2、如果委托指向的是静态方法,那么_methodPtrAux可能指向类似JIT stub的东西,该stub在多次调用后可能会被改写为jmp到实际调用目标方法;也可能一开始就指向目标方法JIT后的地址。
(CLRv2中,“多次”是3次;采取哪个版本的_methodPtrAux取决于创建委托实例所在的方法在被JIT编译时,目标方法是否已经被JIT编译)

抽象的描述还是让人摸不着头脑,来看看代码例子:
using System;
using System.Reflection;

namespace TestCLR2Crash {
    static class Program {
        static void Main( string[ ] args ) {
            Func<int, int> iden = x => x;
            Func<int, int> succ = x => x + 1;
            var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance );
            var succPtrAux = ( IntPtr ) methPtrAuxInfo.GetValue( succ );
            methPtrAuxInfo.SetValue( iden, succPtrAux );
            Console.WriteLine( iden( 0xBEEF ).ToString( "X" ) ); // BEF0
        }
    }
}

先注意一些C#的实现细节。Main里的iden与succ所指向的lambda都没有捕获任何自由变量,所以由C#编译器先改写生成对应的私有静态方法。这样,iden与succ就属于“指向静态方法的委托”的情况,可以留意一下相应的_target、_methodPtr与_methodPtrAux的表现。特别的,iden与succ的_target成员指向各自自身;它们的_methodPtr都指向同一个stub,用于剥离第一个隐藏参数并调用_methodPtrAux;由于Main()方法被JIT的时候,两个lambda对应的静态方法尚未被JIT,所以iden与succ的_methodPtrAux各自指向不同的stub(而不是直接指向实际调用目标方法)。

在代码中,我们把succ的_methodPtrAux提取出来,并设置到iden对应的域里。然后在调用iden时,可以看到实际被调用的是succ指向的那个lambda。

既然能把函数指针改到一个有效的函数地址上,那要是改为null的话呢?
using System;
using System.Reflection;

namespace TestCLR2Crash {
    static class Program {
        static void Main( string[ ] args ) {
            Func<int, int> iden = x => x;
            var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_methodPtrAux", BindingFlags.NonPublic | BindingFlags.Instance );
            methPtrAuxInfo.SetValue( iden, IntPtr.Zero );
            Console.WriteLine( iden( 0xBEEF ).ToString( "X" ) );
        }
    }
}

我们就让CLR挂掉而出现AV(access violation)了:
引用
Unhandled Exception: System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

可惜CLR的实现比较严谨,AV也还是被默认的异常处理捕捉到了。不过如果指向什么别的地方,说不定就能在触发AV前先干点好事了,呵呵。

再次注意到像这样操纵VM内部的指针需要足够的安全权限才行,否则通过反射也无法像这样修改私有变量的值。所以并不会很不安全,可以放心。

说真的,即便写个会爆栈的程序,CLR也会扔出类似的错误信息:
引用
Process is terminated due to StackOverflowException.

改委托内部的函数指针不够好玩……

===========================================================================

回复中cescshen同学问了个有趣的问题,说为什么改变_target也可以改变实际被调用的对象。我把我的回帖复制上来~
以下内容都是以PC上的32位x86的CLR,版本2.0.50727.3082为前提的讨论。

cescshen 写道
发错,这儿不能删自己的留言。。

var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_target", BindingFlags.NonPublic | BindingFlags.Instance ); 

改成这样的话,也能出结果,这个怎么回事?

如果你说的不是遇到了错误,而是看到修改_target后iden的行为变成了succ的,那是因为在_methodPtr所指向的那个stub里,代码是这样的:
mov         eax,ecx         // 把第一参数(_target)复制到EAX
mov         ecx,edx         // 把原本的第二参数(0xBEEF)变为第一参数
add         eax,10h         // 把_target._methodPtrAux的地址设到EAX
jmp         dword ptr [eax] // 间接调用EAX,也就是调用_target._methodPtrAux

注意到CLR里JIT编译的代码的calling convention是类似fastcall的,头两个参数分别位于ECX和EDX。在调用iden的时候,代码是这样的:
mov         ecx,edi                 // 把iden的引用从EDI复制到ECX
mov         edx,0BEEFh              // 0xBEEF复制到EDX作为第二参数
mov         eax,dword ptr [ecx+0Ch] // 把iden._methodPtr复制到EAX
mov         ecx,dword ptr [ecx+4]   // 把iden._target复制到ECX作为第一参数
call        eax                     // 调用_methodPtr


知道从_methodPtr到_methodPtrAux的过程之后,就可以理解为什么改变_target的值也足以改变指向静态方法的委托的行为:因为关键的_methodPtrAux是通过_target来引用的。在正常情况下,_target就指向委托自身,所以没有问题;而改变了_target的值之后,实际被调用的_methodPtrAux就跟着一起变了。

===========================================================================

爆栈帖里有一个回复可以mark一下:
eckes 写道
On Linux/Unix you can easyly make a JVM crash by sending it a Signal to the running process. Note: you should not use "SIGSEGV" for this, since Hotspot catches this signal and rethrows it as a NullPointerException in most places. So it is better to send a SIGBUS for example.
分享到:
评论
10 楼 lindexi-gd 2016-07-08  
我想转载blog.csdn.net/lindexi_gd
9 楼 cescshen 2009-09-03  
var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_target", BindingFlags.NonPublic | BindingFlags.Instance );   
var succPtrAux = methPtrAuxInfo.GetValue( succ ); 

嗯, 我是指改变_target后, iden的行为变成了succ的, 我没有转IntPtr, 所以没有InvalidCastException. 解答真及时啊
8 楼 RednaxelaFX 2009-09-02  
cescshen 写道
发错,这儿不能删自己的留言。。

var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_target", BindingFlags.NonPublic | BindingFlags.Instance ); 

改成这样的话,也能出结果,这个怎么回事?

如果你说的不是遇到了错误,而是看到修改_target后iden的行为变成了succ的,那是因为在_methodPtr所指向的那个stub里,代码是这样的:
mov         eax,ecx         // 把第一参数(_target)复制到EAX
mov         ecx,edx         // 把原本的第二参数变为第一参数
add         eax,10h         // 把_target._methodPtrAux的地址设到EAX
jmp         dword ptr [eax] // 间接调用EAX,也就是调用_target._methodPtrAux

注意到CLR里JIT编译的代码的calling convention是类似fastcall的,头两个参数分别位于ECX和EDX。在调用iden的时候,代码是这样的:
mov         ecx,edi                 // 把iden的引用从EDI复制到ECX
mov         edx,0BEEFh              // 0xBEEF复制到EDX作为第二参数
mov         eax,dword ptr [ecx+0Ch] // 把iden._methodPtr复制到EAX
mov         ecx,dword ptr [ecx+4]   // 把iden._target复制到ECX作为第一参数
call        eax                     // 调用_methodPtr


知道从_methodPtr到_methodPtrAux的过程之后,就可以理解为什么改变_target的值也足以改变指向静态方法的委托的行为:因为关键的_methodPtrAux是通过_target来引用的。在正常情况下,_target就指向委托自身,所以没有问题;而改变了_target的值之后,实际被调用的_methodPtrAux就跟着一起变了。
7 楼 RednaxelaFX 2009-09-02  
night_stalker 写道
想到个把内存搞成筛状的做法:
1.分配一块 n字节 的内存 m1 (如 new Byte[n])
2.分配一块小小的内存 m2    (如 new Byte[1])
3.释放 m1                  (如退出作用域或者弱引用让它可以被 GC 回收)
4.但保存 m2                (如 somelist.appen(m2))
5.n += 1,回到 1 继续。

最后某些 VM 会总空间很足够,就是分不出内存块来。但某些 VM 不会。

---

任何有计数的地方都有可能 overflow, 十万个类或者十万个变量?

---

感觉搞 channel 和反射也比较容易搞 crash,还有大量几乎没人用的 API 也是弱点。

---

靠 IL 搞 crash 就更容易了,当然对于我们不熟 IL 的太不容易了 ……

如果不用unverifiable code的话,MSIL很难让CLR“挂掉”,因为CLR在执行某个方法之前会对里面的MSIL进行校验,如果是有问题的MSIL,那还没到执行的那步就已经被踢了。

内存的那个,你想说的“保存m2”在.NET术语里或许是“把m2给pin住”。
pin住的话,m2就无法移动了,因而会造成内存碎片化。如果对象大小不太大(没超过85000字节)的话,对象是在一般的GC堆上分配空间,而它会在碎片比例开始影响性能时对堆进行压缩;pin住的对象无法移动,所以会影响GC对堆的压缩。如果没有pin住的对象,那CLR的一般的GC堆是不会出现严重的碎片化的。
如果对象大小超过了那个阈值,那么会分配在LOH(大对象堆)上。这个堆是不压缩的,所以会出现碎片化的问题。要故意构造一个让它碎片化的程序,只要不停的在85000字节以上的大小递增的创建对象,中间夹些刚比85000字节大一些的对象,然后留住中间夹的那些,就足够了……
6 楼 RednaxelaFX 2009-09-02  
cescshen 写道
发错,这儿不能删自己的留言。。

var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_target", BindingFlags.NonPublic | BindingFlags.Instance ); 

改成这样的话,也能出结果,这个怎么回事?

帮你把错误的留言删了~ ^ ^
你说的“可以出结果”是指什么?是指iden能按照原本正常的行为执行,还是指看到了错误的结果,还是指出错让CLR退出?

如果是说看到了错误,那……莫非你测试的代码是这样的?
using System;
using System.Reflection;

namespace TestCLR2Crash {
    static class Program {
        static void Main( string[ ] args ) {
            Func<int, int> iden = x => x;
            Func<int, int> succ = x => x + 1;
            var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_target", BindingFlags.NonPublic | BindingFlags.Instance );
            var succPtrAux = ( IntPtr ) methPtrAuxInfo.GetValue( succ );
            methPtrAuxInfo.SetValue( iden, succPtrAux );
            Console.WriteLine( iden( 0xBEEF ).ToString( "X" ) ); // BEF0
        }
    }
}

这样的话,在var succPtrAux = (IntPtr) methPtrAuxInfo.GetValue(succ);这行自然就挂了,因为_target的实际类型是Func<int, int>,而这行代码指定要转换到IntPtr,无法转换,所以抛出InvalidCastException。这是正常现象,都还没涉及到跟指针相关的问题……

不知道你遇到的是不是这个情况呢?
5 楼 night_stalker 2009-09-02  
想到个把内存搞成筛状的做法:
1.分配一块 n字节 的内存 m1 (如 new Byte[n])
2.分配一块小小的内存 m2    (如 new Byte[1])
3.释放 m1                  (如退出作用域或者弱引用让它可以被 GC 回收)
4.但保存 m2                (如 somelist.appen(m2))
5.n += 1,回到 1 继续。

最后某些 VM 会总空间很足够,就是分不出内存块来。但某些 VM 不会。

---

任何有计数的地方都有可能 overflow, 十万个类或者十万个变量?

---

感觉搞 channel 和反射也比较容易搞 crash,还有大量几乎没人用的 API 也是弱点。

---

靠 IL 搞 crash 就更容易了,当然对于我们不熟 IL 的太不容易了 ……
4 楼 cescshen 2009-09-02  
发错,这儿不能删自己的留言。。

var methPtrAuxInfo = typeof( Func<int, int> ).GetField( "_target", BindingFlags.NonPublic | BindingFlags.Instance ); 

改成这样的话,也能出结果,这个怎么回事?
3 楼 lwwin 2009-09-02  
我倒觉得有时候程序crash挺好了…………

经常会因为弄得太好了,结果没办法捕捉错误了……
2 楼 RednaxelaFX 2009-09-02  
iaimstar 写道
  public static void main(String[] args) {
    throw new RuntimeException();
  }

我看到了这个 好搞笑
有人给打成-2了

那是……太直了就没有含蓄美朦胧美了 =v=
1 楼 iaimstar 2009-09-02  
  public static void main(String[] args) {
    throw new RuntimeException();
  }

我看到了这个 好搞笑
有人给打成-2了

相关推荐

    CLR编程,CLR 原理,CLR 内幕

    【CLR编程】是一种基于.NET框架的编程模型,它全称为Common Language Runtime,是Microsoft .NET Framework的核心组成部分。CLR为各种编程语言提供了统一的运行环境,实现了语言间的互操作性。在CLR中,内存管理有着...

    CLRInsideOut.zip

    首先,我们要理解什么是CLR。CLR是.NET Framework的核心,它是一个运行时环境,负责管理代码的执行,包括内存管理、类型安全、异常处理以及安全性等。它支持多种编程语言,使得开发者可以使用自己熟悉的语言(如C#、...

    CLR via C# 第四版.pdf

    ### CLR via C# 第四版知识点解析 #### 一、书籍概述 《CLR via C#》是一本由著名软件架构师Jeffrey Richter撰写的、深入探讨C#语言与公共语言运行时(Common Language Runtime, CLR)之间关系的经典著作。本书自...

    VS2017创建CLR项目.docx

    VS2017 创建 CLR 项目 VS2017 是一款功能强大且广泛使用的集成开发环境(IDE),CLR(Common Language Runtime)是微软公司推出的一个公共语言运行库,用于提供一个中立于语言的平台,以便于开发人员使用不同的编程...

    CLR via C# 第4版.pdf

    根据提供的文件信息,“CLR via C# 第4版.pdf”似乎是一本关于C#语言与CLR(Common Language Runtime)之间关系的专业书籍。尽管没有直接提供书中的具体内容,但我们可以根据标题、描述以及常见知识点来推断本书可能...

    Microsoft System CLR Types for Microsoft SQL Server 2014

    首先,我们要理解什么是CLR。CLR是Microsoft .NET Framework的一部分,它负责管理代码的执行,包括内存管理、类型安全、异常处理以及安全性。当我们在SQL Server中使用CLR类型时,实际上是利用了CLR的这些功能来提升...

    vc2005编程实例CLR vc2005编程实例CLR vc2005编程实例CLR

    《VC2005编程实例:深入理解CLR》 Visual C++ 2005(简称VC2005)是微软公司推出的一款强大的C++集成开发环境,它集成了.NET Framework的Common Language Runtime(CLR),使得C++开发者能够利用.NET平台的优势进行...

    SQL Server 2005 and the CLR_ What do you need to …….ppt

    总的来说,SQL Server 2005与CLR的结合显著提升了SQL Server作为企业级数据库平台的能力,让开发者能够利用.NET的全部潜力来构建高性能、可扩展的数据库解决方案。这一改变不仅提高了开发效率,还促进了不同角色之间...

    Microsoft sql server system clr types 2012

    标题“Microsoft SQL Server System CLR Types 2012”指的是微软SQL Server数据库管理系统的一个关键组件,主要用于处理与.NET Framework交互的系统级CLR(Common Language Runtime)类型。在SQL Server中,CLR集成...

    C#使用CLR调用C++的DLL库

    在.NET框架中,C#与C++之间的交互可以通过CLR(Common Language Runtime)实现,这使得两种语言能够共享代码和资源。本主题将详细介绍如何在C#应用中利用CLR调用C++编写的DLL库,特别是在Windows环境下。我们将分为...

    microsoft system clr types for sql server 2012

    首先,我们要理解CLR(Common Language Runtime)是.NET Framework的核心部分,负责代码的执行和管理。SQL Server的CLR集成允许用户使用.NET编程语言(如C#或VB.NET)编写存储过程、触发器、函数等数据库对象。...

    CLRProfiler

    CLR探查器是一个帮助我们对.net代码监测内存分配情况的工具。CLR探查器由微软提供,你可以从下面的网址下载: ... 注意:CLR探查器有针对.net 1.1和2.0框架的两个版本。... 下载并解压CLR探查器之后,可以从bin目录下...

    Visual C++ 2010 CLR开发

    第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 via C# 第四版

    这本书是.NET开发者提升技能的重要资源,尤其对于想要深入了解C#语言和.NET平台底层工作原理的程序员来说,具有极高的学习价值。 该书涵盖的内容广泛且深入,包括: 1. **CLR基础**:讲解了CLR的基本架构,如何...

    CLR via C# 第4版(中英文版)

    《CLR via C# 第4版》是一本由微软.NET框架的首席架构师斯科特·盖尔茨( Jeffrey Richter)撰写的经典之作。这本书详细深入地探讨了.NET Common Language Runtime (CLR) 的各个方面,是.NET开发者理解底层平台运作...

    微软说明文档CLR

    微软说明文档CLR,详实的介绍,聚焦于Visual C++ 2005与CLR(Common Language Runtime)的深度整合,以及C++/CLI作为.NET平台上能力最接近IL(Intermediate Language)代码的系统级语言的独特地位。本文将深入探讨...

    vs2008 CLR原理

    ### VS2008 CLR(公共语言运行时)原理详解 #### 一、CLR概述 CLR,即Common Language Runtime(公共语言运行时),是.NET Framework的核心组成部分之一,它为托管代码提供了一个执行环境,同时也提供了诸如内存...

    C#调用C++的中间封装(CLR)

    在.NET框架中,微软引入了Common Language Runtime(CLR),它为不同编程语言提供了一个统一的运行环境,使得像C#这样的高级语言可以与C++这样的系统级语言进行交互。本主题将深入探讨如何使用CLR技术在C#中调用C++...

    强大的.net调试工具CLRProfiler

    首先,我们要了解.NET框架中的Common Language Runtime(CLR),它是.NET应用程序的核心组成部分,负责代码的执行、内存管理以及异常处理等工作。当提到“托管堆”,我们指的是CLR为应用程序分配对象内存的区域。...

    clr基础入门教程

    《CLR基础入门教程》深入解析了CLR(Common Language Runtime,公共语言运行时)的基本概念及其在.NET Framework中的作用。本书由瑞奇特(Jeffrey Richter)撰写,基于Microsoft Visual Studio 2010,.NET Framework...

Global site tag (gtag.js) - Google Analytics