Partager via


Gestion d’exceptions x64

Vue d’ensemble de la gestion structurée des exceptions et des conventions de codage de gestion des exceptions C++ sur le x64. Pour obtenir des informations générales sur la gestion des exceptions, consultez Gestion des exceptions dans Visual C++.

Décompresser les données pour la gestion des exceptions, prise en charge du débogueur

Plusieurs structures de données sont requises pour la gestion des exceptions et la prise en charge du débogage.

RUNTIME_FUNCTION, structure

La gestion des exceptions basée sur une table nécessite une entrée de table pour toutes les fonctions qui allouent de l’espace de pile ou appellent une autre fonction (par exemple, les fonctions non-en-feuilles). Les entrées de table de fonctions ont le format suivant :

Taille Value
ULONG Adresse de début de la fonction
ULONG Adresse de fin de fonction
ULONG Dissocier l’adresse d’informations

La structure RUNTIME_FUNCTION doit être alignée sur DWORD en mémoire. Toutes les adresses sont relatives à l’image, c’est-à-dire qu’elles sont des décalages 32 bits par rapport à l’adresse de départ de l’image qui contient l’entrée de la table de fonctions. Ces entrées sont triées et placées dans la section .pdata d’une image PE32+. Pour les fonctions générées dynamiquement [compilateurs JIT], le runtime qui prend en charge ces fonctions doit utiliser RtlInstallFunctionTableCallback ou RtlAddFunctionTable pour fournir ces informations au système d’exploitation. L’échec de ce processus entraîne une gestion et un débogage d’exceptions non fiables.

struct UNWIND_INFO

La structure d’informations de déroulement des données est utilisée pour enregistrer les effets qu’une fonction a sur le pointeur de la pile et où les registres nonvolatiles sont enregistrés sur la pile :

Taille Value
UBYTE : 3 Version
UBYTE : 5 Indicateurs
UBYTE Taille du prologue
UBYTE Nombre de codes de déroulement
UBYTE : 4 Registre d’images
UBYTE : 4 Décalage de registre d’images (mis à l’échelle)
USHORT * n Tableau de codes de déroulement
variable Peut être de forme (1) ou (2) ci-dessous

(1) Gestionnaire d’exceptions

Taille Value
ULONG Adresse du gestionnaire d’exceptions
variable Données de gestionnaire spécifiques au langage (facultatif)

(2) Informations de déroulement chaînées

Taille Value
ULONG Adresse de début de la fonction
ULONG Adresse de fin de fonction
ULONG Dissocier l’adresse d’informations

La structure UNWIND_INFO doit être alignée sur DWORD en mémoire. Voici ce que signifie chaque champ :

  • Version

    Numéro de version des données de déroulement, actuellement 1.

  • Indicateurs

    Trois indicateurs sont actuellement définis :

    Indicateur Description
    UNW_FLAG_EHANDLER La fonction a un gestionnaire d’exceptions qui doit être appelé lors de la recherche de fonctions qui doivent examiner les exceptions.
    UNW_FLAG_UHANDLER La fonction a un gestionnaire d’arrêt qui doit être appelé lors du déroulement d’une exception.
    UNW_FLAG_CHAININFO Cette structure d’informations de déroulement n’est pas la principale pour la procédure. Au lieu de cela, l’entrée d’informations de déroulement chaînée est le contenu d’une entrée de RUNTIME_FUNCTION précédente. Pour plus d’informations, consultez structures d’informations de déroulement chaînées. Si cet indicateur est défini, les indicateurs UNW_FLAG_EHANDLER et UNW_FLAG_UHANDLER doivent être effacés. En outre, le registre d’images et les champs d’allocation de pile fixe doivent avoir les mêmes valeurs que dans les informations de déroulement primaires.
  • Taille du prologue

    Longueur du prolog de fonction en octets.

  • Nombre de codes de déroulement

    Nombre d’emplacements dans le tableau de codes de déroulement. Certains codes de déroulement, par exemple, UWOP_SAVE_NONVOL nécessitent plusieurs emplacements dans le tableau.

  • Registre de trames

    Si elle n’est pas différente de zéro, la fonction utilise un pointeur d’image (FP), et ce champ est le nombre du registre nonvolatile utilisé comme pointeur d’image, en utilisant le même encodage pour le champ d’informations d’opération de UNWIND_CODE nœuds.

  • Décalage du registre d’images (mis à l’échelle)

    Si le champ de registre d’images n’est pas différent de zéro, ce champ est le décalage mis à l’échelle de RSP appliqué au registre FP lorsqu’il est établi. Le registre FP réel est défini sur RSP + 16 * ce nombre, ce qui autorise les décalages de 0 à 240. Ce décalage permet de pointer le registre FP au milieu de l’allocation de pile locale pour les trames de pile dynamiques, ce qui permet une meilleure densité de code par le biais d’instructions plus courtes. (Autrement dit, d’autres instructions peuvent utiliser le formulaire offset signé 8 bits.)

  • Tableau de codes de déroulement

    Tableau d’éléments qui explique l’effet du prologue sur les registres nonvolatiles et le RSP. Consultez la section sur UNWIND_CODE pour connaître les significations des éléments individuels. À des fins d’alignement, ce tableau a toujours un nombre pair d’entrées et l’entrée finale est potentiellement inutilisée. Dans ce cas, le tableau est un tableau plus long que indiqué par le nombre de champs de codes de déroulement.

  • Adresse du gestionnaire d’exceptions

    Pointeur relatif à l’image vers l’exception spécifique à la langue ou le gestionnaire d’arrêt de la fonction, si l’indicateur UNW_FLAG_CHAININFO est clair et l’un des indicateurs UNW_FLAG_EHANDLER ou UNW_FLAG_UHANDLER est défini.

  • Données de gestionnaire spécifiques à la langue

    Données du gestionnaire d’exceptions propres au langage de la fonction. Le format de ces données n’est pas spécifié et entièrement déterminé par le gestionnaire d’exceptions spécifique en cours d’utilisation.

  • Informations de déroulement chaînées

    Si l’indicateur UNW_FLAG_CHAININFO est défini, la structure UNWIND_INFO se termine par trois UWORD. Ces UWORD représentent les informations RUNTIME_FUNCTION pour la fonction du déroulement chaîné.

struct UNWIND_CODE

Le tableau de code de déroulement est utilisé pour enregistrer la séquence d’opérations dans le prologue qui affectent les registres nonvolatiles et le RSP. Chaque élément de code a ce format :

Taille Value
UBYTE Décalage dans le prologue
UBYTE : 4 Déroulage du code d’opération
UBYTE : 4 Informations sur l’opération

Le tableau est trié par ordre décroissant de décalage dans le prolog.

Décalage dans le prologue

Offset (à partir du début du prolog) de la fin de l’instruction qui effectue cette opération, plus 1 (autrement dit, décalage du début de l’instruction suivante).

Déroulage du code d’opération

Remarque : certains codes d’opération nécessitent un décalage non signé à une valeur dans le cadre de la pile locale. Ce décalage est du début, c’est-à-dire l’adresse la plus basse de l’allocation de pile fixe. Si le champ Registre d’images dans l’UNWIND_INFO est égal à zéro, ce décalage est de RSP. Si le champ Registre d’images n’est pas différent de zéro, ce décalage est de l’emplacement où se trouvait le reser lors de l’établissement du registre FP. Il est égal au registre FP moins le décalage de registre FP (16 * le décalage de registre de trame mis à l’échelle dans le UNWIND_INFO). Si un registre FP est utilisé, tout code de déroulement prenant un décalage ne doit être utilisé qu’après l’établissement du registre FP dans le prolog.

Pour tous les opcodes sauf UWOP_SAVE_XMM128 et UWOP_SAVE_XMM128_FAR, le décalage est toujours un multiple de 8, car toutes les valeurs de pile d’intérêt sont stockées sur des limites de 8 octets (la pile elle-même est toujours alignée sur 16 octets). Pour les codes d’opération qui prennent un décalage court (inférieur à 512 Ko), l’USHORT final dans les nœuds de ce code contient le décalage divisé par 8. Pour les codes d’opération qui prennent un décalage long (512 Ko <= offset < 4 Go), les deux derniers nœuds USHORT pour ce code contiennent le décalage (au format little-endian).

Pour les opcodes UWOP_SAVE_XMM128 et UWOP_SAVE_XMM128_FAR, le décalage est toujours un multiple de 16, car toutes les opérations XMM 128 bits doivent se produire sur la mémoire alignée sur 16 octets. Par conséquent, un facteur d’échelle de 16 est utilisé pour UWOP_SAVE_XMM128permettre des décalages inférieurs à 1M.

Le code d’opération de déroulement est l’une des valeurs suivantes :

  • UWOP_PUSH_NONVOL (0) 1 nœud

    Envoyez un registre entier nonvolatile, décrémentant RSP par 8. Les informations d’opération sont le numéro du registre. En raison des contraintes sur les épilogues, UWOP_PUSH_NONVOL les codes de déroulement doivent apparaître en premier dans le prologue et en conséquence, en dernier dans le tableau de codes de déroulement. Cet ordre relatif s’applique à tous les autres codes de déroulement, sauf UWOP_PUSH_MACHFRAME.

  • UWOP_ALLOC_LARGE (1) 2 ou 3 nœuds

    Allouez une zone de grande taille sur la pile. Deux formulaires sont disponibles. Si les informations d’opération sont égales à 0, la taille de l’allocation divisée par 8 est enregistrée dans l’emplacement suivant, ce qui permet une allocation allant jusqu’à 512 Ko - 8. Si les informations d’opération sont égales à 1, la taille non mise à l’échelle de l’allocation est enregistrée dans les deux emplacements suivants au format little-endian, ce qui permet d’allouer jusqu’à 4 Go à 8.

  • UWOP_ALLOC_SMALL (2) 1 nœud

    Allouez une zone de petite taille sur la pile. La taille de l’allocation est le champ d’informations sur l’opération * 8 + 8, ce qui permet d’allouer de 8 à 128 octets.

    Le code de déroulement d’une allocation de pile doit toujours utiliser l’encodage le plus court possible :

    Taille d’allocation Déroulement du code
    8 à 128 octets UWOP_ALLOC_SMALL
    136 à 512 Ko-8 octets UWOP_ALLOC_LARGE, informations sur l’opération = 0
    512 Ko à 4G-8 octets UWOP_ALLOC_LARGE, informations sur l’opération = 1
  • UWOP_SET_FPREG (3) 1 nœud

    Établissez le registre du pointeur d’image en définissant le registre sur un décalage du RSP actuel. Le décalage est égal au champ De registre d’images (mis à l’échelle) dans le UNWIND_INFO * 16, ce qui autorise les décalages de 0 à 240. L’utilisation d’un décalage permet d’établir un pointeur de trame qui pointe vers le milieu de l’allocation de pile fixe, ce qui aide la densité du code en permettant à davantage d’accès d’utiliser des formulaires d’instructions courts. Le champ d’informations sur l’opération est réservé et ne doit pas être utilisé.

  • UWOP_SAVE_NONVOL (4) 2 nœuds

    Enregistrez un registre entier nonvolatile sur la pile à l’aide d’un MOV au lieu d’un push. Ce code est principalement utilisé pour l’habillage réduit, où un registre nonvolatile est enregistré dans la pile dans une position précédemment allouée. Les informations d’opération sont le numéro du registre. Le décalage de pile mis à l’échelle par 8 est enregistré dans l’emplacement de code de l’opération de déroulement suivant, comme décrit dans la remarque ci-dessus.

  • UWOP_SAVE_NONVOL_FAR (5) 3 nœuds

    Enregistrez un registre entier nonvolatile sur la pile avec un décalage long, à l’aide d’un MOV au lieu d’un push. Ce code est principalement utilisé pour l’habillage réduit, où un registre nonvolatile est enregistré dans la pile dans une position précédemment allouée. Les informations d’opération sont le numéro du registre. Le décalage de pile non mis à l’échelle est enregistré dans les deux emplacements de code d’opération de déroulement suivants, comme décrit dans la remarque ci-dessus.

  • UWOP_SAVE_XMM128 (8) 2 nœuds

    Enregistrez tous les 128 bits d’un registre XMM nonvolatile sur la pile. Les informations d’opération sont le numéro du registre. Le décalage de pile mis à l’échelle par 16 est enregistré dans l’emplacement suivant.

  • UWOP_SAVE_XMM128_FAR (9) 3 nœuds

    Enregistrez tous les 128 bits d’un registre XMM nonvolatile sur la pile avec un décalage long. Les informations d’opération sont le numéro du registre. Le décalage de pile non mis à l’échelle est enregistré dans les deux emplacements suivants.

  • UWOP_PUSH_MACHFRAME (10) 1 nœud

    Poussez un cadre de machine. Ce code de déroulement est utilisé pour enregistrer l’effet d’une interruption ou d’une exception matérielle. Deux formulaires sont disponibles. Si les informations d’opération sont égales à 0, l’une de ces images a été envoyée (push) sur la pile :

    Emplacement Value
    RSP+32 SS
    RSP+24 Ancien RSP
    RSP+16 EFLAGS
    RSP+8 CS
    RSP RIP

    Si les informations d’opération sont égales à 1, l’une de ces images a été envoyée (push) :

    Emplacement Value
    RSP+40 SS
    RSP+32 Ancien RSP
    RSP+24 EFLAGS
    RSP+16 CS
    RSP+8 RIP
    RSP Code d'erreur

    Ce code de déroulement apparaît toujours dans un prolog factice, qui n’est jamais réellement exécuté, mais apparaît plutôt avant le point d’entrée réel d’une routine d’interruption, et existe uniquement pour fournir un emplacement pour simuler l’envoi d’une trame d’ordinateur. UWOP_PUSH_MACHFRAME enregistre cette simulation, qui indique que la machine a effectué cette opération conceptuellement :

    1. Adresse de retour POP RIP du haut de la pile dans Temp

    2. Push SS

    3. Envoyer l’ancien RSP

    4. Push EFLAGS

    5. Envoyer (push) CS

    6. Envoyer (push ) temp

    7. Code d’erreur push (si les informations d’opération sont égales à 1)

    L’opération simulée UWOP_PUSH_MACHFRAME décrémente RSP de 40 (les informations d’opération sont égales à 0) ou 48 (les informations d’opération sont égales à 1).

Informations sur l’opération

La signification des bits d’informations d’opération dépend du code d’opération. Pour encoder un registre à usage général (entier), ce mappage est utilisé :

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

Structures d’informations de déroulement chaînées

Si l’indicateur UNW_FLAG_CHAININFO est défini, une structure d’informations de déroulement est secondaire et le champ d’adresse d’exception/chaînée d’informations partagées contient les informations de déroulement principales. Cet exemple de code récupère les informations de déroulement primaires, en supposant qu’il unwindInfo s’agit de la structure dont l’indicateur UNW_FLAG_CHAININFO est défini.

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

Les informations chaînées sont utiles dans deux situations. Tout d’abord, il peut être utilisé pour les segments de code noncontigues. En utilisant des informations chaînées, vous pouvez réduire la taille des informations de déroulement requises, car vous n’avez pas à dupliquer le tableau de codes de déroulement à partir des informations de déroulement principales.

Vous pouvez également utiliser des informations chaînées pour regrouper les enregistrements volatiles. Le compilateur peut retarder l’enregistrement de certains registres volatiles jusqu’à ce qu’il soit en dehors du prolog d’entrée de fonction. Vous pouvez les enregistrer en ayant des informations de déroulement principales pour la partie de la fonction avant le code groupé, puis en configurant des informations chaînées avec une taille non nulle de prolog, où les codes de déroulement dans les informations chaînées reflètent les enregistrements des registres nonvolatiles. Dans ce cas, les codes de déroulement sont toutes les instances de UWOP_SAVE_NONVOL. Un regroupement qui enregistre des registres nonvolatiles à l’aide d’un push ou modifie le registre RSP à l’aide d’une allocation de pile fixe supplémentaire n’est pas pris en charge.

Un élément UNWIND_INFO qui a UNW_FLAG_CHAININFO ensemble peut contenir une entrée RUNTIME_FUNCTION dont l’élément UNWIND_INFO a également UNW_FLAG_CHAININFO ensemble, parfois appelé retour à la ligne de réduction multiple. Finalement, les pointeurs d’informations de déroulement chaînés arrivent à un élément UNWIND_INFO qui a UNW_FLAG_CHAININFO effacé. Cet élément est l’élément UNWIND_INFO principal, qui pointe vers le point d’entrée de procédure réel.

Procédure de déroulement

Le tableau de code de déroulement est trié dans l’ordre décroissant. Lorsqu’une exception se produit, le contexte complet est stocké par le système d’exploitation dans un enregistrement de contexte. La logique de répartition des exceptions est ensuite appelée, qui exécute à plusieurs reprises ces étapes pour rechercher un gestionnaire d’exceptions :

  1. Utilisez le rip actuel stocké dans l’enregistrement de contexte pour rechercher une entrée de table RUNTIME_FUNCTION qui décrit la fonction actuelle (ou la partie fonction, pour les entrées UNWIND_INFO chaînées).

  2. Si aucune entrée de table de fonction n’est trouvée, elle se trouve dans une fonction feuille et RSP traite directement le pointeur de retour. Le pointeur de retour au niveau de [RSP] est stocké dans le contexte mis à jour, le RSP simulé est incrémenté par 8 et l’étape 1 est répétée.

  3. Si une entrée de table de fonctions est trouvée, RIP peut se trouver dans trois régions : a) dans un épilogue, b) dans le prolog ou c) dans le code qui peut être couvert par un gestionnaire d’exceptions.

    • Cas a) Si le rip se trouve dans un épilogue, le contrôle quitte la fonction, il ne peut y avoir aucun gestionnaire d’exceptions associé à cette exception pour cette fonction, et les effets de l’épilogue doivent continuer à calculer le contexte de la fonction appelante. Pour déterminer si le rip se trouve dans un épilogue, le flux de code à partir de RIP est examiné. Si ce flux de code peut être mis en correspondance avec la partie de fin d’une épilogie légitime, il se trouve dans un épilogue et la partie restante de l’épilogue est simulée, avec l’enregistrement de contexte mis à jour à mesure que chaque instruction est traitée. Après ce traitement, l’étape 1 est répétée.

    • Case b) Si le protocole RIP se trouve dans le prologue, le contrôle n’a pas entré la fonction, il ne peut y avoir de gestionnaire d’exceptions associé à cette exception pour cette fonction, et les effets du prologue doivent être annulés pour calculer le contexte de la fonction appelante. La propriété RIP se trouve dans le prolog si la distance entre la fonction et le rip est inférieure ou égale à la taille du prolog encodée dans les informations de déroulement. Les effets du prolog sont débogués en analysant vers l’avant le tableau de codes de déroulement pour la première entrée avec un décalage inférieur ou égal au décalage du rip à partir du début de la fonction, puis en annulant l’effet de tous les éléments restants dans le tableau de code de déroulement. L’étape 1 est ensuite répétée.

    • Cas c) Si le rip ne se trouve pas dans un prolog ou un épilogue et que la fonction a un gestionnaire d’exceptions (UNW_FLAG_EHANDLER est défini), le gestionnaire spécifique au langage est appelé. Le gestionnaire analyse ses données et appelle les fonctions de filtre selon les besoins. Le gestionnaire spécifique à la langue peut retourner que l’exception a été gérée ou que la recherche doit être poursuivie. Il peut également lancer un déroulement directement.

  4. Si le gestionnaire spécifique à la langue retourne un état géré, l’exécution est poursuivie à l’aide de l’enregistrement de contexte d’origine.

  5. S’il n’existe aucun gestionnaire spécifique à la langue ou si le gestionnaire retourne un état « continuer la recherche », l’enregistrement de contexte doit être déwound à l’état de l’appelant. Elle est effectuée en traitant tous les éléments du tableau de code de déroulement, en annulant l’effet de chacun d’eux. L’étape 1 est ensuite répétée.

Lorsque des informations de déroulement chaînées sont impliquées, ces étapes de base sont toujours suivies. La seule différence est que, lors de la marche à pied du tableau de code de déroulement pour décompresser les effets d’un prolog, une fois la fin du tableau atteinte, elle est ensuite liée aux informations de déroulement parent et à l’ensemble du tableau de code de déroulement trouvé il y a marche. Cette liaison se poursuit jusqu’à ce qu’elle arrive à une information de déroulement sans l’indicateur UNW_CHAINED_INFO, puis elle termine la marche à pied de son tableau de code de déroulement.

Le plus petit ensemble de données de déroulement est de 8 octets. Cela représente une fonction qui n’a alloué que 128 octets de pile ou moins, et éventuellement enregistré un registre nonvolatile. Il s’agit également de la taille d’une structure d’informations de déroulement chaînée pour un prolog de longueur nulle sans codes de déroulement.

Gestionnaire spécifique à la langue

L’adresse relative du gestionnaire spécifique à la langue est présente dans l’UNWIND_INFO chaque fois que les indicateurs UNW_FLAG_EHANDLER ou UNW_FLAG_UHANDLER sont définis. Comme décrit dans la section précédente, le gestionnaire spécifique au langage est appelé dans le cadre de la recherche d’un gestionnaire d’exceptions ou dans le cadre d’un déroulement. Il a ce prototype :

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

ExceptionRecord fournit un pointeur vers un enregistrement d’exception, qui a la définition Win64 standard.

EstablisherFrame est l’adresse de la base de l’allocation de pile fixe pour cette fonction.

ContextRecord pointe vers le contexte d’exception au moment où l’exception a été levée (dans le cas du gestionnaire d’exceptions) ou dans le contexte actuel de « déroulement » (dans le cas du gestionnaire d’arrêt).

DispatcherContext pointe vers le contexte du répartiteur pour cette fonction. Elle a cette définition :

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 est la valeur de RIP dans cette fonction. Cette valeur est une adresse d’exception ou l’adresse à laquelle le contrôle a quitté la fonction d’établissement. Le rip est utilisé pour déterminer si le contrôle se trouve dans une construction protégée à l’intérieur de cette fonction, par exemple, un __try bloc pour__except/__try ou .__try/__finally

ImageBase est la base d’images (adresse de chargement) du module contenant cette fonction, à ajouter aux décalages 32 bits utilisés dans l’entrée de fonction et les informations de déroulement pour enregistrer les adresses relatives.

FunctionEntry fournit un pointeur vers l’entrée de fonction RUNTIME_FUNCTION contenant la fonction et les adresses relatives de base d’informations de base d’informations pour cette fonction.

EstablisherFrame est l’adresse de la base de l’allocation de pile fixe pour cette fonction.

TargetIp Fournit une adresse d’instruction facultative qui spécifie l’adresse de continuation du déroulement. Cette adresse est ignorée si EstablisherFrame n’est pas spécifié.

ContextRecord pointe vers le contexte d’exception, à utiliser par le code de répartition/de déroulement de l’exception système.

LanguageHandler pointe vers la routine de gestionnaire de langage spécifique à la langue appelée.

HandlerData pointe vers les données de gestionnaire spécifiques au langage pour cette fonction.

Décompresser les helpers pour MASM

Pour écrire des routines d’assembly appropriées, il existe un ensemble de pseudo-opérations qui peuvent être utilisées en parallèle avec les instructions d’assembly réelles pour créer les données .pdata et .xdata appropriées. Et il existe un ensemble de macros qui fournissent une utilisation simplifiée des pseudo-opérations pour leurs utilisations les plus courantes.

Pseudo-opérations brutes

Pseudo-opération Description
PROC FRAME [ :ehandler] Provoque la génération d’une entrée de table de fonctions dans .pdata et les informations de déroulement dans .xdata pour le comportement de déroulement structuré de la gestion des exceptions d’une fonction. Si le gestionnaire est présent, ce processus est entré dans .xdata en tant que gestionnaire spécifique au langage.

Lorsque l’attribut FRAME est utilisé, il doit être suivi d’un . Directive ENDPROLOG. Si la fonction est une fonction feuille (telle que définie dans les types de fonctions), l’attribut FRAME n’est pas nécessaire, comme le reste de ces pseudo-opérations.
. Registre PUSHREG Génère une entrée de code de déroulement UWOP_PUSH_NONVOL pour le numéro d’inscription spécifié à l’aide du décalage actuel dans le prologue.

Utilisez-le uniquement avec des registres entiers nonvolatiles. Pour les envois de registres volatiles, utilisez un . ALLOCSTACK 8, à la place
. Registre SETFRAME, décalage Renseigne le champ d’enregistrement de cadre et le décalage dans les informations de déroulement à l’aide du registre et du décalage spécifiés. Le décalage doit être un multiple de 16 et inférieur ou égal à 240. Cette directive génère également une entrée de code de déroulement UWOP_SET_FPREG pour le registre spécifié à l’aide du décalage de prologue actuel.
. Taille ALLOCSTACK Génère une UWOP_ALLOC_SMALL ou une UWOP_ALLOC_LARGE avec la taille spécifiée pour le décalage actuel dans le prologue.

L’opérande de taille doit être un multiple de 8.
. Enregistrement SAVEREG, offset Génère un UWOP_SAVE_NONVOL ou une entrée de code de déroulement UWOP_SAVE_NONVOL_FAR pour le registre et le décalage spécifiés à l’aide du décalage de prologue actuel. MASM choisit l’encodage le plus efficace.

le décalage doit être positif et un multiple de 8. offset est relatif à la base de l’image de la procédure, qui est généralement en RSP, ou, si vous utilisez un pointeur d’image, le pointeur d’image non mis à l’échelle.
. SAVEXMM128 inscrire, décaler Génère un UWOP_SAVE_XMM128 ou une entrée de code de déroulement UWOP_SAVE_XMM128_FAR pour le registre XMM spécifié et le décalage à l’aide du décalage de prologue actuel. MASM choisit l’encodage le plus efficace.

le décalage doit être positif et un multiple de 16. offset est relatif à la base de l’image de la procédure, qui est généralement en RSP, ou, si vous utilisez un pointeur d’image, le pointeur d’image non mis à l’échelle.
. PUSHFRAME [code] Génère une entrée de code de déroulement UWOP_PUSH_MACHFRAME. Si le code facultatif est spécifié, l’entrée de code de déroulement reçoit un modificateur de 1. Sinon, le modificateur est 0.
.ENDPROLOG Signale la fin des déclarations de prologue. Doit se produire dans les 255 premiers octets de la fonction.

Voici un exemple de prologue de fonction avec une utilisation appropriée de la plupart des 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

Pour plus d’informations sur l’exemple d’épilogue, consultez le code Epilog dans le prologue x64 et l’épilogue.

Macros MASM

Pour simplifier l’utilisation des pseudo-opérations brutes, il existe un ensemble de macros, définies dans ksamd64.inc, qui peuvent être utilisées pour créer des prologues et des épilogues de procédure classiques.

Macro Description
alloc_stack(n) Alloue une trame de pile de n octets (utilisation sub rsp, n) et émet les informations de déroulement appropriées (.allocstack n)
save_reg reg, loc Enregistre un registre nonvolatile reg sur la pile au loc offset RSP et émet les informations de déroulement appropriées. (.savereg reg, loc)
reg push_reg Envoie un registre nonvolatile reg sur la pile et émet les informations de déroulement appropriées. (.pushreg reg)
reg rex_push_reg Enregistre un registre nonvolatile sur la pile à l’aide d’un push de 2 octets et émet les informations de déroulement appropriées (.pushreg reg). Utilisez cette macro si l’envoi (push) est la première instruction de la fonction, pour vous assurer que la fonction est à chaud.
save_xmm128 reg, loc Enregistre un registre XMM nonvolatile reg sur la pile au loc offset RSP et émet les informations de déroulement appropriées (.savexmm128 reg, loc)
set_frame reg, offset Définit le registre d’images reg comme étant le RSP + offset (à l’aide d’un mov, ou d’un lea), et émet les informations de déroulement appropriées (.set_frame reg, offset)
push_eflags Envoie (push) les eflags avec une pushfq instruction et émet les informations de déroulement appropriées (.alloc_stack 8)

Voici un exemple de prologue de fonction avec une utilisation appropriée des macros :

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

Déroulage des définitions de données en C

Voici une description C des données de déroulement :

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

Voir aussi

Conventions des logiciels x64