Verwendung von Stapeln bei x64-Systemen
Der gesamte Arbeitsspeicher, der über die aktuelle Adresse des RSP hinausgeht, gilt als veränderlich: Das Betriebssystem oder ein Debugger kann diesen Speicher während einer Benutzerdebugsitzung oder einen Interrupthandler überschreiben. Daher muss RSP immer gesetzt werden, bevor versucht wird, Werte in einen Stapelrahmen zu lesen oder zu schreiben.
In diesem Abschnitt wird die Belegung des Stapelspeichers für lokale Variablen und die intrinsische Funktion alloca besprochen.
Stapelbelegung
Im Prolog einer Funktion wird der Stapelspeicher für lokale Variablen, gespeicherte Register, Stapelparameter und Registerparameter zugeordnet.
Der Parameterbereich befindet sich immer unten im Stapel (selbst wenn alloca
verwendet wird), sodass er sich während eines Funktionsaufrufs immer neben der Rücksprungadresse befindet. Er enthält mindestens vier Einträge, jedoch immer genügend Speicherplatz für alle Parameter, die von einer beliebigen aufrufbaren Funktion benötigt werden. Beachten Sie, dass für die Registerparameter immer Speicher belegt wird, selbst wenn die Parameter nie im Stapel gespeichert werden. Einer aufgerufenen Funktion wird garantiert, dass für alle ihre Parameter Speicher belegt wurde. Für Registerargumente werden die Stammadressen benötigt, damit ein zusammenhängender Bereich verfügbar ist, falls die aufgerufene Funktion die Adresse der Argumentliste (va_list) oder ein einzelnes Arguments nehmen muss. Dieser Bereich ist außerdem eine gute Stelle, um Registerargumente während der Thunkingausführung zu speichern, und eignet sich als Debugoption (Argumente sind beim Debugging z. B. einfach zu finden, wenn sie an ihrer Stammadresse gespeichert werden). Selbst wenn die aufgerufene Funktion weniger als 4 Parameter hat, gehören diese 4 Speicherorte auf dem Stapel effektiv der aufgerufenen Funktion und können von der aufgerufenen Funktion auch zu anderen Zwecken als der Speicherung der Werte von Registerparametern verwendet werden. Folglich speichert die aufrufende Funktion während eines Funktionsaufrufs möglicherweise keine Informationen in diesem Stapelbereich.
Wenn Speicherplatz in einer Funktion dynamisch (alloca
) belegt wird, muss ein nicht flüchtiges Register als Rahmenzeiger verwendet werden, um die Basis des festen Stapelteils zu markieren. Dieses Register muss im Prolog gespeichert und initialisiert werden. Beachten Sie bei Verwendung von alloca
Folgendes: Die Registerparameter einer aufgerufenen Funktion können unterschiedliche Stammadressen aufweisen, obwohl die Aufrufe von derselben aufrufenden Funktion getätigt werden.
Der Stapel bleibt immer am 16-Byte-Format ausgerichtet. Die einzigen Ausnahmen sind der Prolog (z. B. nachdem die Rücksprungadresse gepusht wurde) und die unter Funktionstypen angegebenen Stellen, die für bestimmte Klassen von Rahmenfunktionen vorgesehen sind.
Nachstehend finden Sie ein Beispiel für ein Stapellayout, in dem Funktion A eine Funktion B aufruft, die keine Blattfunktion ist. Im Prolog von Funktion A wurde bereits Speicherplatz für alle Register und Stapelparameter belegt, die von B am Ende des Stapels benötigt werden. Der Aufruf pusht die Rücksprungadresse, und der Prolog von B belegt den Speicherplatz für die lokalen Variablen, die nicht flüchtigen Register und den Speicherplatz der Funktion B, der benötigt wird, um Funktionen aufzurufen. Wenn Funktion B alloca
verwendet, wird der Speicherplatz zwischen dem Speicherbereich für die lokalen Variablen oder die nicht flüchtigen Register und dem Stapelbereich für die Parameter belegt.
Wenn die Funktion B eine andere Funktion aufruft, wird die Rücksprungadresse direkt unterhalb der Stammadresse von RCX eingeordnet.
Dynamisches Erstellen des Stapelbereichs für Parameter
Wenn ein Rahmenzeiger verwendet wird, kann der Parameterstapelbereich dynamisch erstellt werden. Der x64-Compiler bietet diese Option derzeit nicht.
Funktionstypen
Es gibt im Grunde zwei Arten von Funktionen. Eine Funktion, die einen Stapelrahmen erfordert, wird als Rahmenfunktion bezeichnet. Eine Funktion, die keinen Stapelrahmen erfordert, wird Blattfunktion genannt.
Eine Rahmenfunktion ist eine Funktion, die Stapelspeicher belegt, andere Funktionen aufruft, nicht flüchtige Register speichert oder die Ausnahmebehandlung verwendet. Außerdem erfordert eine solche Funktion einen Eintrag in eine Funktionstabelle. Eine Rahmenfunktion erfordert außerdem einen Prolog und einen Epilog. Eine Rahmenfunktion kann Stapelspeicher dynamisch belegen und einen Rahmenzeiger einsetzen. Eine Rahmenfunktion verfügt über die vollständige Funktionalität dieses Aufrufstandards.
Wenn eine Rahmenfunktion keine andere Funktion aufruft, ist es nicht erforderlich, den Stapel auszurichten (im Abschnitt Stapelbelegung angesprochen).
Eine Blattfunktion erfordert keinen Eintrag in eine Funktionstabelle. Blattfunktionen können keine Änderungen an nicht flüchtigen Registern vornehmen, einschließlich RSP. Dies bedeutet, dass sie keine Funktionen aufrufen oder Stapelspeicher belegen können. Es ist zulässig, dass der Stapel während der Ausführung unausgerichtet bleibt.
malloc-Ausrichtung
malloc gibt immer Speicher zurück, der korrekt ausgerichtet ist, um ein beliebiges Objekt zu speichern, das eine grundlegende Ausrichtung hat und in den zugeordneten Speicher passen kann. Eine grundlegende Ausrichtung ist eine Ausrichtung, die kleiner oder gleich der größten Ausrichtung ist, die von der Implementierung ohne Ausrichtungsspezifikation unterstützt wird. (In Visual C++ ist dies die Für ein double
oder 8 Byte erforderliche Ausrichtung. Im Code, der auf 64-Bit-Plattformen ausgerichtet ist, beträgt er 16 Bytes.) Eine 4-Byte-Zuordnung würde beispielsweise an einer Grenze ausgerichtet werden, die ein beliebiges vier byte- oder kleineres Objekt unterstützt.
Visual C++ unterstützt Typen mit einer erweiterten Ausrichtung, die auch als Typen mit erhöhter Ausrichtung bezeichnet werden. Die SEE-Typen __m128 und __m256
sowie die Typen, die von __declspec(align( n ))
deklariert werden, wobei n
größer ist als 8, verfügen beispielsweise über eine erweiterte Ausrichtung. Eine Speicherausrichtung an einer Grenze, die für ein Objekt geeignet ist, das eine erweiterte Ausrichtung erfordert, wird von malloc
nicht gewährleistet. Verwenden Sie zur Speicherbelegung für erweitert ausgerichtete Typen _aligned_malloc und verwandte Funktionen.
alloca
_alloca muss an 16-Byte ausgerichtet sein und zusätzlich einen Rahmenzeiger verwenden.
Der zugeordnete Stapel muss danach weiteren Speicherplatz für die Parameter der anschließend aufgerufenen Funktionen enthalten. Dies wird unter Stapelbelegung erläutert.