Ini membuat saya bingung. Saya mencoba untuk mengoptimalkan beberapa tes untuk Noda Time, di mana kami memiliki beberapa jenis initializer checking. Saya pikir saya akan mencari tahu apakah suatu tipe memiliki tipe initializer (konstruktor statis atau variabel statis dengan initializers) sebelum memuat semuanya ke yang baru AppDomain
. Yang mengejutkan saya, tes kecil ini melemparkan NullReferenceException
- meskipun tidak ada nilai null dalam kode saya . Ini hanya melempar pengecualian ketika dikompilasi tanpa informasi debug.
Berikut ini adalah program singkat namun lengkap untuk menunjukkan masalahnya:
using System;
class Test
{
static Test() {}
static void Main()
{
var cctor = typeof(Test).TypeInitializer;
Console.WriteLine("Got initializer? {0}", cctor != null);
}
}
Dan transkrip kompilasi dan keluaran:
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
Sekarang Anda akan melihat saya menggunakan .NET 4.5 (kandidat rilis) - yang mungkin relevan di sini. Agak sulit bagi saya untuk mengujinya dengan berbagai kerangka kerja asli lainnya (khususnya "vanilla". NET 4) tetapi jika ada orang lain yang memiliki akses mudah ke mesin dengan kerangka kerja lain, saya akan tertarik dengan hasilnya.
Detail lainnya:
- Saya menggunakan mesin x64, tetapi masalah ini terjadi pada rakitan x86 dan x64
- Itu adalah "debug-ness" dari kode panggilan yang membuat perbedaan - meskipun dalam kasus uji di atas itu menguji itu pada perakitan sendiri, ketika saya mencoba ini terhadap Noda Time saya tidak perlu mengkompilasi ulang
NodaTime.dll
untuk melihat perbedaan - hanyaTest.cs
yang disebut itu. - Menjalankan rakitan "rusak" di Mono 2.10.8 tidak melempar
Ada ide? Kerangka bug?
EDIT: Ingin tahu dan ingin tahu. Jika Anda menerima Console.WriteLine
panggilan:
using System;
class Test
{
static Test() {}
static void Main()
{
var cctor = typeof(Test).TypeInitializer;
}
}
Sekarang hanya gagal ketika dikompilasi dengan csc /o- /debug-
. Jika Anda mengaktifkan optimasi, ( /o+
) itu berhasil. Tetapi jika Anda memasukkan Console.WriteLine
panggilan sesuai aslinya, kedua versi akan gagal.
NullReferenceException
(yang harus selalu menunjukkan bug) itu benar-benar terlihat cerdik. Saya sangat curiga jika ini adalah bug .NET 4.5, saya telah melewatkan jendela untuk memperbaikinya ...csc /o+ /debug- Test.cs
gagal juga untuk saya, yang aneh.Jawaban:
dengan
csc test.cs
:Mencoba memuat dari
[rsi+8]
kapan@rsi
NULL. Mari kita periksa fungsinya:@rsi
dimuat di awal dari[rsp+20h]
sehingga harus dilewati oleh pemanggil. Mari kita lihat si penelepon:(Pembongkaran saya menunjukkan
System.Console.get_In
karena saya menambahkanConsole.GetLine()
dalam test.cs untuk memiliki kesempatan untuk istirahat di debugger. Saya memvalidasi itu tidak mengubah perilaku).Kami dalam panggilan ini:
000007fe8d45010c 41ff5228 call qword ptr [r10+28h]
(alamat retensi bingkai AV kami adalah instruksi tepat setelah inicall
).Mari kita bandingkan ini dengan apa yang terjadi ketika kita kompilasi
csc /debug test.cs
. Kita dapat mengaturbp 000007fee5735360
, untungnya modul memuat di alamat yang sama. Pada instruksi yang memuat@rsi
:Perhatikan bahwa
@rsi
00000000002debd8. Melangkah melalui fungsi menunjukkan bahwa ini alamat yang akan disensor nanti di tempat ketika bom exe buruk (mis.@rsi
Tidak berubah). Tumpukan ini sangat menarik karena menunjukkan bingkai tambahan :Panggilannya sama dengan
call qword ptr [r10+28h]
yang telah kita lihat sebelumnya, jadi dalam kasus buruk fungsi ini mungkin diuraikan dalamMain()
, jadi fakta bahwa ada bingkai tambahan adalah herring merah. Jika kita melihat persiapan inicall qword ptr [r10+28h]
kita melihat instruksi ini:mov qword ptr [rsp+20h],rcx
. Inilah yang memuat alamat yang akhirnya menjadi dereferensi@rsi
. Dalam kasus yang baik, ini adalah bagaimana@rcx
dimuat:Dalam kasus buruk itu terlihat sangat berbeda:
Ini sangat berbeda. Tidak seperti kasus bagus yang memanggil CORINFO_HELP_GETSHARED_GCSTATIC_BASE dan membaca apa yang berakhir sebagai pointer kritis yang menyebabkan AV dari beberapa anggota diimbangi
1F0
dalam struktur pengembalian, kode yang dioptimalkan memuatnya dari alamat statis. Dan tentu saja 12721220h berisi NULL:Sayangnya sudah terlambat bagi saya untuk menggali lebih dalam sekarang, ketidaksepakatan
CORINFO_HELP_GETSHARED_GCSTATIC_BASE
jauh dari sepele. Saya memposting ini dengan harapan seseorang yang lebih berpengetahuan di CLR internal dapat masuk akal (seperti yang Anda lihat, saya benar-benar mempertimbangkan masalah ini hanya dari instruksi asli POV dan IL yang sepenuhnya diabaikan).sumber
Karena saya percaya saya telah menemukan beberapa temuan baru yang menarik tentang masalah tersebut, saya memutuskan untuk menambahkannya sebagai jawaban, mengakui pada saat yang sama bahwa mereka tidak membahas "mengapa itu terjadi" dalam pertanyaan awal. Mungkin seseorang yang tahu lebih banyak tentang cara kerja internal dari tipe-tipe yang terlibat mungkin memposting jawaban yang membangun berdasarkan juga pada pengamatan yang saya posting.
Saya juga berhasil mereproduksi masalah pada mesin saya dan saya telah melacak koneksi dengan System.Runtime.InteropServices._Type Interface , yang diimplementasikan oleh
System.Type
kelas.Awalnya, saya telah menemukan setidaknya 3 pendekatan penyelesaian untuk memperbaiki masalah:
Cukup dengan melemparkan
Type
ke_Type
dalamMain
metode:Atau memastikan bahwa pendekatan 1 digunakan sebelumnya di dalam metode:
Atau dengan menambahkan bidang statis ke
Test
kelas dan menginisialisasi (dengan casting ke_Type
):Kemudian, saya menemukan bahwa jika kita tidak ingin melibatkan
System.Runtime.InteropServices._Type
antarmuka dalam workaround, masalahnya tidak terjadi dengan:Menambahkan bidang statis ke
Test
kelas dan menginisialisasi (tanpa membuangnya_Type
):Atau dengan menginisialisasi
cctor
variabel itu sendiri sebagai bidang statis kelas:Saya menantikan tanggapan Anda.
sumber