ARM64-Ausnahmebehandlung
Windows auf ARM64 verwendet denselben strukturierten Mechanismus für die Ausnahmebehandlung bei asynchronen (von der Hardware generierten) und synchronen (von der Software generierten) Ausnahmen. Sprachspezifische Ausnahmehandler werden auf von Windows strukturierter Ausnahmebehandlung mithilfe von Sprachhilfsfunktionen erstellt. In diesem Dokument wird die Ausnahmebehandlung in Windows unter ARM64 beschrieben. Es veranschaulicht die Sprachhilfsprogramme, die von Code verwendet werden, der vom Microsoft ARM-Assembler und dem MSVC-Compiler generiert wird.
Ziele und Motivation
Die Datenkonventionen für die Ausnahmeentladung und diese Beschreibung dienen folgendem Zweck:
Die Beschreibung soll dafür ausreichen, dass Entladevorgänge generell ohne Codetests durchgeführt werden können.
Für die Codeanalyse muss der Code ausgelagert werden. Es verhindert, dass sie in einigen Fällen abwickelt, in denen es nützlich ist (Ablaufverfolgung, Sampling, Debugging).
Die Codeanalyse ist komplex. Der Compiler muss darauf achten, nur Anweisungen zu generieren, die der Entlader decodieren kann.
Wenn das Entspannen nicht vollständig mithilfe von Entspanncodes beschrieben werden kann, müssen sie in einigen Fällen auf die Decodierung von Anweisungen zurückgreifen. Die Anweisungsdecodierung erhöht die Gesamtkomplexität und sollte idealerweise vermieden werden.
Die Entladung innerhalb des Prologs und Epilogs soll unterstützt werden.
- Die Entladung wird unter Windows nicht nur für die Ausnahmebehandlung verwendet. Es ist wichtig, dass der Code auch dann korrekt entladen werden kann, wenn gerade eine Prolog- oder Epilogsequenz ausgeführt wird.
Es soll nur eine minimale Menge an Speicherplatz in Anspruch genommen werden.
Die Entladungscodes dürfen nicht aggregiert werden, damit die Binärgröße nicht erheblich ansteigt.
Da die Entladungscodes wahrscheinlich im Arbeitsspeicher gesperrt sind, stellt ein kleiner Speicherbedarf einen minimalen Aufwand für jede geladene Binärdatei sicher.
Annahmen
Bei der Beschreibung der Ausnahmebehandlung werden folgende Annahmen getroffen:
Prologe und Epiloge sind tendenziell gespiegelt. Wenn wir uns dieses allgemeine Merkmal zunutze machen, kann die Größe der Metadaten, die zum Beschreiben der Entladung erforderlich sind, erheblich reduziert werden. Innerhalb des Funktionstexts spielt es keine Rolle, ob die Prologvorgänge rückgängig gemacht oder die Epilogvorgänge vorwärts durchgeführt werden. Beides sollte zum gleichen Ergebnis führen.
Funktionen sind im Allgemeinen relativ klein. Einige Optimierungen für den Speicherplatz basieren auf dieser Tatsache, damit Daten möglichst effizient gepackt werden können.
Es gibt keinen bedingten Code in Epilogen.
Dediziertes Framezeigerregister: Wenn das
sp
Register in einem anderen Register (x29
) im Prolog gespeichert wird, bleibt dieses Register während der gesamten Funktion unverändert. Es bedeutet, dass das Originalsp
jederzeit wiederhergestellt werden kann.Sofern dies
sp
nicht in einem anderen Register gespeichert ist, erfolgt die gesamte Manipulation des Stapelzeigers ausschließlich innerhalb des Prologs und Epilogs.Das Stapelrahmenlayout ist wie im nächsten Abschnitt beschrieben organisiert.
ARM64-Stapelrahmenlayout
Bei framechainierten Funktionen kann das und lr
das fp
Paar je nach Optimierungsüberlegung an einer beliebigen Position im lokalen Variablenbereich gespeichert werden. Ziel ist es, die Anzahl der Lokalisierer zu maximieren, die von einer einzelnen Anweisung basierend auf dem Framezeiger (x29
) oder Stapelzeiger () erreichtsp
werden können. Für alloca
Funktionen muss sie jedoch verkettet werden und x29
auf den unteren Rand des Stapels zeigen. Die Speicherbereiche für nicht flüchtige Register werden am Anfang des Bereichsstapels für die lokale Variable positioniert, um die Abdeckung des Adressierungsmodus für Registerpaare zu verbessern. Im Folgenden finden Sie Beispiele, die einige der effizientesten Prologsequenzen veranschaulichen. Der Eindeutigkeit halber und um eine bessere Cachelokalität zu erzielen wird eine aufsteigende Speicherreihenfolge für die nicht flüchtigen Register in allen kanonischen Prologen verwendet. #framesz
unten steht für die Größe des gesamten Stapels (mit Ausnahme des Bereichs alloca
). #localsz
und #outsz
geben die lokale Bereichsgröße (einschließlich des Speicherbereichs für das Paar <x29, lr>
) und die Größe für ausgehende Parameter an.
Verkettet, #localsz <= 512
stp x19,x20,[sp,#-96]! // pre-indexed, save in 1st FP/INT pair stp d8,d9,[sp,#16] // save in FP regs (optional) stp x0,x1,[sp,#32] // home params (optional) stp x2,x3,[sp,#48] stp x4,x5,[sp,#64] stp x6,x7,[sp,#82] stp x29,lr,[sp,#-localsz]! // save <x29,lr> at bottom of local area mov x29,sp // x29 points to bottom of local sub sp,sp,#outsz // (optional for #outsz != 0)
Verkettet, #localsz > 512
stp x19,x20,[sp,#-96]! // pre-indexed, save in 1st FP/INT pair stp d8,d9,[sp,#16] // save in FP regs (optional) stp x0,x1,[sp,#32] // home params (optional) stp x2,x3,[sp,#48] stp x4,x5,[sp,#64] stp x6,x7,[sp,#82] sub sp,sp,#(localsz+outsz) // allocate remaining frame stp x29,lr,[sp,#outsz] // save <x29,lr> at bottom of local area add x29,sp,#outsz // setup x29 points to bottom of local area
Nicht gekettete, Blattfunktionen (
lr
nicht gespeichert)stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] str x23,[sp,#32] stp d8,d9,[sp,#40] // save FP regs (optional) stp d10,d11,[sp,#56] sub sp,sp,#(framesz-80) // allocate the remaining local area
Auf alle Lokalen wird basierend auf
sp
.<x29,lr>
zeigt auf den vorherigen Frame. Bei frame size <= 512, thesub sp, ...
can be optimized if the regs saved area is moved to the bottom of stack. Der Nachteil ist, dass es nicht mit anderen Layouts oben konsistent ist. Außerdem nehmen gespeicherte Regs Teil des Bereichs für Paar-Regs und vor- und postindizierten Offsetadressierungsmodus.Nicht gekettete, nicht blattlose Funktionen (im int gespeicherten Bereich gespeichert
lr
)stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] // ... stp x23,lr,[sp,#32] // save last Int reg and lr stp d8,d9,[sp,#48] // save FP reg-pair (optional) stp d10,d11,[sp,#64] // ... sub sp,sp,#(framesz-80) // allocate the remaining local area
Alternativ bei einer geraden Anzahl gespeicherter Integerregister:
stp x19,x20,[sp,#-80]! // pre-indexed, save in 1st FP/INT reg-pair stp x21,x22,[sp,#16] // ... str lr,[sp,#32] // save lr stp d8,d9,[sp,#40] // save FP reg-pair (optional) stp d10,d11,[sp,#56] // ... sub sp,sp,#(framesz-80) // allocate the remaining local area
Nur
x19
gespeichert:sub sp,sp,#16 // reg save area allocation* stp x19,lr,[sp] // save x19, lr sub sp,sp,#(framesz-16) // allocate the remaining local area
* Die Reg save area allocation isn't folded into the
stp
because a pre-indexed reg-lrstp
can't be represented with the relax codes.Auf alle Lokalen wird basierend auf
sp
.<x29>
zeigt auf den vorherigen Frame.Verkettet, #framesz <= 512, #outs = 0
stp x29,lr,[sp,#-framesz]! // pre-indexed, save <x29,lr> mov x29,sp // x29 points to bottom of stack stp x19,x20,[sp,#(framesz-32)] // save INT pair stp d8,d9,[sp,#(framesz-16)] // save FP pair
Im Vergleich zum ersten Prologbeispiel oben hat dieses Beispiel einen Vorteil: Alle Registerspeicheranweisungen sind bereit, nach nur einer Stapelzuweisungsanweisung auszuführen. Das bedeutet, dass es keine Antiabhängigkeit gibt, die
sp
den Parallelismus auf Anweisungsebene verhindert.Verkettet, Rahmengröße > 512 (optional für Funktionen ohne
alloca
)stp x29,lr,[sp,#-80]! // pre-indexed, save <x29,lr> stp x19,x20,[sp,#16] // save in INT regs stp x21,x22,[sp,#32] // ... stp d8,d9,[sp,#48] // save in FP regs stp d10,d11,[sp,#64] mov x29,sp // x29 points to top of local area sub sp,sp,#(framesz-80) // allocate the remaining local area
Zur Optimierung kann an jeder Position im lokalen Bereich platziert werden,
x29
um eine bessere Abdeckung für "reg-pair" und vor-/postindizierten Offset-Adressierungsmodus bereitzustellen. Auf Lokale unterhalb von Framezeigern kann basierend aufsp
.Verkettet, Rahmengröße > 4K, mit oder ohne alloca(),
stp x29,lr,[sp,#-80]! // pre-indexed, save <x29,lr> stp x19,x20,[sp,#16] // save in INT regs stp x21,x22,[sp,#32] // ... stp d8,d9,[sp,#48] // save in FP regs stp d10,d11,[sp,#64] mov x29,sp // x29 points to top of local area mov x15,#(framesz/16) bl __chkstk sub sp,sp,x15,lsl#4 // allocate remaining frame // end of prolog ... sub sp,sp,#alloca // more alloca() in body ... // beginning of epilog mov sp,x29 // sp points to top of local area ldp d10,d11,[sp,#64] ... ldp x29,lr,[sp],#80 // post-indexed, reload <x29,lr>
Informationen zur ARM64-Ausnahmebehandlung
.pdata
Aufzeichnungen
Die .pdata
Datensätze sind ein sortiertes Array von Elementen mit fester Länge, die jede Stapelmanipulationsfunktion in einer PE-Binärdatei beschreiben. Der Ausdruck "Stapelmanipulation" ist wichtig: Blattfunktionen, die keinen lokalen Speicher erfordern und keine nicht veränderliche Register speichern/wiederherstellen müssen, erfordern keinen .pdata
Datensatz. Diese Datensätze sollten explizit weggelassen werden, um Speicherplatz zu sparen. Eine Entspannung von einer dieser Funktionen kann die Absenderadresse direkt abrufen, lr
um zum Aufrufer zu wechseln.
Jeder .pdata
Datensatz für ARM64 ist 8 Bytes lang. Das allgemeine Format jedes Datensatzes platziert die 32-Bit-RVA der Funktion im ersten Wort, gefolgt von einem zweiten Wort, das entweder einen Zeiger auf einen Block mit variabler Länge .xdata
enthält, oder ein verpacktes Wort, das eine kanonische Funktionsabspannsequenz beschreibt.
Die Felder haben folgenden Zweck:
Function Start RVA (RVA für Funktionsbeginn) ist der 32-Bit-RVA des Funktionsbeginns.
Das Kennzeichen ist ein 2-Bit-Feld, das angibt, wie die verbleibenden 30 Bit des zweiten
.pdata
Worts interpretiert werden. Wenn Flag den Wert 0 hat, bilden die übrigen Bits den Exception Information RVA (RVA für Ausnahmeinformationen), wobei die beiden niedrigsten Bits implizit 0 sind. Wenn Flag ungleich 0 (null) ist, bilden die übrigen Bits die Struktur Packed Unwind Data (Gepackte Entladedaten).Exception Information RVA (RVA für Ausnahmeinformationen) enthält die Adresse der Ausnahmeinformationsstruktur mit variabler Länge, die im
.xdata
-Abschnitt gespeichert wird. Diese Daten müssen 4-Bytes ausgerichtet sein.Packed Unwind Data (Gepackte Entladedaten) enthält eine komprimierte Beschreibung der Vorgänge, die für die Entladung einer Funktion nötig sind. Dabei wird von einer kanonischen Form ausgegangen. In diesem Fall ist kein
.xdata
-Datensatz erforderlich.
.xdata
Aufzeichnungen
Wenn das gepackte Entladeformat nicht zur Beschreibung der Entladung einer Funktion ausreicht, muss ein .xdata
-Datensatz mit variabler Länge erstellt werden. Die Adresse dieses Datensatzes wird im zweiten Wort des .pdata
-Datensatzes gespeichert. Das Format des .xdata
gepackten Variablenlängensatzes von Wörtern:
Diese Daten sind in vier Abschnitte unterteilt:
Eine 1-Wort- oder 2-Wort-Kopfzeile, die die Gesamtgröße der Struktur beschreibt und Schlüsselfunktionsdaten bereitstellt. Das zweite Wort ist nur vorhanden, wenn die Felder Epilog Count (Epiloganzahl) und Code Words (Codewörter) auf 0 festgelegt sind. Der Header enthält diese Bitfelder:
a. Function Length (Funktionslänge) ist ein 18-Bit-Feld. Es gibt die Gesamtlänge der Funktion in Byte geteilt durch 4 an. Wenn eine Funktion größer als 1M ist, müssen mehrere
.pdata
Datensätze.xdata
verwendet werden, um die Funktion zu beschreiben. Weitere Informationen finden Sie im Abschnitt Große Funktionen.b. Vers (Version) ist ein 2-Bit-Feld. Es beschreibt die Version der verbleibenden
.xdata
. Derzeit ist nur Version 0 definiert, sodass Werte von 1 bis 3 nicht zulässig sind.c. X ist ein 1-Bit-Feld. Es gibt das Vorhandensein (1) oder Fehlen (0) von Ausnahmedaten an.
d. E ist ein 1-Bit-Feld. Es weist darauf hin, dass Informationen, die einen einzelnen Epilog beschreiben, in die Kopfzeile (1) gepackt werden, anstatt später mehr Bereichswörter (0) zu erfordern.
e. Epilog Count (Epiloganzahl) ist ein 5-Bit-Feld mit zwei Bedeutungen, die vom Status des E-Bits abhängen:
Wenn E den Wert 0 aufweist, wird die Gesamtanzahl der in Abschnitt 2 beschriebenen Epilogbereiche angegeben. Sind über 31 Bereiche in der Funktion vorhanden, muss das Feld Code Words (Codewörter) auf 0 festgelegt werden, damit angegeben wird, dass ein Ausnahmewort erforderlich ist.
Wenn E den Wert 1 aufweist, legt dieses Feld den Index des ersten Entladungscodes fest, der den einzigen vorhandenen Epilog beschreibt.
f. Code Words (Codewörter) ist ein 5-Bit-Feld, das die Anzahl der 32-Bit-Wörter angibt, die für alle Entladungscodes in Abschnitt 3 benötigt werden. Wenn mehr als 31 Wörter (d. h. 124 Entspanncodes) erforderlich sind, muss dieses Feld 0 sein, um anzugeben, dass ein Erweiterungswort erforderlich ist.
g. Extended Epilog Count (Erweiterte Epiloganzahl) und Extended Code Words (Erweiterte Codewörter) sind 16-Bit- bzw. 8-Bit-Felder. Sie bieten mehr Speicherplatz für das Codieren ungewöhnlich vieler Epiloge oder Entladungscodewörter. Das Erweiterungswort, das diese Felder enthält, ist nur vorhanden, wenn die Felder Epilog Count (Epiloganzahl) und Code Words (Codewörter) im ersten Headerwort den Wert 0 aufweisen.
Wenn die Anzahl der Epilogs nicht null ist, kommt eine Liste der Informationen zu Epilogbereichen, die ein Wort zu einem Wort gepackt haben, nach der Kopfzeile und optionaler erweiterter Kopfzeile. Sie werden nach aufsteigendem Startoffset gespeichert. Jeder Bereich enthält die folgenden Bits:
a. Epilog Start Offset (Startoffset des Epilogs) ist ein 18-Bit-Feld, das den Epilogoffset in Byte geteilt durch 4 enthält, der relativ zum Funktionsbeginn ist.
b. Res (Reserviert) ist ein 4-Bit-Feld, das für künftige Erweiterungen reserviert ist. Sein Wert muss 0 sein.
c. Epilog Start Index (Startindex des Epilogs) ist ein 10-Bit-Feld (2 Bits länger als Extended Code Words (Erweiterte Codewörter)). Es gibt den Byteindex des ersten Entladungscodes an, der diesen Epilog beschreibt.
Nach der Liste der Epilogbereichen folgt ein Bytearray, das Entladungscodes enthält. Diese werden in einem späteren Abschnitt ausführlich beschrieben. Dieses Array ist am Ende aufgefüllt bis zur nächsten vollen Wortgrenze. Entladungscodes werden in dieses Array geschrieben. Dabei wird mit demjenigen begonnen, der dem Hauptteil der Funktion am nächsten ist, und der Vorgang wird bis zum Funktionsende fortgesetzt. Die Bytes für jeden Abspanncode werden in big-endischer Reihenfolge gespeichert, sodass das wichtigste Byte zuerst abgerufen wird, wodurch der Vorgang und die Länge des restlichen Codes identifiziert werden.
Wenn das X-Bit im Header auf 1 festgelegt wurde, werden die Informationen zum Ausnahmehandler angezeigt. Diese bestehen aus einem einzelnen Exception Handler RVA (RVA für Ausnahmehandler), die die Adresse des Ausnahmehandlers bereitstellt. Dieser folgt die für den Ausnahmehandler erforderliche Datenmenge, deren Länge variiert.
Der .xdata
Datensatz ist so konzipiert, dass es möglich ist, die ersten 8 Bytes abzurufen und sie zum Berechnen der vollständigen Größe des Datensatzes zu verwenden, abzüglich der Länge der folgenden Ausnahmedaten mit variabler Größe. Der folgende Codeausschnitt berechnet die Datensatzgröße:
ULONG ComputeXdataSize(PULONG Xdata)
{
ULONG Size;
ULONG EpilogScopes;
ULONG UnwindWords;
if ((Xdata[0] >> 22) != 0) {
Size = 4;
EpilogScopes = (Xdata[0] >> 22) & 0x1f;
UnwindWords = (Xdata[0] >> 27) & 0x1f;
} else {
Size = 8;
EpilogScopes = Xdata[1] & 0xffff;
UnwindWords = (Xdata[1] >> 16) & 0xff;
}
if (!(Xdata[0] & (1 << 21))) {
Size += 4 * EpilogScopes;
}
Size += 4 * UnwindWords;
if (Xdata[0] & (1 << 20)) {
Size += 4; // Exception handler RVA
}
return Size;
}
Obwohl der Prolog und jeder Epilog einen Index in den Entladungscodes besitzen, teilen sich die beiden die Tabelle. Es ist möglich (und nicht ungewöhnlich), dass sie denselben Codes gemeinsam nutzen können. (Ein Beispiel finden Sie unter Beispiel 2 in der Abschnitt "Beispiele ".) Compilerautoren sollten insbesondere für diesen Fall optimieren. Der Grund dafür ist, dass der größte Index, der angegeben werden kann, 255 ist, wodurch die Gesamtanzahl der Abspanncodes für eine bestimmte Funktion begrenzt wird.
Entladungscodes
Das Array von Abspanncodes ist ein Pool von Sequenzen, die genau beschreiben, wie die Auswirkungen des Prologs rückgängig werden. Sie werden in derselben Reihenfolge gespeichert, in der die Vorgänge rückgängig gemacht werden müssen. Die Entladungscodes sind quasi eine Reihe von Minianweisungen, die als eine Bytezeichenfolge codiert sind. Nach Abschluss der Ausführung befindet sich die Absenderadresse an die aufrufende Funktion im lr
Register. Alle nicht flüchtigen Register werden darüber hinaus zu dem Zeitpunkt wiederhergestellt, zu dem die Funktion aufgerufen wurde.
Würden Ausnahmen garantiert immer nur innerhalb einer Funktion und niemals im Prolog oder Epilog auftreten, wäre nur eine Sequenz nötig. Für das Windows-Entlademodell ist jedoch erforderlich, dass Code aus einem partiell ausgeführten Prolog oder Epilog entladen werden kann. Damit diese Anforderung erfüllt wird, wurden die Entladungscodes so entwickelt, dass eine eindeutige 1:1-Zuordnung zu jedem relevanten Opcode im Prolog und Epilog vorhanden ist. Diese Konzeption hat mehrere Auswirkungen:
Es ist möglich, die Länge von Prolog und Epilog zu berechnen, indem die Anzahl der Entladungscodes gezählt wird.
Indem die Anzahl der Anweisungen nach dem Anfang eines Epilogbereichs gezählt wird, ist es möglich, die entsprechende Anzahl von Entladungscodes zu überspringen. Wir können den Rest einer Sequenz ausführen, um die teilweise ausgeführte Entspannung durch den Epilog abzuschließen.
Indem die Anzahl der Anweisungen vor dem Ende des Prologs gezählt wird, ist es möglich, die entsprechende Anzahl von Entladungscodes zu überspringen. Wir können den Rest der Sequenz ausführen, um nur die Teile des Prologs rückgängig zu machen, die die Ausführung abgeschlossen haben.
Die Entladungscodes werden der folgenden Tabelle entsprechend codiert. Alle Abspanncodes sind ein/Doppelbyte, mit Ausnahme des Codes, das einen riesigen Stapel (alloc_l
) zuordnet. Insgesamt gibt es 22 Entspanncodes. Jeder Entladungscode ordnet genau eine Anweisung im Prolog oder Epilog zu, um das Entladen partiell ausgeführter Prologe und Epiloge zu ermöglichen.
Entladungscode | Bits und Interpretation |
---|---|
alloc_s |
000xxxxx: Zuordnen eines kleinen Stapels mit Größe < 512 (2^5 * 16). |
save_r19r20_x |
001zzzzz: Paar speichern <x19,x20> bei [sp-#Z*8]! , vorindizierter Offset >= -248 |
save_fplr |
01zzzzzz: Das Paar <x29,lr> wird unter [sp+#Z*8] gespeichert, der Offset ist <= 504. |
save_fplr_x |
10zzzzzz: Speicherpaar <x29,lr> bei [sp-(#Z+1)*8]! , vorindizierter Offset >= -512 |
alloc_m |
11000xxx'xxxxxxxx: Große Stapel mit Größe < 32K zuordnen (2^11 * 16). |
save_regp |
110010xx'xxzzzzzz: Paar speichern x(19+#X) bei [sp+#Z*8] , Offset <= 504 |
save_regp_x |
110011xx'xxzzzzzz: Paar speichern x(19+#X) bei [sp-(#Z+1)*8]! , vorindizierter Offset >= -512 |
save_reg |
110100xx'xxzzzzzz: reg x(19+#X) at [sp+#Z*8] , offset <= 504 |
save_reg_x |
1101010x'xxxzzzzz: reg x(19+#X) at [sp-(#Z+1)*8]! , vorindizierter Offset >= -256 |
save_lrpair |
1101011x'xxzzzzzz: Das Paar <x(19+2*#X),lr> wird unter [sp+#Z*8] gespeichert, der Offset ist <= 504. |
save_fregp |
1101100x'xxzzzzzz: Paar d(8+#X) speichern bei [sp+#Z*8] , Offset <= 504 |
save_fregp_x |
1101101x'xxzzzzzz: Save pair d(8+#X) at [sp-(#Z+1)*8]! , pre-indexed offset >= -512 |
save_freg |
1101110x'xxzzzzzz: reg d(8+#X) at [sp+#Z*8] , offset <= 504 |
save_freg_x |
11011110'xxxzzzzz: reg d(8+#X) at [sp-(#Z+1)*8]! , vorindizierter Offset >= -256 |
alloc_l |
11100000'xx'xx'xxxxxx'xxxxxx: Große Stapel mit Größe < 256M zuordnen (2^24 * 16) |
set_fp |
11100001: Einrichten x29 mit mov x29,sp |
add_fp |
11100010'xxxxxxxx: Einrichten x29 mit add x29,sp,#x*8 |
nop |
11100011: Es ist kein Entladevorgang erforderlich. |
end |
11100100: Ende des Entladungscodes, Impliziert ret in Epilog. |
end_c |
11100101: Ende des Entladungscodes im aktuellen verketteten Bereich |
save_next |
11100110: Das nächste nicht flüchtige Integer- oder FP-Registerpaar wird gespeichert. |
11100111: reserviert | |
11101xxx: Für folgende benutzerdefinierte Stapelfälle reserviert, wird nur für ASM-Routinen generiert. | |
111010000: Benutzerdefinierter Stapel für MSFT_OP_TRAP_FRAME |
|
11101001: Benutzerdefinierter Stapel für MSFT_OP_MACHINE_FRAME |
|
11101010: Benutzerdefinierter Stapel für MSFT_OP_CONTEXT |
|
11101011: Benutzerdefinierter Stapel für MSFT_OP_EC_CONTEXT |
|
11101100: Benutzerdefinierter Stapel für MSFT_OP_CLEAR_UNWOUND_TO_CALL |
|
11101101: reserviert | |
11101110: reserviert | |
11101111: reserviert | |
11110xxx: reserviert | |
11111000'yyy : reserviert | |
11111001'yy'yy: reserviert | |
11111010'yy'y'yy: reserviert | |
11111011'yy'y'yy'y'y: reserviert | |
pac_sign_lr |
11111100: Signieren der Absenderadresse lr mit pacibsp |
11111101: reserviert | |
11111110: reserviert | |
11111111: reserviert |
Die Anweisungen mit großen Werten umfassen mehrere Bytes, wobei die wichtigsten Bits zuerst gespeichert sind. Mit dieser Konzeption können Sie die Gesamtgröße des Entladungscodes in Byte ermitteln, indem Sie nur das erste Byte des Codes suchen. Da jeder Entladungscode genau einer Anweisung in einem Prolog oder Epilog zugeordnet ist, können Sie die Größe des Prologs oder Epilogs berechnen. Gehen Sie von der Sequenz von Anfang zum Ende, und verwenden Sie eine Nachschlagetabelle oder ein ähnliches Gerät, um die Länge des entsprechenden Opcodes zu bestimmen.
Die postindizierte Offsetadressierung ist in einem Prolog nicht zulässig. Alle Offsetbereiche (#Z) stimmen mit der Codierung der stp
/str
Adressierung überein, wobei save_r19r20_x
248 für alle Speicherbereiche ausreichend ist (10 Int-Register + 8 RP-Register + 8 Eingaberegister).
save_next
muss ein Speichervorgang für ein flüchtiges Integer- oder FP-Registerpaar folgen: save_regp
, save_regp_x
, save_fregp
, save_fregp_x
, save_r19r20_x
oder noch einmal save_next
. Dabei wird das nächste Registerpaar im nächsten 16-Byte-Slot in aufsteigender Reihenfolge gespeichert. save_next
verweist auf das erste FP-Registerpaar, wenn es auf den save-next
-Befehl folgt, der das letzte Integerregisterpaar angibt.
Da die Größen von regulären Rückgabe- und Sprunganweisungen identisch sind, ist es nicht erforderlich, in Tail-Call-Szenarien einen getrennten end
Entschärfungscode zu verwenden.
end_c
ist so konzipiert, dass nicht zusammenhängende Funktionsfragmente zu Optimierungszwecken verarbeitet werden. Ein end_c
Wert, der das Ende der Abspanncodes im aktuellen Bereich angibt, muss eine weitere Reihe von Abspanncodes folgen, die mit einer echten end
Endung enden. Die Abspanncodes zwischen end_c
und end
stellen die Prologvorgänge in der übergeordneten Region dar (ein "Phantom"-Prolog). Weitere Details und Beispiele werden im folgenden Abschnitt beschrieben.
Gepackte Entladedaten
Bei Funktionen, deren Prologe und Epiloge der unten beschriebenen kanonischen Form entsprechen, können gepackte Entladedaten verwendet werden. Es beseitigt die Notwendigkeit eines .xdata
Datensatzes vollständig und reduziert die Kosten für die Bereitstellung von Entschlangen von Daten erheblich. Die kanonischen Prologs und Epilogs sind so konzipiert, dass sie die allgemeinen Anforderungen einer einfachen Funktion erfüllen: Eine, die keinen Ausnahmehandler erfordert und deren Einrichtungs- und Abbruchvorgänge in einer Standardreihenfolge durchführt.
Das Format eines .pdata
Datensatzes mit verpackten Ruhedaten sieht wie folgt aus:
Die Felder haben folgenden Zweck:
- Function Start RVA (RVA für Funktionsbeginn) ist der 32-Bit-RVA des Funktionsbeginns.
- Flag ist ein 2-Bit-Feld, wie oben beschrieben, mit den folgenden Bedeutungen:
- 00 = verpackte Abspanndaten, die nicht verwendet werden; Verbleibende Bits zeigen auf einen
.xdata
Datensatz - 01 = Gepackte Entladedaten, die mit einem einzelnen Prolog und Epilog am Anfang und Ende des Bereichs verwendet werden.
- 10 = Gepackte Entladedaten, die für Code ohne Prolog und Epilog verwendet werden. Diese sind zum Beschreiben separater Funktionssegmente nützlich.
- 11 = reserviert
- 00 = verpackte Abspanndaten, die nicht verwendet werden; Verbleibende Bits zeigen auf einen
- Function Length (Funktionslänge) ist ein 11-Bit-Feld, das die Länge der gesamten Funktion in Byte geteilt durch 4 angibt. Wenn die Funktion größer als 8k ist, muss stattdessen ein vollständiger
.xdata
Datensatz verwendet werden. - Frame Size (Framegröße) ist ein 9-Bit-Feld, das die Byteanzahl des Stapels, der dieser Funktion zugeordnet ist, geteilt durch 16 angibt. Funktionen, die mehr als (8k-16) Bytes des Stapels zuweisen, müssen einen vollständigen
.xdata
Datensatz verwenden. Sie enthält den lokalen Variablenbereich, den Ausgehenden Parameterbereich, den angerufenen Int- und den FP-Bereich sowie den Bereich des Startparameters. Er schließt den dynamischen Zuordnungsbereich aus. - CR ist ein 2-Bit-Flag, das angibt, ob die Funktion zusätzliche Anweisungen zum Einrichten einer Framekette und Rückgabeverknüpfung enthält:
- 00 = nicht gekettete Funktion,
<x29,lr>
Paar wird nicht im Stapel gespeichert. - 01 = Nicht verkettete Funktion,
<lr>
wird im Stapel gespeichert. - 10 = verkettete Funktion mit einer
pacibsp
signierten Absenderadresse - 11 = Verkettete Funktion, eine Anweisung für ein Speicher-Lade-Paar wird im Prolog oder Epilog
<x29,lr>
verwendet.
- 00 = nicht gekettete Funktion,
- H ist ein 1-Bit-Flag, das angibt, ob die Funktion die Integerparameterregister (x0–x7) herausgreift, indem sie am Anfang der Funktion gespeichert werden. (0 = registriert sich nicht zu Hause, 1 = Häuserregister).
- RegI ist ein 4-Bit-Feld, das die Anzahl der nicht flüchtigen Int-Register (x19–x28) angibt, die am kanonischen Stapelspeicherort gespeichert sind.
- RegF ist ein 3-Bit-Feld, das die Anzahl der nicht flüchtigen FP-Register (d8–d15) angibt, die am kanonischen Stapelspeicherort gespeichert sind. (RegF=0: Es wird kein RP-Register gespeichert; RegF>0: RegF+1 RP-Register werden gespeichert). Gepackte Entladedaten können nicht für Funktionen verwendet werden, bei denen nur ein FP-Register gespeichert wird.
Kanonische Prologe, die in die Kategorien 1, 2 (ohne ausgehenden Parameterbereich), 3 und 4 im obigen Abschnitt fallen, können durch das gepackte Entladeformat dargestellt werden. Die Epiloge für kanonische Funktionen folgen einem ähnlichen Format. Es besteht jedoch der Unterschied, dass H keine Auswirkung hat, die set_fp
-Anweisung ausgelassen wird und die Reihenfolge der Schritte und die Anweisungen in jedem Schritt im Epilog rückgängig gemacht werden. Der Algorithmus für Gepackt .xdata
folgt den folgenden Schritten, die in der folgenden Tabelle beschrieben sind:
Schritt 0: Vorabberechnung der Größe der einzelnen Bereiche.
Schritt 1: Signieren der Absenderadresse.
Schritt 2: Speichern sie gespeicherte Int-Register.
Schritt 3: Dieser Schritt ist spezifisch für Typ 4 in frühen Abschnitten. lr
wird am Ende des Int-Bereichs gespeichert.
Schritt 4: Speichern sie gespeicherte FP-Register.
Schritt 5: Speichern von Eingabeargumenten im Home-Parameterbereich.
Schritt 6: Zuordnen des verbleibenden Stapels, einschließlich lokaler Fläche, <x29,lr>
Paar und ausgehender Parameterbereich. 6a entspricht kanonischem Typ 1. 6b und 6c sind für kanonische Typ 2. 6d und 6e gelten sowohl für Typ 3 als auch für Typ 4.
Schrittnummer | Flagwerte | Anzahl der Anweisungen | Opcode | Entladungscode |
---|---|---|---|---|
0 | #intsz = RegI * 8; if (CR==01) #intsz += 8; // lr #fpsz = RegF * 8; if(RegF) #fpsz += 8; #savsz=((#intsz+#fpsz+8*8*H)+0xf)&~0xf) #locsz = #famsz - #savsz |
|||
1 | CR == 10 | 1 | pacibsp |
pac_sign_lr |
2 | 0 <RegI<= 10 | RegI / 2 + RegI % 2 |
stp x19,x20,[sp,#savsz]! stp x21,x22,[sp,#16] ... |
save_regp_x save_regp ... |
3 | CR == 01 * | 1 | str lr,[sp,#(intsz-8)] * |
save_reg |
4 | 0 <RegF<= 7 | (RegF + 1) / 2 + (RegF + 1) % 2) |
stp d8,d9,[sp,#intsz] **stp d10,d11,[sp,#(intsz+16)] ... str d(8+RegF),[sp,#(intsz+fpsz-8)] |
save_fregp ... save_freg |
5 | H == 1 | 4 | stp x0,x1,[sp,#(intsz+fpsz)] stp x2,x3,[sp,#(intsz+fpsz+16)] stp x4,x5,[sp,#(intsz+fpsz+32)] stp x6,x7,[sp,#(intsz+fpsz+48)] |
nop nop nop nop |
6a | (CR == 10 || CR == 11) &&#locsz <= 512 |
2 | stp x29,lr,[sp,#-locsz]! mov x29,sp *** |
save_fplr_x set_fp |
6b | (CR == 10 || CR == 11) && 512 < #locsz <= 4080 |
3 | sub sp,sp,#locsz stp x29,lr,[sp,0] add x29,sp,0 |
alloc_m save_fplr set_fp |
6c | (CR == 10 || CR == 11) &&#locsz > 4080 |
4 | sub sp,sp,4080 sub sp,sp,#(locsz-4080) stp x29,lr,[sp,0] add x29,sp,0 |
alloc_m alloc_s /alloc_m save_fplr set_fp |
6d | (CR == 00 || CR == 01) &&#locsz <= 4080 |
1 | sub sp,sp,#locsz |
alloc_s /alloc_m |
6e | (CR == 00 || CR == 01) &&#locsz > 4080 |
2 | sub sp,sp,4080 sub sp,sp,#(locsz-4080) |
alloc_m alloc_s /alloc_m |
* Wenn CR == 01 und RegI eine ungerade Zahl ist, werden Schritt 3 und der letzte save_reg
in Schritt 2 mit einer save_regp
zusammengeführt.
** Wenn RegI == CR == 0 und RegF != 0, der erste stp
für den Gleitkommapunkt führt die Vorschritte aus.
Im Epilog ist keine entsprechende Anweisung mov x29,sp
vorhanden. Verpackte Abspanndaten können nicht verwendet werden, wenn eine Funktion eine Wiederherstellung von sp
x29
.
Entladen partieller Prologe und Epiloge
In den am häufigsten abwickelnden Situationen tritt die Ausnahme oder der Aufruf im Textkörper der Funktion auf, weg vom Prolog und allen Epilogs. In diesen Situationen ist die Entspannung einfach: Der Abspanner führt einfach die Codes im ausspannenden Array aus. Sie beginnt bei Index 0 und wird fortgesetzt, bis ein end
Opcode erkannt wird.
Die Entladung gestaltet sich schwieriger, wenn eine Ausnahme oder ein Interrupt auftritt, während ein Prolog oder Epilog ausgeführt wird. In diesen Fällen wird der Stapelrahmen nur teilweise konstruiert. Das Problem besteht darin, durchgeführte Aktionen genau zu bestimmen, damit diese ordnungsgemäß rückgängig gemacht werden können.
Nehmen Sie diese Prolog- und Epilogsequenz als Beispiel:
0000: stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store)
0004: stp d8,d9,[sp,#224] // save_fregp 0, 224
0008: stp x19,x20,[sp,#240] // save_regp 0, 240
000c: mov x29,sp // set_fp
...
0100: mov sp,x29 // set_fp
0104: ldp x19,x20,[sp,#240] // save_regp 0, 240
0108: ldp d8,d9,[sp,224] // save_fregp 0, 224
010c: ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load)
0110: ret lr // end
Neben jedem Opcode befindet sich der entsprechende Entladungscode, der diesen Vorgang beschreibt. Sie sehen, dass die Sequenz der Entladungscodes für den Prolog im Vergleich zu derjenigen für den Epilog gespiegelt ist (von der letzten Anweisung des Epilogs abgesehen). Es ist eine häufige Situation: Deshalb gehen wir immer davon aus, dass die Abwickelcodes für den Prolog in umgekehrter Reihenfolge aus der Ausführungsreihenfolge des Prologs gespeichert werden.
Für Prolog und Epilog gibt es also gemeinsamen Entladungscodes:
set_fp
, , save_regp 0,240
save_fregp,0,224
, , save_fplr_x_256
end
Der Epilogfall ist geradlinig, da eine normale Reihenfolge vorliegt. Ab Offset 0 innerhalb des Epilogs (der bei Offset beginnt 0x100 in der Funktion) wird erwartet, dass die vollständige Abspannsequenz ausgeführt wird, da noch keine Bereinigung durchgeführt wurde. Wenn wir uns eine Anweisung weiter (am Offset 2 im Epilog) befinden, können wir den ersten Entladungscode überspringen. Diese Situation lässt sich verallgemeinern, indem wir annehmen, dass eine 1:1-Zuordnung zwischen Opcodes und Entladungscodes vorliegt. Dann sollten die ersten n Entladungscodes übersprungen und von dieser Position aus begonnen werden, um mit der Entladung von Anweisung n aus im Epilog zu beginnen.
Eine ähnliche Logik funktioniert für den Prolog, die Reihenfolge ist jedoch umgekehrt. Wenn wir mit der Auflösung von Offset 0 im Prolog beginnen, soll nichts ausgeführt werden. Wenn wir den Entladevorgang bei Offset 2 starten, der schon eine Anweisung weiter ist, soll die Entladesequenz einen Entladungscode vor dem Ende beginnen. (Denken Sie daran, dass die Codes in umgekehrter Reihenfolge gespeichert werden.) Und auch hier können wir verallgemeinern: Wenn wir mit dem Entspannen von Anweisung n im Prolog beginnen, sollten wir anfangen , Codes vom Ende der Liste der Codes auszuführen.
Prolog- und Epilogcodes stimmen nicht immer genau überein, weshalb das Ausspannarray möglicherweise mehrere Codesequenzen enthalten muss. Verwenden Sie folgende Logik, um den Offset zu bestimmen, an dem mit der Verarbeitung der Codes begonnen werden soll:
Wenn Sie sich von innerhalb des Körpers der Funktion entspannen, beginnen Sie mit der Ausführung von Abwickelcodes bei Index 0, und fahren Sie fort, bis sie einen
end
Opcode erreicht haben.Wenn die Entladung in einem Epilog erfolgt, verwenden Sie den epilogspezifischen Startindex, der vom Epilogbereich als Startpunkt angegeben wird. Berechnen Sie, wie viele Bytes sich der jeweilige PC vom Beginn des Epilogs weg befindet. Durchlaufen Sie die Entladungscodes dann vorwärts, wodurch diese übersprungen werden, bis alle bereits ausgeführten Anweisungen einbezogen sind. Beginnen Sie an diesem Punkt mit der Ausführung.
Wenn Sie im Prolog eine Entladung durchführen, sollten Sie Index 0 als Ausgangspunkt verwenden. Berechnen Sie die Länge des Prologcodes aus der Sequenz und dann, wie viele Bytes der jeweilige PC sich vom Ende des Prologs weg befindet. Durchlaufen Sie die Entladungscodes dann vorwärts, wodurch diese übersprungen werden, bis alle noch nicht ausgeführten Anweisungen einbezogen sind. Beginnen Sie an diesem Punkt mit der Ausführung.
Diese Regeln haben zur Folge, dass Entladungscodes für den Prolog immer zuerst im Array stehen müssen. Dabei handelt es sich auch um die Codes, die für die Entladung im Text verwendet werden. Alle epilogspezifischen Codesequenzen sollten unmittelbar folgen.
Funktionsfragmente
Aus Codeoptimierungszwecken und anderen Gründen kann es vorzuziehen sein, eine Funktion in getrennte Fragmente (auch als Regionen bezeichnet) aufzuteilen. Bei der Aufteilung erfordert jedes resultierende Funktionsfragment einen eigenen separaten (und möglicherweise .xdata
) Datensatz .pdata
.
Für jedes getrennte sekundäre Fragment, das einen eigenen Prolog enthält, wird erwartet, dass im Prolog keine Stapelanpassung erfolgt. Sämtlicher Stapelspeicher, der für einen sekundären Bereich erforderlich ist, muss vom übergeordneten Bereich (Hostbereich) zugeordnet werden. Diese Präallocation behält die Stapelzeigermanipulation streng im ursprünglichen Prolog der Funktion bei.
Ein typischer Fall von Funktionsfragmenten ist "Codetrennung", bei dem der Compiler einen Codebereich aus seiner Hostfunktion verschieben kann. Es gibt drei ungewöhnliche Fälle, die sich aus der Codetrennung ergeben könnten.
Beispiel
(Bereich 1: Anfang)
stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store) stp x19,x20,[sp,#240] // save_regp 0, 240 mov x29,sp // set_fp ...
(Bereich 1: Ende)
(Bereich 3: Anfang)
...
(Bereich 3: Ende)
(Bereich 2: Anfang)
... mov sp,x29 // set_fp ldp x19,x20,[sp,#240] // save_regp 0, 240 ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load) ret lr // end
(Bereich 2: Ende)
Nur Prolog (Bereich 1: Alle Epiloge befinden sich in separaten Bereichen):
Nur der Prolog muss beschrieben werden. Dieser Prolog kann nicht im kompakten
.pdata
Format dargestellt werden. Im vollständigen.xdata
Fall kann sie durch Festlegen von Epilog Count = 0 dargestellt werden. Dies wird in Bereich 1 im obigen Beispiel veranschaulicht.Entladungscodes:
set_fp
,save_regp 0,240
,save_fplr_x_256
,end
Nur Epiloge (Bereich 2: Der Prolog befindet sich in der Hostregion.):
Es wird davon ausgegangen, dass durch das Zeitsteuerelement in diese Region springt, alle Prologcodes ausgeführt wurden. Eine Teilentladung kann in Epilogen auf dieselbe Weise wie in einer normalen Funktion erfolgen. Dieser Bereichstyp kann nicht durch komprimiert
.pdata
dargestellt werden. In einem vollständigen.xdata
Datensatz kann es mit einem "Phantom"-Prolog codiert werden, das durch ein Codepaar in eckigemend_c
Klammern geklammert wirdend
. Der führendeend_c
-Code gibt an, dass die Größe des Prologs 0 (null) ist. Der Startindex des einzelnen Epilogs zeigt aufset_fp
.Entladungscode für Bereich 2:
end_c
,set_fp
,save_regp 0,240
,save_fplr_x_256
,end
.Keine Prologe oder Epiloge (Bereich 3: Prologe und alle Epiloge befinden sich in anderen Fragmenten.):
Komprimierungsformat
.pdata
kann über die Einstellung Flag = 10 angewendet werden. Mit vollständiger.xdata
Aufzeichnung, Epilog Count = 1. Der Entladungscode ist mit dem oben aufgeführten Code für Bereich 2 identisch, aber der Startindex des Epilogs verweist auch aufend_c
. Eine Teilentladung erfolgt in diesem Codebereich nie.
Ein weiterer komplizierterer Fall von Funktionsfragmenten ist "Wrapping verkleinern". Der Compiler kann das Speichern einiger gespeicherter Register verzögern, bis außerhalb des Funktionseintrags prolog.
(Bereich 1: Anfang)
stp x29,lr,[sp,#-256]! // save_fplr_x 256 (pre-indexed store) stp x19,x20,[sp,#240] // save_regp 0, 240 mov x29,sp // set_fp ...
(Bereich 2: Anfang)
stp x21,x22,[sp,#224] // save_regp 2, 224 ... ldp x21,x22,[sp,#224] // save_regp 2, 224
(Bereich 2: Ende)
... mov sp,x29 // set_fp ldp x19,x20,[sp,#240] // save_regp 0, 240 ldp x29,lr,[sp],#256 // save_fplr_x 256 (post-indexed load) ret lr // end
(Bereich 1: Ende)
Im Prolog von Bereich 1 wird der Stapelspeicher im Voraus zugeordnet. Sie sehen, dass in Bereich 2 derselbe Entladungscode vorhanden ist, auch wenn dieser aus der Hostfunktion heraus verschoben wurde.
Region 1: set_fp
, save_regp 0,240
, save_fplr_x_256
, . end
Epilog Start Index verweist wie gewohnt auf set_fp
.
Bereich 2: save_regp 2, 224
, end_c
, set_fp
, save_regp 0,240
, save_fplr_x_256
, end
Der Startindex des Epilogs zeigt auf den ersten Entladungscode, save_regp 2, 224
.
Große Funktionen
Fragmente können verwendet werden, um Funktionen zu beschreiben, die größer als die 1M-Grenze sind, die von den Bitfeldern in der .xdata
Kopfzeile auferlegt werden. Um eine ungewöhnlich große Funktion wie diese zu beschreiben, muss sie in Fragmente unterteilt werden, die kleiner als 1M sind. Jedes Fragment sollte so angepasst werden, dass es Epiloge nicht aufteilt.
Nur das erste Fragment der Funktion enthält einen Prolog. Alle anderen sind als Fragmente ohne Prolog gekennzeichnet. Je nach Anzahl der vorhandenen Epiloge kann jedes Fragment null oder mehr Epiloge enthalten. Bedenken Sie, dass jeder Epilogbereich in einem Fragment dessen Startoffset relativ zum Start des Fragments, nicht zum Start der Funktion festlegt.
Wenn ein Fragment keinen Prolog hat und kein Epilog, erfordert es dennoch einen eigenen .pdata
(und möglicherweise .xdata
) Datensatz, um zu beschreiben, wie man sich vom Körper der Funktion erholen kann.
Beispiele
Beispiel 1: Rahmenverkettung, Kompaktform
|Foo| PROC
|$LN19|
str x19,[sp,#-0x10]! // save_reg_x
sub sp,sp,#0x810 // alloc_m
stp fp,lr,[sp] // save_fplr
mov fp,sp // set_fp
// end of prolog
...
|$pdata$Foo|
DCD imagerel |$LN19|
DCD 0x416101ed
;Flags[SingleProEpi] functionLength[492] RegF[0] RegI[1] H[0] frameChainReturn[Chained] frameSize[2080]
Beispiel 2: Rahmenkette, Vollform mit Spiegel prolog & Epilog
|Bar| PROC
|$LN19|
stp x19,x20,[sp,#-0x10]! // save_regp_x
stp fp,lr,[sp,#-0x90]! // save_fplr_x
mov fp,sp // set_fp
// end of prolog
...
// begin of epilog, a mirror sequence of Prolog
mov sp,fp
ldp fp,lr,[sp],#0x90
ldp x19,x20,[sp],#0x10
ret lr
|$pdata$Bar|
DCD imagerel |$LN19|
DCD imagerel |$unwind$cse2|
|$unwind$Bar|
DCD 0x1040003d
DCD 0x1000038
DCD 0xe42291e1
DCD 0xe42291e1
;Code Words[2], Epilog Count[1], E[0], X[0], Function Length[6660]
;Epilog Start Index[0], Epilog Start Offset[56]
;set_fp
;save_fplr_x
;save_r19r20_x
;end
Der Startindex des Epilogs [0] zeigt auf die gleiche Entladungscodesequenz im Prolog.
Beispiel 3: Variadic unchained Function
|Delegate| PROC
|$LN4|
sub sp,sp,#0x50
stp x19,lr,[sp]
stp x0,x1,[sp,#0x10] // save incoming register to home area
stp x2,x3,[sp,#0x20] // ...
stp x4,x5,[sp,#0x30]
stp x6,x7,[sp,#0x40] // end of prolog
...
ldp x19,lr,[sp] // beginning of epilog
add sp,sp,#0x50
ret lr
AREA |.pdata|, PDATA
|$pdata$Delegate|
DCD imagerel |$LN4|
DCD imagerel |$unwind$Delegate|
AREA |.xdata|, DATA
|$unwind$Delegate|
DCD 0x18400012
DCD 0x200000f
DCD 0xe3e3e3e3
DCD 0xe40500d6
DCD 0xe40500d6
;Code Words[3], Epilog Count[1], E[0], X[0], Function Length[18]
;Epilog Start Index[4], Epilog Start Offset[15]
;nop // nop for saving in home area
;nop // ditto
;nop // ditto
;nop // ditto
;save_lrpair
;alloc_s
;end
Der Startindex des Epilogs [4] verweist auf die Mitte des Entladungscodes im Prolog (partielle Wiederverwendung des Entladungsarrays).
Siehe auch
Übersicht über ARM64-ABI-Konventionen
ARM-Ausnahmebehandlung