왜 타입의 이니셜 라이저를 발견하면 NullReferenceException이 발생합니까?


194

이것은 나를 혼란스럽게했다. Noda Time에 대한 테스트를 최적화하려고 시도했습니다. 여기서 유형 초기화 프로그램 검사가 있습니다. 나는 new에 모든 것을로드하기 전에 유형에 유형 이니셜 라이저 (정적 생성자 또는 이니셜 라이저 있는 정적 변수) 있는지 알아낼 것이라고 생각했습니다 AppDomain. 놀랍게도 코드에 NullReferenceExceptionnull 값이 없지만 작은 테스트가 실패했습니다 . 그것은 단지 디버그 정보를 컴파일 할 때 예외가 발생합니다.

다음은 문제를 설명하기위한 짧지 만 완전한 프로그램입니다.

using System;

class Test
{
    static Test() {}

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

그리고 컴파일과 출력의 사본 :

c:\Users\Jon\Test>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:\Users\Jon\Test>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:\Users\Jon\Test>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:\Users\Jon\Test>test
Got initializer? True

이제 .NET 4.5 (릴리스 후보)를 사용하고 있음을 있습니다. 다양한 다른 원래 프레임 워크 (특히 "vanilla".NET 4)로 테스트하는 것이 다소 까다 롭지 만 다른 프레임 워크가있는 컴퓨터에 쉽게 액세스 할 수 있다면 결과에 관심이 있습니다.

기타 세부 사항:

  • x64 컴퓨터에 있지만 x86 및 x64 어셈블리에서이 문제가 발생합니다.
  • 그것은의 "디버그 다움"의 호출 차이를 만드는 코드 - 나는 다시 컴파일 할 필요가 없었다 노다 시간 I에 대해이 시도 때도 위의 테스트 케이스에 불구하고, 자신의 어셈블리를 테스트하는 것 NodaTime.dll의 차이를 볼 수 - 그냥 Test.cs그것을 참조했다.
  • 모노 2.10.8에 조립 "깨진"실행 하지 않는 던져

어떤 아이디어? 프레임 워크 버그?

편집 : 호기심과 호기심. Console.WriteLine전화 를받는 경우 :

using System;

class Test
{
    static Test() {}

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

로 컴파일 할 때만 실패합니다 csc /o- /debug-. 최적화를 켜면 ( /o+) 작동합니다. 그러나 Console.WriteLine원본에 따라 통화 를 포함하면 두 버전 모두 실패합니다.


92
Heh- "내 코드에 null 값이 없더라도"실제로 "SOC 코드에 버그가 없습니다"카드가 성공적으로 재생 된 것으로 기록 된 SO 역사상 처음 일 수 있습니다.
Marc Gravell

1
.NET 4 Framework, Visual C # 컴파일러 4.0.30319.1를 사용하여 cmdline에서 첫 번째 테스트를 수행하는 디버그없이 True 만 반환합니다.
Kerry

2
@MarcGravell : 예, 일반적으로 " 코드 에는 버그가 없습니다"라는 말에 회의적 이지만, 단일 표현에 문제가 있고 예외는 NullReferenceException( 항상 버그를 나타내야 함) 실제로는 피하다 봐. 이 경우에 나는 강하게 의심 이다 닷넷 4.5 버그, 나는 ... 그것을 해결하기 위해 창을 놓친
존 소총

15
@ JonSkeet : 우리 모두는 MS의 SP1이 실제 RTM임을 알고 있습니다 .p
leppie

1
@ leppie : 아뇨, csc /o+ /debug- Test.cs나도 실패합니다.
Jon Skeet

답변:


284

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+8]언제 부터로드하려고하면 @rsiNULL입니다. 기능을 검사 할 수 있습니다.

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

( 디버거에서 침입 할 수있는 기회를 갖기 위해 test.cs에 in을 System.Console.get_In추가했기 때문에 분해가 표시 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

참고 @rsi00000000002debd8입니다. 이 기능을 단계별로 실행하면 잘못된 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사소한 것이 아닙니다. 나는 CLR 내부에 더 많은 지식을 가진 사람이 이해할 수 있기를 희망하여 이것을 게시하고 있습니다.


46
디버깅 기술에 대해 이보다 더 많은 담당자가 필요합니다.
JSB ձոգչ

23
이것은 최적화 버그입니다. CORINFO *는 함수 포인터이며 JIT_GetSharedGCStaticBase를 호출합니다. 내 생각에 그것은 새로운 4.5 background jit 기능에 의해 트립되고 초기화되기 전에 필드에 액세스하고 클래스를 치트하는 것을 잊어 버렸습니다. connect.microsoft.com에서보고
Hans Passant

28
필요 없음. 우리는 이미 찾고 있습니다. 우리가 RuntimeType의 인스턴스를 직접 할당하기 때문에 Type의 cctor가 호출되지 않으므로 Type.EmptyTypes는 null로 유지되며 GetConstructor에 전달됩니다.
Kirill Osenkov

3
이러한 디버깅 기술을 얻기 위해 읽을 수있는 책이 있습니까? (바람직하게 "Idiots Guide to"로 시작하거나 "
For

1
@IgbyLargeman : 고급 Windows 디버깅 은 꽤 좋습니다.
Remus Rusanu

10

문제에 대한 새로운 흥미로운 발견을 발견했다고 생각하면서, 나는 그들이 원래의 질문에서 "왜 그런지"를 다루지 않는다는 것을 인정하면서 그것들을 답으로 추가하기로 결정했습니다 . 어쩌면 관련된 유형의 내부 작업에 대해 더 많이 알고있는 사람은 내가 게시 한 관찰 내용을 기반으로 덕화 답변을 게시 할 수 있습니다.

또한 내 컴퓨터에서 문제를 재현 하고 클래스 로 구현 된 System.Runtime.InteropServices._Type Interface 와의 연결을 추적했습니다 System.Type.

처음에는 문제를 해결하기위한 적어도 3 가지 해결 방법이 있습니다.

  1. 메소드 내부 에를 캐스팅하면 Type됩니다 ._TypeMain

    var cctor = ((_Type)typeof(Test)).TypeInitializer;
  2. 또는 방법 1에서 방법 1을 이전에 사용했는지 확인하십시오.

    var warmUp = ((_Type)typeof(Test)).TypeInitializer; 
    var cctor = ((Type)typeof(Test)).TypeInitializer;
  3. 또는 Test클래스에 정적 필드를 추가 하고 초기화하여 (로 캐스팅 _Type) :

    static ConstructorInfo _dummy1 = (typeof(object) as _Type).TypeInitializer;

나중에 System.Runtime.InteropServices._Type해결 방법에 인터페이스 를 포함시키지 않으려는 경우 다음 중 한 가지 방법으로 문제가 발생하지 않음을 발견했습니다.

  1. Test클래스에 정적 필드 추가 및 초기화 (로 캐스팅하지 않고 _Type) :

    static ConstructorInfo _dummy2 = typeof(object).TypeInitializer;
  2. 또는 cctor변수 자체를 클래스의 정적 필드로 초기화하여 :

    static ConstructorInfo cctor = typeof(Test).TypeInitializer;

귀하의 의견을 기다리고 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.