.NET9 Pre4 UnsafeAccessor泛型

文摘   2024-05-22 15:24   湖北  

点击上方蓝字 江湖评谈设为关注/星标




前言

.NET9 PreView4 CLR里面添加了对于UnsafeAccessorAttribute特性泛型的支持。而对于UnsafeAccessorAttribute本身的支持则在.NET8里面。本篇看下Pre4里面的这个特性用法以及原理。

用法

来看看一个简单的例子:

internal class Program{    public class Class<T>    {       private T _field;       private void M<U>(T t, U u) { Console.WriteLine(t);Console.WriteLine(u); }    }    class Accessors<V>    {       [UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_field")]        public extern static ref V GetSetPrivateField(Class<V> c);
        [UnsafeAccessor(UnsafeAccessorKind.Method, Name = "M")]        public extern static void CallM<W>(Class<V> c, V v, W w);     }
    public static void AccessGenericType(Class<int> c)    {        ref int f = ref Accessors<int>.GetSetPrivateField(c);        f = 10;        Console.WriteLine(f);        Accessors<int>.CallM<string>(c, 1"hello");     }
     static void Main(string[] args)     {         Class<int> c1 = new Class<int>() { };                  AccessGenericType(c1);         Console.ReadLine();     }}

例子取自官方,稍微改了下。Class<T>里面的一个字段T及函数M,通过UnsafeAccessorAttribute进行访问和赋值。这里需要注意的点是,如果通过UnsafeAccessorAttribute访问字段,则UnsafeAccessorAttribute特性声明的方法参数需是类。比如本例的GetSetPrivateField它的参数需要字段所在类。如果是通过UnsafeAccessorAttribute特性访问方法,比如本例的CallM访问M方法,则它的方法(Call)第一个参数是方法(M)所在类(Class<V>),后面是M方法的参数(T t, U u),顺序相同。

(注意以上代码需在.NET9 PreView4里面运行,vs开启preview版本方法:工具-》选项-》环境-》预览功能->使用.NET SDK预览版勾选,下载.NET9 Preivew4安装,重启VS即可),结果打印如下:

101hello

原理

原理其实也比较简单,以GetSetPrivateField为例(也可以看看CLR的GenerateAccessor)。这个函数里面被roslyn compile了一个.cctor

.method public hidebysig static !V&  GetSetPrivateField(class ConsoleApp1.Program/Class`1<!V> c) cil managed{  .custom instance void [System.Runtime]System.Runtime.CompilerServices.UnsafeAccessorAttribute::.ctor(valuetype [System.Runtime]System.Runtime.CompilerServices.UnsafeAccessorKind) = ( 01 00 03 00 00 00 01 00 54 004 461 665 06   // ........T..Name.                                                                                                                                                                                     5F 66 69 65 6C 64 )                               // _field

JIT导入加载之后,会被识别出它是byref(即C#里的ref),它的加载实际上是.ctor的引用结果放到栈(ldflda)上

IL to import:IL_0000  02                ldarg.0IL_0001  7c 01 00 00 0a    ldflda       0xA000001IL_0006  2a

然后识别,看到此时JIT已经知道它是byref了

*************** In compInitDebuggingInfo() for ConsoleApp1.Program+Accessors`1[int]:GetSetPrivateField(ConsoleApp1.Program+Class`1[int]):byref*************** In fgFindBasicBlocks() for ConsoleApp1.Program+Accessors`1[int]:GetSetPrivateField(ConsoleApp1.Program+Class`1[int]):byref

下面要做的就是把它变成_filed的引用,如下图:

STMT00000 ( 0x000[E-] ... ??? )               [000002] ---X-------                         *  RETURN    byref               [000001] ---X-------                         \--*  FIELD_ADDR byref  ConsoleApp1.Program+Class`1[int]:_field

如此即可通过UnsafeAccessorAttribute特性访问_field字段了。那么JIT实际上是把GetSetPrivateField变形成了如下:

伪代码:ref GetSetPrivateField(){    return c1._field;}

结尾

finsh here

往期精彩回顾

微软古早漏洞:永恒之蓝

2024年5月Tiobe编程语言排行榜:Go会挤掉C#吗?


江湖评谈
记录,分享,自由。
 最新文章