Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Cet article décrit les processus et conventions standard qu’utilise une fonction (l’appelant) pour effectuer des appels dans une autre fonction (l’appelé) dans le code x64.
Pour plus d’informations sur la __vectorcall convention d’appel, consultez __vectorcall.
Pour plus d’informations sur la __preserve_none convention d’appel, consultez __preserve_none.
Convention d'appel par défaut
L’interface binaire d’application x64 (ABI) utilise par défaut une convention d’appel rapide à quatre registres. Un espace est alloué sur la pile d'appel en tant que magasin fantôme pour que les appelants puissent sauvegarder ces registres.
Il existe une correspondance stricte un-à-un entre les arguments d’un appel de fonction et les registres utilisés pour ces arguments. Tout argument qui ne correspond pas à 8 octets, ou qui n’est pas 1, 2, 4 ou 8 octets, doit être passé par référence. Un seul argument n’est jamais réparti entre plusieurs registres.
La pile de registres x87 n’est pas utilisée. Il peut être utilisé par l’appelé, mais considérez-le volatile entre les appels de fonction. Toutes les opérations en virgule flottante sont effectuées à l'aide des 16 registres XMM.
Les arguments entiers sont passés dans les RCXregistres , RDX, R8et R9. Les arguments à virgule flottante sont passés dans XMM0L, XMM1L, XMM2L et XMM3L. Les arguments de 16 octets sont passés par référence. Le passage de paramètre est décrit en détail dans Passage de paramètre. Ces registres, et RAX, , R10R11XMM4, et , et XMM5, sont considérés comme volatiles, ou potentiellement modifiés par un appelé lors du retour. L'utilisation des registres est documentée en détail dans l'utilisation des registres x64 et les registres sauvegardés par l'appelant/appelé.
Pour les fonctions prototypées, tous les arguments sont convertis dans les types attendus de l'appelant avant d'être transmis. L'appelant est responsable de l'allocation de l'espace pour les paramètres de l'appelé. L’appelant doit toujours allouer suffisamment d’espace pour stocker quatre paramètres d’inscription, même si l’appelé ne prend pas autant de paramètres. Cette convention simplifie la prise en charge des fonctions en langage C sans prototype et des fonctions variadiques en C/C++. Pour les fonctions vararg ou non prototypées, toutes les valeurs à virgule flottante doivent être dupliquées dans le registre à usage général correspondant. Tous les paramètres au-delà des quatre premiers doivent être stockés sur la pile après le shadow store précédant l'appel. Les détails de la fonction Vararg sont disponibles dans Varargs. Les informations sur les fonctions non prototypées sont détaillées dans fonctions non prototypées.
Alignment
La plupart des structures sont alignées sur leur alignement naturel. Les exceptions principales sont le pointeur de pile et malloc ou alloca la mémoire, qui sont alignés sur 16 octets pour faciliter les performances. L’alignement supérieur à 16 octets doit être effectué manuellement. Étant donné que 16 octets sont une taille d’alignement courante pour les opérations XMM, cette valeur doit fonctionner pour la plupart du code. Pour plus d’informations sur la disposition et l’alignement de la structure, consultez la disposition de type x64 et de stockage. Pour plus d’informations sur la disposition de la pile, consultez l’utilisation de la pile x64.
Indépendance
Les fonctions feuilles sont des fonctions qui ne modifient pas les registres nonvolatiles. Une fonction non feuille peut modifier le nonvolatile RSP, par exemple en appelant une fonction. Il peut également changer RSP en allouant davantage d’espace de pile pour les variables locales. Pour récupérer des registres nonvolatiles lorsqu’une exception est gérée, les fonctions non actives sont annotées avec des données statiques. Les données décrivent comment dérouler correctement la fonction à une instruction arbitraire. Ces données sont stockées sous forme de données pdata ou de procédure, qui à leur tour font référence à xdata, aux données de gestion des exceptions. Le xdata contient les informations de déroulement, et peut pointer vers des pdata supplémentaires ou une fonction gestionnaire d'exception.
Les prologs et les épilogues sont très restreints afin qu’ils puissent être correctement décrits dans xdata. Le pointeur de pile doit rester aligné sur 16 octets dans toute région de code qui ne fait pas partie d'un épilogue ou d'un prologue, sauf dans les fonctions feuilles. Les fonctions feuilles peuvent être déroulées simplement en simulant un retour, de sorte que pdata et xdata ne sont pas nécessaires. Pour plus d’informations sur la structure appropriée des prologs de fonction et des épilogues, consultez le prologue x64 et l’épilogue. Pour plus d'informations sur la gestion des exceptions, ainsi que sur la gestion des exceptions et le déroulement de pdata et xdata, reportez-vous à la rubrique Gestion des exceptions x64.
Passage de paramètres
Par défaut, la convention d’appel x64 transmet les quatre premiers arguments à une fonction dans les registres. Les registres utilisés pour ces arguments dépendent de la position et du type de l’argument. Les arguments restants sont transmis sur la pile dans l’ordre de droite à gauche. L’appelant réserve l’espace de pile requis et écrit ces arguments dans la mémoire de pile à l’aide des instructions de stockage ou de déplacement, en conservant l’alignement de 8 octets pour chaque argument.
Les arguments de valeur entière dans les quatre positions les plus à gauche sont passés dans l’ordre de gauche à droite dans RCX, RDX, R8et R9, respectivement. Le cinquième argument et les arguments supérieurs sont passés sur la pile comme décrit précédemment. Tous les arguments entiers dans les registres sont alignés à droite, de manière à ce que la fonction appelée puisse ignorer les bits supérieurs du registre et accéder uniquement à la partie du registre nécessaire.
Tous les arguments à virgule flottante et à double précision parmi les quatre premiers paramètres sont passés dans XMM0 - XMM3, selon leur position. Les valeurs à virgule flottante sont placées uniquement dans les registres entiersRCX, RDXet R8R9 lorsqu’il existe des arguments varargs. Pour plus d’informations, consultez Varargs. De même, les XMM0 - XMM3 registres sont ignorés lorsque l’argument correspondant est un type entier ou pointeur.
Les types __m128, les tableaux et les chaînes de caractères ne sont jamais transmis par valeur immédiate. Au lieu de cela, un pointeur est transmis à la mémoire allouée par l'appelant. Les structures et les unions de taille 8, 16, 32 ou 64 bits, ainsi que les types __m64, sont transmis comme s'il s'agissait d'entiers de même taille. Les structures ou unions d'une autre taille sont transmises sous la forme d'un pointeur vers la mémoire allouée par l'appelant. Pour ces types d’agrégation passés en tant que pointeur, y compris __m128, la mémoire temporaire allouée par l’appelant doit être alignée sur 16 octets.
Les fonctions intrinsèques qui n’allouent pas d’espace de pile et n’appellent pas d’autres fonctions, utilisent parfois d’autres registres volatiles pour passer des arguments de registre supplémentaires. Cette optimisation est rendue possible par la liaison étroite entre le compilateur et l’implémentation de fonction intrinsèque.
L'appelant est responsable du dumping des paramètres de registre dans leur espace d'ombre si nécessaire.
Le tableau suivant résume la façon dont les paramètres sont passés, par type et position à partir de la gauche :
| Type de paramètre | cinquième et plus | quatrième | Troisième | second | le plus à gauche |
|---|---|---|---|---|---|
| virgule flottante | stack | XMM3 |
XMM2 |
XMM1 |
XMM0 |
| entier | stack | R9 |
R8 |
RDX |
RCX |
Agrégats (8, 16, 32 ou 64 bits) et __m64 |
stack | R9 |
R8 |
RDX |
RCX |
| Autres agrégats, en tant que pointeurs | stack | R9 |
R8 |
RDX |
RCX |
__m128, en tant que pointeur |
stack | R9 |
R8 |
RDX |
RCX |
Exemple de passage d'argument 1 - tous les entiers
func1(int a, int b, int c, int d, int e, int f);
// a in RCX, b in RDX, c in R8, d in R9, f then e passed on stack
Exemple de passage d'argument 2 - tous les flottants
func2(float a, double b, float c, double d, float e, float f);
// a in XMM0, b in XMM1, c in XMM2, d in XMM3, f then e passed on stack
Exemple de passage d'argument 3 - mélange d'entiers et de flottants
func3(int a, double b, int c, float d, int e, float f);
// a in RCX, b in XMM1, c in R8, d in XMM3, f then e passed on stack
Exemple d’argument passant 4 - __m64, __m128 et agrégats
func4(__m64 a, __m128 b, struct c, float d, __m128 e, __m128 f);
// a in RCX, ptr to b in RDX, ptr to c in R8, d in XMM3,
// ptr to f passed on stack, then ptr to e passed on stack
Varargs
Si les paramètres sont transmis via des varargs (par exemple, des arguments de type « ellipses »), la convention normale de passage des paramètres de registre s'applique. Cette convention inclut le déversement du cinquième argument et des suivants sur la pile. C'est à l'appelant qu'il incombe de dumper les arguments dont l'adresse a été prise. Pour les valeurs à virgule flottante uniquement, le registre entier et le registre à virgule flottante doivent contenir la valeur, dans le cas où l’appelé attend la valeur dans les registres entiers.
Fonctions non prototypées
Pour les fonctions qui ne sont pas entièrement prototypées, l'appelant transmet les valeurs entières comme des entiers et les valeurs à virgule flottante comme des valeurs à double précision. Pour les valeurs à virgule flottante uniquement, le registre des entiers et le registre des virgules flottantes contiennent tous deux la valeur flottante au cas où l'appelant attendrait la valeur dans les registres des entiers.
func1();
func2() { // RCX = 2, RDX = XMM1 = 1.0, and R8 = 7
func1(2, 1.0, 7);
}
Valeurs de retour
Une valeur de retour scalaire qui peut s’adapter à 64 bits, y compris le __m64 type, est retournée via RAX. Les types noncalaires, y compris les floats, les doubles et les types vectoriels tels que __m128, __m128i__m128d sont retournés dans XMM0. L’état des bits inutilisés dans la valeur renvoyée dans RAX ou dans XMM0 n’est pas défini.
Les types définis par l'utilisateur peuvent être retournés par valeur depuis des fonctions globales et des fonctions de membres statiques. Pour retourner un type défini par l’utilisateur par valeur RAX, il doit avoir une longueur de 1, 2, 4, 8, 16, 32 ou 64 bits. Il ne doit pas avoir de constructeur, de destructeur ou d’opérateur d’affectation de copie défini par l’utilisateur. Il ne peut avoir aucun membre de données privé ou protégé non statique et aucun membre de données non statiques de type référence. Il ne peut pas avoir de classes de base ou de fonctions virtuelles. Et il ne peut avoir que des membres de données qui répondent également à ces exigences. Cette définition est essentiellement identique à un type POD C++03. Étant donné que la définition a changé dans la norme C++11, nous vous déconseillons d’utiliser std::is_pod ce test. Sinon, l’appelant doit allouer de la mémoire pour la valeur de retour et passer un pointeur vers celle-ci comme premier argument. Les arguments restants sont ensuite déplacés d’un argument vers la droite. Le même pointeur doit être renvoyé par la fonction appelée dans RAX.
Ces exemples montrent comment les paramètres et les valeurs de retour sont passés pour les fonctions avec les déclarations spécifiées :
Exemple de valeur de retour 1 - résultat 64 bits
__int64 func1(int a, float b, int c, int d, int e);
// Caller passes a in RCX, b in XMM1, c in R8, d in R9, e passed on stack,
// callee returns __int64 result in RAX.
Exemple de valeur de retour 2 - résultat 128 bits
__m128 func2(float a, double b, int c, __m64 d);
// Caller passes a in XMM0, b in XMM1, c in R8, d in R9,
// callee returns __m128 result in XMM0.
Exemple de valeur de retour 3 - résultat de type utilisateur par pointeur
struct Struct1 {
int j, k, l; // Struct1 exceeds 64 bits.
};
Struct1 func3(int a, double b, int c, float d);
// Caller allocates memory for Struct1 returned and passes pointer in RCX,
// a in RDX, b in XMM2, c in R9, d passed on the stack;
// callee returns pointer to Struct1 result in RAX.
Exemple de valeur de retour 4 - Résultat de type utilisateur par valeur
struct Struct2 {
int j, k; // Struct2 fits in 64 bits, and meets requirements for return by value.
};
Struct2 func4(int a, double b, int c, float d);
// Caller passes a in RCX, b in XMM1, c in R8, and d in XMM3;
// callee returns Struct2 result by value in RAX.
Registres sauvegardés par l'appelant/l'appelé
L’ABI x64 prend en compte les registres RAX, , RCXRDXR8, R9, , R10et R11XMM0-XMM5 volatiles. Lorsqu’elles sont présentes, les parties supérieures et YMM0-YMM15ZMM0-ZMM15sont également volatiles. Sur AVX512VL, les ZMMYMMXMM registres 16-31 sont également volatiles. Lorsque la prise en charge d’AMX est présente, les TMM registres de vignettes sont volatiles. Considérez les registres volatiles comme détruits lors des appels de fonction, à moins qu'une analyse telle que l'optimisation de programme complet ne prouve le contraire pour des raisons de sécurité.
L’ABI x64 considère les registres RBX, RBP, RDI, RSI, RSP, R12, R13, R14, R15, et XMM6-XMM15 comme non volatils. Ils doivent être enregistrés et restaurés par une fonction qui les utilise.
Lorsque la prise en charge d’APX est présente, les registres sont volatiles R16-R29 .
R30 et R31 sont nonvolatiles.
Pointeurs de fonction
Les pointeurs de fonction sont simplement des pointeurs vers l’étiquette de la fonction respective. Il n’existe aucune exigence de table des matières (TOC) pour les pointeurs de fonction.
Prise en charge de la virgule flottante pour les anciens codes
Les registres de pile MMX et en virgule flottante (MM0-MM7/ST0-ST7) sont conservés lors des changements de contexte. Il n’existe aucune convention d’appel explicite pour ces registres. L’utilisation de ces registres est strictement interdite en mode noyau.
FPCSR
L’état de registre inclut également le mot de contrôle FPU x87. La convention d’appel détermine ce registre comme nonvolatile.
Le registre de mots de contrôle du processeur complet x87 est défini à l’aide des valeurs standard suivantes au début de l’exécution du programme :
| Register[bits] | Setting |
|---|---|
FPCSR\[0:6] |
Masque des exceptions tous les bits à 1 (toutes les exceptions masquées) |
FPCSR\[7] |
Réservé - 0 |
FPCSR\[8:9] |
Contrôle de précision - 10B (double précision) |
FPCSR\[10:11] |
Contrôle d'arrondi - 0 (arrondi au plus proche) |
FPCSR\[12] |
Contrôle infini - 0 (non utilisé) |
Une fonction appelée qui modifie l’un des champs de FPCSR doit les restaurer avant de retourner à l’appelant. En outre, un appelant qui a modifié l’un de ces champs doit les restaurer à leurs valeurs standard avant d’appeler un appelé, sauf si, par accord, l’appelé attend les valeurs modifiées.
Il existe deux exceptions aux règles relatives à la nonvolatilité des indicateurs de contrôle :
Dans les fonctions où l’objectif documenté de la fonction donnée consiste à modifier les indicateurs nonvolatiles
FPCSR.Lorsqu’il est provablement correct que la violation de ces règles entraîne un programme qui se comporte de la même façon qu’un programme qui ne viole pas les règles, par exemple par le biais d’une analyse complète du programme.
Bien qu’il soit considéré comme non volatile, il n’existe aucun descripteur statique de déroulement indiquant où il a été sauvegardé et à partir de quel emplacement il doit être restauré. Le code résistant aux exceptions qui modifie FPCSR doit recourir à un mécanisme de finalisation en cas d’exception (par exemple, un destructeur C++ ou une clause __finally) afin de le restaurer explicitement lors du désamorçage de la pile.
MXCSR
L’état du registre comprend également MXCSR. La convention d’appel divise ce registre en une partie volatile et une partie nonvolatile. La partie volatile se compose des six indicateurs d’état, en MXCSR\[0:5], tandis que le reste du registre, MXCSR\[6:15]est considéré comme nonvolatile.
La partie nonvolatile est définie sur les valeurs standard suivantes au début de l’exécution du programme :
| Register[bits] | Setting |
|---|---|
MXCSR\[6] |
Les dénormaux sont des zéros - 0 |
MXCSR\[7:12] |
Masque des exceptions tous les bits à 1 (toutes les exceptions masquées) |
MXCSR\[13:14] |
Contrôle d'arrondi - 0 (arrondi au plus proche) |
MXCSR\[15] |
Rinçage à zéro en cas de sous-écoulement masqué - 0 (désactivé) |
Un appelé qui modifie l’un des champs nonvolatiles à l’intérieur MXCSR doit les restaurer avant de revenir à son appelant. En outre, un appelant qui a modifié l’un de ces champs doit les restaurer à leurs valeurs standard avant d’appeler un appelé, sauf si, par accord, l’appelé attend les valeurs modifiées.
Il existe deux exceptions aux règles relatives à la nonvolatilité des indicateurs de contrôle :
Dans les fonctions où l’objectif documenté de la fonction donnée consiste à modifier les indicateurs nonvolatiles
MXCSR.Lorsqu’il est provablement correct que la violation de ces règles entraîne un programme qui se comporte de la même façon qu’un programme qui ne viole pas les règles, par exemple par le biais d’une analyse complète du programme.
Ne faites aucune hypothèse sur l’état MXCSR de portion volatile du registre sur une limite de fonction, sauf si la documentation de la fonction la décrit explicitement.
Bien qu’une partie de MXCSR soit considérée comme non volatile, il n’existe aucun descripteur de déroulement statique indiquant où elle a été sauvegardée et depuis où elle doit être restaurée. Le code sécurisé vis-à-vis des exceptions qui modifie les parties non volatiles de MXCSR doit recourir à un mécanisme de finalisation en cas d’exception (par exemple, un destructeur C++ ou une clause __finally) afin de le restaurer explicitement lors du dépilement de la pile d’appels.
setjmp/longjmp
Lorsque vous incluez setjmpex.h ou setjmp.h, tous les appels à setjmp ou longjmp entraînent un déroulement qui invoque des destructeurs et des appels à __finally. Ce comportement diffère de x86, où l'inclusion de setjmp.h fait que les clauses __finally et les destructeurs ne sont pas appelés.
Un appel à setjmp préserve le pointeur de pile actuel, les registres non volatils et les registres MXCSR. Les appels à longjmp reviennent au site d’appel du plus récent appel à setjmp et réinitialisent le pointeur de pile, les registres non volatils et les registres MXCSR dans l’état préservé par le plus récent appel à setjmp.
Si APX est pris en charge, R30 et R31 ne doivent pas être modifiés dans une fonction, à partir du moment où setjmp est appelé jusqu’au moment où l’appel qui aboutit finalement à longjmp est effectué. Cette limitation résulte du fait que R30 et R31 ne sont pas enregistrés dans le cadre de jmp_buf ; cette définition de structure ne peut pas être modifiée. En revanche, ils sont restaurés par le mécanisme de désempilement. L’exemple suivant montre comment la différence sur la façon dont les données sont restaurées influence cette restriction :
jmp_buf jmpbuffer;
void function_a() {
...
int val = setjmp(jmpbuffer); // At this time R30 is 10
...
if (val == 0) {
function_b(); // At this time R30 is 20
}
...
}
void function_b() {
...
longjmp(jmpbuffer, 1);
...
}
Dans cet exemple, la valeur de R30 change entre le moment où setjmp est appelé et le moment où function_b est appelé. Dans function_b, longjmp décompresse la pile jusqu’à ce qu’elle atteigne la fonction appelée setjmp (function_a dans ce cas). La valeur restaurée pour R30 sera 20 (la valeur au point function_b a été appelée), et non 10 (la valeur à laquelle setjmp elle a été appelée). Cela signifie que lorsque setjmp revient pour la deuxième fois (à la suite de longjmp), la valeur de R30 sera définie sur 20 au lieu de 10, ce qui est incorrect. C’est pourquoi les compilateurs doivent s’assurer que R30 et R31 restent constants à partir du moment où setjmp est appelé jusqu’au dernier endroit de la fonction qui pourrait finalement conduire à l’appel de longjmp.
Puisque longjmp peut être appelé depuis un filtre d’exception (et pas seulement depuis une sous-routine), cela implique en pratique que R30 et R31 doivent rester constants à partir du moment où setjmp est appelé et jusqu’à la fin de la fonction.