Ausnahmebehandlung bei x64-Systemen

Hier finden Sie eine Übersicht über die strukturierte Ausnahmebehandlung, die C++-Ausnahmebehandlung, Programmierkonventionen und Verhaltensweisen unter x64. Allgemeine Informationen zur Ausnahmebehandlung finden Sie unter Exception Handling in Microsoft C++.

Unwind-Informationen für Ausnahmebehandlung und Debuggerunterstützung

Zur Wiederherstellung nichtflüchtiger Register bei der Behandlung einer Ausnahme werden Nicht-Blatt-Funktionen mit statischen Daten versehen. Diese Daten, die gemeinhin als "Funktionsentspannungsinformationen" bezeichnet werden, beschreiben, wie die Funktion bei einer beliebigen Anweisung ordnungsgemäß entspannt wird. Diese Daten werden als pdata oder Prozedurdaten gespeichert, die sich wiederum auf xdata, die Ausnahmebehandlungsdaten, beziehen.

Die Unwind-Informationen einer Funktion bestehen aus mehreren Datenstrukturen, die im Folgenden beschrieben werden.

Informationen zu Unwind-Informationen mit Unterstützung für Intel APX (Advanced Performance Extensions) finden Sie in der Vorschau auf die Unwind-V3-Spezifikation.

Struktur RUNTIME_FUNCTION

Die tabellenbasierte Ausnahmebehandlung erfordert einen Tabelleneintrag für alle Funktionen, die Stapelspeicher zuordnen oder eine andere Funktion (z. B. Non-Leaf-Funktionen) aufrufen. Funktionstabelleneinträge weisen das folgende Format auf:

Größe Wert
ULONG Startadresse der Funktion
ULONG Endadresse der Funktion
ULONG Unwind-Info-Adresse

Die RUNTIME_FUNCTION Struktur muss im Speicher DWORD ausgerichtet sein. Alle Adressen sind imagerelativ, also 32-Bit-Offsets von der Startadresse des Images, das den Funktionstabelleneintrag enthält. Diese Einträge werden sortiert und in den .pdata Abschnitt eines PE32+-Bilds eingefügt. Für dynamisch generierte Funktionen [JIT-Compiler] muss die Laufzeitumgebung zur Unterstützung dieser Funktionen entweder RtlInstallFunctionTableCallback oder RtlAddFunctionTable verwenden, um diese Informationen dem Betriebssystem bereitzustellen. Wenn dies nicht der Fall ist, führt dies zu einer unzuverlässigen Ausnahmebehandlung und zum Debuggen von Prozessen.

Struktur UNWIND_INFO

Die Unwind-Informationsstruktur erfasst die Auswirkungen, die eine Funktion auf den Stapelzeiger ausübt, und wo die nichtflüchtigen Register auf dem Stapel gespeichert sind:

Größe Wert
UBYTE: 3 Version
UBYTE: 5 Flaggen
UBYTE Prologgröße
UBYTE Anzahl der Unwind-Codes
UBYTE: 4 Rahmenregister
UBYTE: 4 Frameregisteroffset (skaliert)
USHORT * n Entwirrungscode-Array
Variable Kann entweder dem folgenden Format (1) oder (2) entsprechen

Ausnahmebehandler

Größe Wert
ULONG Adresse des Ausnahmehandlers
Variable Sprachspezifische Handlerdaten (optional)

(2) Verkettete Entladeinformationen

Größe Wert
ULONG Startadresse der Funktion
ULONG Endadresse der Funktion
ULONG Unwind-Info-Adresse

Die UNWIND_INFO-Struktur muss im Speicher DWORD ausgerichtet sein. Die einzelnen Felder bedeuten Folgendes:

  • Version

    Versionsnummer der Entladedaten, derzeit 1

  • Flaggen

    Derzeit sind drei Flags definiert:

    Flagge Beschreibung
    UNW_FLAG_EHANDLER Die Funktion verfügt über einen Ausnahmehandler, den das Betriebssystem aufruft, um den Status der Ausnahme zu untersuchen und potenziell zu behandeln. Sprachfeatures wie die C-Klausel __try registrieren einen solchen Handler.
    UNW_FLAG_UHANDLER Die Funktion verfügt über eine Beendigungsroutine, die das Betriebssystem beim Abwickeln des Stacks aufruft. Dieser Handler kann Ressourcen freigeben, die von der Funktion im Ausnahmesicheren Code zugewiesen wurden. Sprachmerkmale wie Destruktoren lokaler C++-Objekte und C-__finally-Klauseln registrieren einen solchen Beendigungshandler.
    UNW_FLAG_CHAININFO Diese Unwind-Informationsstruktur ist nicht die primäre für die Prozedur. Stattdessen ist der verkettete Informationseintrag der Inhalt eines vorherigen RUNTIME_FUNCTION-Eintrags. Weitere Informationen finden Sie unter Verkettete Unwind-Informationsstrukturen. Wenn dieses Flag gesetzt ist, müssen die Flags UNW_FLAG_EHANDLER und UNW_FLAG_UHANDLER zurückgesetzt werden. Außerdem müssen die Felder für das Frameregister und die feste Stapelzuordnung dieselben Werte wie die primären Entladeinformationen aufweisen.
  • Prologgröße

    Die Länge des Funktionsprologs in Bytes

  • Anzahl der Unwind-Codes

    Die Anzahl der Slots im Array der Unwind-Codes. Einige Unwind-Codes, wie UWOP_SAVE_NONVOL, benötigen mehr als einen Eintrag im Array.

  • Frameregister

    Wenn der Wert ungleich null ist, verwendet die Funktion einen Frame Pointer (FP), und dieses Feld ist die Nummer des nichtflüchtigen Registers, das als Framezeiger verwendet wird, wobei für das Vorgangsinfofeld von UNWIND_CODE-Knoten dieselbe Codierung verwendet wird.

  • Frameregisteroffset (skaliert)

    Dieses Feld ist ein skalierter Offset zwischen dem RSP Registerwert und dem ausgewählten Frame Pointer (FP)-Registerwert. Das ausgewählte FP-Register wird auf RSP + 16 * dieser Zahl gesetzt, d. h. Sie können Offsets im Bereich von 0 bis 240 verwenden. Dieser Offset zeigt das FP-Register auf die Mitte der lokalen Stackbelegung für dynamische Stack-Frames, sodass Sie dadurch mit kürzeren Anweisungen eine bessere Code-Dichte erzielen. (Das heißt, dass mehr Instruktionen die 8-Bit-signierte-Offset-Form verwenden können.)

  • Unwind-Codes-Array

    Ein Array von Elementen, das die Auswirkungen des Prologs auf die nichtflüchtigen Register und auf RSP beschreibt. Die Bedeutung der einzelnen Elemente finden Sie im Abschnitt Code für Unwind-Operationen. Um eine ordnungsgemäße Datenausrichtung aufrechtzuerhalten, enthält dieses Array immer eine gerade Anzahl von Einträgen, und der endgültige Eintrag kann nicht verwendet werden. In diesem Fall ist das Array um ein Element länger als durch das Feld für die Anzahl der Unwind-Codes angegeben.

  • Adresse des Ausnahmehandlers

    Ein bildrelativer Zeiger auf den sprachspezifischen Ausnahme- oder Beendigungshandler der Funktion, wenn die Kennzeichnung UNW_FLAG_CHAININFO klar ist und eines der Flags UNW_FLAG_EHANDLERUNW_FLAG_UHANDLER oder festgelegt ist.

  • Sprachspezifische Handlerdaten

    Die sprachspezifischen Ausnahmehandlerdaten der Funktion – das Format dieser Daten ist nicht angegeben und wird vollständig durch den verwendeten Ausnahmehandler bestimmt.

  • Verkettete Entladeinformationen

    Wenn die Kennzeichnung UNW_FLAG_CHAININFO festgelegt ist, endet die UNWIND_INFO Struktur mit drei UWORDs. Diese UWORDstellen die RUNTIME_FUNCTION Informationen für die Funktion des verketteten Abspanns dar.

Struktur UNWIND_CODE

Verwenden Sie das Unwind-Code-Array, um die Abfolge der Vorgänge im Prolog aufzuzeichnen, die die nichtflüchtigen Register und RSP betreffen. Jedes Codeelement weist das folgende Format auf:

Größe Wert
UBYTE Offset im Prolog
UBYTE: 4 Code für den Entladevorgang
UBYTE: 4 Vorgangsinformationen

Das Array wird in der absteigenden Reihenfolge des Offsets im Prolog sortiert.

Offset im Prolog

Der Offset (ab dem Anfang des Prologs) des Endes der Anweisung, die diesen Vorgang ausführt, plus 1 (d. h. der Offset des Anfangs der nächsten Anweisung).

Code für den Entladevorgang

Bestimmte Operationscodes erfordern einen vorzeichenlosen Offset für einen Wert im lokalen Stack-Frame. Dieser Offset erfolgt vom Anfang her, das heißt, von der niedrigsten Adresse der festen Stack-Zuweisung. Wenn das Feld "Frame Register" in der UNWIND_INFO 0 ist, ist dieser Offset von RSP. Wenn das Feld „Frame Register“ ungleich null ist, wird dieser Offset von der Stelle aus berechnet, an der sich RSP befand, als das FP-Register eingerichtet wurde. Es entspricht dem FP-Register minus dem FP-Registeroffset (16 * der skalierte Rahmenregisterversatz im UNWIND_INFO). Wenn ein FP-Register verwendet wird, darf ein Unwind-Code, der einen Offset verwendet, nur dann eingesetzt werden, nachdem das FP-Register im Prolog etabliert wurde.

Für alle Opcodes mit Ausnahme von UWOP_SAVE_XMM128 und UWOP_SAVE_XMM128_FAR ist der Offset immer ein Vielfaches von 8, da alle relevanten Stapelwerte in 8-Byte-Grenzen gespeichert werden (der Stapel selbst ist immer auf 16 Byte ausgerichtet). Bei Operationscodes, die einen kurzen Offset (weniger als 512K) verwenden, enthält das letzte USHORT in den Knoten für diesen Code den durch 8 geteilten Offset. Für Vorgangscodes, die einen langen Offset verwenden (512K <= offset < 4GB), enthalten die letzten beiden USHORT-Knoten dieses Codes den Offset (im Little-Endian-Format).

Bei den Opcodes UWOP_SAVE_XMM128 und UWOP_SAVE_XMM128_FAR ist der Offset immer ein Vielfaches von 16, da alle 128-Bit-XMM-Operationen auf 16-Byte-ausgerichtetem Speicher erfolgen müssen. Aus diesem Grund wird ein Skalierungsfaktor von 16 für UWOP_SAVE_XMM128 verwendet, der Offsets von weniger als 1 MB zulässt.

Der Code für den Entladungsvorgang entspricht einem der folgenden Werte:

  • UWOP_PUSH_NONVOL (0) 1 Knoten

    Speichern Sie ein nichtflüchtiges Integer-Register, wobei RSP um 8 verringert wird. Die Vorgangsinformationen umfassen die Registernummer. Aufgrund der Einschränkungen für Epiloge müssen UWOP_PUSH_NONVOL-Entwirrungscodes zuerst im Prolog und entsprechend zuletzt im Entwirrungscode-Array erscheinen. Diese relative Reihenfolge gilt für alle weiteren Unwind-Codes außer UWOP_PUSH_MACHFRAME.

  • UWOP_ALLOC_LARGE (1) 2 oder 3 Knoten

    Hiermit wird dem Stapel ein großer Bereich zugewiesen. Es gibt zwei Formulare. Wenn die Vorgangsinformationen gleich 0 (null) sind, wird die Größe der Zuordnung geteilt durch 8 im nächsten Slot aufgezeichnet, sodass eine Zuordnung von bis zu 512 KB – 8 möglich ist. Wenn die Vorgangsinformationen gleich 1 sind, wird die nicht skalierte Größe der Zuordnung in den nächsten zwei Slots im Little-Endian-Format aufgezeichnet, sodass eine Zuordnung von bis zu 4 GB – 8 möglich ist.

  • UWOP_ALLOC_SMALL (2) 1 Knoten

    Hiermit wird dem Stapel ein kleiner Bereich zugewiesen. Die Größe der Zuordnung ist das Vorgangsinfofeld * 8 + 8, sodass Zuordnungen von 8 bis 128 Bytes möglich sind.

    Für den Entladungscode einer Stapelzuordnung sollte immer die kürzestmögliche Codierung verwendet werden:

    Zuordnungsgröße Entladungscode
    8 bis 128 Bytes UWOP_ALLOC_SMALL
    136 bis 512 KB – 8 Byte UWOP_ALLOC_LARGE, Vorgangsinformationen = 0
    512K bis 4G-8 Byte UWOP_ALLOC_LARGE, Vorgangsinformationen = 1
  • UWOP_SET_FPREG (3) 1 Knoten

    Richten Sie das Framezeigerregister ein, indem Sie das Register auf einen bestimmten Offset relativ zum aktuellen RSP setzen. Der Offset entspricht dem Feld „Frame Register Offset (skaliert)“ in UNWIND_INFO, multipliziert mit 16, wodurch Offsets von 0 bis 240 möglich sind. Durch die Verwendung eines Offsets kann ein Framezeiger festgelegt werden, der auf die Mitte der festen Stapelzuordnung zeigt, wodurch die Codedichte verbessert wird, indem mehr Zugriffe auf kurze Anweisungsformen möglich sind. Das Feld für die Vorgangsinformationen ist reserviert und sollte nicht verwendet werden.

  • UWOP_SAVE_NONVOL (4) 2 Knoten

    Verwenden Sie MOV statt PUSH, um ein nicht flüchtiges Integerregister im Stapel zu speichern. Dieser Code wird hauptsächlich für das Shrink Wrapping verwendet, bei dem ein nicht flüchtiges Register im Stapel an einer zuvor zugewiesenen Position gespeichert wird. Die Vorgangsinformationen umfassen die Registernummer. Der Offset des mit dem Faktor 8 skalierten Stapels wird im nächsten Unwind-Operation-Code-Slot aufgezeichnet, wie in der Anmerkung oben beschrieben.

  • UWOP_SAVE_NONVOL_FAR (5) 3 Knoten

    Speichern Sie ein nichtflüchtiges Integer-Register mit einem langen Offset auf dem Stack, indem Sie MOV anstelle von PUSH verwenden. Dieser Code wird hauptsächlich für das Shrink Wrapping verwendet, bei dem ein nicht flüchtiges Register im Stapel an einer zuvor zugewiesenen Position gespeichert wird. Die Vorgangsinformationen umfassen die Registernummer. Der Offset des nicht skalierten Stapels wird wie zuvor beschrieben in den nächsten zwei Codeslots des Entladevorgangs aufgezeichnet.

  • UWOP_SAVE_XMM128 (8) 2 Knoten

    Speichern Sie alle 128 Bits eines nicht volatile XMM Registers im Stapel. Die Vorgangsinformationen umfassen die Registernummer. Der Offset des nach 16 skalierten Stapels wird im nächsten Slot aufgezeichnet.

  • UWOP_SAVE_XMM128_FAR (9) 3 Knoten

    Speichern Sie alle 128 Bit eines nichtflüchtigen XMMRegisters im Stack mit großem Offset. Die Vorgangsinformationen umfassen die Registernummer. Der Offset des nicht skalierten Stapels wird in den nächsten beiden Slots aufgezeichnet.

  • UWOP_PUSH_MACHFRAME (10) 1 Knoten

    Eine Maschinenstruktur schieben. Dieser Unwindcode zeichnet die Auswirkung eines Hardwareinterrupts oder einer Ausnahme auf. Es hat zwei Formen. Ein Wert von 0 gibt an, dass die Hardware einen Frame wie dies auf dem Stapel verschoben hat:

    Standort Wert
    RSP+32 SS
    RSP+24 Alt RSP
    RSP+16 EFLAGS
    RSP+8 CS
    RSP RIP

    Ein Wert von 1 gibt an, dass die Hardware einen Frame wie dies auf dem Stapel verschoben hat:

    Standort Wert
    RSP+40 SS
    RSP+32 Alt RSP
    RSP+24 EFLAGS
    RSP+16 CS
    RSP+8 RIP
    RSP Fehlercode

    Dieser Unwind-Code erscheint immer in einem Dummy-Prolog, der nie tatsächlich ausgeführt wird, sondern vor dem realen Einstiegspunkt einer Unterbrechungsroutine platziert ist und existiert nur, um das Schieben eines Maschinen-Frames zu simulieren. UWOP_PUSH_MACHFRAME zeichnet diese Simulation auf. Daraus geht hervor, dass der Computer diesen Vorgang konzeptionell ausgeführt hat:

    1. Hole die Rücksprungadresse vom Stack-Top in TempRIP

    2. Drücken SS

    3. Alte Daten übertragen RSP

    4. Drücken Sie EFLAGS

    5. Drücken CS

    6. Temp drücken

    7. Pushfehlercode (wenn die Vorgangsinformationen gleich 1 sind)

    Der simulierte UWOP_PUSH_MACHFRAME Vorgang verringert RSP um 40 (wenn die Op-Info 0 ist) oder um 48 (wenn die Op-Info 1 ist).

Vorgangsinformationen

Die Bedeutung der Operationsinformationsbits hängt vom Opcode ab. Folgende Zuordnung wird verwendet, um ein allgemeines Register (Integer) zu codieren:

bit Registrieren
0 RAX
1 RCX
2 RDX
3 RBX
4 RSP
5 RBP
6 RSI
7 RDI
8 bis 15 R8 bis R15

Verkettete Entladeinformationsstrukturen

Wenn das UNW_FLAG_CHAININFO Flag gesetzt ist, dann ist eine Unwind-Informationsstruktur eine sekundäre Struktur, und das gemeinsame Adressfeld für Ausnahmebehandlung/verkettete Informationen enthält die primären Unwind-Informationen. Dieser Beispielcode ruft die primären Unwindinformationen ab, wobei davon ausgegangen wird, dass unwindInfo die Struktur ist, in der das Flag UNW_FLAG_CHAININFO gesetzt ist.

PRUNTIME_FUNCTION primaryUwindInfo = (PRUNTIME_FUNCTION)&(unwindInfo->UnwindCode[( unwindInfo->CountOfCodes + 1 ) & ~1]);

Verkettete Informationen sind in zwei Situationen nützlich. Sie können einerseits für nicht zusammenhängende Codesegmente verwendet werden. Mithilfe von verketteten Informationen können Sie die Größe der erforderlichen Entspanninformationen reduzieren, da Sie das Array der Abspanncodes nicht aus den primären Entspanninformationen duplizieren müssen.

Sie können verkettete Informationen andererseits verwenden, um flüchtige Registerspeicherungen zu gruppieren. Der Compiler kann das Speichern einiger veränderliche Register verzögern, bis er sich außerhalb des Prologs des Funktionseintrags befindet. Sie können sie aufzeichnen, indem Sie für den Teil der Funktion vor dem gruppierten Code primäre Unwind-Informationen verwenden und dann verkettete Informationen mit einer Prologgröße ungleich null einrichten, wobei die Unwind-Codes in den verketteten Informationen das Sichern der nichtflüchtigen Register widerspiegeln. In diesem Fall sind die Unwind-Codes allesamt Instanzen von UWOP_SAVE_NONVOL. Eine Gruppierung, die nichtflüchtige Register mithilfe von PUSH sichert oder das Register RSP mithilfe einer zusätzlichen festen Stackzuweisung ändert, wird nicht unterstützt.

Ein UNWIND_INFO Element, das UNW_FLAG_CHAININFO gesetzt hat, kann einen RUNTIME_FUNCTION-Eintrag enthalten, dessen UNWIND_INFO Element ebenfalls UNW_FLAG_CHAININFO gesetzt hat, was manchmal als mehrfache Schrumpfverpackung bezeichnet wird. Schließlich gelangen die verketteten Unwind-Informationszeiger zu einem UNWIND_INFO Element, bei dem UNW_FLAG_CHAININFO nicht gesetzt ist. Dieses Element ist das primäre UNWIND_INFO Element, das auf den tatsächlichen Einstiegspunkt der Prozedur verweist.

Entladeprozedur

Das Unwind-Code-Array wird in absteigender Reihenfolge sortiert. Wenn eine Ausnahme auftritt, speichert das Betriebssystem den vollständigen Kontext in einem Kontextdatensatz. Anschließend wird die Dispatchlogik für Ausnahmen aufgerufen, die wiederholt die folgenden Schritte ausführt, um einen Ausnahmehandler zu finden:

  1. Verwenden Sie den aktuell im Kontextdatensatz gespeicherten RIP, um nach einem RUNTIME_FUNCTION-Tabelleneintrag zu suchen, der die aktuelle Funktion (oder den Funktionsteil bei verketteten UNWIND_INFO-Einträgen) beschreibt.

  2. Wenn die Suche keinen Funktionstabelleneintrag findet, wird davon ausgegangen, dass der Code Teil einer Blattfunktion ist und RSP den Rückgabezeiger direkt adressiert. Der Rücksprungzeiger an [RSP] wird im aktualisierten Kontext gespeichert, der simulierte RSP wird um 8 inkrementiert, und Schritt 1 wird wiederholt.

  3. Wenn die Suche einen Funktionstabelleneintrag findet, RIP kann in drei Bereichen liegen: a) in einem Epilog, b) im Prolog oder c) im Code, der möglicherweise von einem Ausnahmehandler abgedeckt wird.

    • Fall a) Wenn sich RIP innerhalb eines Epilogs befindet, verlässt die Kontrolle die Funktion. Dieser Ausnahme kann kein Ausnahmehandler für diese Funktion zugeordnet sein. Die Effekte des Epilogs müssen weiterhin den Kontext der aufrufenden Funktion ermitteln. Um festzustellen, ob sich RIP innerhalb eines Epilogs befindet, wird der Codestrom ab RIP untersucht. Wenn dieser Codestrom dem abschließenden Teil eines gültigen Epilogs entspricht, dann befindet er sich in einem Epilog. Der verbleibende Teil des Epilogs wird simuliert, wobei der Kontextdatensatz aktualisiert wird, während jede Anweisung verarbeitet wird. Nach diesem Vorgang wird Schritt 1 wiederholt.

      • Fall b) Wenn das RIP Steuerelement innerhalb des Prologs liegt, hat das Steuerelement die Funktion nicht eingegeben. Dieser Ausnahme kann kein Ausnahmehandler für diese Funktion zugeordnet sein. Die Auswirkungen des Prologs müssen rückgängig gemacht werden, um den Kontext der Aufruferfunktion zu berechnen. Das RIP befindet sich im Prolog, wenn der Abstand vom Funktionsanfang bis zum RIP kleiner oder gleich der in den Unwind-Informationen codierten Prologgröße ist. Der Unwinder durchsucht das Array der Unwind-Codes vorwärts nach dem ersten Eintrag mit einem Offset, der kleiner oder gleich dem Offset von RIP relativ zum Funktionsanfang ist, und macht dann die Wirkung aller verbleibenden Einträge im Array der Unwind-Codes rückgängig. Anschließend wird Schritt 1 wiederholt.
    • Fall c) Wenn sich RIP nicht innerhalb eines Prologs oder Epilogs befindet und die Funktion einen Exception-Handler hat (UNW_FLAG_EHANDLER ist gesetzt), wird der sprachspezifische Handler aufgerufen. Der Handler scannt die Daten und ruft entsprechend die Filterfunktionen auf. Der sprachspezifische Handler kann zurückgeben, dass die Ausnahme behandelt wurde oder dass die Suche fortgesetzt werden soll. Außerdem kann eine Entladung direkt initiiert werden.

  4. Wenn der sprachspezifische Handler einen behandelten Status zurückgibt, wird die Ausführung mit dem ursprünglichen Kontextdatensatz fortgesetzt.

  5. Wenn kein sprachspezifischer Handler vorhanden ist oder der Handler den Status „Suche fortsetzen“ zurückgibt, muss der Kontextdatensatz auf den Zustand des Aufrufers zurückgesetzt werden. Der Unwinder macht die Wirkung jedes Elements im Unwind-Code-Array rückgängig. Anschließend wird Schritt 1 wiederholt.

Wenn verkettete Entladeinformationen vorhanden sind, werden diese grundlegenden Schritte trotzdem befolgt. Der einzige Unterschied besteht darin, dass beim Durchlaufen des Unwind-Code-Arrays, um die Auswirkungen eines Prologs rückgängig zu machen, nach Erreichen des Endes des Arrays auf die übergeordneten Unwind-Informationen verwiesen wird und dort das gesamte Unwind-Code-Array durchlaufen wird. Diese Verknüpfung wird fortgesetzt, bis sie zu einer Entlastungsinfo ohne Kennzeichnung UNW_CHAINED_INFO gelangt ist, und dann endet sie mit dem Durchlaufen des Entlastungscodearrays.

Der kleinste Satz von Unwind-Daten beträgt 8 Byte. Dieser Satz stellt eine Funktion dar, die nur 128 Bytes Stapel oder weniger zugeordnet und möglicherweise ein nicht volatiles Register gespeichert hat. Der Wert entspricht auch der Größe einer verketteten Unwind-Informationsstruktur für einen Null-Längen-Prolog ohne Unwind-Codes.

Sprachspezifische Handler

Die UNWIND_INFO Struktur stellt die relative Adresse des sprachspezifischen Handlers bereit, wenn entweder UNW_FLAG_EHANDLER oder UNW_FLAG_UHANDLER Flags festgelegt werden. Wie im vorherigen Abschnitt beschrieben, ruft die Suche nach einem Ausnahmehandler oder dem Abwickelvorgang den sprachspezifischen Handler auf. Der Handler verwendet diesen Prototyp:

typedef EXCEPTION_DISPOSITION (*PEXCEPTION_ROUTINE) (
    IN PEXCEPTION_RECORD ExceptionRecord,
    IN ULONG64 EstablisherFrame,
    IN OUT PCONTEXT ContextRecord,
    IN OUT PDISPATCHER_CONTEXT DispatcherContext
);

ExceptionRecord stellt einen Zeiger auf einen Ausnahmedatensatz bereit, der der Standarddefinition für Win64 folgt.

EstablisherFrame ist die Adresse der Basis der festen Stapelzuordnung für diese Funktion.

ContextRecord zeigt auf den Ausnahmekontext zu dem Zeitpunkt, als die Ausnahme ausgelöst wurde (im Fall eines Ausnahmehandlers), oder auf den aktuellen Entladekontext (im Fall eines Beendigungshandlers).

DispatcherContext verweist auf den Dispatcherkontext für diese Funktion. Hierbei gilt folgende Definition:

typedef struct _DISPATCHER_CONTEXT {
    ULONG64 ControlPc;
    ULONG64 ImageBase;
    PRUNTIME_FUNCTION FunctionEntry;
    ULONG64 EstablisherFrame;
    ULONG64 TargetIp;
    PCONTEXT ContextRecord;
    PEXCEPTION_ROUTINE LanguageHandler;
    PVOID HandlerData;
} DISPATCHER_CONTEXT, *PDISPATCHER_CONTEXT;

ControlPc ist der Wert dieser RIP Funktion. Dieser Wert ist entweder eine Ausnahmeadresse oder die Adresse, an der das Steuerelement die erstellende Funktion verlassen hat. Dies RIP wird verwendet, um zu ermitteln, ob sich das Steuerelement innerhalb eines geschützten Konstrukts innerhalb dieser Funktion befindet, z. B. einen __try Block für__try/__except oder .__try/__finally

ImageBase ist die Bildbasis (Ladeadresse) des Moduls, das diese Funktion enthält. Die im Funktionseintrag und in den Unwindinformationen verwendeten 32-Bit-Offsets sollten zur ImageBase hinzugefügt werden, um die endgültige Adresse zu erhalten.

FunctionEntry liefert einen Zeiger auf den RUNTIME_FUNCTIONFunktionseintrag, der die bildbasisrelativen Adressen der Funktion und der Unwindinformationen für diese Funktion enthält.

EstablisherFrame ist die Adresse der Basis der festen Stapelzuordnung für diese Funktion.

TargetIp liefert eine optionale Instruktionsadresse, die die Fortsetzungsadresse des Unwind-Vorgangs angibt. Diese Adresse wird ignoriert, wenn EstablisherFrame nicht angegeben wird.

ContextRecord zeigt auf den Ausnahmekontext, der vom Ausnahmeverteiler-/Rückwicklungscode des Systems verwendet werden soll.

LanguageHandler zeigt auf die sprachspezifische Handlerroutine, die aufgerufen wird.

HandlerData zeigt auf die sprachspezifischen Handlerdaten für diese Funktion.

Entladehilfsprogramme für MASM

Verwenden Sie zum Schreiben ordnungsgemäßer Assemblyroutinen eine Reihe von Pseudovorgängen zusammen mit den tatsächlichen Assemblyanweisungen. Diese Pseudovorgänge erstellen die entsprechenden .pdata und .xdata. Verwenden Sie außerdem eine Reihe von Makros, die die Verwendung dieser Pseudovorgänge für die am häufigsten verwendeten Verwendungen vereinfachen.

Roh-Pseudovorgänge

Pseudovorgänge Beschreibung
PROC FRAME [:ehandler] Bewirkt, dass MASM einen Eintrag in der Funktionstabelle in .pdata und Unwindinformationen in .xdata für das strukturierte Ausnahmebehandlungs-Unwindverhalten einer Funktion generiert. Wenn ehandler vorhanden ist, wird diese Prozedur im .xdata als der sprachspezifische Handler eingetragen.

Wenn Sie das FRAME-Attribut verwenden, lassen Sie darauf eine .ENDPROLOG-Direktive folgen. Wenn es sich bei der Funktion um eine Blattfunktion handelt (wie in Funktionstypen definiert), ist das FRAME-Attribut nicht erforderlich, ebenso wie der Rest dieser Pseudovorgänge.
.PUSHREG Register Generiert einen UWOP_PUSH_NONVOL Unwindcode-Eintrag für die angegebene Registernummer unter Verwendung des aktuellen Offsets im Prolog.

Verwenden Sie diesen nur mit nicht flüchtigen Integerregistern. Verwenden Sie stattdessen für Pushes von flüchtigen Registern .ALLOCSTACK 8.
.SETFRAME Register, Offset Füllt das Frameregisterfeld und den Offset in den Entladeinformationen mit dem angegebenen Register und Offset aus. Der Offset muss ein Vielfaches von 16 und kleiner oder gleich 240 sein. Diese Direktive generiert auch einen UWOP_SET_FPREG Unwindcode-Eintrag für das angegebene Register unter Verwendung des aktuellen Prolog-Offsets.
.ALLOCSTACK Größe Generiert ein UWOP_ALLOC_SMALL oder ein UWOP_ALLOC_LARGE mit der angegebenen Größe für den aktuellen Offset im Prolog.

Der Größe-Operand muss ein Vielfaches von 8 sein.
.SAVEREG Register, Offset Generiert entweder einen UWOP_SAVE_NONVOL- oder einen UWOP_SAVE_NONVOL_FAR-Unwindcode-Eintrag für das angegebene Register und den Offset unter Verwendung des aktuellen Prolog-Offsets. MASM wählt die effizienteste Codierung aus.

Der Offset muss positiv und ein Vielfaches von 8 sein. offset ist relativ zur Basis des Frames der Prozedur, die sich in der Regel in RSP oder, bei Verwendung eines Frame-Pointers, im unskalierten Frame-Pointer befindet.
.SAVEXMM128 Register, Offset Generiert unter Verwendung des aktuellen Prolog-Offsets entweder einen UWOP_SAVE_XMM128-Eintrag oder einen UWOP_SAVE_XMM128_FAR-Unwindcodeeintrag für das angegebene Register XMM und den Offset. MASM wählt die effizienteste Codierung aus.

Der Offset muss positiv und ein Vielfaches von 16 sein. offset bezieht sich auf die Basis des Stackframes der Prozedur, die sich im Allgemeinen in RSP befindet, oder, bei Verwendung eines Frame-Pointers, auf den unskalierten Frame-Pointer.
.PUSHFRAME [Code] Generiert einen UWOP_PUSH_MACHFRAME Unwind-Code-Eintrag. Wenn Sie den optionalen code angeben, erhält der Unwind-Code-Eintrag den Modifier 1. Andernfalls wird der Modifizierer 0 angewendet.
.ENDPROLOG Kennzeichnet das Ende der Prolog-Erklärungen. muss in den ersten 255 Byte der Funktion auftreten.

Im Folgenden finden Sie ein Beispiel für einen Funktionsprolog mit korrekter Verwendung der meisten Opcodes:

sample PROC FRAME
    db      048h; emit a REX prefix, to enable hot-patching
    push rbp
    .pushreg rbp
    sub rsp, 040h
    .allocstack 040h
    lea rbp, [rsp+020h]
    .setframe rbp, 020h
    movdqa [rbp], xmm7
    .savexmm128 xmm7, 020h ;the offset is from the base of the frame
                           ;not the scaled offset of the frame
    mov [rbp+018h], rsi
    .savereg rsi, 038h
    mov [rsp+010h], rdi
    .savereg rdi, 010h ; you can still use RSP as the base of the frame
                       ; or any other register you choose
    .endprolog

; you can modify the stack pointer outside of the prologue (similar to alloca)
; because we have a frame pointer.
; if we didn't have a frame pointer, this would be illegal
; if we didn't make this modification,
; there would be no need for a frame pointer

    sub rsp, 060h

; we can unwind from the next AV because of the frame pointer

    mov rax, 0
    mov rax, [rax] ; AV!

; restore the registers that weren't saved with a push
; this isn't part of the official epilog, as described in section 2.5

    movdqa xmm7, [rbp]
    mov rsi, [rbp+018h]
    mov rdi, [rbp-010h]

; Here's the official epilog

    lea rsp, [rbp+020h] ; deallocate both fixed and dynamic portions of the frame
    pop rbp
    ret
sample ENDP

Weitere Informationen zum Epilogbeispiel finden Sie im Artikel Prolog und Epilog bei x64-Systemen unter Epilogcode.

MASM-Makros

Um die Verwendung von Unformatierten Pseudovorgängen zu vereinfachen, verwenden Sie den Satz von Makros, die in ksamd64.incdefiniert sind. Mit diesen Makros können Sie typische Prozedurprologe und Epiloge erstellen.

Makro Beschreibung
alloc_stack(n) Weist einen Stackframe von n Bytes (unter Verwendung von sub rsp, n) zu, und erzeugt die entsprechenden Unwind-Informationen (.allocstack n)
save_reg reg, loc Speichert ein nichtflüchtiges Register reg auf dem Stapel bei RSP Offset loc und erzeugt die entsprechenden Unwind-Informationen (.savereg reg, loc)
push_reg register Legt ein nichtflüchtiges Register reg auf dem Stack ab und erzeugt die entsprechenden Unwind-Informationen (.pushreg reg).
rex_push_reg reg Speichert ein nicht unvolatiles Register auf dem Stapel mithilfe eines 2-Byte-Pushs und gibt die entsprechenden Entspanninformationen (.pushreg reg) aus. Verwenden Sie dieses Makro, wenn der Push die erste Anweisung in der Funktion ist, damit sichergestellt wird, dass die Funktion hotpatchfähig ist.
save_xmm128 reg, loc Speichert ein nichtflüchtiges XMM Register reg auf dem Stack bei RSP Offset loc und erzeugt die entsprechenden Unwindinformationen (.savexmm128 reg, loc)
set_frame reg, offset Legt das Frame-Register reg auf den RSP + Offset fest (unter Verwendung eines mov oder eines lea) und gibt die entsprechenden Unwind-Informationen aus (.set_frame reg, offset)
push_eflags Verschiebt die Eflags mithilfe einer pushfq Anweisung und gibt die entsprechenden Entschlackungsinformationen (.alloc_stack 8) aus.

Hier ist eine Beispielfunktion prolog mit der richtigen Verwendung der Makros:

sampleFrame struct
    Fill     dq ?; fill to 8 mod 16
    SavedRdi dq ?; Saved Register RDI
    SavedRsi dq ?; Saved Register RSI
sampleFrame ends

sample2 PROC FRAME
    alloc_stack(sizeof sampleFrame)
    save_reg rdi, sampleFrame.SavedRdi
    save_reg rsi, sampleFrame.SavedRsi
    .end_prolog

; function body

    mov rsi, sampleFrame.SavedRsi[rsp]
    mov rdi, sampleFrame.SavedRdi[rsp]

; Here's the official epilog

    add rsp, (sizeof sampleFrame)
    ret
sample2 ENDP

Definitionen von Unwind-Daten in C

Hier ist eine C-Beschreibung der Unwind-Daten:

typedef enum _UNWIND_OP_CODES {
    UWOP_PUSH_NONVOL = 0, /* info == register number */
    UWOP_ALLOC_LARGE,     /* no info, alloc size in next 2 slots */
    UWOP_ALLOC_SMALL,     /* info == size of allocation / 8 - 1 */
    UWOP_SET_FPREG,       /* no info, FP = RSP + UNWIND_INFO.FPRegOffset*16 */
    UWOP_SAVE_NONVOL,     /* info == register number, offset in next slot */
    UWOP_SAVE_NONVOL_FAR, /* info == register number, offset in next 2 slots */
    UWOP_SAVE_XMM128 = 8, /* info == XMM reg number, offset in next slot */
    UWOP_SAVE_XMM128_FAR, /* info == XMM reg number, offset in next 2 slots */
    UWOP_PUSH_MACHFRAME   /* info == 0: no error-code, 1: error-code */
} UNWIND_CODE_OPS;

typedef unsigned char UBYTE;

typedef union _UNWIND_CODE {
    struct {
        UBYTE CodeOffset;
        UBYTE UnwindOp : 4;
        UBYTE OpInfo   : 4;
    };
    USHORT FrameOffset;
} UNWIND_CODE, *PUNWIND_CODE;

#define UNW_FLAG_EHANDLER  0x01
#define UNW_FLAG_UHANDLER  0x02
#define UNW_FLAG_CHAININFO 0x04

typedef struct _UNWIND_INFO {
    UBYTE Version       : 3;
    UBYTE Flags         : 5;
    UBYTE SizeOfProlog;
    UBYTE CountOfCodes;
    UBYTE FrameRegister : 4;
    UBYTE FrameOffset   : 4;
    UNWIND_CODE UnwindCode[1];
/*  UNWIND_CODE MoreUnwindCode[((CountOfCodes + 1) & ~1) - 1];
*   union {
*       OPTIONAL ULONG ExceptionHandler;
*       OPTIONAL ULONG FunctionEntry;
*   };
*   OPTIONAL ULONG ExceptionData[]; */
} UNWIND_INFO, *PUNWIND_INFO;

typedef struct _RUNTIME_FUNCTION {
    ULONG BeginAddress;
    ULONG EndAddress;
    ULONG UnwindData;
} RUNTIME_FUNCTION, *PRUNTIME_FUNCTION;

#define GetUnwindCodeEntry(info, index) \
    ((info)->UnwindCode[index])

#define GetLanguageSpecificDataPtr(info) \
    ((PVOID)&GetUnwindCodeEntry((info),((info)->CountOfCodes + 1) & ~1))

#define GetExceptionHandler(base, info) \
    ((PEXCEPTION_HANDLER)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))

#define GetChainedFunctionEntry(base, info) \
    ((PRUNTIME_FUNCTION)((base) + *(PULONG)GetLanguageSpecificDataPtr(info)))

#define GetExceptionDataPtr(info) \
    ((PVOID)((PULONG)GetLanguageSpecificData(info) + 1))

Siehe auch

Softwarekonventionen bei x64-Systemen