Vue d’ensemble des conventions ABI ARM64EC
ARM64EC est une interface ABI (Application Binary Interface) qui permet aux binaires ARM64 de s’exécuter de façon native et interopérable avec du code x64. Plus précisément, l’interface ABI ARM64EC suit les conventions logicielles x64, notamment la convention d’appel, l’utilisation de piles et l’alignement de données, ce qui rend ARM64EC et le code x64 interopérables. Le système d’exploitation émule la partie x64 du binaire. (EC dans ARM64EC est la contraction d’émulation compatible.)
Pour plus d’informations sur les interfaces ABI x64 et ARM64, consultez Vue d’ensemble des conventions de l’interface ABI x64 et Vue d’ensemble des conventions de l’interface ABI ARM64.
ARM64EC ne résout pas les différences de modèle de mémoire entre les architectures x64 et ARM. Pour plus d’informations, consultez Problèmes courants de migration ARM Visual C++.
Définitions
- ARM64 : flux de code pour les processus ARM64 qui contiennent du code ARM64 traditionnel.
- ARM64EC : flux de code qui utilise un sous-ensemble du jeu de registres ARM64 pour assurer l’interopérabilité avec le code x64.
Mappage de registres
Les processus x64 peuvent avoir des threads qui exécutent du code ARM64EC. Donc, il est toujours possible de récupérer un contexte de registre x64. ARM64EC utilise un sous-ensemble des registres de base ARM64 qui mappent aux registres x64 émulés, 1 à 1. Il est important de noter qu’ARM64EC n’utilise jamais de registres en dehors de ce sous-ensemble, sauf pour lire l’adresse TEB (Thread Environment Block) de x18
.
Les processus ARM64 natifs ne doivent pas régresser en performances quand des fonctions, aussi nombreuses soient-elles, sont recompilées sous la forme ARM64EC. Pour maintenir les performances, l’interface ABI suit ces principes :
Le sous-ensemble de registres ARM64EC englobe tous les registres qui font partie de la convention d’appel de fonction ARM64.
La convention d’appel ARM64EC mappe directement à la convention d’appel ARM64.
Des routines d’assistance spéciales telles que __chkstk_arm64ec
utilisent des conventions d’appel personnalisées et des registres. Ces registres sont également inclus dans le sous-ensemble ARM64EC des registres.
Mappage de registres pour les registres d’entiers
Registre ARM64EC | Registre x64 | Convention d’appel ARM64EC | Convention d’appel ARM64 | Convention d’appel x64 |
---|---|---|---|---|
x0 |
rcx |
volatile | volatile | volatile |
x1 |
rdx |
volatile | volatile | volatile |
x2 |
r8 |
volatile | volatile | volatile |
x3 |
r9 |
volatile | volatile | volatile |
x4 |
r10 |
volatile | volatile | volatile |
x5 |
r11 |
volatile | volatile | volatile |
x6 |
mm1 (64 bits bas du registre R1 x87) |
volatile | volatile | volatile |
x7 |
mm2 (64 bits bas du registre R2 x87) |
volatile | volatile | volatile |
x8 |
rax |
volatile | volatile | volatile |
x9 |
mm3 (64 bits bas du registre R3 x87) |
volatile | volatile | volatile |
x10 |
mm4 (64 bits bas du registre R4 x87) |
volatile | volatile | volatile |
x11 |
mm5 (64 bits bas du registre R5 x87) |
volatile | volatile | volatile |
x12 |
mm6 (64 bits bas du registre R6 x87) |
volatile | volatile | volatile |
x13 |
S/O | non autorisée | volatile | N/A |
x14 |
N/A | non autorisée | volatile | S/O |
x15 |
mm7 (64 bits bas du registre R7 x87) |
volatile | volatile | volatile |
x16 |
16 bits hauts de chacun des registres R0 -R3 x87 |
volatile(xip0 ) |
volatile(xip0 ) |
volatile |
x17 |
16 bits hauts de chacun des registres R4 -R7 x87 |
volatile(xip1 ) |
volatile(xip1 ) |
volatile |
x18 |
GS.base | fixed(TEB) | fixed(TEB) | fixed(TEB) |
x19 |
r12 |
non volatile | non volatile | non volatile |
x20 |
r13 |
non volatile | non volatile | non volatile |
x21 |
r14 |
non volatile | non volatile | non volatile |
x22 |
r15 |
non volatile | non volatile | non volatile |
x23 |
S/O | non autorisée | non volatile | N/A |
x24 |
N/A | non autorisée | non volatile | S/O |
x25 |
rsi |
non volatile | non volatile | non volatile |
x26 |
rdi |
non volatile | non volatile | non volatile |
x27 |
rbx |
non volatile | non volatile | non volatile |
x28 |
S/O | non autorisée | non autorisée | S/O |
fp |
rbp |
non volatile | non volatile | non volatile |
lr |
mm0 (64 bits bas du registre R0 x87) |
Les deux | Les deux | Les deux |
sp |
rsp |
non volatile | non volatile | non volatile |
pc |
rip |
pointeur d’instruction | pointeur d’instruction | pointeur d’instruction |
PSTATE sous-ensemble : N /Z /C /V /SS 1, 2 |
RFLAGS sous-ensemble : SF /ZF /CF /OF /TF |
volatile | volatile | volatile |
S/O | RFLAGS sous-ensemble : PF /AF |
N/A | N/A | volatile |
S/O | RFLAGS sous-ensemble : DF |
N/A | N/A | non volatile |
1 Évitez de lire, écrire ou calculer directement des mappages entre PSTATE
et RFLAGS
. Ces bits peuvent être utilisés et sont susceptibles de changer.
2 L’indicateur de retenue ARM64EC C
est l’inverse de l’indicateur de retenue x64 CF
pour les opérations de soustraction. Aucun traitement particulier n’est nécessaire, car l’indicateur est volatil et donc jeté lors de la transition d’une fonction à l’autre (ARM64EC et x64).
Mappage de registres pour les registres vectoriels
Registre ARM64EC | Registre x64 | Convention d’appel ARM64EC | Convention d’appel ARM64 | Convention d’appel x64 |
---|---|---|---|---|
v0 -v5 |
xmm0 -xmm5 |
volatile | volatile | volatile |
v6 -v7 |
xmm6 -xmm7 |
volatile | volatile | non volatile |
v8 -v15 |
xmm8 -xmm15 |
volatile 1 | volatile 1 | non volatile |
v16 -v31 |
xmm16 -xmm31 |
non autorisée | volatile | non autorisée (l’émulateur x64 ne prend pas en charge AVX-512) |
FPCR 2 |
MXCSR[15:6] |
non volatile | non volatile | non volatile |
FPSR 2 |
MXCSR[5:0] |
volatile | volatile | volatile |
1 Ces registres ARM64 sont particuliers dans le sens où les 64 bits inférieurs ne sont pas volatils alors que les 64 bits supérieurs le sont. Du point de vue d’un appelant x64, ils sont effectivement volatils parce que l’appelé jette les données.
2 Évitez de lire, écrire ou calculer directement des mappages de FPCR
et de FPSR
. Ces bits peuvent être utilisés et sont susceptibles de changer.
Compression de structs
ARM64EC suit les mêmes règles de compression de structs que celles utilisées pour x64 afin de garantir l’interopérabilité entre le code ARM64EC et le code x64. Pour plus d’informations et des exemples de compression de structs x64, consultez Vue d’ensemble des conventions de l’interface ABI x64.
Routines ABI d’assistance d’émulation
Le code ARM64EC et les thunks utilisent des routines d’assistance d’émulation pour passer d’une fonction x64 à une fonction ARM64EC.
Le tableau suivant décrit chaque routine ABI spéciale et les registres que l’interface ABI utilise. Les routines ne modifient pas les registres conservés listés sous la colonne ABI. Aucune hypothèse ne doit être établie sur les registres non listés. Sur disque, les pointeurs de routine ABI sont nuls. Au moment du chargement, le chargeur met à jour les pointeurs pour les faire pointer vers les routines de l’émulateur x64.
Nom | Description | ABI |
---|---|---|
__os_arm64x_dispatch_call_no_redirect |
Appelée par un thunk de sortie pour appeler une cible x64 (une fonction x64 ou une séquence fast-forward x64). La routine envoie (push) l’adresse de retour ARM64EC (dans le registre LR ) suivie de l’adresse de l’instruction qui réussit une instruction blr x16 qui appelle l’émulateur x64. Elle exécute ensuite l’instruction blr x16 |
valeur de retour en x8 (rax ) |
__os_arm64x_dispatch_ret |
Appelée par un thunk d’entrée pour revenir à son appelant x64. Elle affiche l’adresse de retour x64 de la pile et appelle l’émulateur x64 pour y accéder | S/O |
__os_arm64x_check_call |
Appelée par le code ARM64EC avec un pointeur vers un thunk de sortie et l’adresse cible ARM64EC indirecte à exécuter. La cible ARM64EC est considérée comme corrigeable et l’exécution revient toujours à l’appelant avec les mêmes données avec lesquelles elle a été appelée, ou avec des données modifiées | Arguments :x9 : Adresse ciblex10 : Adresse du thunk de sortiex11 : Adresse de la séquence fast-forwardSortie : x9 : Si la fonction cible a été déviée, elle contient l’adresse de la séquence fast-forwardx10 : Adresse du thunk de sortiex11 : Si la fonction a été déviée, elle contient l’adresse du thunk de sortie. Sinon, l’adresse cible est redirigée versRegistres conservés : x0 -x8 , x15 (chkstk ). et q0 -q7 |
__os_arm64x_check_icall |
Appelée par le code ARM64EC, avec un pointeur vers un thunk de sortie, pour gérer un accès à une adresse cible qui est x64 ou ARM64EC. Si la cible est x64 et que le code x64 n’a pas été corrigé, la routine définit le registre d’adresses cibles. Elle pointe vers la version ARM64EC de la fonction s’il en existe une. Sinon, elle définit le registre pour le faire pointer vers le thunk de sortie qui passe à la cible x64. Ensuite, elle retourne au code ARM64EC de l’appelant, qui accède ensuite à l’adresse dans le registre. Cette routine est une version non optimisée de __os_arm64x_check_call , où l’adresse cible n’est pas connue au moment de la compilationUtilisée sur un site d’appel d’un appel indirect |
Arguments :x9 : Adresse ciblex10 : Adresse du thunk de sortiex11 : Adresse de la séquence fast-forwardSortie : x9 : Si la fonction cible a été déviée, elle contient l’adresse de la séquence fast-forwardx10 : Adresse du thunk de sortiex11 : Si la fonction a été déviée, elle contient l’adresse du thunk de sortie. Sinon, l’adresse cible est redirigée versRegistres conservés : x0 -x8 , x15 (chkstk ) et q0 -q7 |
__os_arm64x_check_icall_cfg |
Identique à __os_arm64x_check_icall , mais vérifie également que l’adresse spécifiée est une cible d’appel indirect de graphe de flux de contrôle valide |
Arguments :x10 : Adresse du thunk de sortiex11 : Adresse de la fonction cibleSortie : x9 : Si la cible est x64, adresse de la fonction. Sinon, non définiex10 : Adresse du thunk de sortiex11 : Si la cible est x64, elle contient l’adresse du thunk de sortie. Sinon, adresse de la fonctionRegistres conservés : x0 -x8 , x15 (chkstk ) et q0 -q7 |
__os_arm64x_get_x64_information |
Obtient la partie demandée du contexte du registre x64 en direct | _Function_class_(ARM64X_GET_X64_INFORMATION) NTSTATUS LdrpGetX64Information(_In_ ULONG Type, _Out_ PVOID Output, _In_ PVOID ExtraInfo) |
__os_arm64x_set_x64_information |
Définit la partie demandée du contexte du registre x64 en direct | _Function_class_(ARM64X_SET_X64_INFORMATION) NTSTATUS LdrpSetX64Information(_In_ ULONG Type,_In_ PVOID Input, _In_ PVOID ExtraInfo) |
__os_arm64x_x64_jump |
Utilisée dans un ajusteur sans signature et d’autres thunks qui transfèrent directement (jmp ) un appel à une autre fonction qui peut avoir une signature, en reportant l’application potentielle du thunk droit à la cible réelle |
Arguments :x9 : cible à atteindreTous les registres de paramètres conservés (transférés) |
Thunks
Les thunks sont les mécanismes de bas niveau permettant de prendre en charge les fonctions ARM64EC et x64 qui s’appellent mutuellement. Il en existe deux types : les thunks d’entrée pour entrer des fonctions ARM64EC et les thunks de sortie pour appeler des fonctions x64.
Thunk d’entrée et thunks d’entrée intrinsèques : appel de fonction x64 à ARM64EC
Pour prendre en charge les appelants x64 lorsqu’une fonction C/C++ est compilée en tant qu’ARM64EC, la chaîne d’outils génère un seul thunk d’entrée constitué de code machine ARM64EC. Les intrinsèques ont un thunk d’entrée à eux. Toutes les autres fonctions partagent un thunk d’entrée avec toutes les fonctions qui ont une convention d’appel, des paramètres et un type de retour correspondants. Le contenu du thunk dépend de la convention d’appel de la fonction C/C++.
En plus de la gestion des paramètres et de l’adresse de retour, le thunk réduit les différences de volatilité entre les registres vectoriels ARM64EC et x64 qu’entraîne le mappage de registres vectoriels ARM64EC :
Registre ARM64EC | Registre x64 | Convention d’appel ARM64EC | Convention d’appel ARM64 | Convention d’appel x64 |
---|---|---|---|---|
v6 -v15 |
xmm6 -xmm15 |
volatile, mais enregistrée/restaurée dans le thunk d’entrée (x64 à ARM64EC) | volatile ou partiellement volatile pour les 64 bits supérieurs | non volatile |
Le thunk d’entrée effectue les actions suivantes :
Nombre de paramètres | Utilisation de la pile |
---|---|
0-4 | Stocke ARM64EC v6 et v7 dans l’espace d’accueil alloué par l’appelantÉtant donné que l’appelé est ARM64EC, qui n’a pas la notion d’espace d’accueil, les valeurs stockées ne sont pas écrasées. Alloue 128 octets supplémentaires sur la pile et stocke ARM64EC v8 via v15 . |
5-8 | x4 = 5ème paramètre de la pilex5 = 6ème paramètre de la pilex6 = 7ème paramètre de la pilex7 = 8ème paramètre de la pileSi le paramètre est SIMD, les registres v4 -v7 sont utilisés à la place |
+9 | Alloue les octets AlignUp(NumParams - 8 , 2) * 8 sur la pile. *Copie le 9ème paramètre et tous ceux qui restent dans cette zone |
* L’alignement de la valeur sur un nombre pair garantit que la pile reste alignée sur 16 octets
Si la fonction accepte un paramètre entier de 32 bits, le thunk est autorisé à envoyer (push) uniquement 32 bits au lieu des 64 bits du registre parent.
Ensuite, le thunk utilise une instruction ARM64 bl
pour appeler la fonction ARM64EC. Une fois la fonction retournée, le thunk :
- Annule toutes les allocations de la pile
- Appelle la fonction d’assistance de l’émulateur
__os_arm64x_dispatch_ret
pour afficher l’adresse de retour x64 et reprendre l’émulation x64.
Thunk de sortie : appel de fonction ARM64EC à x64
Pour chaque appel qu’une fonction ARM64EC C/C++ effectue au code x64 potentiel, la chaîne d’outils MSVC génère un thunk de sortie. Le contenu du thunk dépend des paramètres de l’appelé x64 et si celui-ci utilise la convention d’appel standard ou __vectorcall
. Le compilateur obtient ces informations d’une déclaration de fonction pour l’appelé.
Premièrement, le thunk envoie (push) l’adresse de retour qui se trouve dans le registre ARM64EC lr
et une valeur factice de 8 octets pour garantir que la pile est alignée sur 16 octets. Deuxièmement, le thunk gère les paramètres :
Nombre de paramètres | Utilisation de la pile |
---|---|
0-4 | Alloue 32 octets d’espace d’accueil sur la pile |
5-8 | Alloue AlignUp(NumParams - 4, 2) * 8 autres octets plus haut dans la pile. * Copie le 5ème paramètre et les suivants du x4 -x7 d’ARM64EC vers cet espace supplémentaire |
+9 | Copie le 9ème paramètre et tous ceux qui restent dans l’espace supplémentaire |
* L’alignement de la valeur sur un nombre pair garantit que la pile reste alignée sur 16 octets.
Troisièmement, le thunk appelle la fonction d’assistance de l’émulateur __os_arm64x_dispatch_call_no_redirect
pour appeler l’émulateur x64 afin d’exécuter la fonction x64. L’appel doit être une instruction blr x16
(x16
est un registre volatil, ce qui est pratique). Une instruction blr x16
est requise, car l’émulateur x64 analyse cette instruction en tant qu’indicateur.
La fonction x64 tente généralement de revenir à la fonction d’assistance de l’émulateur à l’aide d’une instruction ret
x64. À ce stade, l’émulateur x64 détecte qu’il se trouve dans du code ARM64EC. Il lit ensuite l’indicateur de 4 octets précédent qui se trouve être l’instruction ARM64 blr x16
. Étant donné que cet indicateur indique que l’adresse de retour se trouve dans cette fonction d’assistance, l’émulateur accède directement à cette adresse.
La fonction x64 est autorisée à revenir à la fonction d’assistance de l’émulateur avec une instruction de branche, notamment jmp
et call
x64. L’émulateur gère également ces scénarios.
Lorsque la fonction d’assistance revient ensuite au thunk, celui-ci :
- Annule toute allocation de la pile
- Fait apparaître le registre ARM64EC
lr
- Exécute une instruction ARM64
ret lr
.
Décoration de noms de fonction ARM64EC
Un nom de fonction ARM64EC a une décoration secondaire appliquée après toute décoration spécifique au langage. Pour les fonctions avec liaison C (compilées en tant que C ou à l’aide de extern "C"
), un #
précède le nom. Pour les fonctions C++ décorées, une balise $$h
est insérée dans le nom.
foo => #foo
?foo@@YAHXZ => ?foo@@$$hYAHXZ
__vectorcall
La chaîne d’outils ARM64EC ne prend pas en charge __vectorcall
actuellement. Le compilateur émet une erreur lorsqu’il détecte l’utilisation de __vectorcall
avec ARM64EC.
Voir aussi
Présentation de l’interface ABI ARM64EC et du code d’assembly
Problèmes courants de migration ARM Visual C++
Noms décorés