Das hat mich verblüfft. Ich habe versucht, einige Tests für Noda Time zu optimieren, bei denen wir eine Überprüfung des Typinitialisierers durchführen. Ich dachte , dass ich herausfinden, ob ein Typ hat eine Typeninitialisierer (statischen Konstruktor oder statische Variablen mit initializers) vor dem Laden alles in eine neuen AppDomain
. Zu meiner Überraschung warf ein kleiner Test dies NullReferenceException
- obwohl mein Code keine Nullwerte enthält. Es nur wirft die Ausnahme , wenn ohne Debug - Informationen kompiliert.
Hier ist ein kurzes, aber vollständiges Programm, um das Problem zu demonstrieren:
using System;
class Test
{
static Test() {}
static void Main()
{
var cctor = typeof(Test).TypeInitializer;
Console.WriteLine("Got initializer? {0}", cctor != null);
}
}
Und eine Abschrift der Zusammenstellung und Ausgabe:
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
Jetzt werden Sie feststellen, dass ich .NET 4.5 (den Release-Kandidaten) verwende - was hier möglicherweise relevant ist. Es ist etwas schwierig für mich, es mit den verschiedenen anderen Original-Frameworks (insbesondere "Vanilla" .NET 4) zu testen, aber wenn jemand anderen einfachen Zugriff auf Maschinen mit anderen Frameworks hat, würde mich das Ergebnis interessieren.
Andere Details:
- Ich bin auf einem x64-Computer, aber dieses Problem tritt sowohl bei x86- als auch bei x64-Assemblys auf
- Es ist das "Debug-ness" des aufrufenden Codes, das einen Unterschied macht - obwohl es im obigen Testfall auf einer eigenen Assembly getestet wurde, als ich dies gegen Noda Time versuchte, musste ich nicht neu kompilieren
NodaTime.dll
, um die Unterschiede zu sehen - genau das,Test.cs
worauf es sich bezog. - Das Ausführen der "defekten" Assembly unter Mono 2.10.8 wirft nicht
Irgendwelche Ideen? Framework-Fehler?
EDIT: Neugieriger und neugieriger. Wenn Sie den Console.WriteLine
Anruf entgegennehmen:
using System;
class Test
{
static Test() {}
static void Main()
{
var cctor = typeof(Test).TypeInitializer;
}
}
Es schlägt jetzt nur fehl, wenn mit kompiliert csc /o- /debug-
. Wenn Sie Optimierungen aktivieren, /o+
funktioniert ( ). Wenn Sie den Console.WriteLine
Aufruf jedoch wie im Original einschließen, schlagen beide Versionen fehl.
NullReferenceException
(der immer auf einen Fehler hinweisen sollte ), tut er dies wirklich sieh zwielichtig aus. Ich vermute stark , wenn dies ist ein .NET 4.5 Fehler, habe ich das Fenster verpaßt für immer es fest ...csc /o+ /debug- Test.cs
scheitert auch für mich, was seltsam ist.Antworten:
mit
csc test.cs
:Der Versuch, ab
[rsi+8]
wann zu laden,@rsi
ist NULL. Lassen Sie uns die Funktion überprüfen:@rsi
wird am Anfang von geladen,[rsp+20h]
so dass es vom Anrufer übergeben werden muss. Schauen wir uns den Anrufer an:(Meine Disassemblierung wird angezeigt,
System.Console.get_In
weil ich eineConsole.GetLine()
in test.cs hinzugefügt habe, um die Möglichkeit zu haben, den Debugger einzuschalten. Ich habe überprüft, dass dies das Verhalten nicht ändert.)Wir sind in diesem Aufruf:
000007fe8d45010c 41ff5228 call qword ptr [r10+28h]
(Unsere AV-Frame-Ret-Adresse ist die Anweisung direkt danachcall
).Vergleichen wir dies mit dem, was beim Kompilieren passiert
csc /debug test.cs
. Wir können ein einrichtenbp 000007fee5735360
, zum Glück wird das Modul an derselben Adresse geladen. Auf die Anweisung, die geladen wird@rsi
:Beachten Sie, dass dies
@rsi
00000000002debd8 ist. Das Durchlaufen der Funktion zeigt, dass dies die Adresse ist, die später an der Stelle dereferenziert wird, an der sich die schlechten Exe-Bomben befinden (dh@rsi
sich nicht ändern). Der Stapel ist sehr interessant, weil er einen zusätzlichen Rahmen zeigt :Der Aufruf ist derselbe
call qword ptr [r10+28h]
, den wir zuvor gesehen haben. Im schlimmsten Fall wurde diese Funktion wahrscheinlich in den eingefügt.Main()
Die Tatsache, dass es einen zusätzlichen Rahmen gibt, ist also ein roter Hering. Wenn wir uns die Vorbereitung ansehen,call qword ptr [r10+28h]
bemerken wir diese Anweisung :mov qword ptr [rsp+20h],rcx
. Dies ist es, was die Adresse lädt, die schließlich dereferenziert wird@rsi
. Im guten Fall wird@rcx
folgendermaßen geladen:Im schlimmsten Fall sieht es ganz anders aus:
Das ist ganz anders. Im Gegensatz zu dem guten Fall, der CORINFO_HELP_GETSHARED_GCSTATIC_BASE aufruft und liest, was als kritischer Zeiger endet, der den AV von einem Mitglied mit Offset
1F0
in einer Rückgabestruktur verursacht, lädt der optimierte Code ihn von einer statischen Adresse. Und natürlich enthält 12721220h NULL:Leider ist es zu spät für mich, jetzt tiefer zu graben, die Zerlegung von
CORINFO_HELP_GETSHARED_GCSTATIC_BASE
ist alles andere als trivial. Ich poste dies in der Hoffnung, dass jemand, der sich mit CLR-Interna besser auskennt, Sinn machen kann (wie Sie sehen können, habe ich das Problem wirklich nur anhand der nativen Anweisungen POV betrachtet und IL vollständig ignoriert).quelle
Da ich glaube, einige neue interessante Erkenntnisse über das Problem gefunden zu haben, habe ich beschlossen, sie als Antwort hinzuzufügen, wobei ich gleichzeitig anerkannte, dass sie das "Warum passiert es" in der ursprünglichen Frage nicht ansprechen . Vielleicht kann jemand, der mehr über die internen Abläufe der beteiligten Typen weiß, eine erbauliche Antwort veröffentlichen, die auch auf den Beobachtungen basiert, die ich veröffentliche.
Ich habe es auch geschafft, das Problem auf meinem Computer zu reproduzieren, und ich habe eine Verbindung mit der System.Runtime.InteropServices._Type-Schnittstelle verfolgt , die von der
System.Type
Klasse implementiert wird .Anfangs habe ich mindestens drei Problemumgehungsansätze zur Behebung des Problems gefunden:
Einfach durch Casting der
Type
to_Type
innerhalb derMain
Methode:Oder stellen Sie sicher, dass Ansatz 1 zuvor innerhalb der Methode verwendet wurde:
Oder indem Sie der
Test
Klasse ein statisches Feld hinzufügen und es initialisieren (mit Umwandeln in_Type
):Später stellte ich fest, dass
System.Runtime.InteropServices._Type
das Problem auch nicht auftritt , wenn wir die Schnittstelle nicht in die Problemumgehungen einbeziehen möchten :Hinzufügen eines statischen Felds zur
Test
Klasse und Initialisieren (ohne Umwandeln in_Type
):Oder indem Sie die
cctor
Variable selbst als statisches Feld der Klasse initialisieren :Ich freue mich auf Ihr Feedback.
quelle