一:背景

1. 一个有趣的话题

最近在看 硬件异常 相关知识,发现一个有意思的空引用异常问题,拿出来和大家分享一下,为了方便讲述,先上一段有问题的代码。


namespace ConsoleApp2
{
internal class Program
{
static Person person = null; static void Main(string[] args)
{
var age = person.age; Console.WriteLine(age);
}
} public class Person
{
public int age;
}
}

由于 person 是一个 null 对象,很显然这段代码会抛异常,那为什么会抛异常呢? 要想找原因,需要从最底层的汇编研究起。

二:异常原理分析

1. 从汇编上寻找答案

可以使用 Visual Studio 2022 的反汇编窗口,观察 var age = person.age; 处到底生成了什么。


---------------- var age = person.age; ---------------- 081D6154 mov ecx,dword ptr ds:[4C41F4Ch]
081D615A mov ecx,dword ptr [ecx+4]
081D615D mov dword ptr [ebp-3Ch],ecx

这三句汇编还是很好理解的,4C41F4Ch 存放的是 person 对象, ecx+4 是取 person.age,最后一句就是将 age 放在 ebp-3Ch 栈位置上,接下来我们来看下 null 时的 ecx 到底是多少,截图如下:

从图中可以看到,此时的 ecx=0000000,如果大家了解 windows 的虚拟内存布局,应该知道在虚拟内存的 0~0x0000ffff 范围内是属于 null 禁入区,凡是落在这个区一概属访问违例,画个图就像下面这样。

到这里原理就搞清楚了,因为 [ecx+4] = [4] 是落在这个 null 区所致, 但是。。。。 大家有没有发现一个问题,对,就是这里的 [ecx+4],因为这里有一个 +4 偏移来取 age 字段,那我能不能在 person 中多定义一些字段,然后取最后一个字段从而从 null 区 冲出去。。。哈哈。

2. 真的可以冲出 null 区吗

有了这个想法之后,我决定在 Person 类中定义 10w 个 age 字段,参考代码如下:


namespace ConsoleApp2
{
internal class Program
{
static Person person = null; static void Main(string[] args)
{
var str = @"public class Person
{
{0}
}"; var lines = Enumerable.Range(0, 100000).Select(m => $"public int age{m};"); var fields = string.Join("\n", lines); var txt = str.Replace("{0}", fields); File.WriteAllText("Person.cs", txt); Console.WriteLine("person.cs 生成完毕");
}
}
}

代码执行后,Person.cs 就会如期生成,接下来读取 person.age99999 看看有没有奇迹发生,参考代码如下:


internal class Program
{
static Person person = null; static void Main(string[] args)
{
var age = person.age99999; Console.WriteLine(age);
}
}

我去,万万没想到,把 ClassLoader 给弄崩了。。。。 得,那只能改 20000 个 age 试试看吧,参考代码如下:


internal class Program
{
static Person person = null; static void Main(string[] args)
{
var age = person.age19999; Console.WriteLine(age);
}
}

接下来我们将断点放在 var age = person.age19999; 上继续看反汇编代码。


------------- var age = person.age19999; -------------
0804657E mov ecx,dword ptr ds:[49F1F4Ch]
08046584 mov dword ptr [ebp-40h],ecx
08046587 mov ecx,dword ptr [ebp-40h]
0804658A cmp dword ptr [ecx],ecx
0804658C mov ecx,dword ptr [ebp-40h]
0804658F mov ecx,dword ptr [ecx+13880h]
08046595 mov dword ptr [ebp-3Ch],ecx

从上面的汇编代码可以看出几点信息。

  • 汇编代码行数多了。

  • ecx+13880h 冲出了 null 区(FFFF) 的边界。

接下来单步调试汇编,发现在 cmp dword ptr [ecx],ecx 处抛了异常。。。

大家都知道此时的 ecx 的地址是 0 ,从 ecx 上取内容肯定会抛访问违例,而且这段代码很诡异,一般来说 cmp 之后都是类似 jz,jnz 跳转指令,而它仅仅是个半残之句。。。

从这些特征看,这是 JIT 故意在取偏移之前尝试判断 ecx 是不是 null,动机不纯哈。。。。

三:总结

从这些分析中可以得知,JIT 还是很智能的。

  • 当偏移值落在 0~FFFF 禁入区内,JIT 就不生成判断代码来减少代码体积。

  • 在偏移值冲出了 0~FFFF 禁入区,JIT 不得不生成代码来判断。

哈哈,本篇是不是很有意思,希望对大家有帮助。

为什么 C# 访问 null 字段会抛异常?的更多相关文章

  1. 扩展方法where方法查询不到数据,不会抛异常,也不是返回的null

    如题,“扩展方法where方法查询不到数据,不会抛异常,也不是返回的null”,示例代码如下: Product类: public class Product { private string name ...

  2. CloudStack的VO在调用setRemoved方法抛异常的原因

    今天在开发中发现一个问题,本来想对一个VO对象的removed值赋值,然后去update一下这条记录,一个最简单的set方法,但是在调用时直接抛异常了. 1: public void setRemov ...

  3. poco json 中文字符,抛异常JSON Exception -->iconv 转换 备忘录。

    起因 最近linux服务器通信需要用到json. jsoncpp比较出名,但poco 1.5版本以后已经带有json库,所以决定使用poco::json(linux 上已经用到了poco这一套框架). ...

  4. iOS开发——网络篇——UIWebview基本使用,NSInvocation(封装类),NSMethodSignature(签名),JavaScript,抛异常,消除警告

    一.UIWebView简介 1.UIWebView什么是UIWebViewUIWebView是iOS内置的浏览器控件系统自带的Safari浏览器就是通过UIWebView实现的 UIWebView不但 ...

  5. C++11 不抛异常的new operator

    在google cpp style guide里面明确指出:we don't use exceptions C++11的noexcept关键字为这种选择提供了便利. C++11以前,提及malloc和 ...

  6. C#在父窗口中调用子窗口的过程(无法访问已释放的对象)异常,不存在从对象类型System.Windows.Forms.DateTimePicker到已知的托管提供程序本机类型的映射。

    一:C#在父窗口中调用子窗口的过程(无法访问已释放的对象)异常 其实,这个问题与C#的垃圾回收有关.垃圾回收器管 理所有的托管对象,所有需要托管数据的.NET语言(包括 C#)都受运行库的 垃圾回收器 ...

  7. java通过抛异常来返回提示信息

    结论:如果把通过抛异常的方式得到提示信息,可以使用java.lang.Throwable中的构造函数: public Throwable(String message) { fillInStackTr ...

  8. java反射机制(访问私有字段和私有方法)

    来自:http://tutorials.jenkov.com/java-reflection/private-fields-and-methods.html 尽管我们通常认为通过JAVA的反射机制来访 ...

  9. 考虑实现一个不抛异常的swap

    Effective C++:参考自harttle land 类的swap实现与STL容器是一致的:提供swap成员函数, 并特化std::swap来调用那个成员函数. class Widget { p ...

随机推荐

  1. SpringMVC-获得Restful风格的参数

    使用@PathVariable注解:接收请求路径中占位符的值 @RequestMapping("/report18/{username}") @ResponseBody publi ...

  2. CTF中的一些图形密码

    1.传统猪圈密码 猪圈密码又称为亦称朱高密码.共济会暗号.共济会密码或共济会员密码:是一种以特定符号来替换字母的加密方式 在线解密网址:http://moersima.00cha.net/zhuqua ...

  3. 将个人项目发布到mavan中央仓库

    第一步,准备自己的git代码 比如在gitee或者github上的代码,我的是gitee码云上的,开源了一个处理业务日志采集的组件,支持注解方式,支持SpEL表达式,支持变量自定义. 话不多说,直接上 ...

  4. Android第五六周作业

    1.返回键实现对话框弹出是否退出应用程序 package com.example.zuoye1; import androidx.appcompat.app.AlertDialog; import a ...

  5. 最新MATLAB R2021b超详细安装教程(附完整安装文件)

    摘要:本文详细介绍Matlab R2021b的安装步骤,为方便安装这里提供了完整安装文件的百度网盘下载链接供大家使用.从文件下载到证书安装本文都给出了每个步骤的截图,按照图示进行即可轻松完成安装使用. ...

  6. Python装饰器:套层壳我变得更强了

    Python装饰器:套层壳我变得更强了 Python装饰器:套层壳我变得更强了 关于作用域和闭包可以聊点什么? 什么是作用域 什么是闭包 装饰器:套层壳我变得更强了 参考资料 昨天阅读了<Pyt ...

  7. 通过源码了解Java的自动装箱拆箱

    什么叫装箱 & 拆箱? 将int基本类型转换为Integer包装类型的过程叫做装箱,反之叫拆箱. 首先看一段代码 public static void main(String[] args) ...

  8. 从零开始学YC-Framework之初步

    本文主要内容为如下几个方面? YC-Framework的取名出于什么考虑? YC-Framework的特点有哪些? YC-Framework的模块由哪些组成? 为什么要开发YC-Framework? ...

  9. XCTF练习题---MISC---glance-50

    XCTF练习题---MISC---glance-50 flag:TWCTF{Bliss by Charles O'Rear} 解题步骤: 1.观察题目,下载附件 2.下载完成以后,隐隐约约像是一张动图 ...

  10. Kafka核心组件详解

    1.概述 对于Kafka的学习,在研究其系统模块时,有些核心组件是指的我们去了解.今天给大家来剖析一下Kafka的一些核心组件,让大家能够更好的理解Kafka的运作流程. 2.内容 Kafka系统设计 ...