為什麼查找類型的初始化程序會引發 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:\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 程序集都會出現此問題
- 呼叫程式碼的“調試性”會有所不同——儘管在上面的測試案例中它是在自己的程序集中對其進行測試,但當我針對 Noda Time 進行嘗試時,我不必重新編譯
NodaTime.dll即可看到差異——只是Test.cs其中提到了它。- 在 Mono 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(我的反彙編顯示是
System.Console.get_In因為我Console.GetLine()在 test.cs 中添加了一個有機會中斷調試器。我驗證它不會改變行為)。我們在這個呼叫中:(
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 並讀取最終作為導致 AV 從
1F0返回結構中的某個成員偏移的關鍵指針的好案例不同,優化的程式碼從靜態地址載入它。當然 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 內部知識更了解的人能說得通(如您所見,我真的只從本機指令 POV 考慮了這個問題,完全忽略了 IL)。