ARM-Ausnahmebehandlung
Windows auf ARM 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.Dieses Dokument beschreibt die Ausnahmebehandlung in Windows auf ARM und die Sprachhilfen, die von Code verwendet werden, der von MSAM und dem Visual C++-Compiler generiert wurde.
ARM-Ausnahmebehandlung
Windows auf ARM verwendet Entladungscodes zur Steuerung der Stapelentladung während des structured exception handling (SEH).Entladungscodes sind eine Folge von Bytes, die im .xdata-Abschnitt des ausführbaren Bilds gespeichert sind.Sie beschreiben den Betrieb von Funktionseinleitungs- und -epilogcode auf abstrakte Weise, sodass die Wirkung eines Funktionsprologs sich als Vorbereitung auf die Entladung des Stapelrahmens des Aufrufers rückgängig machen lässt.
Das ARM EABI (Embedded Application Binary Interface) legt ein Ausnahmeentladungsmodell fest, das Entladungscodes verwendet, aber für SEH-Entladung in Windows nicht ausreichend ist. Denn hier müssen asynchrone Fälle gehandhabt werden, in denen der Prozessor inmitten des Prologs oder Epilogs einer Funktion ist.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 Microsoft PE- und COFF-Spezifikation.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 Funktionstext auftritt, ist es egal, ob die Vorgänge des Prologs rückgängig gemacht werden oder ob die Vorgänge des Epilogs auf eine nach vorn gerichtete Weise durchgeführt werden.Beides sollte zum gleichen Ergebnis führen.
Prologe und Epiloge entsprechen einander tendenziell.Das lässt sich nutzen, um die Größe der für die Beschreibung der Entladung nötigen Metadaten zu verringern.
Funktionen sind eher relativ klein.Verschiedene Optimierungen stützen sich darauf im Hinblick auf das effiziente Packen von Daten.
Wenn eine Bedingung in einen Epilog eingefügt wird, gilt sie gleichermaßen für jede Anweisung im Epilog.
Wenn der Stapelzeiger (Stack Pointer, SP) in einem anderen Register im Prolog gespeichert wird, muss dieses in der ganzen Funktion unverändert bleiben, 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 – das sind Funktionen, die keine andere Funktionen aufrufen – erfordern keine .pdata-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 0 ist, dann bilden die verbleibenden Bits eine Exception Information RVA (bei der die beiden niedrigsten Bitwerte implizit 0 sind).Wenn Flag nicht null ist, dann bilden die verbleibenden Bits eine Packed Unwind Data-Struktur. |
1 |
2-31 |
Exception Information RVA oder Packed Unwind Data. Exception Information RVA ist die Adresse der Ausnahmeinformationsstruktur mit variabler Länge, die im Abschnitt .xdata gespeichert wird.Diese Daten müssen 4-Bytes ausgerichtet sein. Packed Unwind Data ist eine komprimierte Beschreibung der Vorgänge, die für die Entladung einer Funktion nötig sind, ausgehend von einer kanonischen Form.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.Damit wird kein .xdata-Datensatz benötigt und der erforderliche Platz für die Entladung der Daten deutlich reduziert.Die kanonischen Prologe und Epiloge wurden entwickelt, sodass sie den allgemeinen Anforderung einer einfachen Funktion entsprechen, die keinen Ausnahmehandler erfordert und ihre Setup- und Teardownvorgänge in der standardmäßigen Reihenfolge durchführt.
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:
|
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 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:
|
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 = greift die Register nicht heraus, 1 = Greift Register heraus.) |
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 nicht/stellt nicht wieder her, 1 = speichert/stellt wieder her.) |
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:
|
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 ebenfalls auf 1 festgelegt werden, da Rahmenverknüpfung 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):
Anweisung |
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 (L==0 und R==1 und PF==0) |
16 |
mov r11,sp |
C0-CF/FB |
3b |
C==1 und (L==1 oder 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-rN |
Keine |
0 |
0 |
0 |
1 |
rS-rN |
Keine |
0 |
0 |
1 |
0 |
Keine |
d8-dE |
0 |
0 |
1 |
1 |
rS-r3 |
d8-dE |
0 |
1 |
0 |
0 |
r4-rN, LR |
Keine |
0 |
1 |
0 |
1 |
rS-rN, LR |
Keine |
0 |
1 |
1 |
0 |
LR |
d8-dE |
0 |
1 |
1 |
1 |
rS-r3, LR |
d8-dE |
1 |
0 |
0 |
0 |
r4-rN, r11 |
Keine |
1 |
0 |
0 |
1 |
rS-rN, r11 |
Keine |
1 |
0 |
1 |
0 |
r11 |
d8-dE |
1 |
0 |
1 |
1 |
rS-r3, r11 |
d8-dE |
1 |
1 |
0 |
0 |
r4-rN, r11, LR |
Keine |
1 |
1 |
0 |
1 |
rS-rN, r11, LR |
Keine |
1 |
1 |
1 |
0 |
r11, LR |
d8-dE |
1 |
1 |
1 |
1 |
rS-r3, r11, LR |
d8-dE |
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.
Anweisung |
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 R==0 oder EF==1 |
16/32 |
pop {registers} |
9a |
H==1 und L==0 |
16 |
add sp,sp,#0x10 |
9b |
H==1 und L==1 |
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 den beiden folgenden Änderungen: Erstens wird EF anstelle von PF verwendet; zweitens, wenn Ret = 0, dann wird LR durch PC in der Registerliste ersetzt und der Epilog endet sofort.
Wenn H festgelegt wurde, dann ist entweder Anweisung 9a oder 9b vorhanden.Anweisung 9a wird verwendet, wenn L 0 ist, um anzuzeigen, dass sich LR nicht im Stapel befindet.In diesem Fall wird der Stapel manuell angepasst und Ret muss 1 oder 2 sein, damit eine explizite Rückgabe festgelegt wird.Anweisung 9b wird verwendet, wenn L 1 ist, um ein frühes Ende des Epilogs anzuzeigen und gleichzeitig den Stack zurückzugeben und anzupassen.
Wenn der Epilog noch nicht beendet wurde, dann ist entweder Anweisung 10a oder 10b vorhanden und zeigt eine 16- oder 32-Bit-Verzweigung auf Grundlage des Werts Ret an.
.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 ist ein gepackter Satz an Worten mit variabler Länge, der vier Abschnitte hat:
Ein 1- oder 2-Wort-Header, der die Gesamtgröße der .xdata-Struktur beschreibt und wichtige Funktionsdaten enthält.Das zweite Worte ist nur vorhanden, wenn die Felder Epilogue Count und Code Words beide 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 sämtliche Prologverarbeitung ignoriert werden soll.
0
23-27
Epilogue Count ist ein 5-Bit-Feld mit zwei Bedeutungen, abhängig vom Status des E-Bits:
Wenn E 0 ist, dann ist dieses Feld eine Aufzählung der Gesamtzahl von Ausnahmebereichen, die in Abschnitt 3 beschrieben werden.Sind über 31 Bereiche in der Funktion vorhanden, dann müssen dieses Feld sowie das Code Words-Feld auf 0 festgelegt sein, sodass 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 ist ein 4-Bit-Feld, das die Anzahl der 32-Bit-Worte beschreibt, die benötigt werden, damit alle Entladungscodes in Abschnitt 4 umfasst werden.Sind über 15 Worte für mehr als 63 Entladungscodebytes erforderlich, dann müssen dieses Feld sowie das Epilogue Count-Feld auf 0 festgelegt sein, sodass angegeben wird, dass ein Ausnahmewort erforderlich ist.
1
0-15
Extended Epilogue Count ist ein 16-Bit-Feld, das mehr Raum für das Encoding 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 und Code Words im ersten Headerwort beide auf 0 festgelegt sind.
1
16-23
Extended Code Words ist ein 8-Bit-Feld, das mehr Raum für das Encoding 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 und Code Words im ersten Headerwort beide auf 0 festgelegt sind.
1
24-31
Reserviert
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
Epilogue Start Offset ist ein 18-Bit-Feld, das den Offset des Epilogs in Bytes geteilt durch 2 beschreibt, relativ zum Start der Funktion.
18-19
Res ist ein 2-Bit-Feld, das für künftige Erweiterungen reserviert ist.Sein Wert muss 0 sein.
20-23
Condition ist ein 4-Bit-Feld, das die Bedingung liefert, 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 ist ein 8-Bit-Feld, das den Byte-Index des ersten Entladungscodes angibt, der diesen Epilog beschreibt.
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.
Wenn das X-Feld im Header 1 ist, folgen den Entladungscodebytes die Ausnahmehandlerinformationen.Sie bestehen aus einer Exception Handler RVA, 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 EpilogueScopes;
ULONG Size;
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;
}
return Size;
}
Obwohl der Prolog und jeder Epilog einen Index in den Entladungscodes besitzt, teilen sich die beiden die Tabelle.Es ist nicht ungewöhnlich, dass alle denselben Entladungscode teilen.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 (Code & 0x7F) * 4 ist |
|||
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 Code & 0x0F ist |
|||
D0-D7 |
16 |
pop {r4-rX,lr} wobei X (Code & 0x03) + 4 ist und LR per pop ausgelesen wird, wenn Code & 0x04 |
|||
D8-DF |
32 |
pop {r4-rX,lr} wobei X (Code & 0x03) + 8 ist und LR per pop ausgelesen wird, wenn Code & 0x04 |
|||
E0-E7 |
32 |
vpop {d8-dX} wobei X (Code & 0x07) + 8 ist |
|||
E8-EB |
00-FF |
32 |
addw sp,sp,#X wobei X (Code & 0x03FF) * 4 ist |
||
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 (Code & 0x000F) * 4 ist |
||
EF |
10-FF |
32 |
Verfügbar |
||
F0-F4 |
- |
Verfügbar |
|||
F5 |
00-FF |
32 |
vpop {dS-dE} wobei S (Code & 0x00F0) >> 4 und E Code & 0x000F ist |
||
F6 |
00-FF |
32 |
vpop {dS-dE} wobei S ((Code & 0x00F0) >> 4) + 16 und E (Code & 0x000F) + 16 ist |
||
F7 |
00-FF |
00-FF |
16 |
add sp,sp,#X wobei X (Code & 0x00FFFF) * 4 ist |
|
F8 |
00-FF |
00-FF |
00-FF |
16 |
add sp,sp,#X wobei X (Code & 0x00FFFFFF) * 4 ist |
F9 |
00-FF |
00-FF |
32 |
add sp,sp,#X wobei X (Code & 0x00FFFF) * 4 ist |
|
FA |
00-FF |
00-FF |
00-FF |
32 |
add sp,sp,#X wobei X (Code & 0x00FFFFFF) * 4 ist |
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.Das erklärt die gemeinsamen Epilogenden bx lr oder b <tailcall-target>, die keine gleichen Prologanweisungen haben.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 liegt häufig vor und der Grund, 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.Ausgehend von einer Eins-zu-Eins-Zuordnung von Opcodes und Entladungscodes sollte der Entlader die ersten n Entladungscodes überspringen, wenn die Entladung von Anweisung n im Epilog stattfindet.
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 Fall sollte die Entladung bei n-Entladungscodes vom Ende der Codeliste beginnen, wenn die Entladung von Anweisung n im Prolog erfolgt.
Prolog- und Epilog-Entladungscodes 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:
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.
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.
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 für die Entladung im Text verwendet werden.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 einer oder mehr Epiloge; weitere Epiloge in anderen Fragmenten.
Weder Prolog noch Epilog; Prolog und einer oder mehr Epiloge in anderen Fragmenten.
Nur Epilog; Prolog und möglicherweise weitere 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, außer dass kein Prolog enthalten ist.In diesen Situationen wird davon ausgegangen, dass es Code vor dem Start des Epilogs gibt und er wird als Teil des Funktionstexts betrachtet, der normalerweise durch das Rückgängigmachen des Prologeffekts entladen wird.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 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 sehr große Funktion zu beschreiben, brechen Sie diese einfach in Fragmente herunter, die kleiner als 512 KB sind.Jedes Fragment sollte angepasst sein, sodass es einen Epilog nicht in mehrere Teile aufteilt.
Nur das erste Fragment der Funktion enthält einen Prolog; alle anderen sind mit einer Markierung versehen, wonach sie keinen Prolog enthalten.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 Spezialfall von Funktionsfragmenten ist das Shrink-Wrapping. Bei dieser Technik für das Verschieben von Registerspeicherungen vom Start der Funktion auf später in der Funktion wird für einfache Fälle optimiert, bei denen eine Registerspeicherung nicht erforderlich ist.Das lässt sich als eine äußere Region beschreiben, die den Stapelspeicher zuweist, aber einen minimalen Registersatz speichert sowie einer inneren Region, die weitere Register speichert und wiederherstellt.
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:
Bei Funktionen mit Shrink-Wrapping geht man typischerweise davon aus, dass der Platz für die zusätzlichen Registerspeicherungen im normalen Prolog im Voraus zugewiesen wird und die Registerspeicherungen dann mithilfe von str oder stm erfolgen statt mit push.Damit werden alle Aufruflistenzeigerbearbeitungen im ursprünglichen Prolog der Funktion gehalten.
Die Beispielfunktion mit Shrink-Wrapping muss in drei Regionen aufgeteilt werden, die in den Kommentaren mit A, B und C gekennzeichnet sind.Die erste Region A deckt den Start der Funktion bis zum Ende der zusätzlichen nicht flüchtigen Speicherungen 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 hat einen eigenen .pdata- oder .xdata-Datensatz, der beschreibt, dass ein Fragment weder Prolog noch Epilog hat.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 Region-A-Prolog gespeichert sind, als auch die zusätzlichen Register, die vor Eintritt in Region B gespeichert wurden, als wären sie von einer Vorgangssequenz produziert worden.
Diese Registerspeicherung für Region B können nicht als "innerer Prolog" betrachtet werden, denn der für Region B beschriebene zusammengesetzte Prolog muss sowohl den Region-A-Prolog als auch die zusätzlichen, gespeicherten Register beschreiben.Würde man Fragment B so beschreiben, als hätte es einen Prolog, dann würden die Entladungscodes auch die Größe dieses Prologs implizieren und es gibt keine Möglichkeit, den zusammengesetzten Prolog so zu beschreiben, dass eine Eins-zu-Eins-Zuordnung mit den Opcodes möglich ist, die nur die zusätzlichen Register speichern.
Die zusätzlichen Registerspeicherungen müssen als Teil von Region A betrachtet werden, denn ehe sie abgeschlossen sind, beschreibt der zusammengesetzte Prolog nicht genau den Zustand des Stapels.
Die letzte Region, C., erhält einen eigenen .pdata- oder .xdata-Datensatz, der ein Fragment beschreibt, das keinen Prolog, wohl aber einen Epilog hat.
Ein alternativer Ansatz kann ebenfalls funktionieren, wenn die Stapelbearbeitung vor Eintritt in Region B sich auf eine Anweisung reduzieren lässt:
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
Entscheidend ist hier, dass der Stapel bei jeder Anweisungsgrenze ganz mit den Entladungscodes für die Region übereinstimmt.Findet eine Entladung vor der inneren Pushübertagung in diesem Beispiel statt, wird sie als Teil von Region A betrachtet und nur der Region-A-Prolog wird entladen.Wenn die Entladung nach der inneren Pushübertagung stattfindet, wird sie als Teil von Region B betrachtet, die keinen Prolog hat, aber Entladungscodes besitzt, die sowohl die innere Pushübertragung als auch den ursprünglichen Prolog aus Region A beschreiben.Eine vergleichbare Logik gilt für den inneren pop.
Optimierungen codieren
Da die Entladungscodes so umfangreich sind und kompakte und erweiterte Datenformen nutzen können, gibt es viele Gelegenheiten, die Codierung zu optimieren und so Platz zu sparen.Durch aggressiven Einsatz dieser Techniken lässt sich der Nettoaufwand zur Beschreibung von Funktionen und Fragmenten mithilfe von Entladungscodes recht stark minimieren.
Die wichtigste Optimierung besteht darin, sorgfältig darauf zu achten, die Prolog-/Epiloggrenzen für Entladungszwecke nicht mit ihren logischen Pendants aus Compilerperspektive zu verwechseln.Die Entladungsgrenzen lassen sich schrumpfen und verengen, was für mehr Effizienz sorgt.Ein Prolog kann beispielsweise Code für das Durchführen zusätzlicher Überprüfungen nach dem Stapelsetup enthalten.Doch nachdem die Stapelbearbeitung abgeschlossen ist, müssen keine weiteren Vorgänge mehr codiert werden und alles danach kann aus dem Entladungsprolog entfernt werden.
Die gleiche Regel gilt für Funktionslänge.Wenn es Daten gibt – zum Beispiel ein Literalpool –, der auf einen Epilog in einer Funktion folgt, dann sollte dieser nicht als Teil der Funktionslänge enthalten sein.Durch das Schrumpfen der Funktion auf lediglich den Code, der Teil der Funktion ist, sind die Chancen deutlich größer, dass der Epilog ganz am Ende 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.Das erste, was zur Entladung der Funktion unternommen wird, ist das Wiederherstellen des SP aus dem gespeicherten Register. Weitere Vorgänge haben damit keine Auswirkung auf die Entladung.
Nur aus einer Anweisung bestehende Epiloge müssen überhaupt nicht codiert werden, weder als Bereiche noch als Entladungscodes.Findet eine Entladung statt, ehe diese Anweisung ausgeführt wurde, dann kann davon ausgegangen werden, dass sie aus dem Funktionstext stammt und es ist ausreichend, einfach nur die Prologentladungscodes auszuführen.Wenn die Entladung nach der einzelnen Anweisung ausgeführt wird, dann findet sie per Definition in einer anderen Region statt.
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.Findet die Entladung nach dieser Anweisung statt, dann müssen nur die folgenden Vorgänge berücksichtigt werden.
Entladungscode sollte aggressiv wiederverwendet werden.Der von jedem Epilogbereich festgelegte Index zeigt auf einen willkürlichen Startpunkt im Array der Entladungscodes.Er muss nicht auf den Start einer vorangehenden Sequenz zeigen; er kann auch auf die Mitte zeigen.Der beste Ansatz hier besteht darin, die gewünschte Codesequenz zu generieren und dann nach einer exakten Byteübereinstimmung im bereits codierten Sequenzenpool zu suchen und irgendeine perfekte Übereinstimmung als Startpunkt für die Wiederverwendung zu nutzen.
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, kein Lokal
Prologue:
004535F8: B430 push {r4-r5}
Epilogue:
00453656: BC30 pop {r4-r5}
00453658: 4770 bx lr
.pdata (fest, 2 Worte):
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-Verzweigungrückgabe an
H = 0, gibt an, dass die Parameter nicht herausgegriffen wurden
R=0 und Reg = 1, gibt einen Push/Pop von r4-r5 an
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 Zuteilung
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 (fest, 2 Worte):
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, gibt einen Push/Pop von r4-r7 an
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 Variadic-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 (fest, 2 Worte):
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, gibt einen Push/Pop von r4-r6 an
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: Funktionen 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 (fest, 2 Worte):
Word 0
- Function Start RVA = 0x000592F4 (= 0x004592F4–0x00400000)
Word 1
Flag = 0, gibt an, dass ein .xdata-Datensatz vorhanden ist (aufgrund mehrerer Epiloge erforderlich)
.xdata address - 0x00400000
.xdata (variabel, 6 Worte):
Word 0
Function Length = 0x0001A3 (= 0x000346/2)
Vers = 0, gibt die erste Version von xdata an
X = 0, gibt an, dass keine Ausnahmedaten vorhanden sind
E = 0, gibt eine Liste von Epilogbereichen an
F = 0, gibt eine volle Funktionsbeschreibung einschließlich Prolog an
Epilogue Count = 0x04, gibt insgesamt 4 Epilogbereiche an
Code Words = 0x01, gibt ein 32-Bit-Wort mit Entladungscodes an
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, beginnend bei Wort 5: (gemeinsam von Prolog und Epilog genutzt)
Entladungscode 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 (fest, 2 Worte):
Word 0
- Function Start RVA = 0x00085A20 (= 0x00485A20–0x00400000)
Word 1
Flag = 0, gibt an, dass ein .xdata-Datensatz vorhanden ist (aufgrund mehrerer Epiloge nötig)
.xdata address - 0x00400000
.xdata (variabel, 3 Worte):
Word 0
Function Length = 0x0001A3 (= 0x000346/2)
Vers = 0, gibt die erste Version von xdata an
X = 0, gibt an, dass keine Ausnahmedaten vorhanden sind
E = 0, gibt eine Liste von Epilogbereichen an
F = 0, gibt eine volle Funktionsbeschreibung einschließlich Prolog an
Epilogue Count = 0x001, gibt insgesamt 1 Epilogbereich an
Code Words = 0x01, gibt ein 32-Bit-Wort mit Entladungscodes an
Wort 1: Epilogbereich bei einem Offset von 0xC6 (= 0x18C/2), beginnt den Entladungscodeindex bei 0x00 und mit der Bedingung 0x0E (immer)
Entladungscodes, beginnend bei Wort 2: (gemeinsam von Prolog und Epilog genutzt)
Entladungscode 0 = 0xC6: sp = r6
Entladungscode 1 = 0xDC: pop {r4-r8, lr}
Entladungscode 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 (fest, 2 Worte):
Word 0
- Function Start RVA = 0x00088C24 (= 0x00488C24–0x00400000)
Word 1
Flag = 0, gibt an, dass ein .xdata-Datensatz vorhanden ist (aufgrund mehrerer Epiloge nötig)
.xdata address - 0x00400000
.xdata (variabel, 5 Worte):
Word 0
Function Length =0x000027 (= 0x00004E/2)
Vers = 0, gibt die erste Version von xdata an
X = 1, gibt an, dass Ausnahmedaten vorhanden sind
E = 1, gibt einen einzelnen Epilog an
F = 0, gibt eine volle Funktionsbeschreibung einschließlich Prolog an
Epilogue Count = 0x00, gibt den Start der Epilogentladungscodes bei einem Offset von 0x00 an
Code Words = 0x02, gibt zwei 32-Bit-Worte an Entladungscodes an
Entladungscodes, beginnend bei Wort 1:
Entladungscode 0 = 0xC7: sp = r7
Entladungscode 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 (fest, 2 Worte):
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, gibt an, 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
Referenz
Häufig auftretende ARM-Migrationsprobleme bei Visual C++