.Net8顶级技术:边界检查之IR解析(慎入)

news2025/2/24 11:26:55

前言

C#这种语言之所以号称安全的,面向对象的语言。这个安全两个字可不是瞎叫的哦。因为JIT会检查任何可能超出分配范围的数值,以便使其保持在安全边界内。这里有两个概念,其一边界检查,其二IR解析。后者的生成是前者的功能的保证。啥叫IR,你以为的IL是中间语言,其实并不是,还有一层IR中间表象。.Net8的顶级技术之一(非专属),晓者寥寥。本篇来看看这两项技术。

概括

1.边界检查的缺陷
也叫循环提升,这里边界检查以数组的边界检查为例,看下C#代码
C# Code

using System.Runtime.CompilerServices;
class Program
{
    static void Main()
    {
        int[] array = new int[10_000_000];
        for (int i = 0; i < 1_000_000; i++)
        {
            Test(array);
        }
    }
    [MethodImpl(MethodImplOptions.NoInlining)]
    private static bool Test(int[] array)
    {
        for (int i = 0; i < 0x12345; i++)
        {
            if (array[i] == 42)
            {
                return true;
            }
        }
        return false;
    }
}

JIT并不知道数组array[i]里面的i索引是否超过了array数组的长度。所以每次循环都会检查索引的大小,如果超过则报异常,不超过继续循环,这种功能就叫做边界检查。是.Net6 JIT自动加上去的,但是它有缺陷。

缺陷就在于,每次循环都检查,极大消耗了代码的运行效率。为了避免这种缺陷,是否可以在循环之前判断array数组的长度小于或者循环的最大值。通过这种一次性的判断,取代每次循环的判断,最大化提升代码运行效率。
在.Net8里面这种情况是可行的。
.Net8 JIT Machine Code

G_M000_IG01:                ;; offset=0000H
       4883EC28             sub      rsp, 40
G_M000_IG02:                ;; offset=0004H
       33C0                 xor      eax, eax
       4885C9               test     rcx, rcx
       7429                 je       SHORT G_M000_IG05
       81790845230100       cmp      dword ptr [rcx+08H], 0x12345
       7C20                 jl       SHORT G_M000_IG05
       0F1F40000F1F840000000000 align    [12 bytes for IG03]
G_M000_IG03:                ;; offset=0020H
       8BD0                 mov      edx, eax
       837C91102A           cmp      dword ptr [rcx+4*rdx+10H], 42
       7429                 je       SHORT G_M000_IG08
       FFC0                 inc      eax
       3D45230100           cmp      eax, 0x12345
       7CEE                 jl       SHORT G_M000_IG03
G_M000_IG04:                ;; offset=0032H
      EB17                 jmp      SHORT G_M000_IG06
G_M000_IG05:                ;; offset=0034H
       3B4108               cmp      eax, dword ptr [rcx+08H]
       7323                 jae      SHORT G_M000_IG10
       8BD0                 mov      edx, eax
       837C91102A           cmp      dword ptr [rcx+4*rdx+10H], 42
       7410                 je       SHORT G_M000_IG08
       FFC0                 inc      eax
       3D45230100           cmp      eax, 0x12345
       7CE9                 jl       SHORT G_M000_IG05
G_M000_IG06:                ;; offset=004BH
       33C0                 xor      eax, eax
G_M000_IG07:                ;; offset=004DH
       4883C428             add      rsp, 40
       C3                   ret
G_M00_IG08:                ;; offset=0052H
       B801000000           mov      eax, 1
G_M000_IG09:                ;; offset=0057H
       4883C428             add      rsp, 40
       C3                   ret
G_M000_IG10:                ;; offset=005CH
       E89F82C25F           call     CORINFO_HELP_RNGCHKFAIL
       CC                   int3
; Total bytes of code 98

诚如上面所言,边界检查的判断放在了for循环的外面。if和else分成快速和慢速路径,前者进行了优化。逆向成C#代码如下

if(array!=null && array.length >=0x12345)//数组不能为空,且数组的长度不能小于循环的长度。否则可能边界溢出
{
   for(int i=0;i<0x12345;i++)
   {
     if(array[i]==42)//这里不再检查边界
     {
       return true;
     }
   }
   return false;
}
else
{
  for(int i=0;i<0x2345;i++)
  {
     if(array[i]==42)//边界检查
     return true;
  }
  return flase;
}

边界检查不是本节的重点,重点是这个边界检查是如何通过IR生成的,以及优化。因为IL代码里面并没有。

2.IR的生成
部分代码。常规的认为,C#的运行过程是:
C# Code->
IL ->
Machine Code
一般的认为,IL是中间语言,或者字节码。但是实际上还有一层在JIT里面。如下:
C# Code ->
IL ->
IR ->
Machine Code
这个IR是对IL进行各种骚操作。最重要的一点就是各种优化和变形。这里来看看IR是如何对IL进行边界检查优化的。

看下边界检查的核心IR代码:

***** BB02
STMT00002 ( 0x004[E-] ... 0x009 )
   [000013] ---XG+-----                         *  JTRUE     void  
   [000012] N--XG+-N-U-                         \--*  EQ        int   
   [000034] ---XG+-----                            +--*  COMMA     int   
   [000026] ---X-+-----                            |  +--*  BOUNDS_CHECK_Rng void  
   [000008] -----+-----                            |  |  +--*  LCL_VAR   int    V01 loc0         
   [000025] ---X-+-----                            |  |  \--*  ARR_LENGTH int   
   [000007] -----+-----                            |  |     \--*  LCL_VAR   ref    V00 arg0         
   [000035] n---G+-----                            |  \--*  IND       int   
   [000033] -----+-----                            |     \--*  ARR_ADDR  byref int[]
   [000032] -----+-----                            |        \--*  ADD       byref 
   [000023] -----+-----                            |           +--*  LCL_VAR   ref    V00 arg0         
   [000031] -----+-----                            |           \--*  ADD       long  
   [000029] -----+-----                            |              +--*  LSH       long  
   [000027] -----+---U-                            |              |  +--*  CAST      long <- uint
   [000024] -----+-----                            |              |  |  \--*  LCL_VAR   int    V01 loc0         
   [000028] -----+-N---                            |              |  \--*  CNS_INT   long   2
   [000030] -----+-----                            |              \--*  CNS_INT   long   16
   [000011] -----+-----                            \--*  CNS_INT   int    42

------------ BB03 [00D..019) -> BB02 (cond), preds={BB02} succs={BB04,BB02}

这种看着牛逼轰轰的代码,正是IR。从最里面看起,意思在注释里。

[000031] -----+-----                            |           \--*  ADD       long //把LSH计算的结果加上16,这个16就是下面的CNS_INT long 16的16.
     [000029] -----+-----                            |              +--*  LSH       long  //LSH表示把数组索引左移2位。这个2就是下面的CNS_INT long 2里面的2
     [000027] -----+---U-                            |              |  +--*  CAST      long <- uint//把数组索引的类型从uint转换转换成long类型
     [000024] -----+-----                            |              |  |  \--*  LCL_VAR   int    V01 loc0 //读取本地变量V01,实际上就是数组arrar的索引。
     [000028] -----+-N---                            |              |  \--*  CNS_INT   long   2 //这个2是左移的位数
     [000030] -----+-----                            |              \--*  CNS_INT   long   16//被ADD相加的数值16

继续看

|  \--*  IND       int   
   [000033] -----+-----                            |     \--*  ARR_ADDR  byref int[]
   [000032] -----+-----                            |        \--*  ADD       byref //把前面计算的结果与array数组的地址相加。实际上就是 array + i*4+-x10。一个索引占4个字节,methodtable和array.length各占8字节,这个表达式的结果就是索引位i的array的值,也就是array[i]这个数值。
   [000023] -----+-----                            |           +--*  LCL_VAR   ref    V00 arg0 //获取本地变量V00的地址,这个地址实际上就是数组array的地址。
   [000031] -----+-----                            |           \--*  ADD       long  
   [000029] -----+-----                            |              +--*  LSH       long  
   [000027] -----+---U-                            |              |  +--*  CAST      long <- uint
   [000024] -----+-----                            |              |  |  \--*  LCL_VAR   int    V01 loc0         
   [000028] -----+-N---                            |              |  \--*  CNS_INT   long   2
   [000030] -----+-----                            |              \--*  CNS_INT   long   16

继续看

[000013] ---XG+-----                         *  JTRUE     void //是或者否都进行相应的跳转
   [000012] N--XG+-N-U-                         \--*  EQ        int //判断获取的array[i]是否等于42,这个42是CNS_INT int 42里的42
   [000034] ---XG+-----                            +--*  COMMA     int //计算它的两个值,获取第二个值也就是array[i]
   [000026] ---X-+-----                            |  +--*  BOUNDS_CHECK_Rng void  
   [000008] -----+-----                            |  |  +--*  LCL_VAR   int    V01 loc0 //数组的索引i值
   [000025] ---X-+-----                            |  |  \--*  ARR_LENGTH int //获取数组长度
   [000007] -----+-----                            |  |     \--*  LCL_VAR   ref    V00 arg0  //数组的长度
   [000035] n---G+-----                            |  \--*  IND       int   //获取array[i]的值
   [000033] -----+-----                            |     \--*  ARR_ADDR  byref int[] //获取刚刚array数组地址
    //中间省略,上面已经写过了。
 [000011] -----+-----                            \--*  CNS_INT   int    42

那么翻译成C# Code如下:

if(array[i]==42)
{
  return true;
}
return false

这里还没有循环,因为循环在其它的Basic Block块,这里是BB02块。那么下面就是对着BB02进行优化变形,最终形成了如上边界检查去除所示的结果。关于这点,下篇再看。

结尾

作者:江湖评谈
原文:在此处
文章首发在公众号上,欢迎关注。image

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/558498.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

音视频使用qt测试ffmpeg接口时无法运行

仅仅时把自己过程中遇到的稍微阻塞疑惑问题做出整理&#xff0c;疑惑的是拿到的ffmpeg包中没有dll文件&#xff0c;导致自己研究了一系列。 使用qt加载音视频ffmpeg对应的相关lib库&#xff0c;进行接口&#xff0c;源码的研究。 1&#xff1a;使用源码安装的方式获取相关的动…

【蓝桥杯省赛真题40】Scratch报数游戏 蓝桥杯少儿编程scratch图形化编程 蓝桥杯省赛真题讲解

目录 scratch报数游戏 一、题目要求 编程实现 二、案例分析 1、角色分析

OKR是什么意思啊

一、OKR是什么意思&#xff1f; OKR是"Objective and Key Results"的缩写&#xff0c;即目标和关键结果。它是一种目标管理框架&#xff0c;旨在帮助组织和团队设定明确的目标&#xff0c;并通过关键结果来衡量和追踪目标的实现情况。 为了让大家快速了解什么是OKR…

基于变长频带选择的JPEG图像可逆数据隐藏-文献学习

论文学习 原文题目&#xff1a; Reversible Data Hiding of JPEG Image Based on Adaptive Frequency Band Length 发表期刊&#xff1a; TCSVT 2023&#xff08;中科院1区&#xff09; 作者&#xff1a; Ningxiong Mao, Hongjie He, Fan Chen, Yuan Yuan, Lingfeng Qu 摘要 J…

SolVES模型应用(基于多源环境QGIS\PostgreSQL\ARCGIS\MAXENT\R语言支持下模型应用)

生态系统服务是人类从自然界中获得的直接或间接惠益&#xff0c;可分为供给服务、文化服务、调节服务和支持服务4类&#xff0c;对提升人类福祉具有重大意义&#xff0c;且被视为连接社会与生态系统的桥梁。自从启动千年生态系统评估项目&#xff08;Millennium Ecosystem Asse…

又双叒叕入选!腾讯安全NDR连续四年获Gartner认可

近日&#xff0c;全球权威研究机构 Gartner发布了2023年《Emerging Tech: Security — Adoption Growth Insights for Network Detection and Response》&#xff08;《新兴技术&#xff1a;安全-网络检测与响应的采用增长洞察》&#xff09;&#xff0c;腾讯安全连续四年被列为…

内卷把同事逼成了“扫地僧”,把Git上所有面试题整理成足足24W字测试八股文

互联网大厂更多的是看重学历还是技术&#xff1f; 毫无疑问&#xff0c;是技术&#xff0c;技术水平相近的情况下&#xff0c;肯定学历高/好的会优先一点&#xff0c;这点大家肯定都理解。 说实话&#xff0c;学弟学妹们找工作难&#xff0c;作为面试官招人也难呀&#xff01…

使用laf云开发三分钟上线你自己的Midjourney

文章尾部有demo 江湖惯例&#xff1a;先来一波感谢&#xff0c;感谢laf&#xff0c;让我们可以不使用魔法、免费接入Midjourney&#xff0c;不了解laf的请猛戳 Laf介绍 一、写这篇博客的背景 laf官方最近发布了一个活动&#xff0c;活动链接&#xff0c;新手也可以接入哦&…

云渲染平台为什么越来越多的效果图公司开始使用?

随着3dmax版本的不断更迭&#xff0c;包括常用的V-Ray渲染器和Corona渲染器的不断更新&#xff0c;室内设计行业对于 效果图的渲染要求越来越高。而要求更高的渲染精度和更真实的渲染效果&#xff0c;所需要付出的代价则是不断增长的参数&#xff0c;这会使渲染一张效果图的时间…

chatgpt赋能Python-python_lamba

Python Lambda: 什么是Lambda表达式&#xff1f; Python是一种强大的编程语言&#xff0c;它支持许多编程范式&#xff0c;包括函数式编程。Lambda表达式是函数式编程的一个重要概念。许多人对Lambda表达式感到困惑&#xff0c;认为它们很难理解。本文将介绍Python的Lambda表达…

如何对Windows剪切板里的内容进行取证分析 Windows剪切板取证

前言 无论是在现实中对设备进行取证分析&#xff0c;还是在ctf中做取证类的题目&#xff0c;剪切板里的内容都需要去查看&#xff0c;以免遗漏什么重要信息 剪切板位置 剪切板是计算机操作系统提供的一个临时存储区域&#xff0c;用于在不同应用程序之间复制和粘贴文本、图像…

S72402-NANANA伺服驱动器相当于大脑,执行电机相当于手脚

​ S72402-NANANA伺服驱动器相当于大脑&#xff0c;执行电机相当于手脚 伺服驱动器在控制信号的作用下驱动执行电机&#xff0c;因此驱动器是否能正常工作直接影响设备的整体性能。在伺服控制系统中&#xff0c;伺服驱动器相当于大脑&#xff0c;执行电机相当于手脚。而伺服驱动…

从裸机启动开始运行一个C++程序(三)

先序文章请看 从裸机启动开始运行一个C程序&#xff08;二&#xff09; 从裸机启动开始运行一个C程序&#xff08;一&#xff09; 编写MBR 上一章我们已经成功地在8086上运行了指令&#xff0c;同时也介绍了nasm汇编语言。那么接下来这一章&#xff0c;我们就来看看如何写BIO…

零基础学习网络安全这一块,请问有哪些相关资料可以推荐一下?

一、相关网站推荐 1、FreeBuf 国内关注度最高的全球互联网安全媒体平台&#xff0c;爱好者们交流与分享安全技术的社区&#xff0c;网络安全行业门户。 2、看雪看雪论坛是个软件安全技术交流场所&#xff0c;为安全技术爱好者提供一个技术交流平台和资源。 3、吾爱破解 吾爱破解…

Spring源码阅读:Spring事务传播特性

一、概述 我们平常工作中经常会听到事务的传播级别&#xff0c;但是使用中基本不会太调整这个事务传播级别&#xff0c;因为没什么业务场景需要我们这么做&#xff0c;只需要使用原有的事务传播级别即可解决95%的业务场景。但是为了那5%的业务场景&#xff0c;我们还是还要学习…

半边圆角和选中

为了这效果肝了几天&#xff0c;调了几天&#xff0c;改了几天&#xff0c;头都晕了&#xff01; 在 ViewBinder 里设置背景 adapter.setViewBinder(new SimpleCursorAdapter.ViewBinder(){public boolean setViewValue(View view, Cursor cursor, int columnIndex){ …

如何理解驱动程序、设备树、platform、device、driver之间的关系

前言 利用设备树来使用或者编写驱动程序&#xff0c;需要梳理哪些概念&#xff1f; 如何理解驱动程序、设备树、platform、device、driver之间的关系 前言一、总线设备驱动模型——总线、设备、驱动二、从代码中看driver与device的关系三、设备树的应用&#xff08;一&#xf…

简单分享生鲜超市怎么做同城配送小程序

1、蔬菜生鲜产品展示&#xff1a;用户打开买菜必备软件&#xff0c;就能查看琳琅满目的新鲜水果、蔬菜、肉类、零食等产品&#xff0c;为用户展示更多信息&#xff0c;提升用户下单率。经常更新商品的照片、视频&#xff0c;让客户可以在线浏览和挑选&#xff0c;足不出户就能买…

Baumer工业相机堡盟工业相机通过BGAPISDK使用图像回调函数全帧率保存图像(C++)

Baumer工业相机堡盟工业相机通过BGAPISDK使用图像回调函数全帧率保存图像&#xff08;C&#xff09; Baumer工业相机Baumer工业相机全帧率保存的技术背景Baumer工业相机通过BGAPISDK使用相机图像回调函数1.引用合适的类文件2.通过BGAPISDK在相机图像回调函数全帧率保存 Baumer工…

LabVIEWCompactRIO 开发指南31 在LabVIEW FPGA中使用DMA FIFO

LabVIEWCompactRIO 开发指南31 在LabVIEW FPGA中使用DMA FIFO 要为流数据创建DMA缓冲区&#xff0c;请右键单击FPGA目标并选择New...FIFO。为FIFO结构指定一个描述性名称&#xff0c;并选择“target to host”作为类型。这意味着数据应该通过这个DMA FIFO从FPGA目标流向实时主…