Vue d’ensemble des conventions ABI ARM32
L'interface binaire d'application (ABI) pour le code compilé pour Windows sur processeurs ARM est basée sur l'interface EABI ARM standard. Cet article souligne les principales différences entre Windows on ARM et l'interface standard. Ce document couvre l’ABI ARM32. Pour plus d’informations sur l’ABI ARM64, consultez Vue d’ensemble des conventions ABI ARM64. Pour plus d’informations sur l’EABI ARM standard, consultez L’interface binaire d’application (ABI) pour l’architecture ARM (lien externe).
Configuration de base requise
Windows sur ARM suppose toujours qu’il s’exécute sur une architecture ARMv7. Une prise en charge de la virgule flottante sous la forme de VFPv3-D32 ou version ultérieure doit être présente dans le matériel. L'architecture VFP doit prendre en charge la virgule flottante à simple et double précision sur le matériel. Le runtime Windows ne prend pas en charge l’émulation de virgule flottante pour permettre l’exécution sur du matériel non VFP.
La prise en charge avancée des extensions SIMD (NEON), y compris les opérations à virgule flottante et entières, doit également être présente dans le matériel. Aucune prise en charge de l'émulation n'est fournie au moment de l'exécution.
La prise en charge de la division d’entier (UDIV/SDIV) est recommandée, mais elle n’est pas nécessaire. Les plateformes sans prise en charge de la division d’entier peuvent être pénalisées sur le plan des performances, car ces opérations doivent être interceptées et peut-être corrigées.
Endianness
Windows on ARM s'exécute en mode Little-Endian. Le compilateur MSVC et le runtime Windows s’attendent toujours à des données peu endiennes. L’instruction SETEND dans l’architecture de jeu d’instructions ARM (ISA) permet même au code en mode utilisateur de modifier l’endianité actuelle. Toutefois, cela est déconseillé parce qu’il est dangereux pour une application. Si une exception est générée en mode big-endian, le comportement est imprévisible. Il peut entraîner une erreur d’application en mode utilisateur ou un bogue case activée en mode noyau.
Alignement
Bien que Windows permette au matériel ARM de gérer les accès d'entiers non alignés de manière transparente, des erreurs d'alignement peuvent toujours être générées dans certaines situations. Suivez les règles d'alignement suivantes :
Vous n’avez pas besoin d’aligner les nombres entiers de taille demi-mot (16 bits) et de taille de mot (32 bits) et les magasins. Le matériel les gère avec efficacité et transparence.
Les charges et les magasins à virgule flottante doivent être alignés. Le noyau gère les charges et les magasins non alignés de manière transparente, mais avec une surcharge significative.
Les opérations doubles (LDRD/STRD) et multiples (LDM/STM) de charge et de magasin doivent être alignées. Le noyau gère la plupart de ces opérations de façon transparente, mais avec une surcharge significative.
Tous les accès mémoire non mis en cache doivent être alignés, même dans le cas des accès à des entiers. Les accès non alignés provoquent une erreur d'alignement.
Jeu d'instructions
Le jeu d'instructions pour Windows on ARM est strictement limité à Thumb-2. Tout le code exécuté sur cette plateforme est censé démarrer et rester toujours en mode Pouce. Une tentative de basculement dans le jeu d’instructions ARM hérité peut réussir. Toutefois, si c’est le cas, des exceptions ou des interruptions qui se produisent peuvent entraîner une erreur d’application en mode utilisateur ou un bogue case activée en mode noyau.
L’une des conséquences indirectes de cette exigence est que tous les pointeurs de code doivent avoir le bit inférieur défini. Ensuite, lorsqu’ils sont chargés et branchés via BLX ou BX, le processeur reste en mode Pouce. Il n’essaie pas d’exécuter le code cible en tant qu’instructions ARM 32 bits.
Instructions SDIV/UDIV
L'utilisation des instructions de division d'entier SDIV et UDIV est entièrement prise en charge, même sur les plateformes sans matériel natif pour les traiter. La surcharge supplémentaire par SDIV ou UDIV divise sur un processeur Cortex-A9 est d’environ 80 cycles. Cela est ajouté au temps de division global de 20 à 250 cycles, en fonction des entrées.
Registres entiers
Le processeur ARM prend en charge 16 registres d'entiers :
Inscrire | Volatil ? | Rôle |
---|---|---|
r0 | Volatil | Paramètres, résultat, registre de travail 1 |
r1 | Volatil | Paramètres, résultat, registre de travail 2 |
r2 | Volatil | Paramètre, registre de travail 3 |
r3 | Volatil | Paramètre, registre de travail 4 |
r4 | Non volatil | |
r5 | Non volatil | |
r6 | Non volatil | |
r7 | Non volatil | |
r8 | Non volatil | |
r9 | Non volatil | |
r10 | Non volatil | |
r11 | Non volatil | Pointeur de frame |
r12 | Volatil | Registre de travail d'appels intra-procédure |
r13 (SP) | Non volatil | Pointeur de pile |
r14 (LR) | Non volatil | Registre de liaison |
r15 (PC) | Non volatil | Compteur de programme |
Pour plus d'informations sur l'utilisation des registres de paramètres et de valeurs de retour, consultez la section Passage de paramètres dans cet article.
Windows utilise r11 pour parcourir rapidement le frame de pile. Pour plus d'informations, consultez la section Exploration de pile. En raison de cette exigence, r11 doit toujours pointer vers le lien le plus haut de la chaîne. N’utilisez pas r11 à des fins générales, car votre code ne génère pas de marches de pile correctes pendant l’analyse.
Registres VFP
Windows prend en charge uniquement les variantes ARM qui intègrent une prise en charge du coprocesseur VFPv3-D32. Cela signifie que les registres à virgule flottante sont toujours présents et peuvent être fondés sur le passage de paramètres. Et l’ensemble complet de 32 registres est disponible pour une utilisation. Les registres VFP et leur fonction sont résumés dans ce tableau :
Simples | Doubles | Quadruples | Volatil ? | Rôle |
---|---|---|---|---|
s0-s3 | d0-d1 | q0 | Volatil | Paramètres, résultat, registre de travail |
s4-s7 | d2-d3 | q1 | Volatil | Paramètres, registre de travail |
s8-s11 | d4-d5 | q2 | Volatil | Paramètres, registre de travail |
s12-s15 | d6-d7 | q3 | Volatil | Paramètres, registre de travail |
s16-s19 | d8-d9 | q4 | Non volatil | |
s20-s23 | d10-d11 | q5 | Non volatil | |
s24-s27 | d12-d13 | q6 | Non volatil | |
s28-s31 | d14-d15 | q7 | Non volatil | |
d16-d31 | q8-q15 | Volatil |
Le tableau suivant illustre les champs de bits du registre d'état et de contrôle des nombres à virgule flottante (ou FPSCR, Floating-Point Status and Control Register) :
Bits | Signification | Volatil ? | Rôle |
---|---|---|---|
31-28 | NZCV | Volatil | Indicateurs d'état |
27 | CQ | Volatil | Saturation cumulative |
26 | AHP | Non volatil | Contrôle demi-précision alternatif |
25 | DN | Non volatil | Contrôle du mode NaN par défaut |
24 | FZ | Non volatil | Contrôle du mode de remplacement par zéro (Flush-to-zero) |
23-22 | RMode | Non volatil | Contrôle du mode d'arrondi |
21-20 | Stride | Non volatil | Pas (« stride ») vectoriel, doit toujours être égal à 0 |
18-16 | Len | Non volatil | Longueur de vecteur, doit toujours être égale à 0 |
15, 12-8 | IDE, IXE, et ainsi de suite | Non volatil | Bits d'activation de l'interception d'exceptions, doit toujours être égal à 0 |
7, 4-0 | IDC, IXC, et ainsi de suite | Volatil | Indicateurs d'exception cumulatifs |
Exceptions à virgule flottante
La plupart du matériel ARM ne prend pas en charge les exceptions à virgule flottante IEEE. Sur les variantes de processeur qui comportent des exceptions de virgule flottante matérielles, le noyau Windows intercepte discrètement les exceptions et les désactive implicitement dans le registre FPSCR. Cette action garantit un comportement normalisé entre les variantes du processeur. Sinon, le code développé sur une plateforme qui n’a pas de prise en charge des exceptions peut recevoir des exceptions inattendues lorsqu’il s’exécute sur une plateforme qui a une prise en charge des exceptions.
Passage de paramètres
Windows sur ARM ABI suit les règles ARM pour le passage de paramètres pour les fonctions non variadiques. Les règles ABI incluent les extensions VFP et SIMD avancées. Ces règles suivent la norme d’appel de procédure pour l’architecture ARM, combinée aux extensions VFP. Par défaut, les quatre premiers arguments entiers et jusqu’à huit arguments à virgule flottante ou vectorielle sont passés dans des registres. Tous les autres arguments sont transmis sur la pile. Les arguments sont assignés aux registres ou à la pile à l’aide de cette procédure :
Étape A : Initialisation
L’initialisation est exécutée une seule fois exactement, avant le début du traitement des arguments :
Le numéro NCRN (Next Core Register Number) est défini sur r0.
Les registres VFP sont marqués comme non alloués.
L’adresse NSAA (Next Stacked Argument Address) est définie sur le pointeur de pile (SP) actif.
Si une fonction qui retourne un résultat en mémoire est appelé, l'adresse du résultat est placée dans r0 et le numéro NCRN est défini sur r1.
Étape B : pré-remplissage et extension des arguments
Pour chaque argument de la liste, la première règle correspondante de la liste suivante est appliquée :
Si l'argument est un type composite dont la taille ne peut pas être statiquement déterminée à la fois par l'appelant et l'appelé, l'argument est copié en mémoire et remplacé par un pointeur vers la copie.
Si l'argument est un demi-mot d'un octet ou de 16 bits, il est étendu en mot complet de 32 bits avec des zéros ou des signes et est traité comme un argument de 4 octets.
Si l'argument est un type composite, sa taille est arrondie au multiple de 4 supérieur le plus proche.
Étape C : affectation d’arguments aux registres et à la pile
Pour chaque argument de la liste, les règles suivantes sont appliquées tour à tour jusqu’à ce que l’argument ait été alloué :
Si l’argument est de type VFP et qu’il y a suffisamment de registres VFP non alloués consécutifs du type approprié, l’argument est alloué à la séquence de registres ayant les numéros les plus petits.
Si l'argument est un type VFP, tous les registres non alloués restants sont marqués comme non disponibles. L'adresse NSAA est ajustée vers le haut jusqu'à ce qu'elle soit correctement alignée par rapport au type d'argument et que l'argument soit copié dans la pile à l'adresse NSAA ajustée. L’adresse NSAA est ensuite incrémentée de la taille de l’argument.
Si l’argument nécessite un alignement de 8 octets, le numéro NCRN est arrondi au numéro de registre pair supérieur.
Si la taille de l’argument dans les mots de 32 bits n’est pas supérieure à r4 moins le numéro NCRN, l’argument est copié dans les registres principaux, à partir du numéro NCRN, les bits de poids faible occupant les registres ayant les numéros les plus petits. Le numéro NCRN est incrémenté du nombre de registres utilisés.
Si le numéro NCRN est inférieur à r4 et que l'adresse NSAA est égale au pointeur de pile (SP), l'argument est partagé entre les registres principaux et la pile. La première partie de l’argument est copiée dans les registres principaux, à partir du numéro NCRN, jusqu’à r3 inclus. Le reste de l’argument est copié sur la pile, en commençant par la NSAA. Le numéro NCRN est défini sur r4 et l’adresse NSAA est incrémentée de la taille de l’argument moins le montant passé aux registres.
Si l’argument nécessite un alignement de 8 octets, l’adresse NSAA est arrondie à l’adresse alignée de 8 octets supérieure.
L'argument est copié en mémoire à l'adresse NSAA. L’adresse NSAA est incrémentée de la taille de l’argument.
Les registres VFP ne sont pas utilisés pour les fonctions variadiques, et les règles C de phase 1 et 2 sont ignorées. Cela signifie qu’une fonction variadicique peut commencer par un push facultatif {r0-r3} pour prépendier les arguments d’inscription à tous les arguments supplémentaires passés par l’appelant, puis accéder à la liste d’arguments entière directement à partir de la pile.
Les valeurs de type entier sont retournées dans r0, éventuellement étendues à r1 pour les valeurs de retour de 64 bits. Les valeurs de type virgule flottante VFP/NEON ou SIMD sont retournées dans s0, d0 ou q0, selon le cas.
Pile
La pile doit toujours rester alignée sur 4 octets et doit être alignée sur 8 octets à n’importe quelle limite de fonction. Il est nécessaire de prendre en charge l’utilisation fréquente d’opérations interblocées sur des variables de pile 64 bits. L'interface EABI ARM déclare que la pile est alignée sur 8 octets dans n'importe quelle interface publique. Pour des raisons de cohérence, l'interface ABI de Windows on ARM considère les limites de fonction comme une interface publique.
Les fonctions qui doivent utiliser un pointeur de frame (par exemple, les fonctions qui appellent alloca
ou qui modifient le pointeur de pile dynamiquement) doivent configurer le pointeur de frame dans le registre r11 du prologue des fonctions et le laisser inchangé jusqu'à l'épilogue. Les fonctions qui ne nécessitent pas de pointeur d’image doivent effectuer toutes les mises à jour de pile dans le prologue et laisser le pointeur de pile inchangé jusqu’à ce que l’épilogue.
Les fonctions qui allouent 4 Ko ou plus dans la pile doivent s'assurer que chaque page précédant la dernière page fait l'objet d'une interaction tactile dans l'ordre. Cet ordre garantit qu’aucun code ne peut « sauter sur » les pages de garde que Windows utilise pour développer la pile. En règle générale, l’extension est effectuée par l’assistance __chkstk
, qui passe l’allocation totale de pile en octets divisé par 4 en r4, et qui retourne le montant final d’allocation de pile en octets en octets en r4.
Zone rouge
La zone de 8 octets située juste en dessous du pointeur de pile actif est réservée à l'analyse et à la mise à jour corrective dynamique. Il permet d’insérer du code soigneusement généré, qui stocke 2 registres à [sp, #-8]
des fins arbitraires et les utilise temporairement. Le noyau Windows garantit que ces 8 octets ne seront pas remplacés si une exception ou une interruption se produit en mode utilisateur et en mode noyau.
Pile du noyau
La pile par défaut du mode noyau de Windows représente trois pages (12 Ko). Veillez à ne pas créer de fonctions dotées de mémoires tampons de pile volumineuses en mode noyau. Une interruption pourrait se produire avec une hauteur de pile très basse et entraîner une vérification d'erreur de pile précipitée.
Spécificités C/C++
Les énumérations sont des types d'entier de 32 bits sauf si au moins une valeur de l'énumération nécessite un stockage de double mot de 64 bits. Dans ce cas, l'énumération est promue en un type d'entier de 64 bits.
wchar_t
est défini comme étant l'équivalent de unsigned short
pour préserver la compatibilité avec les autres plateformes.
Marche sur la pile
Le code Windows est compilé avec des pointeurs d’images activés (/Oy (Omission de pointeur frame)) pour activer la marche rapide de la pile. En général, le registre r11 pointe vers le lien suivant dans la chaîne, qui correspond à une paire {r11, lr} qui spécifie le pointeur vers le frame précédent de la pile et l'adresse de retour. Nous recommandons aussi l'activation des pointeurs de frame dans votre code pour un profilage et un traçage améliorés.
Déroulement des exceptions
Le déroulement de la pile pendant le traitement des exceptions est assuré par l'utilisation de codes de déroulement. Les codes de déroulement correspondent à une séquence d'octets stockés dans la section .xdata de l'image exécutable. Ils décrivent l’opération du prologue de fonction et du code épilogue de manière abstraite, afin que les effets du prologue d’une fonction puissent être annulés en préparation du déroulement de l’image de pile de l’appelant.
L'interface EABI ARM spécifie un modèle de déroulement d'exception qui utilise des codes de déroulement. Toutefois, cette spécification n’est pas suffisante pour le déroulement dans Windows, qui doit gérer les cas où le processeur se trouve au milieu du prologue ou de l’épilogue d’une fonction. Pour plus d’informations sur windows sur les données d’exception ARM et le déroulement, consultez Gestion des exceptions ARM.
Nous vous recommandons de décrire le code généré dynamiquement à l'aide des tables de fonctions dynamiques spécifiées dans les appels à RtlAddFunctionTable
et les fonctions associées pour permettre au code généré de participer à la gestion des exceptions.
Compteur de cycles
Si les processeurs ARM exécutant Windows sont tenus de prendre en charge un compteur de cycles, l'utilisation directe du compteur peut causer des problèmes. Pour éviter ces problèmes, Windows on ARM utilise un opcode non défini pour demander une valeur de compteur de cycles 64 bits normalisée. Dans du code C ou C++, utilisez l'intrinsèque __rdpmccntr64
pour émettre l'opcode approprié ; dans un assembly, utilisez l'instruction __rdpmccntr64
. La lecture du compteur de cycles prend environ 60 cycles sur un Cortex-A9.
Le compteur est un vrai compteur de cycles, pas une horloge ; ainsi, la fréquence de comptage varie selon la fréquence du processeur. Si vous voulez mesurer le temps d'horloge écoulé, utilisez QueryPerformanceCounter
.
Voir aussi
Problèmes courants de migration ARM Visual C++
Gestion des exceptions ARM