Lernprogramm für "Ildasm.exe"
Dieses Lernprogramm enthält eine Einführung in den MSIL Disassembler-Tool (Ildasm.exe), der in .NET Framework SDK enthalten ist. Ildasm.exe analysiert alle EXE- sowie DLL-Assemblies von .NET Framework und zeigt die Informationen in einem lesbaren Format an. Ildasm.exe zeigt nicht nur den MSIL-Code (Microsoft Intermediate Language-Code), sondern auch Namespaces und Typen einschließlich der dazugehörigen Schnittstellen an. Sie können Ildasm.exe zum Überprüfen systemeigener .NET Framework-Assemblies, wie Mscorlib.dll, sowie zum Überprüfen von .NET Framework-Assemblies verwenden, die von anderen zur Verfügung gestellt oder von Ihnen erstellt wurden. Die meisten .NET Framework-Entwickler halten Ildasm.exe für unverzichtbar.
Verwenden Sie für dieses Lernprogramm die Visual C#-Version des WordCount-Beispiels, das mit dem SDK geliefert wird. Sie können auch die Visual Basic-Version verwenden, das erzeugte MSIL ist jedoch für die beiden Sprachen unterschiedlich, und die Bildschirmgrafik ist auch nicht identisch. WordCount befindet sich im Verzeichnis <FrameworkSDK>\Samples\Applications\WordCount\. Führen Sie zum Erstellen und Ausführen des Beispiels die Anweisungen in der Datei Readme.htm aus. In diesem Lernprogramm wird Ildasm.exe zum Überprüfen der Assembly WordCount.exe verwendet.
Erstellen Sie zunächst das WordCount-Beispiel, und laden Sie es mit Hilfe der folgenden Befehlszeile in Ildasm.exe:
ildasm WordCount.exe
Dadurch wird das in der folgenden Abbildung dargestellte Fenster Ildasm.exe aufgerufen.
In der Struktur im Fenster Ildasm.exe werden die in WordCount.exe enthaltenen Assemblymanifestinformationen sowie vier globale Klassentypen angezeigt: App, ArgParser, WordCountArgParser und WordCounter.
Wenn Sie auf einen der Typen in der Struktur doppelklicken, werden weitere Informationen zum Typ angezeigt. In der folgenden Abbildung wurde der Klassentyp WordCounter erweitert.
In dieser Abbildung werden alle WordCounter-Member angezeigt. Die folgende Tabelle enthält eine Beschreibung der grafischen Symbole.
Symbol | Bedeutung |
---|---|
![]() |
Weitere Informationen |
![]() |
Namespace |
![]() |
Klasse |
![]() |
Schnittstelle |
![]() |
Werteklasse |
![]() |
Enum |
![]() |
Methode |
![]() |
Statische Methode |
![]() |
Feld |
![]() |
Statisches Feld |
![]() |
Ereignis |
![]() |
Eigenschaft |
![]() |
Manifest oder ein Klasseninformationselement |
Wenn Sie auf den Eintrag .class public auto ansi beforefieldinit doppelklicken, werden folgende Informationen angezeigt:
Anhand dieser Abbildung können Sie sofort sehen, dass der Typ WordCounter vom Typ System.Object abgeleitet ist.
Der Typ WordCounter enthält einen anderen Typ mit der Bezeichnung WordOccurrence. Sie können den Typ WordOccurrence erweitern, um dessen Member anzuzeigen (siehe folgende Abbildung).
Anhand der Struktur können Sie sehen, dass WordOccurrence die System.IComparable-Schnittstelle implementiert, insbesondere die CompareTo-Methode. In dem verbleibenden Teil dieser Konversation wird der Typ WordOccurrence ignoriert und stattdessen der Typ WordCounter beachtet.
Wie Sie sehen, enthält der Typ WordCounter fünf private-Felder: totalBytes, totalChars, totalLines, totalWords und wordCounter. Bei den ersten vier Feldern handelt es sich um Instanzen vom Typ int64, während das Feld wordCounter ein Verweis auf einen Typ System.Collections.SortedList ist.
Wenn Sie den Feldern folgen, können Sie die Methoden sehen. Bei der ersten Methode (.ctor) handelt es sich um einen Konstruktor. Dieser Typ hat zwar nur einen Konstruktor, andere Typen können jedoch über mehrere Konstruktoren verfügen, die jeweils eine andere Signatur aufweisen. Der WordCounter-Konstruktor hat den Rückgabetyp void (wie alle Konstruktoren) und akzeptiert keine Parameter. Wenn Sie auf die Konstruktormethode doppelklicken, wird ein neues Fenster angezeigt, in dem der in der Methode enthaltende MSIL-Code angezeigt wird (siehe folgende Abbildung).
MSIL-Code kann problemlos gelesen und verstanden werden kann. (Sie finden die gesamten Details in der Spezifikation CIL Instruction Set, die sich in der Datei Partition III CIL.doc im Ordner <FrameworkSDK>\Tool Developers Guide\Docs befindet.) Anhand des oberen Bereichs können Sie sehen, dass für diesen Konstruktor 50 Bytes MSIL-Code erforderlich sind. Anhand dieser Zahl können Sie nicht erkennen, wie viel systemeigener Code vom JIT-Compiler ausgegeben wird, da die Größe von der Host-CPU und von dem zum Erstellen des Codes verwendeten Compiler abhängt.
Die Common Language Runtime ist stapelbasiert. Um Operationen ausführen zu können, legt der MSIL-Code daher zunächst die Operanden mittels Push auf einem virtuellen Stapel ab und führt anschließend den Operator aus. Der Operator zieht die Operanden vom Stapel, führt die erforderliche Operation aus und legt das Ergebnis wieder auf dem Stapel ab. Bei dieser Methode werden maximal acht Operanden gleichzeitig mittels Push auf dem virtuellen Stapel abgelegt. Sie können diese Zahl anhand des .maxstack-Attributs überprüfen, das direkt vor dem MSIL-Code angezeigt wird.
Betrachten Sie nun die ersten MSIL-Anweisungen, die in den folgenden vier Zeilen wiedergegeben werden:
IL_0000: ldarg.0 ; Load the object's 'this' pointer on the stack
IL_0001: ldc.i4.0 ; Load the constant 4-byte value of 0 on the stack
IL_0002: conv.i8 ; Convert the 4-byte 0 to an 8-byte 0
IL_0003: stfld int64 WordCounter::totalLines
Die Anweisung bei IL_0000 lädt den ersten Parameter, der an die Methode auf dem virtuellen Stapel übergeben wurde. An jede Instanzmethode wird stets die Adresse des Objektspeichers übergeben. Dieses Argument wird als "Argument Zero" bezeichnet und nie explizit in der Signatur der Methode angezeigt. Obwohl es also so aussieht, als ob die .ctor-Methode 0 Argumente empfängt, empfängt sie tatsächlich ein Argument. Anschließend lädt die Anweisung bei IL_0000 den Zeiger auf dieses Objekt auf den virtuellen Stapel.
Die Anweisung bei IL_0001 lädt den konstanten 4-Byte-Wert 0 auf den virtuellen Stapel.
Die Anweisung bei IL_0002 nimmt den Wert von oben aus dem Stapel (den 4-Byte-Nullwert) und konvertiert ihn in einen 8-Byte-Nullwert, wodurch der 8-Byte-Nullwert oben auf dem Stapel abgelegt wird.
An dieser Stelle enthält der Stapel zwei Operanden: den 8-Byte-Nullwert und den Zeiger auf dieses Objekt. Die Anweisung bei IL_0003 verwendet beide Operanden, um den Wert aus dem oberen Bereich des Stapels (den 8-Byte-Nullwert) im Feld totalLines des Objekts zu speichern, das auf dem Stapel gekennzeichnet wird.
Dieselbe MSIL-Anweisungsfolge wird für das totalChars-Feld, das totalBytes-Feld und das totalWords-Feld wiederholt.
Die Initialisierung des Feldes wordCounter beginnt mit der Anweisung IL_0020 (siehe unten):
IL_0020: ldarg.0
IL_0021: newobj instance void [mscorlib]System.Collections.SortedList::.ctor()
IL_0026: stfld class [mscorlib]System.Collections.SortedList WordCounter::wordCounter
Die Anweisung bei IL_0020 legt den this-Zeiger für WordCounter mittels Push auf dem virtuellen Stapel ab. Dieser Operand wird nicht von der newobj-Anweisung, sondern von der stfld-Anweisung bei IL_0026 verwendet.
Die Anweisung bei IL_0021 weist die Laufzeit an, ein neues System.Collections.SortedList-Objekt zu erstellen und den entsprechenden Konstruktor ohne Argumente aufzurufen. Wenn newobj zurückgegeben wird, befindet sich die Adresse des SortedList-Objekts auf dem Stapel. An dieser Stelle speichert die stfld-Anweisung bei IL_0026 den Zeiger im SortedList-Objekt im Feld wordCounter des WordCounter-Objekts.
Nachdem alle Felder des WordCounter-Objekts initialisiert wurden, legt die Anweisung bei IL_002b den this-Zeiger mittels Push auf dem virtuellen Stapel ab, und IL_002c ruft den Konstruktor im Basistyp auf (System.Object).
Natürlich ist die letzte Anweisung bei IL_0031 die Rückgabeanweisung, die den WordCounter-Konstruktor dazu veranlasst, zu dem Code zurückzukehren, von dem er erstellt wurde. Konstruktoren müssen void zurückgeben, damit vor der Rückkehr des Konstruktors nichts auf dem Stapel abgelegt wird.
Betrachten wir nun ein anderes Beispiel. Doppelklicken Sie auf die GetWordsByOccurranceEnumerator-Methode, um deren MSIL-Code anzuzeigen, der in der folgenden Abbildung dargestellt ist.
Wie Sie sehen, hat der Code für diese Methode einen Umfang von 69 Bytes, und für die Methode sind vier Slots auf dem virtuellen Stapel erforderlich. Darüber hinaus verfügt diese Methode über drei lokale Variablen: eine vom Typ System.Collection.SortedList und zwei vom Typ System.Collections.IDictionaryEnumerator. Die im Quellcode genannten Variablennamen werden nur dann in den MSIL-Code ausgegeben, wenn die Assembly mit der Option /debug kompiliert wird. Wenn /debug nicht verwendet wird, werden die Variablennamen V_0, V_1 und V_2 anstelle von sl, de und CS$00000003$00000000 verwendet.
Wenn diese Methode die Ausführung startet, wird zunächst die newobj-Anweisung ausgeführt, die eine neue System.Collections.SortedList erstellt und den Standardkonstruktor dieses Objekts aufruft. Wenn newobj zurückgegeben wird, befindet sich die Adresse des erstellten Objekts auf dem virtuellen Stapel. Die stloc.0-Anweisung (bei IL_0005) speichert diesen Wert in der lokalen Variablen 0 oder sl (V_0 ohne /debug), die eine Variable des Typs System.Collections.SortedList ist.
Bei den Anweisungen IL_0006 und IL_0007 wird der this-Zeiger des WordCounter-Objekts (in "Argument Zero" an die Methode übergeben) auf den Stapel geladen und die GetWordsAlphabeticallyEnumerator-Methode aufgerufen. Wenn die call-Anweisung zurückgegeben wird, befindet sich die Adresse des Enumerators auf dem Stapel. Die stloc.1-Anweisung (bei IL_000c) speichert diese Adresse in der lokalen Variablen 1 oder de (V_1 ohne /debug), die eine Variable des Typs System.Collections.IDictionaryEnumerator ist.
Die br.s-Anweisung bei IL_000d verursacht eine nicht bedingte Verzweigung zur IL-Testbedingung der while-Anweisung. Diese IL-Testbedingung beginnt bei der Anweisung IL_0032. Bei IL_0032 wird die Adresse von de (oder V_1) (d. h. IDictionaryEnumerator) mittels Push auf dem Stapel abgelegt, und bei IL_0033 wird die dazugehörige MoveNext-Methode aufgerufen. Wenn MoveNext den Wert true zurückgibt, ist zu enumerierender Eintrag vorhanden, und die brtrue.s-Anweisung springt zur Anweisung bei IL_000f.
Bei den Anweisungen bei IL_000f und IL_0010 werden die Adressen der Objekte in sl (oder V_0) und de (oder V_1) mittels Push auf dem Stapel abgelegt. Anschließend wird die get_Value-Eigenschaftsmethode des IdictionaryEnumerator-Objekts aufgerufen, um die Anzahl der Instanzen des aktuellen Eintrags zu erhalten. Bei dieser Zahl handelt es sich um einen 32-Bit-Wert, der in System.Int32 gespeichert ist. Der Code wandelt dieses Int32-Objekt in einen int-Werttyp um. Für das Umwandeln eines Verweistyps in einen Werttyp ist die unbox-Anweisung bei IL_0016 erforderlich. Wenn unbox zurückgegeben wird, befindet sich die Adresse des nicht geschachtelten Wertes auf dem Stapel. Die ldind.i4-Anweisung (bei IL_001b) lädt einen 4-Byte-Wert, der auf die gegenwärtig auf dem Stapel abgelegte Adresse verweist, auf den Stapel. Das heißt, die nicht geschachtelte 4-Byte-Ganzzahl wird auf dem Stapel abgelegt.
Bei Anweisung IL_001c wird der Wert von sl (oder V_1), d. h. die Adresse von IDictionaryEnumerator, mittels Push auf dem Stapel abgelegt, und die dazugehörige get_Key-Eigenschaftsmethode wird aufgerufen. Wenn get_Key zurückgegeben wird, befindet sich die Adresse von System.Object auf dem Stapel. Der Code weiß, dass das Wörterbuch Zeichenfolgen enthält, so dass der Compiler dieses Object unter Verwendung der castclass-Anweisung bei IL_0022 in einen String umwandelt.
Die folgenden Anweisungen (von IL_0027 bis IL_002d) erstellen ein neues WordOccurrence-Objekt und übergeben die Adresse des Objekts an die Add-Methode von SortedLists.
Bei Anweisung IL_0032 wird die Testbedingung der while-Anweisung erneut ausgewertet. Wenn MoveNext den Wert true zurückgibt, führt die Schleife erneut eine Iteration aus. Wenn MoveNext jedoch den Wert false zurückgibt, überspringt die Ausführung die Schleife und landet bei Anweisung IL_003a. Die Anweisungen von IL_003a bis IL_0040 rufen die GetEnumerator-Methode des SortLists-Objekts auf. Der zurückgegebene Wert ist ein System.Collections.IDictionaryEnumerator, der auf dem Stapel belassen wird, damit er zum Rückgabewert von GetWordsByOccurrenceEnumerator wird.