[.net] 왜 타입의 이니셜 라이저를 발견하면 NullReferenceException이 발생합니까?
이것은 나를 혼란스럽게했다. Noda Time에 대한 테스트를 최적화하려고 시도했습니다. 여기서 유형 초기화 프로그램 검사가 있습니다. 나는 new에 모든 것을로드하기 전에 유형에 유형 이니셜 라이저 (정적 생성자 또는 이니셜 라이저 가 있는 정적 변수) 가 있는지 알아낼 것이라고 생각했습니다 AppDomain
. 놀랍게도 내 코드에 NullReferenceException
null 값이 없지만 작은 테스트가 실패했습니다 . 그것은 단지 디버그 정보를 컴파일 할 때 예외가 발생합니다.
다음은 문제를 설명하기위한 짧지 만 완전한 프로그램입니다.
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
원본에 따라 통화 를 포함하면 두 버전 모두 실패합니다.
답변
로 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]
언제 부터로드하려고하면 @rsi
NULL입니다. 기능을 검사 할 수 있습니다.
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
참고 @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
사소한 것이 아닙니다. 나는 CLR 내부에 더 많은 지식을 가진 사람이 이해할 수 있기를 희망하여 이것을 게시하고 있습니다.
답변
문제에 대한 새로운 흥미로운 발견을 발견했다고 생각하면서, 나는 그들이 원래의 질문에서 “왜 그런지”를 다루지 않는다는 것을 인정하면서 그것들을 답으로 추가하기로 결정했습니다 . 어쩌면 관련된 유형의 내부 작업에 대해 더 많이 알고있는 사람은 내가 게시 한 관찰 내용을 기반으로 덕화 답변을 게시 할 수 있습니다.
또한 내 컴퓨터에서 문제를 재현 하고 클래스 로 구현 된 System.Runtime.InteropServices._Type Interface 와의 연결을 추적했습니다 System.Type
.
처음에는 문제를 해결하기위한 적어도 3 가지 해결 방법이 있습니다.
-
메소드 내부 에를 캐스팅하면
Type
됩니다 ._Type
Main
var cctor = ((_Type)typeof(Test)).TypeInitializer;
-
또는 방법 1에서 방법 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;
귀하의 의견을 기다리고 있습니다.