为什么会发现一个类型的初始化器抛出一个NullReferenceException?

这让我很难过。 我试图优化Noda Time的一些测试,我们有一些类型初始化程序检查。 我想在将所有内容加载到一个新的AppDomain之前,我会发现一个类型是否有一个类型初始值设定项(静态构造函数或具有初始值设定项的静态变量)。 令我惊讶的是,尽管在我的代码中没有空值,但是对此的一个小测试抛出了NullReferenceException 。 它仅在编译时没有调试信息时抛出异常。

这是一个简短但完整的程序来演示这个问题:

using System;

class Test
{
    static Test() {}

    static void Main()
    {
        var cctor = typeof(Test).TypeInitializer;
        Console.WriteLine("Got initializer? {0}", cctor != null);
    }    
}

并编辑和输出成绩单:

c:UsersJonTest>csc Test.cs
Microsoft (R) Visual C# Compiler version 4.0.30319.17626
for Microsoft (R) .NET Framework 4.5
Copyright (C) Microsoft Corporation. All rights reserved.


c:UsersJonTest>test

Unhandled Exception: System.NullReferenceException: Object reference not set to
an instance of an object.
   at System.RuntimeType.GetConstructorImpl(BindingFlags bindingAttr, Binder bin
der, CallingConventions callConvention, Type[] types, ParameterModifier[] modifi
ers)
   at Test.Main()

c:UsersJonTest>csc /debug+ Test.cs
Microsoft (R) Visual C# Compiler version 4.0.30319.17626
for Microsoft (R) .NET Framework 4.5
Copyright (C) Microsoft Corporation. All rights reserved.


c:UsersJonTest>test
Got initializer? True

现在你会注意到我正在使用.NET 4.5(候选版本) - 这可能与此有关。 对其他各种原始框架(特别是“vanilla”.NET 4)进行测试对我来说有点棘手,但如果其他人可以轻松访问其他框架的机器,我会对结果感兴趣。

其他详情:

  • 我在x64机器上,但x86和x64程序集都出现此问题
  • 这是调用代码的“调试性”,它会产生变化 - 即使在上面的测试用例中它正在自己的程序集上进行测试,当我对Noda时间进行测试时,我不必重新编译NodaTime.dll即可看到不同之处 - 只是提到它的Test.cs
  • 在Mono 2.10.8上运行“破碎”程序集不会丢失
  • 有任何想法吗? 框架错误?

    编辑:Curiouser和curiouser。 如果你拿出Console.WriteLine调用:

    using System;
    
    class Test
    {
        static Test() {}
    
        static void Main()
        {
            var cctor = typeof(Test).TypeInitializer;
        }    
    }
    

    现在只有在使用csc /o- /debug-编译时才会失败。 如果你打开优化,( /o+ )就会起作用。 但是如果按照原始文件包含Console.WriteLine调用,则两个版本都将失败。


    csc test.cs

    (196c.1874): Access violation - code c0000005 (first chance)
    mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0xa3:
    000007fe`e5735403 488b4608        mov     rax,qword ptr [rsi+8] ds:00000000`00000008=????????????????
    

    @rsi为NULL时尝试从[rsi+8] @rsi 。 让我们来看看这个功能:

    0:000> ln 000007fe`e5735403
    (000007fe`e5735360)   mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0xa3
    0:000> uf 000007fe`e5735360
    Flow analysis was incomplete, some code may be missing
    mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[]):
    000007fe`e5735360 53              push    rbx
    000007fe`e5735361 55              push    rbp
    000007fe`e5735362 56              push    rsi
    000007fe`e5735363 57              push    rdi
    000007fe`e5735364 4154            push    r12
    000007fe`e5735366 4883ec30        sub     rsp,30h
    000007fe`e573536a 498bf8          mov     rdi,r8
    000007fe`e573536d 8bea            mov     ebp,edx
    000007fe`e573536f 48c744242800000000 mov   qword ptr [rsp+28h],0
    000007fe`e5735378 488bb42480000000 mov     rsi,qword ptr [rsp+80h]
    000007fe`e5735380 4889742420      mov     qword ptr [rsp+20h],rsi
    000007fe`e5735385 41b903000000    mov     r9d,3
    ...    
    mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0x97:
    000007fe`e57353f7 488b4b08        mov     rcx,qword ptr [rbx+8]
    000007fe`e57353fb 85c9            test    ecx,ecx
    000007fe`e57353fd 0f848e000000    je      mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0x131 (000007fe`e5735491)
    
    mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0xa3:
    000007fe`e5735403 488b4608        mov     rax,qword ptr [rsi+8]
    000007fe`e5735407 85c0            test    eax,eax
    000007fe`e5735409 7545            jne     mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0xf0 (000007fe`e5735450)
    ...
    

    @rsi[rsp+20h]开始加载,所以它必须由调用者传递。 让我们看看调用者:

    0:000> k3
    Child-SP          RetAddr           Call Site
    00000000`001fec70 000007fe`8d450110 mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0xa3
    00000000`001fecd0 000007fe`ecb6e073 image00000000_01120000!Test.Main()+0x60
    00000000`001fed20 000007fe`ecb6dcb2 clr!CoUninitializeEE+0x7ae1f
    0:000> ln 000007fe`8d450110
    (000007fe`8d4500b0)   image00000000_01120000!Test.Main()+0x60
    0:000> uf 000007fe`8d4500b0
    image00000000_01120000!Test.Main():
    000007fe`8d4500b0 53              push    rbx
    000007fe`8d4500b1 4883ec40        sub     rsp,40h
    000007fe`8d4500b5 e8a69ba658      call    mscorlib_ni!System.Console.get_In() (000007fe`e5eb9c60)
    000007fe`8d4500ba 4c8bd8          mov     r11,rax
    000007fe`8d4500bd 498b03          mov     rax,qword ptr [r11]
    000007fe`8d4500c0 488b5048        mov     rdx,qword ptr [rax+48h]
    000007fe`8d4500c4 498bcb          mov     rcx,r11
    000007fe`8d4500c7 ff5238          call    qword ptr [rdx+38h]
    000007fe`8d4500ca 488d0d7737eeff  lea     rcx,[000007fe`8d333848]
    000007fe`8d4500d1 e88acb715f      call    clr!CoUninitializeEE+0x79a0c (000007fe`ecb6cc60)
    000007fe`8d4500d6 4c8bd8          mov     r11,rax
    000007fe`8d4500d9 48b92012531200000000 mov rcx,12531220h
    000007fe`8d4500e3 488b09          mov     rcx,qword ptr [rcx]
    000007fe`8d4500e6 498b03          mov     rax,qword ptr [r11]
    000007fe`8d4500e9 4c8b5068        mov     r10,qword ptr [rax+68h]
    000007fe`8d4500ed 48c744242800000000 mov   qword ptr [rsp+28h],0
    000007fe`8d4500f6 48894c2420      mov     qword ptr [rsp+20h],rcx
    000007fe`8d4500fb 41b903000000    mov     r9d,3
    000007fe`8d450101 4533c0          xor     r8d,r8d
    000007fe`8d450104 ba38000000      mov     edx,38h
    000007fe`8d450109 498bcb          mov     rcx,r11
    000007fe`8d45010c 41ff5228        call    qword ptr [r10+28h]
    000007fe`8d450110 48bb1032531200000000 mov rbx,12533210h
    000007fe`8d45011a 488b1b          mov     rbx,qword ptr [rbx]
    000007fe`8d45011d 33d2            xor     edx,edx
    000007fe`8d45011f 488bc8          mov     rcx,rax
    000007fe`8d450122 e829452e58      call    mscorlib_ni!System.Reflection.ConstructorInfo.op_Equality(System.Reflection.ConstructorInfo, System.Reflection.ConstructorInfo) (000007fe`e5734650)
    000007fe`8d450127 0fb6c8          movzx   ecx,al
    000007fe`8d45012a 33c0            xor     eax,eax
    000007fe`8d45012c 85c9            test    ecx,ecx
    000007fe`8d45012e 0f94c0          sete    al
    000007fe`8d450131 0fb6c8          movzx   ecx,al
    000007fe`8d450134 894c2430        mov     dword ptr [rsp+30h],ecx
    000007fe`8d450138 488d542430      lea     rdx,[rsp+30h]
    000007fe`8d45013d 488d0d24224958  lea     rcx,[mscorlib_ni+0x682368 (000007fe`e58e2368)]
    000007fe`8d450144 e807246a5f      call    clr+0x2550 (000007fe`ecaf2550)
    000007fe`8d450149 488bd0          mov     rdx,rax
    000007fe`8d45014c 488bcb          mov     rcx,rbx
    000007fe`8d45014f e81cab2758      call    mscorlib_ni!System.Console.WriteLine(System.String, System.Object) (000007fe`e56cac70)
    000007fe`8d450154 90              nop
    000007fe`8d450155 4883c440        add     rsp,40h
    000007fe`8d450159 5b              pop     rbx
    000007fe`8d45015a c3              ret
    

    (我的反汇编显示System.Console.get_In因为我在test.cs中添加了一个Console.GetLine()以便有机会在调试器中破解。我验证它不会改变行为)。

    我们在这个电话中: 000007fe8d45010c 41ff5228 call qword ptr [r10+28h] (我们的AV帧ret地址就是此call后的指令)。

    让我们比较一下当我们编译csc /debug test.cs时会发生什么。 我们可以设置一个bp 000007fee5735360 ,幸运的是模块加载在相同的地址。 在加载@rsi的指令上:

    0:000> r
    rax=000007fee58e2f30 rbx=00000000027c6258 rcx=00000000027c6258
    rdx=0000000000000038 rsi=00000000002debd8 rdi=0000000000000000
    rip=000007fee5735378 rsp=00000000002de990 rbp=0000000000000038
     r8=0000000000000000  r9=0000000000000003 r10=000007fee58831c8
    r11=00000000002de9c0 r12=0000000000000000 r13=00000000002dedc0
    r14=00000000002dec58 r15=0000000000000004
    iopl=0         nv up ei pl nz na po nc
    cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0x18:
    000007fe`e5735378 488bb42480000000 mov     rsi,qword ptr [rsp+80h] ss:00000000`002dea10=a0627c0200000000
    

    请注意@rsi是00000000002debd8。 逐句通过该函数表明,这个地址将在稍后exe地址(即@rsi不会改变)的地方解除引用。 该堆栈非常有趣,因为它显示了一个额外的框架:

    0:000> k3
    Child-SP          RetAddr           Call Site
    00000000`002de990 000007fe`e5eddf68 mscorlib_ni!System.RuntimeType.GetConstructorImpl(System.Reflection.BindingFlags, System.Reflection.Binder, System.Reflection.CallingConventions, System.Type[], System.Reflection.ParameterModifier[])+0x18
    00000000`002de9f0 000007fe`8d460119 mscorlib_ni!System.Type.get_TypeInitializer()+0x48
    00000000`002dea30 000007fe`ecb6e073 good!Test.Main()+0x49*** WARNING: Unable to verify checksum for good.exe
    
    0:000> ln 000007fe`e5eddf68
    (000007fe`e5eddf20)   mscorlib_ni!System.Type.get_TypeInitializer()+0x48
    0:000> uf 000007fe`e5eddf20
    mscorlib_ni!System.Type.get_TypeInitializer():
    000007fe`e5eddf20 53              push    rbx
    000007fe`e5eddf21 4883ec30        sub     rsp,30h
    000007fe`e5eddf25 488bd9          mov     rbx,rcx
    000007fe`e5eddf28 ba22010000      mov     edx,122h
    000007fe`e5eddf2d b901000000      mov     ecx,1
    000007fe`e5eddf32 e8d1a075ff      call    CORINFO_HELP_GETSHARED_GCSTATIC_BASE (000007fe`e5638008)
    000007fe`e5eddf37 488b88f0010000  mov     rcx,qword ptr [rax+1F0h]
    000007fe`e5eddf3e 488b03          mov     rax,qword ptr [rbx]
    000007fe`e5eddf41 4c8b5068        mov     r10,qword ptr [rax+68h]
    000007fe`e5eddf45 48c744242800000000 mov   qword ptr [rsp+28h],0
    000007fe`e5eddf4e 48894c2420      mov     qword ptr [rsp+20h],rcx
    000007fe`e5eddf53 41b903000000    mov     r9d,3
    000007fe`e5eddf59 4533c0          xor     r8d,r8d
    000007fe`e5eddf5c ba38000000      mov     edx,38h
    000007fe`e5eddf61 488bcb          mov     rcx,rbx
    000007fe`e5eddf64 41ff5228        call    qword ptr [r10+28h]
    000007fe`e5eddf68 90              nop
    000007fe`e5eddf69 4883c430        add     rsp,30h
    000007fe`e5eddf6d 5b              pop     rbx
    000007fe`e5eddf6e c3              ret
    0:000> ln 000007fe`8d460119
    

    这个调用和我们以前见过的call qword ptr [r10+28h]是一样的,所以在坏的情况下,这个函数可能在Main()被内联,所以有一个额外的帧的事实是一个红色的鲱鱼。 如果我们看看这个call qword ptr [r10+28h]的准备,我们注意到这条指令: mov qword ptr [rsp+20h],rcx 。 这是加载最终解除引用为@rsi的地址。 在这种情况下,这就是@rcx加载的方式:

    000007fe`e5eddf32 e8d1a075ff      call    CORINFO_HELP_GETSHARED_GCSTATIC_BASE (000007fe`e5638008)
    000007fe`e5eddf37 488b88f0010000  mov     rcx,qword ptr [rax+1F0h]
    

    在不好的情况下,它看起来非常不同:

    000007fe`8d4600d9 48b92012721200000000 mov rcx,12721220h
    000007fe`8d4600e3 488b09          mov     rcx,qword ptr [rcx]
    

    这是非常不同的。 与调用CORINFO_HELP_GETSHARED_GCSTATIC_BASE并读取返回结构中的偏移量为1F0某些成员的AV的临界指针读取的内容不同,优化代码将从静态地址加载它。 当然12721220h包含NULL:

    0:000> dp 12721220h L8
    00000000`12721220  00000000`00000000 00000000`00000000
    00000000`12721230  00000000`00000000 00000000`02722198
    00000000`12721240  00000000`027221c8 00000000`027221f8
    00000000`12721250  00000000`02722228 00000000`02722258
    

    不幸的是,我现在深入挖掘已经太迟了,CORINFO_HELP_GETSHARED_GCSTATIC_BASE的CORINFO_HELP_GETSHARED_GCSTATIC_BASE远不是微不足道的。 我发布这篇文章的目的是希望有更多关于CLR内部知识的人能够理解(正如你所看到的,我真的认为这个问题仅仅来自本地指令POV,并完全忽略了IL)。


    因为我相信我发现了一些关于这个问题的新的有趣发现,所以我决定将它们作为答案添加进去,同时承认它们并未解决原始问题中的“为什么会发生” 。 也许一个对相关类型的内部工作有更多了解的人可能会根据我发布的观察结果发布一个启发性的答案。

    我也设法在我的机器上重现了这个问题,并且跟踪了由System.Type类实现的System.Runtime.InteropServices._Type接口的连接。

    最初,我发现至少有三种解决方法可以解决这个问题:

  • 只需在Main方法中将Type_Type

    var cctor = ((_Type)typeof(Test)).TypeInitializer;
    
  • 或者确保以前在方法中使用了方法1:

    var warmUp = ((_Type)typeof(Test)).TypeInitializer; 
    var cctor = ((Type)typeof(Test)).TypeInitializer;
    
  • 或者通过向Test类添加一个静态字段并初始化它(将其转换为_Type ):

    static ConstructorInfo _dummy1 = (typeof(object) as _Type).TypeInitializer;
    
  • 之后,我发现如果我们不想在变通方法中涉及System.Runtime.InteropServices._Type接口,则问题不会通过以下方式发生:

  • Test类中添加一个静态字段并对其进行初始化(而不是将其转换为_Type ):

    static ConstructorInfo _dummy2 = typeof(object).TypeInitializer;
    
  • 或者通过初始化cctor变量本身作为类的静态字段:

    static ConstructorInfo cctor = typeof(Test).TypeInitializer;
    
  • 我期待着您的反馈。

    链接地址: http://www.djcxy.com/p/68917.html

    上一篇: Why would finding a type's initializer throw a NullReferenceException?

    下一篇: Method throws null reference exception after returning non