Share via


ARM-Ausnahmebehandlung

Windows auf ARM verwendet den gleichen strukturierten Ausnahmebehandlungsmechanismus für asynchrone hardwaregenerierte Ausnahmen und synchrone softwaregenerierte Ausnahmen. Sprachspezifische Ausnahmehandler werden auf von Windows strukturierter Ausnahmebehandlung mithilfe von Sprachhilfsfunktionen erstellt. Dieses Dokument beschreibt die Ausnahmebehandlung in Windows auf ARM und die Sprachhilfsprogramme sowohl des Microsoft ARM-Assemblers als auch des MSVC-Compilers.

ARM-Ausnahmebehandlung

Windows auf ARM verwendet Entladungscodes, um die Stapelentladung während der strukturierten Ausnahmebehandlung (SEH) zu steuern. Entladungscodes sind eine Folge von Bytes, die im .xdata-Abschnitt des ausführbaren Images gespeichert sind. Diese Codes beschreiben den Betrieb von Funktionsprologen und Epilogcode auf abstrakte Weise. Der Handler verwendet sie, um die Effekte der Funktionsprologe rückgängig zu machen, wenn sie sich auf den Stapelframe des Aufrufers entspannen.

Die ARM EABI (eingebettete Anwendungs-Binärschnittstelle) gibt ein Modell für die Ausnahmeauskopplung an, das Ausspanncodes verwendet. Das Modell reicht nicht aus, um SEH in Windows zu entspannen. Es muss asynchrone Fälle behandeln, in denen sich der Prozessor in der Mitte des Prologs oder Epilogs einer Funktion befindet. Windows teilt außerdem die Entladungssteuerung in Entladung auf Funktionsebene und für den sprachspezifischen Bereich auf, was in der ARM EABI nicht definiert ist. Deshalb legt Windows auf ARM mehr Details für die Entladung von Daten und Verfahren fest.

Annahmen

Ausführbare Dateien für Windows auf ARM verwenden das portierbare ausführbare Format (Portable Executable, PE). Weitere Informationen finden Sie unter PE-Format. Ausnahmebehandlungsinformationen werden in den Abschnitten .pdata und .xdata des Bilds gespeichert.

Der Ausnahmebehandlungsmechanismus geht von bestimmten Annahmen über den Code aus, der ABI für Windows auf ARM folgt:

  • Wenn eine Ausnahme im Textkörper einer Funktion auftritt, kann der Handler die Vorgänge des Prologs rückgängig machen oder die Vorgänge des Epilogs vorwärts ausführen. Beides sollte zum gleichen Ergebnis führen.

  • Prologe und Epiloge entsprechen einander tendenziell. Dieses Feature kann verwendet werden, um die Größe der Metadaten zu verringern, die zum Beschreiben des Entspannens erforderlich sind.

  • Funktionen sind eher relativ klein. Mehrere Optimierungen basieren auf dieser Beobachtung für eine effiziente Verpackung von Daten.

  • Wenn eine Bedingung in einen Epilog eingefügt wird, gilt sie gleichermaßen für jede Anweisung im Epilog.

  • Wenn der Prolog den Stapelzeiger (SP) in einem anderen Register speichert, muss dieses Register in der gesamten Funktion erneut Standard unverändert sein, sodass der ursprüngliche SP jederzeit wiederhergestellt werden kann.

  • Sämtliche Bearbeitungen des SP müssen strikt innerhalb von Prolog und Epilog erfolgen, außer wenn er in einem anderen Register gespeichert ist.

  • Folgende Vorgänge sind nötig, damit ein Stapelrahmen entladen werden kann:

    • Anpassen von r13 (SP) in 4-Byte-Schritten.

    • Eines oder mehrere Ganzzahlregister per Pop entfernen.

    • Eines oder mehrere VFP-Register (Virtual Floating-Poin, Gleitkomma) per Pop entfernen.

    • Einen willkürlichen Registerwert nach r13 (SP) kopieren.

    • SP mithilfe eines kleinen Postdekrementvorgangs aus dem Stapel laden.

    • Einen Rahmentyp aus einigen klar definierten Rahmentypen analysieren.

.pdata-Datensätze

Die .pdata-Datensätze in einem PE-Format-Bild sind ein geordnetes Array von Elementen mit fester Länge, die jede Stapelbearbeitungsfunktion beschreiben. Blattfunktionen (Funktionen, die keine anderen Funktionen aufrufen) erfordern .pdata keine Datensätze, wenn sie den Stapel nicht bearbeiten. (Das heißt, sie erfordern keinerlei lokales Speichern und müssen nicht flüchtige Register weder speichern noch wiederherstellen.). Datensätze für diese Funktionen können im .pdata-Bereich weggelassen werden, sodass Platz gespart wird. Ein Entladevorgang aus einer dieser Funktionen kann einfach die Rückgabeadresse aus dem Link Register (LR) an den Programmzähler (Program Counter, PC) kopieren, um nach oben zum Aufrufer zu bewegen.

Jeder .pdata-Datensatz für ARM ist 8 Byte lang. Das allgemeine Format für einen Datensatz platziert die relative virtuelle Adresse (RVA) des Funktionsstarts in das erste 32-Bit-Wort gefolgt von einem zweiten Wort, das entweder einen Zeiger zu einem .xdata-Block mit variabler Länge enthält oder ein gepacktes Wort, dass die Entladesequenz einer kanonischen Funktion beschreibt, wie in dieser Tabelle dargestellt:

Wortoffset Bits Zweck
0 0-31 Function Start RVA ist der 32-Bit-RVA des Starts der Funktion. Wenn die Funktion Thumb-Code enthält, muss der niedrigste Bitwert dieser Adresse festgelegt werden.
1 0-1 Flag ist ein 2-Bit-Feld, das angibt, wie die verbleibenden 30 Bit des zweiten .pdata-Worts zu interpretieren sind. 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).
1 2-31 Exception Information RVA (RVA für Ausnahmeinformationen) oder 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.

Gepackte Entladedaten

Bei Funktionen, deren Prolog und Epilog der unten beschriebenen kanonischen Form entsprechen, können gepackte Entladedaten verwendet werden. Dadurch wird die Notwendigkeit eines .xdata Datensatzes beseitigt und der erforderliche Platz für die Bereitstellung der Entspanndaten erheblich reduziert. Die kanonischen Prologe und Epiloge sind so konzipiert, dass sie die allgemeinen Anforderungen einer einfachen Funktion erfüllen, die keinen Ausnahmehandler erfordert, und führt die Einrichtungs- und Abbruchvorgänge in einer Standardreihenfolge aus.

Diese Tabelle zeigt das Format eines .pdata-Datensatzes, der Entladedaten gepackt hat:

Wortoffset Bits Zweck
0 0-31 Function Start RVA ist der 32-Bit-RVA des Starts der Funktion. Wenn die Funktion Thumb-Code enthält, muss der niedrigste Bitwert dieser Adresse festgelegt werden.
1 0-1 Flag ist ein 2-Bit-Feld, das folgende Bedeutungen hat:

00 = gepackte Entladedaten werden nicht verwendet; verbleibende Bits zeigen auf den .xdata-Datensatz.
01 = gepackte Entladedaten
10 = gepackte Entladedaten, bei denen davon ausgegangen wird, dass die Funktion keinen Prolog hat. Das ist nützlich beim Beschreiben von Funktionsfragmenten, die nicht mit dem Start der Funktion verbunden sind.
11 = Reserviert
1 2-12 Function Length ist ein 11-Bit-Feld, das die Länge der gesamten Funktion in Bytes geteilt durch 2 enthält. Wenn die Funktion größer als 4.000 KB ist, muss stattdessen ein voller .xdata-Datensatz verwendet werden.
1 13-14 Ret ist ein 2-Bit-Feld, das angibt, wie die Funktion zurückgibt:

00 = Rückgabe über Pop {pc} (das L-Flag muss in diesem Fall auf 1 eingestellt sein).
01 = Rückgabe mithilfe eines 16-Bit-Branches
10 = Rückgabe mithilfe eines 32-Bit-Branches
11 = kein Epilog Das ist nützlich, wenn ein nicht verbundenes Funktionsfragment beschrieben werden soll, das nur aus ein Prolog bestehen kann, aber dessen Epilog sich anderswo befindet.
1 15 H ist ein 1-Bit-Flag, das angibt, ob die Funktion die Ganzzahlparameterregister (r0-r3) „herausgreift“, indem es sie an den Beginn der Funktion schiebt und die 16 Bytes des Stapel vor der Rückgabe freigibt. (0 = registriert sich nicht zu Hause, 1 = Häuserregister.)
1 16-18 Reg ist ein 3-Bit-Feld, das den Index des letzten gespeicherten, nicht flüchtigen Registers angibt. Wenn das R-Bit 0 ist, dann werden nur die Ganzzahlregister gespeichert und es wird davon ausgegangen, dass sie sich im Bereich r4-rN befinden, wobei N gleich 4 + Reg ist. Wenn das R-Bit 1 ist, dann werden nur die Gleitkommaregister gespeichert und es wird davon ausgegangen, dass sie sich im Bereich d8-dN befinden, wobei N gleich 8 + Reg ist. Die spezielle Kombination von R = 1 und Reg = 7 gibt an, das keine Register gespeichert wurden.
1 19 R ist ein 1-Bit-Flag das angibt, ob die gespeicherten nicht flüchtigen Register Ganzzahlregister (0) oder Gleitkommaregister (1) sind. Wenn R auf 1 eingestellt und das Feld Reg auf 7 eingestellt ist, werden keine nicht flüchtigen Register per Push abgelegt.
1 20 L ist ein 1-Bit-Flag das angibt, ob die Funktion LR zusammen mit anderen Registern speichert/wiederherstellt, wie im Feld Reg angegeben. (0 = speichert/wiederhergestellt nicht, 1 = speichert/wiederhergestellt.)
1 21 C ist ein 1-Bit-Flag das angibt, ob die Funktion zusätzliche Anweisungen für das Einrichten einer Rahmenkette für schnelles Stack Walking (1) enthält oder nicht (0). Wen das Bit festgelegt wurde, wird r11 implizit in der Liste nicht flüchtiger Ganzzahlregister gespeichert. (Sie die Einschränkungen unten für den Fall, dass das C-Flag verwendet wird.)
1 22-31 Stack Adjust ist ein 10-Bit-Feld das die Anzahl von Bytes eines Stapels geteilt durch 4 angibt, die dieser Funktion zugeordnet sind. Es lassen sich allerdings nur Werte zwischen 0x000 und 0x3F3 direkt codieren. Funktionen, die über 4044 Bytes an Stapel zuzuordnen, müssen einen vollständigen .xdata-Datensatz verwenden. Wenn das Stack Adjust-Feld 0x3F4 oder größer ist, haben die niedrigste 4 Bits eine besondere Bedeutung:

Die Bits 0-1 enthalten die Anzahl der Worte der Stapelanpassung (1-4) minus 1.
Das Bit 2 ist auf 1 festgelegt, wenn der Prolog diese Anpassung in seinen Pushübertragungsvorgang kombiniert hat.
Das Bit 3 ist auf 1 festgelegt, wenn der Epilog diese Anpassung in seinen Popvorgang kombiniert hat.

Wegen möglicher Redundanzen in den oben genannten Codierungen gelten folgende Einschränkungen:

  • Wenn das C-Flag auf 1 festgelegt wurde:

    • Das L Flag muss auch auf 1 festgelegt werden, da die Rahmenkette sowohl r11 als auch LR erfordert.

    • r11 darf nicht im Registersatz enthalten sein, der von Reg beschrieben wird. Das bedeutet, wenn r4-r11 per Push abgelegt werden, darf Reg nur r4-r10 beschreiben, denn das C-Flag impliziert r11.

  • Wenn das Feld Ret auf 0 festgelegt wurde, muss das L Flag 1 sein.

Werden diese Einschränkungen übergangen, kann dies eine nicht unterstützte Sequenz hervorrufen.

Was die Erläuterungen unten angeht, so werden zwei Pseudoflags von Stack Adjust abgeleitet:

  • PF oder „Prologue Folding“ zeigt an, dass Stack Adjust 0x3F4 oder größer ist und Bit 2 festgelegt wurde.

  • EF oder „Epilogue Folding“ zeigt an, dass Stack Adjust 0x3F4 oder größer ist und Bit 3 festgelegt wurde.

Prologe für nicht kanonische Funktionen können bis zu 5 Anweisungen haben (beachten Sie, dass 3a und 3b sich gegenseitig ausschließen):

Anleitung Es wird davon ausgegangen, dass Opcode vorhanden ist, wenn: Größe Opcode Entladungscodes
1 H==1 16 push {r0-r3} 04
2 C==1 oder L==1 oder R==0 oder PF==1 16/32 push {registers} 80-BF/D0-DF/EC-ED
3a C==1 und (R==1 und PF==0) 16 mov r11,sp FB
3b C==1 und (R==0 oder PF==1) 32 add r11,sp,#xx FC
4 R==1 und Reg != 7 32 vpush {d8-dE} E0-E7
5 Stack Adjust != 0 und PF==0 16/32 sub sp,sp,#xx 00-7F/E8-EB

Anweisung 1 ist immer vorhanden, wenn das H-Bit auf 1 festgelegt wurde.

Um die Rahmenverknüpfung festzulegen, ist entweder Anweisung 3a oder 3b vorhanden, wenn das C-Bit festgelegt wird. Es ist ein 16-Bit-mov, wenn kein Register außer r11 und LR per Push abgelegt wird; ansonsten ist es ein 32-Bit-add.

Wird eine nicht gefaltete Anpassung festgelegt, ist Anweisung die ausdrückliche Stapelanpassung.

Die Anweisungen 2 und 4 werden abhängig davon festgelegt, ob eine Pushübertagung erforderlich ist. Diese Tabelle fasst zusammen, welche Speicher auf Grundlage der Felder C, L, R und PF gespeichert werden. In allen Fällen ist N gleich Reg + 4, E ist gleich Reg + 8 und S ist gleich (~Stack Adjust) & 3.

C L R PF Ganzzahlregister per Push abgelegt VFP-Register per Push abgelegt
0 0 0 0 r4 - r*N* Keine
0 0 0 1 r*S* - r*N* Keine
0 0 1 0 Keine d8 - d*E*
0 0 1 1 r*S* - r3 d8 - d*E*
0 1 0 0 r4 - r**N, LR Keine
0 1 0 1 r*S* - r**N, LR Keine
0 1 1 0 LR d8 - d*E*
0 1 1 1 r*S* - r3, LR d8 - d*E*
1 0 0 0 (ungültige Codierung) (ungültige Codierung)
1 0 0 1 (ungültige Codierung) (ungültige Codierung)
1 0 1 0 (ungültige Codierung) (ungültige Codierung)
1 0 1 1 (ungültige Codierung) (ungültige Codierung)
1 1 0 0 r4 - r*N, r11, LR Keine
1 1 0 1 r*S* - r*N, r11, LR Keine
1 1 1 0 r11, LR d8 - d*E*
1 1 1 1 r*S* - r3, r11, LR d8 - d*E*

Die Epiloge für kanonische Funktionen ermöglichen es, einer ähnlichen Form zu folgen, doch rückwärts und mit ein paar zusätzlichen Optionen. Der Epiloge kann bis zu 5 Anweisungen lang sein und seine Form wird streng durch das Format des Prologs diktiert.

Anleitung Es wird davon ausgegangen, dass Opcode vorhanden ist, wenn: Größe Opcode
6 Stack Adjust!=0 und EF==0 16/32 add sp,sp,#xx
7 R==1 und Reg!=7 32 vpop {d8-dE}
8 C==1 oder (L==1 und (H==0 oder !=0)) oder RetR==0 oder EF==1 16/32 pop {registers}
9a H==1 und (L==0 oder Ret!=0) 16 add sp,sp,#0x10
9b H== 1 und L==1 und Ret==0 32 ldr pc,[sp],#0x14
10a Ret==1 16 bx reg
10b Ret==2 32 b address

Anweisung 6 ist die ausdrückliche Stapelanpassung, wenn eine nicht gefaltete Anpassung festgelegt wurde. Da PF unabhängig von EF ist, ist es möglich, dass eine Anweisung 5 ohne 6 vorhanden ist und umgekehrt.

Anweisungen 7 und 8 verwenden dieselbe Logik wie der Prolog, um zu bestimmen, welche Register aus dem Stapel wiederhergestellt werden, aber mit diesen drei Änderungen: zuerst wird anstelle von PF; zweitens verwendet, wenn Ret = 0 und H = 0, dann wird LR durch PC in der Registerliste ersetzt, und der Epilog endet sofort; dritten, EF wenn Ret = 0 und H = 1, dann wird LR aus der Registerliste weggelassen und von Anweisung 9b angezeigt.

Wenn H festgelegt wurde, dann ist entweder Anweisung 9a oder 9b vorhanden. Anweisung 9a wird verwendet, wenn Ret es sich um nonzero handelt, was auch das Vorhandensein von 10a oder 10b impliziert. Wenn L=1, dann wurde LR als Teil der Anweisung 8 eingetaucht. Anweisung 9b wird verwendet, wenn L 1 und Ret null ist, um ein frühes Ende des Epilogs anzugeben und den Stapel gleichzeitig zurückzugeben und anzupassen.

Wenn der Epilog noch nicht beendet wurde, ist entweder anweisung 10a oder 10b vorhanden, um eine 16-Bit- oder 32-Bit-Verzweigung basierend auf dem Wert von Ret.

.xdata-Datensätze

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 von .xdata besteht aus mehreren Worten mit variabler Länge, der vier Abschnitte hat:

  1. Ein 1- oder 2-Wort-Header, der die Gesamtgröße der .xdata-Struktur beschreibt und wichtige Funktionsdaten enthält. Das zweite Wort ist nur vorhanden, wenn die Felder Epilog Count (Epiloganzahl) und Code Words (Codewörter) auf 0 festgelegt sind. Die Felder sind in dieser Tabelle aufgeteilt:

    Word Bits Zweck
    0 0-17 Function Length ist ein 18-Bit-Feld, das die Gesamtlänge der Funktion in Bytes geteilt durch 2 angibt. Wenn eine Funktion größer als 512 KB ist, dann müssen mehrere .pdata- und .xdata-Datensätze verwendet werden, um die Funktion zu beschreiben. Weitere Informationen finden Sie im Abschnitt "Große Funktionen" in diesem Dokument.
    0 18-19 Vers ist ein 2-Bit-Feld, das die Version der verbleibenden .xdata beschreibt. Nur Version 0 ist derzeit definiert; die Werte 1-3 sind reserviert.
    0 20 X ist ein 1-Bit-Feld, das das Vorhandensein (1) oder Fehlen (0) von Ausnahmedaten angibt.
    0 21 E ist ein 1-Bit-Feld, das die Informationen angibt, ob ein einzelner Epilog in den Header gepackt ist (1) oder ob zusätzliche Bereichswörter später erforderlich sind (0).
    0 22 F ist ein 1-Bit-Feld, das angibt, dass dieser Datensatz ein Funktionsfragment (1) oder eine vollständige Funktion (0) beschreibt. Ein Fragment impliziert, dass es keinen Prolog gibt und dass alle Prologverarbeitungen ignoriert werden sollten.
    0 23-27 Epilog Count (Epiloganzahl) ist ein 5-Bit-Feld mit zwei Bedeutungen, die vom Status des E-Bits abhängen:

    - Bei E 0 ist dieses Feld eine Anzahl der in Abschnitt 2 beschriebenen Epilogbereiche. Sind über 31 Bereiche in der Funktion vorhanden, müssen dieses Feld und das Feld Code Words (Codewörter) auf 0 festgelegt werden, damit angegeben wird, dass ein Ausnahmewort erforderlich ist.
    Wenn E 1 ist, legt dieses Feld den Index des ersten Entladungscodes fest, der nur den Epilog beschreibt.
    0 28-31 Code Words (Codewörter) ist ein 4-Bit-Feld, das die Anzahl der 32-Bit-Wörter angibt, die für alle Entladungscodes in Abschnitt 4 benötigt werden. Wenn mehr als 15 Wörter für mehr als 63 Entladungscodebytes erforderlich sind, müssen dieses Feld und das Feld Epilogue Count (Epiloganzahl) auf 0 festgelegt werden, um anzugeben, dass ein Erweiterungswort erforderlich ist.
    1 0 - 15 Extended Epilogue Count (Erweiterte Epiloganzahl) ist ein 16-Bit-Feld, das mehr Raum für die Codierung bietet als eine ungewöhnlich große Anzahl von Epilogen. Das Erweiterungswort, das dieses Feld enthält, ist nur vorhanden, wenn die Felder Epilogue Count (Epiloganzahl) und Code Words (Codewörter) im ersten Headerwort auf 0 festgelegt sind.
    1 16-23 Extended Code Words (Erweiterte Codewörter) ist ein 8-Bit-Feld, das mehr Raum für die Codierung bietet als eine ungewöhnlich große Anzahl von Entladungscodeworten. Das Erweiterungswort, das dieses Feld enthält, ist nur vorhanden, wenn die Felder Epilogue Count (Epiloganzahl) und Code Words (Codewörter) im ersten Headerwort auf 0 festgelegt sind.
    1 24-31 Reserviert
  2. Nach den Ausnahmedaten – wenn das E-Bit im Header auf 0 festgelegt wurde – ist eine Liste von Informationen über Epilogbereiche, die auf ein Wort gepackt und in der Reihenfolge zunehmender Startoffsets gespeichert werden. Jeder Bereich enthält folgende Felder:

    Bits Zweck
    0-17 Epilog Start Offset (Startoffset des Epilogs) ist ein 18-Bit-Feld, das den Epilogoffset in Byte geteilt durch 2 beschreibt, der relativ zum Funktionsbeginn ist.
    18-19 Res (Reserviert) ist ein 2-Bit-Feld, das für künftige Erweiterungen reserviert ist. Sein Wert muss 0 sein.
    20-23 Condition (Bedingung) ist ein 4-Bit-Feld, das die Bedingung angibt, unter der der Epilog ausgeführt wird. Für bedingungslose Epiloge muss es auf 0xE festgelegt sein, was "immer" bedeutet. (Ein Epilog muss vollständig beginnt oder bedingungslos sein und im Thumb-2-Modus beginnt der Epilog mit der ersten Anweisung nach dem IT-Opcode.)
    24-31 Epilogue Start Index (Epilogstartindex) ist ein 8-Bit-Feld, das den Byte-Index des ersten Entladungscodes angibt, der diesen Epilog beschreibt.
  3. Nach der Liste von Epilogbereichen kommt ein Array von Bytes, das Entladungscodes enthält. Diese werden detailliert im Abschnitt Entladungscodes dieses Artikels beschrieben. Dieses Array ist am Ende aufgefüllt bis zur nächsten vollen Wortgrenze. Die Bytes werden in Little-Endian-Reihenfolge gespeichert, sodass sie im Little-Endian-Modus direkt abgerufen werden können.

  4. Wenn das X-Feld im Header 1 ist, folgen die Ausnahmehandlerinformationen auf die Entladungscodebytes. Sie bestehen aus einer Exception Handler RVA (RVA für Ausnahmehandler), die die Adresse des Ausnahmehandlers enthält, unmittelbar gefolgt von der vom Ausnahmehandler benötigten Datenmenge (mit variabler Länge).

Der .xdata-Datensatz ist so gestaltet, dass es möglich ist, die ersten 8 Bytes abzurufen und die volle Größe des Datensatzes zu berechnen, ausgenommen die Länge der folgenden Ausnahmedaten mit variabler Größe. Dieser Codeausschnitt berechnet die Datensatzgröße:

ULONG ComputeXdataSize(PULONG Xdata)
{
    ULONG Size;
    ULONG EpilogueScopes;
    ULONG UnwindWords;

    if ((Xdata[0] >> 23) != 0) {
        Size = 4;
        EpilogueScopes = (Xdata[0] >> 23) & 0x1f;
        UnwindWords = (Xdata[0] >> 28) & 0x0f;
    } else {
        Size = 8;
        EpilogueScopes = Xdata[1] & 0xffff;
        UnwindWords = (Xdata[1] >> 16) & 0xff;
    }

    if (!(Xdata[0] & (1 << 21))) {
        Size += 4 * EpilogueScopes;
    }

    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 nicht ungewöhnlich, dass sie alle die gleichen Abspanncodes gemeinsam nutzen können. Wir empfehlen, dass Compiler-Autoren für diesen Fall optimieren, denn der größte Index, der festgelegt werden kann, ist 255 und das begrenzt die Gesamtzahl der Entladungscodes, die für eine bestimmte Funktion mögliche sind.

Entladungscodes

Das Array an Entladungscodes ist ein Pool von Anweisungsequenzen, der genau beschreibt, wie die Wirkungen des Prologs rückgängig gemacht werden und in welcher Reihenfolge. Die Entladungscodes sind ein Satz von Minianweisungen, die als eine Zeichenfolge von Bytes codiert wurden. Wenn die Ausführung abgeschlossen ist, befindet sich die Rückgabeadresse im LR-Register und alle nicht flüchtigen Register werden auf ihre Werte zu Beginn des Funktionsaufrufs zurückgesetzt.

Würden Ausnahmen garantiert immer nur innerhalb einer Funktion auftreten und niemals im Prolog oder Epilog, dann wäre nur eine Entladesequenz nötig. Doch das Windows-Entlademodell erfordert die Fähigkeit, von innerhalb eines teilweise ausgeführten Prologs oder Epilogs aus zu entladen. Damit dieser Anforderung Rechnung getragen wird, wurden die Entladungscodes sorgfältig so entwickelt, dass sie eine unzweifelhafte Eins-zu-Eins-Zuordnung zu jedem relevanten Opcode im Prolog und Epilog haben. Das hat eine Reihe von Auswirkungen:

  • Es ist möglich, die Länge von Prolog und Epilog zu berechnen, indem man die Anzahl von Entladungscodes zählt. Das ist selbst mit Thumb-2-Anweisungen mit variabler Länge möglich, da es eine eindeutige Zuordnung für 16- und 32-Bit-Opcodes gibt.

  • Durch Zählen der Anzahl von Anweisungen nach dem Start eines Epilogbereichs ist es möglich, die gleiche Anzahl von Entladungscodes zu überspringen und den Rest einer Sequenz auszuführen, um die teilweise ausgeführte Entladung abzuschließen, die der Epilog durchgeführt hat.

  • Durch Zählen der Anzahl von Anweisungen vor dem Ende eines Prologs ist es möglich, die gleiche Anzahl von Entladungscodes zu überspringen und den Rest einer Sequenz auszuführen, sodass nur die Teile des Prologs rückgängig gemacht werden, die der Prolog durchgeführt hat.

Die folgende Tabelle zeigt die Zuordnung von Entladungscodes zu Opcodes. Die häufigsten Codes sind nur ein Byte, während weniger übliche zwei, drei oder sogar vier Bytes benötigen. Jeder Code wird vom signifikantesten bis zum am wenigsten signifikanten Byte gespeichert. Die Entladungscodestruktur unterscheidet sich von der Entladung, die in ARM EABI beschrieben ist, da diese Entladungscodes mit einer Eins-zu-Eins-Zuordnung zu den Opcodes in Prolog und Epilog entwickelt wurden, was die Entladung teilweise ausgeführter Prologe und Epiloge möglich macht.

Byte 1 Byte 2 Byte 3 Byte 4 Opsize Erklärung
00-7F 16 add sp,sp,#X

wobei X ist (Code & 0x7F) * 4
80-BF 00-FF 32 pop {r0-r12, lr}

wobei LR per Pop ausgelesen wird, wenn Code & 0x2000 und r0-r12 per Pop ausgelesen werden, falls das korrespondierende Bit in Code & 0x1FFF festgelegt wurde
C0-CF 16 mov sp,rX

wobei X gleich Code & 0x0F ist
D0-D7 16 pop {r4-rX,lr}

wobei X gleich (Code & 0x03) + 4 ist und LR per Pop ausgelesen wird, wenn Code & 0x04 vorliegt
D8-DF 32 pop {r4-rX,lr}

wobei X gleich (Code & 0x03) + 8 ist und LR per Pop ausgelesen wird, wenn Code & 0x04 vorliegt
E0-E7 32 vpop {d8-dX}

wobei X gleich (Code & 0x07) + 8 ist
E8-EB 00-FF 32 addw sp,sp,#X

wobei X ist (Code & 0x03FF) * 4
EC-ED 00-FF 16 pop {r0-r7,lr}

wobei LR per Pop ausgelesen wird, wenn Code & 0x0100 und r0-r7 per Pop ausgelesen werden, falls das korrespondierende Bit in Code & 0x00FF festgelegt wurde
EE 00-0F 16 Microsoft-spezifisch
EE 10-FF 16 Verfügbar
EF 00-0F 32 ldr lr,[sp],#X

wobei X ist (Code & 0x000F) * 4
EF 10-FF 32 Verfügbar
F0-F4 - Verfügbar
F5 00-FF 32 vpop {dS-dE}

dabei ist S (Code & 0x00F0) >> 4 und E ist Code & 0x000F
F6 00-FF 32 vpop {dS-dE}

dabei ist S ((Code & 0x00F0) 4) >> + 16 und E ist (Code & 0x000F) + 16
F7 00-FF 00-FF 16 add sp,sp,#X

wobei X ist (Code & 0x00FFFF) * 4
F8 00-FF 00-FF 00-FF 16 add sp,sp,#X

wobei X ist (Code & 0x00FFFFFF) * 4
F9 00-FF 00-FF 32 add sp,sp,#X

wobei X ist (Code & 0x00FFFF) * 4
FA 00-FF 00-FF 00-FF 32 add sp,sp,#X

wobei X ist (Code & 0x00FFFFFF) * 4
FB 16 nop (16-Bit)
FC 32 nop (32-Bit)
FD 16 end + 16-Bit nop im Epilog
FE 32 end + 32-Bit nop im Epilog
FF - end

Dies zeigt einen Bereich hexadezimaler Werte für jedes Byte in einem Entladungscode (Code) zusammen mit der Opcodegröße (Opsize) und der entsprechenden ursprünglichen Anweisungsinterpretation. Leere Zellen weisen auf kürzere Entladungscodes hin. Die Anweisungen mit großen Werten umfassen mehrere Bytes, wobei die signifikanten zuerst gespeichert sind. Das Feld Opsize zeigt die implizite Opcodegröße, die jedem Thumb-2-Vorgang zugeordnet ist. Die offensichtlichen Doppeleinträge in der Tabelle bei verschiedenen Codierungen werden verwendet, damit zwischen den verschiedenen Opcodegrößen unterschieden werden kann.

Die Entladungscodes wurden entwickelt, sodass das erste Byte des Codes sowohl die Gesamtgröße des Codes in Bytes als auch die Größe des entsprechenden Opcodes im Anweisungsstream verrät. Um die Größe von Prolog oder Epilog berechnen zu können, gehen Sie die Entladungscodes vom Beginn der Sequenz bis zum Ende durch und verwenden eine Nachschlagetabelle oder eine vergleichbare Methode, um zu bestimmen, wie lange der entsprechende Opcode ist.

Die Entladungscodes 0xFD und 0xFE sind gleich dem regulären Endcode 0xFF, machen aber einen zusätzlichen nop-Opcode im Epilogfall aus, entweder 16- oder 32-Bit. Bei Prologen sind die Codes 0xFD, 0xFE und 0xFF genau gleich. Dies umfasst die gemeinsamen Epilogenden bx lr oder b <tailcall-target>, die keine identischen Prologanweisungen aufweisen. Das erhöht die Möglichkeit, Entladungssequenzen zwischen Prolog und Epilog zu teilen.

In vielen Fällen sollte es möglich sein, denselben Satz an Entladungscodes für Prologe und Epiloge zu verwenden. Um jedoch die Entladung teilweise ausgeführter Prologe und Epiloge bewältigen zu können, benötigen Sie möglicherweise mehrere Entladungscodesequenzen, die sich hinsichtlich Sortierung oder Verhalten unterscheiden. Deshalb hat jeder Epilog seinen eigenen Index im Entladungsarray, sodass angezeigt wird, wo mit der Umsetzung zu beginnen ist.

Entladen partieller Prologe und Epiloge

Der häufigste Entladungsfall ist eine Ausnahme, die im Text der Funktion auftritt, jenseits vom Prolog und allen Epilogen. In diesem Fall führt der Entlader die Codes im Entladungsarray aus, beginnend bei Index 0, und er fährt fort, bis ein Endopcode festgestellt wird.

Wenn während der Ausführung eines Prologs oder Epilogs eine Ausnahme auftritt, wird der Stapelrahmen nur teilweise konstruiert und der Entlader muss genau bestimmen, was vorgefallen ist, um dies korrekt rückgängig machen zu können.

Betrachten Sie beispielsweise diese Prolog-Epilog-Sequenz:

0000:   push  {r0-r3}         ; 0x04
0002:   push  {r4-r9, lr}     ; 0xdd
0006:   mov   r7, sp          ; 0xc7
...
0140:   mov   sp, r7          ; 0xc7
0142:   pop   {r4-r9, lr}     ; 0xdd
0146:   add   sp, sp, #16     ; 0x04
0148:   bx    lr

Neben jedem Opcode befindet sich der entsprechende Entladungscode, der diesen Vorgang beschreibt. Die Sequenz der Entladungscodes für den Prolog ist ein Spiegelbild derjenigen für den Epilog, abgesehen von der letzten Anweisung. Dieser Fall tritt häufig auf und ist der Grund dafür, weshalb immer davon ausgegangen wird, dass die Entladungscodes für den Prolog in umgekehrter Reihenfolge zur ihrer Ausführung gespeichert werden. Damit erhalten wir einen gemeinsamen Satz von Entladungscodes:

0xc7, 0xdd, 0x04, 0xfd

Der Code 0xFD ist ein Spezialcode für das Ende der Sequenz und bedeutet, dass der Epilog eine 16-Bit-Anweisung länger als der Prolog ist. Damit ist es öfter möglich, Entladungscodes gemeinsam zu nutzen.

Im Beispiel beginnt die Entladung mit dem Epilogfall, wenn beim Ausführen des Funktionstextes zwischen Prolog und Epilog eine Ausnahme auftritt, und zwar bei Offset 0 im Epilogcode. Das entspricht dem Offset 0x140 im Beispiel. Der Entlader führt die volle Entladesequenz aus, da keine Bereinigung vorgenommen wurde. Wenn stattdessen die Ausnahme eine Anweisung nach dem Beginn des Epilogcodes auftritt, kann der Entlader erfolgreich entladen, indem er den ersten Entladungscode überspringt. Bei einer 1:1-Zuordnung von Opcodes und Entladungscodes, sollte der Entlader die ersten n Entladungscodes überspringen, wenn eine Entladung der Anweisung n im Epilog erfolgt.

Die gleiche Logik gilt umgekehrt für den Prolog. Findet die Entladung von Offset 0 im Prolog statt, muss nichts ausgeführt werden. Findet die Entladung von einer Anweisung weiter innen statt, sollte die Entladungssequenz einen Entladungscode vom Ende starten, da Prolog-Entladungscodes in umgekehrter Reihenfolge gespeichert werden. Im Allgemeinen gilt: Wenn die Entladung bei der Anweisung n im Prolog beginnt, sollten zuerst n Entladungscodes vom Ende der Codeliste ausgeführt werden.

Prolog und Epilog entspannen Codes stimmen nicht immer genau überein. In diesem Fall muss das Entladungscode-Array eine Reihe von Codesequenzen enthalten. Verwenden Sie folgende Logik, um den Offset für den Beginn der Verarbeitungscodes zu bestimmen:

  1. Wenn die Entladung im Funktionstext erfolgt, beginnen Sie mit der Ausführung der Entladungscodes bei Index 0 und fahren Sie fort, bis ein Endopcode erreicht wird.

  2. Wenn die Entladung in einem Epilog erfolgt, verwenden Sie den epilogspezifischen Index, der vom Epilogbereich zur Verfügung gestellt wird. Kalkulieren Sie, wie viele Bytes sich der PC vom Beginn des Epilogs befindet. Springen Sie in den Entladungscodes vorwärts, bis alle bereits ausgeführten Anweisungen einbezogen sind. Der Start der Entladesequenz beginnt hier.

  3. Erfolgt die Entladung im Prolog, beginnen Sie ab Index 0 in den Entladungscodes. Kalkulieren Sie die Länge des Prologcodes aus der Sequenz und dann, wie viele Bytes der PC sich vom Ende des Prologs befindet. Springen Sie in den Entladungscodes vorwärts, bis alle nicht ausgeführten Anweisungen einbezogen sind. Der Start der Entladesequenz beginnt hier.

Die Entladungscodes für den Prolog müssen immer zuerst im Array sein. sie sind auch die Codes, die verwendet werden, um sich im Allgemeinen zu entspannen, wenn sie sich von innerhalb des Körpers entspannen. Jede epilogspezifische Codesequenz muss unmittelbar nach der Prologcodesequenz folgen.

Funktionsfragmente

Zur Codeoptimierung kann es hilfreich sein, eine Funktion in nicht verbundene Teile aufteilen. Wenn das geschieht, erfordert jedes Funktionsfragment einen eigenen .pdata-Datensatz – und möglicherweise einen .xdata-Datensatz.

Angenommen der Funktionsprolog befindet sich am Beginn der Funktion und kann nicht geteilt werden, dann gibt es vier Funktionsfragmentfälle:

  • Nur Prolog; alle Epiloge in anderen Fragmenten.

  • Prolog und ein oder mehrere Epilogen; weitere Epiloge in anderen Fragmenten.

  • Weder Prolog noch Epilog; Prolog und einer oder mehr Epiloge in anderen Fragmenten.

  • Nur Epiloge; Prolog und möglicherweise mehr Epiloge in anderen Fragmenten.

In ersterem Fall muss nur der Prolog beschrieben werden. Das lässt sich in im kompakten .pdata-Format erledigen, in dem der Prolog normalerweise beschrieben und ein Ret-Wert von 3 festgelegt wird, der angibt, dass kein Epilog vorhanden ist. Im vollen .xdata-Format kann das erledigt werden, indem man wie üblich die Prologentladungscodes in Index 0 festlegt und eine Epiloganzahl von 0 angibt.

Der zweite Fall ist genauso wie eine normale Funktion. Wenn es nur einen Epilog im Fragment gibt und sich dieser am Ende des Fragments befindet, dann kann ein kompakter .pdata-Datensatz verwendet werden. Andernfalls muss ein vollständiger .xdata-Datensatzes eingesetzt werden. Beachten Sie, dass die für den Epilogstart festgelegten Offsets relativ zum Start des Fragments und nicht dem ursprünglichen Start der Funktion sind.

Der dritte und vierte Fall ist jeweils eine Variante des ersten und zweiten Falls, mit der Ausnahme, dass kein Prolog enthalten ist. In diesen Fällen wird davon ausgegangen, dass es Code vor beginn des Epilogs gibt und es als Teil des Körpers der Funktion betrachtet wird, was normalerweise durch Rückgängigmachen der Auswirkungen des Prologs entwunden würde. Diese Fälle müssen deshalb mit einem Pseudoprolog codiert werden, der beschreibt, wie im Text entladen wird, aber als 0-Länge bei der Bestimmung behandelt wird, ob beim Start des Fragments eine teilweise Entladung durchgeführt wird. Alternativ lässt sich dieser Pseudoprolog beschreiben, indem dieselben Entladungscodes wie beim Epilog verwendet werden, denn sie führen mutmaßlich gleiche Vorgänge durch.

Im dritten und vierten Fall wird das Vorhandensein eines Pseudoprologs entweder durch Festlegen des Flag-Felds im .pdata-Datensatz auf 2 angegeben oder durch Festlegen des F-Flags im .xdata-Header auf 1. In beiden Fällen wird die Überprüfung auf eine teilweise Prologentladung ignoriert und alle Nicht-Epilogentladungen werden als voll betrachtet.

Große Funktionen

Fragmente lassen sich für die Beschreibung von Funktionen einsetzen, die größer als das 512 KB-Limit sind, das von den Bit-Feldern im .xdata-Header vorgegeben wird. Um eine größere Funktion zu beschreiben, unterteilen Sie sie einfach in Fragmente, die kleiner als 512 KB sind. Jedes Fragment sollte so angepasst werden, dass er keinen Epilog in mehrere Teile aufteilt.

Nur das erste Fragment der Funktion enthält einen Prolog. Alle anderen Fragmente sind als prolog gekennzeichnet. Je nach Anzahl der Epiloge kann jedes Fragment null oder mehr Epiloge enthalten. Beachten Sie, dass jeder Epilogbereich in einem Fragment dessen Startoffset relativ zum Start des Fragments und nicht dem Start der Funktion festlegt.

Wenn ein Fragment keinen Prolog und Epilog hat, erfordert er dennoch einen eigenen .pdata-Datensatz – möglicherweise auch einen .xdata-Datensatz – für die Beschreibung, wie die Entladung im Text der Funktion stattfindet.

Shrink-Wrapping

Ein komplexerer Sonderfall von Funktionsfragmenten wird als Schrumpfumbruch bezeichnet. Es ist eine Technik zum Zurückstellen von Registerspeichern von Anfang der Funktion auf später in der Funktion. Es wird für einfache Fälle optimiert, für die keine Registrierung erforderlich ist. Dieser Fall besteht aus zwei Teilen: es gibt einen äußeren Bereich, der den Stapelplatz zuweist, aber einen minimalen Satz von Registern spart, und einen inneren Bereich, der andere Register speichert und wiederhergestellt.

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatiles
    sub    sp, sp, #0x100    ; A: allocate all stack space up front
    ...                      ; A:
    add    r0, sp, #0xE4     ; A: prepare to do the inner save
    stm    r0, {r5-r11}      ; A: save remaining non-volatiles
    ...                      ; B:
    add    r0, sp, #0xE4     ; B: prepare to do the inner restore
    ldm    r0, {r5-r11}      ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C:

Schrumpfende Funktionen werden in der Regel erwartet, dass sie den Speicherplatz für die zusätzlichen Registerspeicher im regulären Prolog vorab zuordnen und dann die Register mithilfe str oder stm anstelle von push. Diese Aktion behält alle Stapelzeigermanipulation im ursprünglichen Prolog der Funktion bei.

Die Beispielfunktion zum Verkleinern muss in drei Bereiche unterteilt werden, die als A, Bund C in den Kommentaren markiert sind. Der erste A Bereich deckt den Anfang der Funktion bis zum Ende der zusätzlichen, nicht veränderlich gespeicherten Daten ab. Es muss ein .pdata- oder .xdata-Datensatz konstruiert werden, der beschreibt, dass dieses Fragment einen Prolog und keine Epiloge hat.

Die mittlere B Region erhält einen eigenen .pdata Oder .xdata Datensatz, der ein Fragment beschreibt, das keinen Prolog und keinen Epilog enthält. Allerdings müssen die Entladungscodes für diese Region immer noch vorhanden sein, denn sie wird als Funktionstext betrachtet. Die Codes müssen einen zusammengesetzten Prolog beschreiben, der sowohl die ursprünglichen Register darstellt, die im Prolog der Region A gespeichert sind, als auch die zusätzlichen Register, die vor der Eingabe der Region Bgespeichert wurden, als ob sie von einer Folge von Vorgängen erstellt wurden.

Das Register für die Region kann nicht als "innerer Prolog" betrachtet werden, da der für die Region B beschriebene zusammengesetzte Prolog sowohl den Prolog der Region BA als auch die zusätzlichen Register beschreiben muss. Wenn Fragment B einen Prolog hatte, würden die Abspanncodes auch die Größe dieses Prologs bedeuten, und es gibt keine Möglichkeit, den zusammengesetzten Prolog so zu beschreiben, dass 1:1 mit den Opcodes zugeordnet wird, die nur die zusätzlichen Register speichern.

Die zusätzlichen Registerspeicher müssen als Teil der Region Abetrachtet werden, da der zusammengesetzte Prolog den Zustand des Stapels nicht genau beschreibt, bis sie abgeschlossen sind.

Die letzte C Region erhält einen eigenen .pdata Oder .xdata Datensatz, der ein Fragment beschreibt, das keinen Prolog hat, aber einen Epilog hat.

Ein alternativer Ansatz kann auch funktionieren, wenn die Stapelmanipulation vor dem Eingeben des Bereichs B auf eine Anweisung reduziert werden kann:

ShrinkWrappedFunction
    push   {r4, lr}          ; A: save minimal non-volatile registers
    sub    sp, sp, #0xE0     ; A: allocate minimal stack space up front
    ...                      ; A:
    push   {r4-r9}           ; A: save remaining non-volatiles
    ...                      ; B:
    pop    {r4-r9}           ; B: restore remaining non-volatiles
    ...                      ; C:
    pop    {r4, pc}          ; C: restore non-volatile registers

Der wichtigste Einblick ist, dass der Stapel auf jeder Anweisungsgrenze vollständig mit den Abspanncodes für die Region konsistent ist. Wenn ein Abspann vor dem inneren Push in diesem Beispiel auftritt, wird er als Teil der Region Abetrachtet. Nur der Prolog der Region A ist entwounden. Wenn die Entspannung nach dem inneren Schub auftritt, wird sie als Teil der Region Bbetrachtet, die keinen Prolog hat. Es hat jedoch Entschöfungscodes, die sowohl den inneren Push als auch den ursprünglichen Prolog aus der Region Abeschreiben. Ähnliche Logik für den inneren Pop.

Optimierungen codieren

Die Fülle der Abspanncodes und die Möglichkeit, kompakte und erweiterte Formen von Daten zu nutzen, bieten viele Möglichkeiten, die Codierung zu optimieren, um den Platz weiter zu reduzieren. Durch aggressiven Einsatz dieser Techniken lässt sich der Nettoaufwand zur Beschreibung von Funktionen und Fragmenten mithilfe von Entladungscodes minimieren.

Die wichtigste Optimierungsidee: Verwechseln Sie prologe und epiloge Grenzen nicht zum Entspannen mit logischen Prolog- und Epiloggrenzen aus compilerischer Perspektive. Die Entladungsgrenzen lassen sich schrumpfen und verengen, was für mehr Effizienz sorgt. Beispielsweise kann ein Prolog Code nach dem Stapelsetup enthalten, um Überprüfungen durchzuführen. Sobald die Stapelmanipulation abgeschlossen ist, müssen keine weiteren Vorgänge codiert werden, und darüber hinaus kann alles, was aus dem entspannenden Prolog entfernt werden kann.

Die gleiche Regel gilt für Funktionslänge. Wenn daten (z. B. ein Literalpool) vorhanden sind, die einem Epilog in einer Funktion folgen, sollte sie nicht als Teil der Funktionslänge einbezogen werden. Durch Verkleinern der Funktion auf den Code, der Teil der Funktion ist, ist die Wahrscheinlichkeit viel größer, dass der Epilog am Ende liegt und ein kompakter .pdata Datensatz verwendet werden kann.

In einem Prolog ist es üblicherweise nicht nötig, weitere Opcodes aufzuzeichnen, nachdem der Stapelzeiger in ein anderes Register gespeichert wurde. Um die Funktion zu entschärfen, besteht zunächst die Wiederherstellung von SP aus dem gespeicherten Register. Weitere Vorgänge wirken sich nicht auf den Abspann aus.

Nur aus einer Anweisung bestehende Epiloge müssen überhaupt nicht codiert werden, weder als Bereiche noch als Entladungscodes. Wenn eine Entspannung stattfindet, bevor diese Anweisung ausgeführt wird, ist es sicher, davon auszugehen, dass sie sich innerhalb des Texts der Funktion befindet. Die Ausführung der Prolog-Entspanncodes reicht aus. Wenn die Entspannung erfolgt, nachdem die einzelne Anweisung ausgeführt wurde, erfolgt sie per Definition in einer anderen Region.

Epiloge mit mehreren Anweisungen müssen die erste Anweisung des Epilogs nicht codieren, aus demselben Grund wie im vorangehenden Punkt: Wenn die Entladung vor dem Ausführen der Anweisung stattfindet, ist eine vollständige Prologentladung ausreichend. Wenn die Entspannung nach dieser Anweisung stattfindet, müssen nur die späteren Operationen berücksichtigt werden.

Entladungscode sollte aggressiv wiederverwendet werden. Der Index jedes Epilogbereichs gibt Punkte auf einen beliebigen Ausgangspunkt im Array der Abspanncodes an. Es muss nicht auf den Anfang einer vorherigen Sequenz zeigen. sie kann in der Mitte zeigen. Der beste Ansatz besteht darin, die Abwickelcodesequenz zu generieren. Suchen Sie dann im bereits codierten Sequenzpool nach einer exakten Byte-Übereinstimmung. Verwenden Sie irgendeine perfekte Übereinstimmung als Startpunkt für die Wiederverwendung.

Wenn nach dem Ignorieren von Epilogen mit nur einer Anweisung keine Epiloge verbleiben, dann sollten Sie den Einsatz eines kompakten .pdata-Formats in Betracht ziehen; das wird beim Fehlen eines Epilogs viel wahrscheinlicher.

Beispiele

In diesen Beispielen ist die Abbildbasis bei 0x00400000.

Beispiel 1: Blattfunktion, keine Lokalen

Prologue:
  004535F8: B430      push        {r4-r5}
Epilogue:
  00453656: BC30      pop         {r4-r5}
  00453658: 4770      bx          lr

.pdata (behoben, 2 Wörter):

  • Word 0

    • Function Start RVA = 0x000535F8 (= 0x004535F8-0x00400000)
  • Word 1

    • Flag = 1, gibt kanonische Prolog- und Epilogformate an

    • Function Length = 0x31 (= 0x62/2)

    • Ret = 1, gibt eine 16-Bit-Verzweigungsrückgabe an

    • H = 0, gibt an, dass die Parameter nicht herausgegriffen wurden

    • R = 0 und Reg = 1, der Push/Pop von r4-r5 angibt

    • L = 0, gibt an, dass es kein LR-Speichern/Wiederherstellen gibt

    • C = 0, gibt an, dass es keine Rahmenverknüpfung gibt

    • Stack Adjust = 0, gibt an, dass es keine Stapelanpassungen gibt

Beispiel 2: Geschachtelte Funktion mit lokaler Zuordnung

Prologue:
  004533AC: B5F0      push        {r4-r7, lr}
  004533AE: B083      sub         sp, sp, #0xC
Epilogue:
  00453412: B003      add         sp, sp, #0xC
  00453414: BDF0      pop         {r4-r7, pc}

.pdata (behoben, 2 Wörter):

  • Word 0

    • Function Start RVA = 0x000533AC (= 0x004533AC -0x00400000)
  • Word 1

    • Flag = 1, gibt kanonische Prolog- und Epilogformate an

    • Function Length = 0x35 (= 0x6A/2)

    • Ret = 0, gibt eine Pop-{pc}-Rückgabe an

    • H = 0, gibt an, dass die Parameter nicht herausgegriffen wurden

    • R = 0 und Reg = 3, der Push/Pop von r4-r7 angibt

    • L = 1, gibt an, dass kein LR gespeichert/wiederhergestellt wurde

    • C = 0, gibt an, dass es keine Rahmenverknüpfung gibt

    • Stack Adjust = 3 (= 0x0C/4)

Beispiel 3: Geschachtelte variadische Funktion

Prologue:
  00453988: B40F      push        {r0-r3}
  0045398A: B570      push        {r4-r6, lr}
Epilogue:
  004539D4: E8BD 4070 pop         {r4-r6}
  004539D8: F85D FB14 ldr         pc, [sp], #0x14

.pdata (behoben, 2 Wörter):

  • Word 0

    • Function Start RVA = 0x00053988 (= 0x00453988-0x00400000)
  • Word 1

    • Flag = 1, gibt kanonische Prolog- und Epilogformate an

    • Function Length = 0x2A (= 0x54/2)

    • Ret = 0, gibt eine Rückgabe im Pop-{pc}-Stil an (in diesem Fall eine ldr pc,[sp],#0x14-Rückgabe)

    • H = 1, gibt an, dass die Parameter herausgegriffen wurden

    • R = 0 und Reg = 2, der Push/Pop von r4-r6 angibt

    • L = 1, gibt an, dass kein LR gespeichert/wiederhergestellt wurde

    • C = 0, gibt an, dass es keine Rahmenverknüpfung gibt

    • Stack Adjust = 0, gibt an, dass es keine Stapelanpassungen gibt

Beispiel 4: Funktion mit mehreren Epilogen

Prologue:
  004592F4: E92D 47F0 stmdb       sp!, {r4-r10, lr}
  004592F8: B086      sub         sp, sp, #0x18
Epilogues:
  00459316: B006      add         sp, sp, #0x18
  00459318: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  0045943E: B006      add         sp, sp, #0x18
  00459440: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  004595D4: B006      add         sp, sp, #0x18
  004595D6: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459606: B006      add         sp, sp, #0x18
  00459608: E8BD 87F0 ldm         sp!, {r4-r10, pc}
  ...
  00459636: F028 FF0F bl          KeBugCheckEx     ; end of function

.pdata (behoben, 2 Wörter):

  • Word 0

    • Function Start RVA = 0x000592F4 (= 0x004592F4-0x00400000)
  • Word 1

    • Flag = 0, gibt an, dass ein .xdata-Datensatz vorhanden ist (für mehrere Epiloge erforderlich)

    • .xdata address: 0x00400000

.xdata (variabel, 6 Worte):

  • Word 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0, der die erste Version von.xdata

    • X = 0, der keine Ausnahmedaten angibt

    • E = 0, gibt eine Liste von Epilogbereichen an

    • F = 0, der eine vollständige Funktionsbeschreibung angibt, einschließlich Prolog

    • Epilogue Count = 0x04, der die 4 Gesamt-Epilogbereiche angibt

    • Code Words = 0x01, das ein 32-Bit-Wort der Abspanncodes angibt

  • Worte 1-4 beschreiben 4 Epilogbereiche an 4 Stellen. Jeder Bereich besitzt einen gemeinsamen Satz an Entladungscodes, der mit dem Prolog geteilt wird mit einem Offset 0x00; er ist bedingungslos, legt die Bedingung 0x0E (immer) fest.

  • Entladungscodes ab Wort 5: (von Prolog und Epilog gemeinsam genutzt)

    • Ausspanncode 0 = 0x06: sp += (6 << 2)

    • Entladungscode 1 = 0xDE: pop {r4-r10, lr}

    • Entladungscode 2 = 0xFF: end

Beispiel 5: Funktion mit dynamischem Stapel und innerem Epilog

Prologue:
  00485A20: B40F      push        {r0-r3}
  00485A22: E92D 41F0 stmdb       sp!, {r4-r8, lr}
  00485A26: 466E      mov         r6, sp
  00485A28: 0934      lsrs        r4, r6, #4
  00485A2A: 0124      lsls        r4, r4, #4
  00485A2C: 46A5      mov         sp, r4
  00485A2E: F2AD 2D90 subw        sp, sp, #0x290
Epilogue:
  00485BAC: 46B5      mov         sp, r6
  00485BAE: E8BD 41F0 ldm         sp!, {r4-r8, lr}
  00485BB2: B004      add         sp, sp, #0x10
  00485BB4: 4770      bx          lr
  ...
  00485E2A: F7FF BE7D b           #0x485B28    ; end of function

.pdata (behoben, 2 Wörter):

  • Word 0

    • Function Start RVA = 0x00085A20 (= 0x00485A20-0x00400000)
  • Word 1

    • Flag = 0, gibt an, dass ein .xdata-Datensatz vorhanden ist (für mehrere Epiloge erforderlich)

    • .xdata address: 0x00400000

.xdata (variabel, 3 Worte):

  • Word 0

    • Function Length = 0x0001A3 (= 0x000346/2)

    • Vers = 0, der die erste Version von.xdata

    • X = 0, der keine Ausnahmedaten angibt

    • E = 0, gibt eine Liste von Epilogbereichen an

    • F = 0, der eine vollständige Funktionsbeschreibung angibt, einschließlich Prolog

    • Epilogue Count = 0x001, der den Gesamtumfang von 1 Epilog angibt

    • Code Words = 0x01, das ein 32-Bit-Wort der Abspanncodes angibt

  • Word 1: Epilogbereich beim Offset 0xC6 (= 0x18C/2), Starten des Abwickelns des Codeindex bei 0x00 und mit einer Bedingung von 0x0E (immer)

  • Entladungscodes ab Wort 2: (von Prolog und Epilog gemeinsam genutzt)

    • Entladungscode 0 = 0xC6: sp = r6

    • Entladungscode 1 = 0xDC: pop {r4-r8, lr}

    • Abwickeln von Code 2 = 0x04: sp += (4 << 2)

    • Entladungscode 3 = 0xFD: Ende, zählt als 16-Bit-Anweisung für Epilog

Beispiel 6: Funktion mit Ausnahmehandler

Prologue:
  00488C1C: 0059 A7ED dc.w  0x0059A7ED
  00488C20: 005A 8ED0 dc.w  0x005A8ED0
FunctionStart:
  00488C24: B590      push        {r4, r7, lr}
  00488C26: B085      sub         sp, sp, #0x14
  00488C28: 466F      mov         r7, sp
Epilogue:
  00488C6C: 46BD      mov         sp, r7
  00488C6E: B005      add         sp, sp, #0x14
  00488C70: BD90      pop         {r4, r7, pc}

.pdata (behoben, 2 Wörter):

  • Word 0

    • Function Start RVA = 0x00088C24 (= 0x00488C24-0x00400000)
  • Word 1

    • Flag = 0, gibt an, dass ein .xdata-Datensatz vorhanden ist (für mehrere Epiloge erforderlich)

    • .xdata address: 0x00400000

.xdata (variabel, 5 Worte):

  • Word 0

    • Function Length =0x000027 (= 0x00004E/2)

    • Vers = 0, der die erste Version von.xdata

    • X = 1, der ausnahmedaten angibt

    • E = 1, gibt einen einzelnen Epilog an

    • F = 0, der eine vollständige Funktionsbeschreibung angibt, einschließlich Prolog

    • Epilogue Count = 0x00, der angibt, dass epiloge Entspanncodes bei Offset 0x00

    • Code Words = 0x02, der zwei 32-Bit-Wörter mit Abspanncodes angibt

  • Entladungscodes, beginnend bei Wort 1:

    • Entladungscode 0 = 0xC7: sp = r7

    • Ausspanncode 1 = 0x05: sp += (5 << 2)

    • Entladungscode 2 = 0xED/0x90: pop {r4, r7, lr}

    • Entladungscode 4 = 0xFF: end

  • Wort 3 legt einen Ausnahmehandler fest = 0x0019A7ED (= 0x0059A7ED – 0x00400000)

  • Worte 4 und darüber hinaus sind inline gesetzte Ausnahmedaten

Beispiel 7: Funclet

Function:
  00488C72: B500      push        {lr}
  00488C74: B081      sub         sp, sp, #4
  00488C76: 3F20      subs        r7, #0x20
  00488C78: F117 0308 adds        r3, r7, #8
  00488C7C: 1D3A      adds        r2, r7, #4
  00488C7E: 1C39      adds        r1, r7, #0
  00488C80: F7FF FFAC bl          target
  00488C84: B001      add         sp, sp, #4
  00488C86: BD00      pop         {pc}

.pdata (behoben, 2 Wörter):

  • Word 0

    • Function Start RVA = 0x00088C72 (= 0x00488C72-0x00400000)
  • Word 1

    • Flag = 1, gibt kanonische Prolog- und Epilogformate an

    • Function Length = 0x0B (= 0x16/2)

    • Ret = 0, gibt eine Pop-{pc}-Rückgabe an

    • H = 0, gibt an, dass die Parameter nicht herausgegriffen wurden

    • R = 0 und Reg = 7, was angibt, dass keine Register gespeichert/wiederhergestellt wurden

    • L = 1, gibt an, dass kein LR gespeichert/wiederhergestellt wurde

    • C = 0, gibt an, dass es keine Rahmenverknüpfung gibt

    • Stack Adjust = 1, gibt eine 1 × 4 Byte-Stapelanpassung an

Siehe auch

Übersicht über ARM-ABI-Konventionen
Häufig auftretende ARM-Migrationsprobleme bei Visual C++