Hallo an alle. Diesmal lachen wir weiter über den normalen Methodenaufruf. Ich schlage vor, dass Sie sich mit dem Methodenaufruf mit Parametern vertraut machen, ohne Parameter zu übergeben. Wir versuchen auch, den Referenztyp in eine Zahl umzuwandeln - seine Adresse, ohne Zeiger und unsicheren Code zu verwenden.
Haftungsausschluss
Bevor Sie mit der Geschichte beginnen, empfehle ich dringend, dass Sie den vorherigen
Beitrag über StructLayout lesen , weil Die dort vereinbarten Dinge werden hier nicht wiederholt.
Ich möchte auch warnen, dass dieser Artikel kein Material enthält, das in realen Projekten verwendet werden sollte.
Einige erste Informationen
Bevor wir beginnen, erinnern wir uns daran, wie C # -Code konvertiert wird.
Nehmen wir ein einfaches Beispiel. Ich möchte Sie daran erinnern, dass ich nur virtuelle Methoden verwende, um Spaß mit StructLayout zu haben.
public class Helper { public virtual void Foo(int param) { } } public class Program { public void Main() { Helper helper = new Helper(); var param = 5; helper.Foo(param); } }
Dieser Code enthält nichts Kompliziertes, aber die von JiT generierten Anweisungen enthalten mehrere wichtige Punkte. Ich schlage vor, nur ein kleines Fragment des generierten Codes zu analysieren.
1: mov dword [ebp-0x8], 0x5 2: mov ecx, [ebp-0xc] 3: mov edx, [ebp-0x8] 4: mov eax, [ecx] 5: mov eax, [eax+0x28] 6: call dword [eax+0x10]
In diesem kleinen Beispiel können Sie Fastcall beobachten - eine Vereinbarung zum Übergeben von Parametern durch Register (die ersten beiden Parameter von links nach rechts in den Registern ecx und edx), und die verbleibenden Parameter werden auf dem Stapel von rechts nach links übergeben. Der erste (implizite) Parameter ist die Adresse der Klasseninstanz, für die die Methode aufgerufen wird. Es wird als erster impliziter Parameter für jede Instanzmethode übergeben. Der zweite Parameter ist eine lokale Variable vom Typ int (in unserem Fall).
In der
ersten Zeile sehen wir also die lokale Variable 5, hier gibt es nichts Interessantes.
In der
zweiten Zeile kopieren wir die Adresse der Helper-Instanz in das ecx-Register. Dies ist die Adresse der Methodentabelle selbst.
Die dritte Zeile enthält das Kopieren der lokalen Variablen 5 in das edx-Register
Die vierte Zeile kopiert die Adresse der Methodentabelle in das eax-Register
Die fünfte Zeile enthält eine
Verschiebung des eax-Registers um 40 Bytes, wobei ein Wert aus dem Speicher an eine Adresse geladen wird, die 40 Bytes größer ist als die Adresse der Methodentabelle: die Adresse des Anfangs der Methoden in der Methodentabelle. (Die Methodentabelle enthält verschiedene Informationen, die zuvor gespeichert wurden. Zu diesen Informationen gehören beispielsweise die Adresse der Methodentabelle der Basisklasse, die EEClass-Adresse, verschiedene Flags, einschließlich des Garbage Collector-Flags usw.). Dementsprechend wird die Adresse der ersten Methode aus der Methodentabelle jetzt im eax-Register gespeichert.
In der
sechsten Zeile wird die Methode von Anfang an mit Offset 16 aufgerufen, dh mit der fünften in der Methodentabelle. Warum ist unsere einzige Methode die fünfte? Ich erinnere Sie daran, dass das Objekt 4 virtuelle Methoden (ToString, Equals, GetHashCode und Finalize) hat, die dementsprechend in allen Klassen vorhanden sind.
Lass uns weiter üben
Es ist Zeit, eine kleine Demonstration zu starten. Ich schlage hier einen solchen Rohling vor (sehr ähnlich dem Rohling aus dem vorherigen Artikel).
[StructLayout(LayoutKind.Explicit)] public class CustomStructWithLayout { [FieldOffset(0)] public Test1 Test1; [FieldOffset(0)] public Test2 Test2; } public class Test1 { public virtual int Useless(int param) { Console.WriteLine(param); return param; } } public class Test2 { public virtual int Useless() { return 888; } } public class Stub { public void Foo(int stub) { } }
Und das folgende Zeug der Hauptmethode:
class Program { static void Main(string[] args) { Test2 fake = new CustomStructWithLayout { Test2 = new Test2(), Test1 = new Test1() }.Test2; Stub bar = new Stub(); int param = 55555; bar.Foo(param); fake.Useless(); Console.Read(); } }
Wie Sie sich vorstellen können, wird aus der Erfahrung des vorherigen Artikels die nutzlose (int j) Methode vom Typ Test1 aufgerufen.
Aber was wird abgeleitet? Ich glaube, ein aufmerksamer Leser hat diese Frage bereits beantwortet. 55555 wird auf der Konsole angezeigt.
Aber schauen wir uns die Fragmente des generierten Codes an.
mov ecx, [ebp-0x20] mov edx, [ebp-0x10] cmp [ecx], ecx call Stub.Foo(Int32) nop mov ecx, [ebp-0x1c] mov eax, [ecx] mov eax, [eax+0x28] call dword [eax+0x10]
Ich denke, Sie erkennen das Aufrufmuster der virtuellen Methode, es beginnt nach L00cc: nop. Wie wir sehen können, wird erwartet, dass in ecx die Adresse der Instanz geschrieben wird, auf der die Methode aufgerufen wird. Aber seitdem Wenn wir eine Methode wie Test2 aufrufen, die keine Parameter hat, wird nichts in edx geschrieben. Zuvor wurde jedoch die Methode aufgerufen, die den Parameter über das edx-Register durchlief, und der Wert blieb darin. und wir können es im Ausgabefenster beobachten.
Es gibt noch eine andere interessante Nuance. Ich habe speziell einen aussagekräftigen Typ verwendet. Ich schlage vor, den Parametertyp der Foo-Methode vom Typ Stub durch einen beliebigen Referenztyp zu ersetzen, z. B. eine Zeichenfolge. Der Parametertyp der Useless-Methode wird jedoch nicht geändert. Unten sehen Sie das Ergebnis auf meinem Computer mit einigen klarstellenden Elementen: WinDBG und Calculator :)
Klickbares BildDas Ausgabefenster zeigt die Adresse des Referenztyps im Dezimalzahlensystem an
Zusammenfassung
Sie haben das Wissen über das Aufrufen von Methoden mithilfe der Fastcall-Konvention aufgefrischt und sofort das wunderbare edx-Register verwendet, um die Parameter-2-Methoden gleichzeitig zu übergeben. Sie kümmerten sich auch nicht um alle Typen und erinnerten sich daran, dass nur Bytes die Adresse des Objekts ohne Verwendung von Zeigern und unsicherem Code leicht empfangen konnten. Außerdem habe ich vor, die empfangene Adresse für noch weniger anwendbare Zwecke zu verwenden!
Vielen Dank für Ihre Aufmerksamkeit!
PS C # -Code finden Sie
hier