Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
In diesem Artikel werden die Standardprozesse und Konventionen beschrieben, die eine Funktion (der Aufrufer) verwendet, um Aufrufe in eine andere Funktion (den Angerufenen) in x64-Code auszuführen.
Weitere Informationen zur __vectorcall Anrufkonvention finden Sie unter __vectorcall.
Weitere Informationen zur __preserve_none Anrufkonvention finden Sie unter __preserve_none.
Voreingestellte Aufrufkonventionen
Die x64 Application Binary Interface (ABI) verwendet standardmäßig eine Konvention für schnelle Aufrufe mit vier Registern. Der Speicherplatz wird im Aufrufstack als Schattenspeicher für Aufgerufene zum Speichern dieser Register zugeordnet.
Es gibt eine strikte 1:1-Entsprechung zwischen den Argumenten eines Funktionsaufrufs und den für diese Argumente verwendeten Registern. Jedes Argument, das nicht in acht Bytes passt oder nicht die Größe von 1, 2, 4 oder 8 Bytes hat, muss als Referenz übergeben werden. Ein einzelnes Argument wird nie auf mehrere Register verteilt.
Der x87-Registerstapel wird nicht verwendet. Es kann vom Angerufenen verwendet werden, sollte jedoch als flüchtig über Funktionsaufrufe hinweg betrachtet werden. Alle Gleitkommaoperationen werden mithilfe der 16 XMM-Register ausgeführt.
Ganzzahlige Argumente werden in den Registern RCX, RDX, R8 und R9 übergeben. Gleitkommaargumente werden in XMM0L, XMM1L, XMM2L und XMM3L übergeben. Argumente mit einer Größe von 16 Bytes werden als Verweis übergeben. Die Parameterübergabe wird im Abschnitt Parameterübergabe ausführlich beschrieben. Diese Register und RAX, R10, R11, XMM4und , und XMM5, werden als veränderlich betrachtet oder potenziell von einem Angerufenen bei der Rückgabe geändert. Die Registernutzung wird ausführlich in x64-Registerverwendung und Caller-/Callee-gespeicherte Register dokumentiert.
Bei prototypisierten Funktionen werden alle Argumente vor dem Übergeben in die erwarteten Typen des Aufrufenden konvertiert. Der Aufrufer ist für die Zuweisung von Speicherplatz für die Parameter des Aufgerufenen verantwortlich. Der Aufrufer muss immer ausreichend Speicherplatz für vier Registerparameter zuweisen, auch wenn der Aufgerufene nicht so viele Parameter verwendet. Diese Konvention vereinfacht die Unterstützung für C-Sprachfunktionen und Vararg-C- oder -C++-Funktionen ohne Prototyp. Für Vararg-Funktionen oder Funktionen ohne Prototyp müssen alle Gleitkommawerte im entsprechenden allgemeinen Register dupliziert werden. Alle auf die ersten vier Parameter folgenden Parameter müssen vor dem Aufruf im Stapel nach dem Schattenspeicher gespeichert werden. Details zu Vararg-Funktionen finden Sie im Abschnitt Varargs. Informationen zu Funktionen ohne Prototyp finden Sie unter Funktionen ohne Prototyp.
Ausrichtung
Die meisten Strukturen weisen ihre natürliche Ausrichtung auf. Die primären Ausnahmen sind der Stapelzeiger und der malloc- oder alloca-Arbeitsspeicher, die auf 16 Bytes ausgerichtet sind, um die Leistung zu unterstützen. Eine Ausrichtung auf über 16 Bytes muss manuell erfolgen. Da 16 Bytes jedoch einer gängigen Ausrichtungsgröße für XMM-Vorgänge entsprechen, sollte dieser Wert für die meisten Codes funktionieren. Weitere Informationen zum Strukturlayout und zur Ausrichtung finden Sie unter x64-Typ und Speicherlayout. Weitere Informationen zum Stapellayout finden Sie unter Verwendung von Stapeln bei x64-Systemen.
Unentladbarkeit
Blattfunktionen sind Funktionen, die keine nicht volatile Register ändern. Eine Nicht-Blattfunktion kann beispielsweise nichtflüchtige RSP ändern, etwa durch den Aufruf einer Funktion. Oder RSP könnte sich ändern, indem mehr Stack-Speicher für lokale Variablen reserviert wird. Zur Wiederherstellung nichtflüchtiger Register bei der Behandlung einer Ausnahme werden Nicht-Blatt-Funktionen mit statischen Daten versehen. Die Daten beschreiben, wie die Funktion in einer beliebigen Anweisung ordnungsgemäß entladen wird. Diese Daten werden als pdata oder Prozedurdaten gespeichert, die sich wiederum auf xdata, die Ausnahmebehandlungsdaten, beziehen. Die xdata-Daten enthalten die Entladungsinformationen und können auf zusätzliche pdata-Daten oder eine Ausnahmehandlerfunktion verweisen.
Prologe und Epiloge sind stark eingeschränkt, sodass sie in xdata-Daten angemessen beschrieben werden können. Der Stapelzeiger muss (abgesehen von Leaf-Funktionen) in jedem Codebereich, der nicht Teil eines Epilogs oder Prologs ist, auf 16 Bytes ausgerichtet bleiben. Leaf-Funktionen können einfach durch Simulieren einer Rückgabe aufgewickelt werden, sodass pdata und xdata nicht erforderlich sind. Ausführliche Informationen zur ordnungsgemäßen Struktur von Funktionsprologen und -epilogen finden Sie unter Prolog und Epilog bei x64-Systemen. Weitere Informationen zur Ausnahmebehandlung und dem Entladen von pdata- und xdata-Daten finden Sie unter Ausnahmebehandlung bei x64-Systemen.
Parameterübergabe
Die x64-Aufrufkonvention übergibt standardmäßig die ersten vier Argumente an eine Funktion in Registern. Die für diese Argumente verwendeten Register sind abhängig von der Position und vom Typ des Arguments. Verbleibende Argumente werden im Stack in der Reihenfolge von rechts nach links übergeben. Der Aufrufer reserviert den erforderlichen Stapelspeicher und schreibt diese Argumente mithilfe von Speicherungs- oder Verschiebungsanweisungen, wobei die Ausrichtung von 8 Byte für jedes Argument beibehalten wird.
Ganzzahlige Argumente in den vier Positionen ganz links werden von links nach rechts jeweils in RCX, RDX, R8 und R9 übergeben. Das fünfte und jedes weitere höhere Argument wird wie zuvor beschrieben an den Stapel übergeben. Alle Integerargumente werden in Registern rechtsbündig ausgerichtet, sodass der Aufgerufene die oberen Bits des Registers ignorieren und nur auf den notwendigen Teil des Registers zugreifen kann.
Alle Gleitkomma- und Doppelpräzisionsargumente unter den ersten vier Parametern werden abhängig von ihrer Position in XMM0 - XMM3 übergeben. Gleitkommawerte werden nur dann in den Integer-Registern RCX, RDX, R8 und R9 abgelegt, wenn varargs-Argumente vorhanden sind. Weitere Informationen finden Sie unter Varargs. Ebenso werden die XMM0 - XMM3 Register ignoriert, wenn das entsprechende Argument eine ganze Zahl oder ein Zeigertyp ist.
__m128-Typen, -Arrays und -Zeichenfolgen werden nie als unmittelbarer Wert übergeben. Stattdessen wird ein Zeiger an den vom Aufrufer zugeordneten Arbeitsspeicher übergeben. Strukturen und Unions mit einer Größe von 8, 16, 32 oder 64 Bits sowie __m64-Typen werden wie Integer der gleichen Größe übergeben. Strukturen oder Unions anderer Größen werden als Zeiger auf den vom Aufrufer zugeordneten Arbeitsspeicher übergeben. Für diese als Zeiger übergebenen Aggregattypen, einschließlich __m128, muss der temporäre Speicher, der vom Aufrufer zugewiesen wird, auf 16 Byte ausgerichtet sein.
Intrinsische Funktionen, die keinen Stapelbereich zuordnen und keine anderen Funktionen aufrufen, verwenden manchmal andere flüchtige Register, um zusätzliche Registerargumente zu übergeben. Diese Optimierung wird durch die enge Bindung zwischen dem Compiler und der Implementierung der intrinsischen Funktion ermöglicht.
Der Aufgerufene ist dafür verantwortlich, die Registerparameter bei Bedarf im Schattenspeicher abzulegen.
Die folgende Tabelle gibt Aufschluss darüber, wie Parameter übergeben werden, nach Typ und Position von links:
| Parametertyp | Fünfter und höher | vierter | dritte | Sekunde | Ganz links |
|---|---|---|---|---|---|
| Gleitkomma | stack | XMM3 |
XMM2 |
XMM1 |
XMM0 |
| Ganzzahl | stack | R9 |
R8 |
RDX |
RCX |
Aggregate (8, 16, 32 oder 64 Bits) und __m64 |
stack | R9 |
R8 |
RDX |
RCX |
| Andere Aggregate als Zeiger | stack | R9 |
R8 |
RDX |
RCX |
__m128, als Zeiger |
stack | R9 |
R8 |
RDX |
RCX |
Beispiel 1 für Argumentübergabe: Alle Integerwerte
func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e passed on stack
Beispiel für Argumentübergabe 2 - alle Gleitkommawerte
func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e passed on stack
Beispiel 3 für Argumentübergabe: Kombination aus Ints und Floats
func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e passed on stack
Beispiel 4 für Argumentübergabe: __m64, __m128 und Aggregate
func4(__m64 a, __m128 b, struct c, float d, __m128 e, __m128 f);
// a in RCX, ptr to b in RDX, ptr to c in R8, d in XMM3,
// ptr to f passed on stack, then ptr to e passed on stack
VarArgs
Wenn Parameter über VarArgs übergeben werden (z. B. Ellipsenargumente), wird die übliche Konvention für die Übertragung von Registerparametern angewendet. Diese beinhaltet das Übertragen des fünften und der nachfolgenden Argumente an den Stapel. Es ist Aufgabe des Aufgerufenen, Argumente auszugeben, deren Adresse verwendet wird. Nur für Gleitkommawerte müssen sowohl das Integerregister als auch das Gleitkommaregister denselben Wert enthalten, für den Fall, dass der Aufrufer erwartet, den Wert in den Integerregistern zu finden.
Funktionen ohne Prototyp
Bei Funktionen ohne Prototyp übergibt der Aufrufer ganzzahlige Werte als Integer und Gleitkommawerte als Werte mit doppelter Genauigkeit. Nur im Falle von Gleitkommawerten enthalten sowohl das Integerregister als auch das Gleitkommaregister den Gleitkommawert, falls der Aufgerufene den Wert in den Integerregistern erwartet.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Rückgabewerte
Ein skalarer Rückgabewert, der in 64 Bit passen kann, einschließlich des __m64 Typs, wird durch RAXzurückgegeben. Nichtkalare Typen, einschließlich Floats, Doubles und Vektortypen wie __m128, __m128i__m128d werden in XMM0zurückgegeben. Der Zustand nicht verwendeter Bits in dem in RAX oder XMM0 zurückgegebenen Wert ist nicht definiert.
Benutzerdefinierte Typen können als Wert von globalen Funktionen und statischen Memberfunktionen zurückgegeben werden. Um in RAX einen benutzerdefinierten Typ als Wert zurückzugeben, muss er eine Größe von 1, 2, 4, 8, 16, 32 oder 64 Bit haben. Er darf außerdem keinen benutzerdefinierten Konstruktor, Destruktor oder Kopierzuweisungsoperator aufweisen. Es kann keinerlei private oder geschützte nicht-statische Datenmitglieder und noch keine nicht-statischen Datenmitglieder des Referenztyps enthalten. Er darf keine Basisklassen oder virtuellen Funktionen aufweisen. Außerdem darf es nur Datenmitglieder enthalten, die diese Anforderungen ebenfalls erfüllen. Diese Definition ist im Wesentlichen identisch mit einem C++03 POD-Typ. Da sich die Definition im C++11-Standard geändert hat, wird die Verwendung std::is_pod für diesen Test nicht empfohlen. Andernfalls muss der Aufrufer Speicher für den Rückgabewert zuweisen und einen Zeiger als erstes Argument an ihn übergeben. Die verbleibenden Argumente werden dann um ein Argument nach rechts verschoben. Derselbe Zeiger muss vom Angerufenen zurückgegeben werden.RAX
Diese Beispiele zeigen, wie Parameter und Rückgabewerte für Funktionen mit den angegebenen Deklarationen übergeben werden:
Beispiel für Rückgabewert 1: Ergebnis 64 Bit
__int64 func1(int a, float b, int c, int d, int e);
// Caller passes a in RCX, b in XMM1, c in R8, d in R9, e passed on stack,
// callee returns __int64 result in RAX.
Beispiel für Rückgabewert 2: Ergebnis 128 Bit
__m128 func2(float a, double b, int c, __m64 d);
// Caller passes a in XMM0, b in XMM1, c in R8, d in R9,
// callee returns __m128 result in XMM0.
Beispiel für Rückgabewert 3 - Benutzertyp-Ergebnis als Zeiger
struct Struct1 {
int j, k, l; // Struct1 exceeds 64 bits.
};
Struct1 func3(int a, double b, int c, float d);
// Caller allocates memory for Struct1 returned and passes pointer in RCX,
// a in RDX, b in XMM2, c in R9, d passed on the stack;
// callee returns pointer to Struct1 result in RAX.
Beispiel für Rückgabewert 4 - Benutzertyp-Ergebnis als Wert
struct Struct2 {
int j, k; // Struct2 fits in 64 bits, and meets requirements for return by value.
};
Struct2 func4(int a, double b, int c, float d);
// Caller passes a in RCX, b in XMM1, c in R8, and d in XMM3;
// callee returns Struct2 result by value in RAX.
Gespeicherte Register des Aufrufers und des Aufgerufenen
Die x64-ABI betrachtet die Register RAX, RCX, RDX, R8, R9, R10, R11 und XMM0-XMM5 als volatile. Wenn vorhanden, sind die oberen Teile von YMM0-YMM15 und ZMM0-ZMM15 sind auch veränderlich. Auf AVX512VL sind die ZMMYMMXMM Register 16-31 ebenfalls veränderlich. Wenn AMX-Unterstützung vorhanden ist, sind die TMM-Kachelregister flüchtig. Sehen Sie flüchtige Register bei Funktionsaufrufen als zerstört an, es sei denn, die Sicherheit ist durch eine Analyse wie z. B. eine Optimierung des gesamten Programms gewährleistet.
Die x64 ABI berücksichtigt Register RBX, , RBP, RDI, RSI, RSP, R12, R13, R14und R15XMM6-XMM15 nichtvolatile. Sie müssen von einer Funktion, die sie verwendet, gespeichert und wiederhergestellt werden.
Wenn die APX-Unterstützung vorhanden ist, sind Register R16-R29 veränderlich.
R30 und R31 sind nicht unvolatile.
Funktionszeiger
Funktionszeiger sind einfach nur Zeiger auf die Bezeichnung der jeweiligen Funktion. Es gibt keine Anforderung für das Inhaltsverzeichnis für Funktionszeiger.
Gleitkommaunterstützung für älteren Code
Die MMX- und Gleitkommastapelregister (MM0-MM7/ST0-ST7) bleiben bei Kontextwechseln erhalten. Es gibt keine explizite Aufrufkonvention für diese Register. Die Verwendung dieser Register ist im Kernelmoduscode strengstens untersagt.
FPCSR
Der Registerzustand umfasst auch das x87-FPU-Steuerwort. Die Aufrufkonvention legt dieses Register als nicht flüchtig an.
Das x87-FPU-Steuerwortregister wird zu Beginn der Programmausführung mithilfe der folgenden Standardwerte festgelegt:
| Register[bits] | Einstellung |
|---|---|
FPCSR\[0:6] |
Ausnahmemasken für alle 1s (alle Ausnahmen maskiert) |
FPCSR\[7] |
Reserviert: 0 |
FPCSR\[8:9] |
Genauigkeitssteuerung: 10B (doppelte Genauigkeit) |
FPCSR\[10:11] |
Rundungssteuerung: 0 (Runden auf nächste Zahl) |
FPCSR\[12] |
Unendlichkeitssteuerung: 0 (nicht verwendet) |
Ein Angerufener, der eines der darin enthaltenen FPCSR Felder ändert, muss sie wiederherstellen, bevor er zum Aufrufer zurückkehrt. Darüber hinaus muss ein Aufrufer, der eines dieser Felder geändert hat, die Standardwerte wiederherstellen, bevor er einen Aufgerufenen aufruft, es sei denn, der Aufgerufene erwartet nach Vereinbarung die geänderten Werte.
Es gibt zwei Ausnahmen von den Regeln zur Nichtvolatilität der Steuerungsflaggen.
In Funktionen, in denen der dokumentierte Zweck der angegebenen Funktion darin besteht, die nichtvolatile
FPCSRKennzeichnungen zu ändern.Dies liegt vor, wenn dieser Verstoß gegen die Regeln tatsächlich dazu führt, dass sich ein Programm wie ein Programm verhält, das die Regeln nicht verletzt (z. B. durch eine vollständige Programmanalyse).
Obwohl es als nichtflüchtig gilt, gibt es keinen statischen Unwind-Deskriptor, aus dem hervorgeht, wo es gespeichert wurde und von wo es wiederhergestellt werden soll. Ausnahmesicherer Code, der FPCSR verändert, sollte eine Abschlussroutine für Ausnahmen (z. B. einen C++-Destruktor oder eine __finally-Klausel) verwenden, um FPCSR beim Abwickeln des Stacks explizit wiederherzustellen.
MXCSR
Der Registerzustand enthält auch MXCSR. Die Aufrufkonvention dividiert dieses Register in einen flüchtigen und einen nicht flüchtigen Teil. Der veränderliche Teil besteht aus den sechs Statuskennzeichnungen in MXCSR\[0:5], MXCSR\[6:15]während der Rest des Registers als nicht volatile betrachtet wird.
Der nicht flüchtige Teil wird zu Beginn der Programmausführung auf die folgenden Standardwerte festgelegt:
| Register[bits] | Einstellung |
|---|---|
MXCSR\[6] |
Denormals sind Nullen - 0 |
MXCSR\[7:12] |
Ausnahmemasken für alle 1s (alle Ausnahmen maskiert) |
MXCSR\[13:14] |
Rundungssteuerung: 0 (Runden auf nächste Zahl) |
MXCSR\[15] |
Auf Null setzen bei maskiertem Unterlauf: 0 (aus) |
Eine aufgerufene Funktion, die eines der nichtflüchtigen Felder in MXCSR ändert, muss es wiederherstellen, bevor sie an den Aufrufer zurückkehrt. Darüber hinaus muss ein Aufrufer, der eines dieser Felder geändert hat, die Standardwerte wiederherstellen, bevor er einen Aufgerufenen aufruft, es sei denn, der Aufgerufene erwartet nach Vereinbarung die geänderten Werte.
Es gibt zwei Ausnahmen von den Regeln zur Nichtvolatilität der Steuerungsflaggen.
In Funktionen, in denen der dokumentierte Zweck der angegebenen Funktion darin besteht, die nichtvolatile
MXCSRKennzeichnungen zu ändern.Dies liegt vor, wenn dieser Verstoß gegen die Regeln tatsächlich dazu führt, dass sich ein Programm wie ein Programm verhält, das die Regeln nicht verletzt (z. B. durch eine vollständige Programmanalyse).
Machen Sie keine Annahmen über den veränderliche Teilzustand des MXCSR Registers über eine Funktionsgrenze hinweg, es sei denn, die Funktionsdokumentation beschreibt sie explizit.
Obwohl Teile von MXCSR als nicht flüchtig gelten, gibt es keinen statischen Unwind-Deskriptor, der beschreibt, wo sie gespeichert wurden und von wo sie wiederhergestellt werden sollten. Ausnahmesicherer Code, der die nichtflüchtigen Teile von MXCSR verändert, sollte auf eine Ausnahmeabschlussroutine (z. B. einen C++-Destruktor oder eine __finally-Klausel) zurückgreifen, um diese beim Abwickeln des Stapels explizit wiederherzustellen.
setjmp/longjmp
Wenn Sie setjmpex.h oder setjmp.h einschließen, führen alle Aufrufe von setjmp oder longjmp zu einer Auflösung, die Destruktoren aufruft und __finally-Aufrufe tätigt. Dieses Verhalten unterscheidet sich von x86, bei dem setjmp.h dazu führt, dass __finally Klauseln und Destruktoren nicht ausgeführt werden.
Ein Aufruf von setjmp bewahrt den aktuellen Stackzeiger, nichtflüchtige Register und MXCSR-Register. Aufrufe von longjmp kehren zur zuletzt erfolgten Aufrufstelle von setjmp zurück und setzen den Stapelzeiger, nichtflüchtige Register und MXCSR-Register auf den Zustand zurück, der durch den letzten Aufruf von setjmp gesichert wurde.
Wenn APX unterstützt wird, sollten R30 und R31 in einer Funktion ab dem Zeitpunkt, an dem setjmp aufgerufen wird, bis zu dem Zeitpunkt, an dem der Aufruf erfolgt, der letztlich zu longjmp führt, nicht geändert werden. Diese Einschränkung ist darauf zurückzuführen, dass R30 und R31 nicht als Teil von jmp_buf gespeichert werden – diese Strukturdefinition kann nicht geändert werden. Stattdessen werden sie durch den Unwinder wiederhergestellt. Im folgenden Beispiel wird veranschaulicht, wie sich der Unterschied bei der Wiederherstellung der Daten auf diese Einschränkung auswirkt:
jmp_buf jmpbuffer;
void function_a() {
...
int val = setjmp(jmpbuffer); // At this time R30 is 10
...
if (val == 0) {
function_b(); // At this time R30 is 20
}
...
}
void function_b() {
...
longjmp(jmpbuffer, 1);
...
}
In diesem Beispiel ändert sich der Wert von R30 von dem Punkt an, an dem setjmp aufgerufen wird, bis zu dem Punkt, an dem function_b aufgerufen wird. In function_b, wickelt longjmp den Stack ab, bis es die Funktion erreicht, die setjmp aufgerufen hat (function_a in diesem Fall). Der wiederhergestellte R30 Wert lautet 20 (der Wert am Punkt function_b wurde aufgerufen), nicht 10 (der Wert, an dem setjmp aufgerufen wurde). Dies bedeutet, dass, wenn setjmp zum zweiten Mal zurückkehrt (als Ergebnis von longjmp), der Wert von R30 auf 20 statt auf 10 gesetzt wird, was falsch ist. Aus diesem Grund müssen Compiler sicherstellen, dass R30 und R31 ab dem Punkt, an dem setjmp aufgerufen wird, bis zur letzten Stelle in der Funktion, die letztendlich dazu führen könnte, dass longjmp aufgerufen wird, konstant bleiben.
Da longjmp von einem Ausnahmefilter aus aufgerufen werden kann (nicht nur aus einer Unterroutine), bedeutet dies faktisch, dass R30 und R31 ab dem Punkt, an dem setjmp aufgerufen wird, bis zum Ende der Funktion konstant bleiben sollten.