Neden bir türün başlatıcısı bulmak bir NullReferenceException oluşturur?


194

Bu beni çok üzdü. Bazı tip başlatıcı kontrolümüz olan Noda Time için bazı testleri optimize etmeye çalışıyordum. Ben bir tür olup olmadığını öğrenmek düşündüm sahiptir yeni yüklenirken her şeyi önce başlatıcı türü (statik yapıcısı veya başlatıcıları ile statik değişkenler) AppDomain. Sürpriz olarak, bu küçük testi attı NullReferenceException- hiçbir boş değerler rağmen orada benim kod. Bu sadece hiçbir hata ayıklama bilgileri ile derlenmiş zaman istisna atar.

İşte sorunu göstermek için kısa ama eksiksiz bir program:

using System;

class Test
{
    static Test() {}

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

Ve bir derleme ve çıktı transkripti:

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

Şimdi burada alakalı olabilecek .NET 4.5 (sürüm adayı) kullandığımı fark edeceksiniz . Çeşitli diğer orijinal çerçevelerle (özellikle "vanilya" .NET 4) test etmek benim için biraz zor ama başkalarının diğer çerçevelere sahip makinelere kolay erişimi varsa, sonuçlarla ilgilenirim.

Diğer detaylar:

  • Bir x64 makinesindeyim, ancak bu sorun hem x86 hem de x64 derlemelerinde oluşur
  • Fark yaratan arama kodunun "hata ayıklaması" - yukarıdaki test durumunda kendi montajında ​​test etmesine rağmen, bunu Noda Time'a karşı denediğimde NodaTime.dll, farklılıkları görmek için yeniden derlemek zorunda kalmadım - sadece Test.csona atıfta bulundu.
  • Mono 2.10.8 üzerine montaj "kırık" Running gelmez atmak

Herhangi bir fikir? Çerçeve hatası?

EDIT: Meraklı ve meraklı. Eğer dışarı alırsak Console.WriteLineçağrıyı:

using System;

class Test
{
    static Test() {}

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

Artık yalnızca derlendiğinde başarısız oluyor csc /o- /debug-. Optimizasyonları açarsanız ( /o+) çalışır. Ancak Console.WriteLineçağrıyı orijinaline göre eklerseniz , her iki sürüm de başarısız olur.


92
Heh - "kodumda boş değer olmamasına rağmen" bu kaydedilen SO geçmişinde ilk kez "hata kodumda değil" kartının başarılı bir şekilde oynandığı olabilir.
Marc Gravell

1
.NET 4 Framework, Visual C # derleyicisi ile cmdline'dan ilk testi yapmadan Hata Ayıklama yapılmadan True döndürür 4.0.30319.1
Kerry

2
@MarcGravell: Normalde "hiçbir hata yoktur demenin çok şüpheci olduğum sürece Evet, benim bu durumda, kod" Orada tehlikede tek ifadesidir, ve ne zaman istisna olduğu NullReferenceException(hangi hep bir hata belirtir) gerçekten öyle tehlikeli görünüyorsun. Bu ise şiddetle şüpheli olan bir .NET 4.5 hata, ben ... o sabit alma için pencere kaçırdığınızı
Jon Skeet

15
@JonSkeet: Hepimiz MS'in SP1'inin gerçek RTM olduğunu biliyoruz; p
leppie

1
@ leppie: Hayır, csc /o+ /debug- Test.csbenim için de başarısız oluyor, bu tuhaf.
Jon Skeet

Yanıtlar:


284

ile 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]Ne zaman yüklenmeye çalışılacağı @rsiNULL. İşlevi inceleyelim:

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)
...

@rsibaşından itibaren yüklenir, bu [rsp+20h]yüzden arayan tarafından geçirilmelidir. Arayan kişiye bakalım:

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

(Benim demonte gösterir System.Console.get_Inçünkü Console.GetLine()hata ayıklayıcı kırmak için bir test.cs bir ekledi . Davranışı değiştirmez doğruladı).

Bu çağrıdayız: 000007fe8d45010c 41ff5228 call qword ptr [r10+28h](AV çerçeve ret adresimiz bundan sonraki talimattır call).

Bunu derlediğimiz zamankiyle karşılaştıralım csc /debug test.cs. bp 000007fee5735360Neyse ki, aynı adrese modül yükleri kurabiliriz . Yüklenen talimatta @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 olduğunu unutmayın . Fonksiyonun üzerinden geçilmesi, bunun daha sonra kötü exe bombalarının (yani @rsideğişmediği) yerinden çıkarılacak adresin olduğunu gösterir . Yığın çok ilginç çünkü ekstra bir çerçeve gösteriyor :

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

Çağrı, call qword ptr [r10+28h]daha önce gördüğümüzle aynı , bu yüzden kötü durumda bu işlev muhtemelen satır içine alındı Main(), bu yüzden fazladan bir çerçeve olması kırmızı bir ringa balığı. Bu hazırlanmasında bakarsak call qword ptr [r10+28h]bu talimat dikkat edin: mov qword ptr [rsp+20h],rcx. Sonunda kayıt dışı bırakılan adresi yükleyen budur @rsi. İyi durumda, bu nasıl @rcxyüklenir:

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

Kötü durumda çok farklı görünüyor:

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

Bu çok farklı. CORINFO_HELP_GETSHARED_GCSTATIC_BASE'i çağıran ve 1F0dönüş yapısındaki ofsette bazı üyelerden AV'ye neden olan kritik işaretçiyi okuyan iyi durumdan farklı olarak , optimize edilmiş kod statik bir adresten yükler. Ve tabii ki 12721220h NULL içerir:

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

Ne yazık ki şu anda daha derine inmek için çok geç, sökülmesi CORINFO_HELP_GETSHARED_GCSTATIC_BASEönemsiz olmaktan çok uzak. Bunu CLR içlerinde daha bilgili birinin mantıklı olabileceğini umuyorum.


46
Hata ayıklama becerileriniz için bundan çok daha fazla tekrar hak ediyorsunuz.
JSB ձոգչ

23
Bu bir optimize edici hatadır. CORINFO * bir işlev işaretçisidir, JIT_GetSharedGCStaticBase öğesini çağırır. Benim tahminim, yeni 4.5 arka plan jit özelliği tarafından tetiklendi ve başlatılmadan önce bir alana erişerek, sınıftan çıkmayı unutuyor. Bunu connect.microsoft.com adresinde bildirin
Hans Passant

28
Gerek yok. Biz zaten bakıyoruz. Kesinlikle haklısınız, olan şey şu ki, doğrudan Type ReakType örneğini doğrudan çağırmayacağımız için Type.EmptyTypes boş kalır ve GetConstructor'a iletilen şey budur.
Kirill Osenkov

3
Bu hata ayıklama becerilerini elde etmek için okuyabileceğim bir kitap var mı? (Tercihen "Aptallar Rehberi" ile başlayan veya "Aptallar için" ile biten)
Igby Largeman

1
@IgbyLargeman: Gelişmiş Windows Hata Ayıklama oldukça iyi.
Remus Rusanu

10

Sorunla ilgili bazı yeni ilginç bulgular bulduğuma inandığım için, onları bir cevap olarak eklemeye karar verdim, aynı zamanda orijinal sorudaki "neden olduğunu" ele almadıklarını da kabul ettim . Belki de ilgili türlerin iç işleri hakkında daha fazla bilgi sahibi olan biri, yayınladığım gözlemlere de dayanarak bir düzenleme cevabı gönderebilir.

Ayrıca sorunu makinemde yeniden oluşturmayı başardım ve sınıf tarafından uygulanan System.Runtime.InteropServices._Type Arabirimi ile bir bağlantı izledim System.Type.

Başlangıçta, sorunu çözmek için en az 3 geçici çözüm buldum:

  1. Basitçe döküm yoluyla Typeiçin _Typeiçeride Mainyöntemle:

    var cctor = ((_Type)typeof(Test)).TypeInitializer;
  2. Veya yaklaşım 1'in daha önce yöntemin içinde kullanıldığından emin olun:

    var warmUp = ((_Type)typeof(Test)).TypeInitializer; 
    var cctor = ((Type)typeof(Test)).TypeInitializer;
  3. Veya Testsınıfa statik bir alan ekleyerek ve başlatarak (bunu yayınlayarak _Type):

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

Daha sonra, System.Runtime.InteropServices._Typearabirimi geçici çözümlere dahil etmek istemiyorsak , sorunun şu şekilde de oluşmadığını keşfettim :

  1. TestSınıfa statik bir alan ekleme ve başlangıç durumuna getirme (dönüştürmeden _Type):

    static ConstructorInfo _dummy2 = typeof(object).TypeInitializer;
  2. Veya cctordeğişkenin kendisini sınıfın statik alanı olarak başlatarak:

    static ConstructorInfo cctor = typeof(Test).TypeInitializer;

Geri bildiriminizi dört gözle bekliyorum.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.