Freigeben über


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 Original sp 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

Diagramm mit Stapelrahmenlayout für Funktionen.

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.

  1. 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)
    
  2. 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
    
  3. 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, the sub 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.

  4. 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-lr stp can't be represented with the relax codes.

    Auf alle Lokalen wird basierend auf sp. <x29> zeigt auf den vorherigen Frame.

  5. 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.

  6. 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 auf sp.

  7. 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.

PDATA-Datensatzlayout.

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:

XDATA-Datensatzlayout.

Diese Daten sind in vier Abschnitte unterteilt:

  1. 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:

    1. 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.

    2. 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.

  2. 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.

  3. 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.

  4. 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_x248 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 endEndung 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:

PDATA-Datensatz mit gepackten Entladedaten.

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
  • 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.
  • 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_regpzusammengefü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,240save_fregp,0,224, , save_fplr_x_256end

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:

  1. 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.

  2. 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.

  3. 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)

  1. 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

  2. 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 .pdatadargestellt werden. In einem vollständigen .xdata Datensatz kann es mit einem "Phantom"-Prolog codiert werden, das durch ein Codepaar in eckigem end_c Klammern geklammert wird end . Der führende end_c-Code gibt an, dass die Größe des Prologs 0 (null) ist. Der Startindex des einzelnen Epilogs zeigt auf set_fp.

    Entladungscode für Bereich 2: end_c, set_fp, save_regp 0,240, save_fplr_x_256, end.

  3. 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 auf end_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