Prologue et épilogue x64
Chaque fonction qui alloue de l’espace de pile, appelle d’autres fonctions, enregistre des registres nonvolatiles ou utilise une gestion des exceptions doit avoir un prolog dont les limites d’adresse sont décrites dans les données de déroulement associées à l’entrée de table de fonctions correspondante. Pour plus d’informations, consultez gestion des exceptions x64. Le prolog enregistre les registres d’arguments dans leurs adresses d’accueil si nécessaire, envoie (push) des registres nonvolatiles sur la pile, alloue la partie fixe de la pile pour les locaux et temporaires, et établit éventuellement un pointeur d’image. Les données de déroulement associées doivent décrire l’action du prologue et fournir les informations nécessaires pour annuler l’effet du code de prologue.
Si l’allocation fixe dans la pile est supérieure à une page (autrement dit, supérieure à 4 96 octets), il est possible que l’allocation de pile s’étende sur plusieurs pages de mémoire virtuelle et, par conséquent, l’allocation doit être case activée ed avant son allocation. Une routine spéciale pouvant être appelée à partir du prologue et qui ne détruit aucun des registres d’arguments est fournie à cet effet.
La méthode recommandée pour enregistrer des registres nonvolatiles consiste à les déplacer vers la pile avant l’allocation de pile fixe. Si l’allocation de pile fixe est effectuée avant l’enregistrement des registres nonvolatiles, le déplacement 32 bits est probablement nécessaire pour traiter la zone de registre enregistrée. (Apparemment, les envois de registres sont aussi rapides que les mouvements et devraient rester ainsi pour l’avenir prévisible malgré la dépendance implicite entre les push.) Les registres nonvolatiles peuvent être enregistrés dans n’importe quel ordre. Toutefois, la première utilisation d’un registre nonvolatile dans le prologue doit être de l’enregistrer.
Code prolog
Le code d’un prologue classique peut être :
mov [RSP + 8], RCX
push R15
push R14
push R13
sub RSP, fixed-allocation-size
lea R13, 128[RSP]
...
Ce prolog stocke le registre d’arguments RCX dans son emplacement d’accueil, enregistre les registres nonvolatiles R13-R15, alloue la partie fixe du cadre de pile et établit un pointeur d’image qui pointe vers 128 octets dans la zone d’allocation fixe. L’utilisation d’un décalage permet à plusieurs zones d’allocation fixes d’être traitées avec des décalages d’un octet.
Si la taille d’allocation fixe est supérieure ou égale à une page de mémoire, une fonction d’assistance doit être appelée avant de modifier RSP. Cet assistance, , __chkstk
sonde la plage de pile à allouer pour s’assurer que la pile est étendue correctement. Dans ce cas, l’exemple de prologue précédent serait à la place :
mov [RSP + 8], RCX
push R15
push R14
push R13
mov RAX, fixed-allocation-size
call __chkstk
sub RSP, RAX
lea R13, 128[RSP]
...
L’assistance __chkstk
ne modifie pas les registres autres que R10, R11 et les codes de condition. En particulier, il retourne RAX inchangé et laisse tous les registres nonvolatiles et les registres de passage d’arguments non modifiés.
Code Epilog
Le code Epilog existe à chaque sortie d’une fonction. Alors qu’il n’y a normalement qu’un seul prologue, il peut y avoir beaucoup d’épilogues. Le code Epilog réduit la pile à sa taille d’allocation fixe (si nécessaire), libère l’allocation de pile fixe, restaure les registres nonvolatiles en dépilant leurs valeurs enregistrées à partir de la pile et en retourne.
Le code d’épilogie doit suivre un ensemble strict de règles pour le code de déroulement afin de déroutant de manière fiable les exceptions et les interruptions. Ces règles réduisent la quantité de données de déroulement requises, car aucune donnée supplémentaire n’est nécessaire pour décrire chaque épilogue. Au lieu de cela, le code de déroulement peut déterminer qu’un épilogue est exécuté en analysant vers l’avant via un flux de code pour identifier un épilogue.
Si aucun pointeur d’image n’est utilisé dans la fonction, l’épilogue doit d’abord libérer la partie fixe de la pile, les registres nonvolatiles sont dépilés et le contrôle est retourné à la fonction appelante. Par exemple :
add RSP, fixed-allocation-size
pop R13
pop R14
pop R15
ret
Si un pointeur d’image est utilisé dans la fonction, la pile doit être réduite à son allocation fixe avant l’exécution de l’épilogue. Cette action ne fait pas partie techniquement de l’épilogue. Par exemple, l’épilogue suivant peut être utilisé pour annuler le prologue précédemment utilisé :
lea RSP, -128[R13]
; epilogue proper starts here
add RSP, fixed-allocation-size
pop R13
pop R14
pop R15
ret
Dans la pratique, lorsqu’un pointeur d’image est utilisé, il n’existe aucune bonne raison d’ajuster RSP en deux étapes. Par conséquent, l’épilogue suivant sera utilisé à la place :
lea RSP, fixed-allocation-size - 128[R13]
pop R13
pop R14
pop R15
ret
Ces formulaires sont les seuls à être juridiques pour un épilogue. Il doit se composer d’un add RSP,constant
ou , suivi d’une série de zéro ou plus de 8 octets pops et a ou a jmp
return
lea RSP,constant[FPReg]
. (Seul un sous-ensemble d’instructions jmp
est autorisé dans l’épilogue. Le sous-ensemble est exclusivement la classe d’instructions jmp
avec des références de mémoire ModRM où la valeur du champ mod ModRM est 00. L’utilisation d’instructions jmp
dans l’épilogue avec la valeur du champ mod ModRM 01 ou 10 est interdite. Consultez le tableau A-15 dans le manuel manuel du programmeur d’architecture AMD x86-64 3 : Instructions générales sur l’usage général et le système, pour plus d’informations sur les références ModRM autorisées.) Aucun autre code ne peut apparaître. En particulier, rien ne peut être planifié dans un épilogue, y compris le chargement d’une valeur de retour.
Lorsqu’un pointeur d’image n’est pas utilisé, l’épilogue doit utiliser add RSP,constant
pour libérer la partie fixe de la pile. Il peut ne pas être utilisé lea RSP,constant[RSP]
à la place. Cette restriction existe afin que le code de déroulement ait moins de modèles à reconnaître lors de la recherche d’épilogues.
En suivant ces règles, le code de déroulement permet de déterminer qu’un épilogue est en cours d’exécution et de simuler l’exécution du reste de l’épilogue pour permettre de recréer le contexte de la fonction appelante.