Partager via


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. Cela peut entraîner une erreur d’application en mode utilisateur ou une vérification de bogue 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, s’il le fait, des exceptions ou des interruptions qui se produisent peuvent entraîner une erreur d’application en mode utilisateur ou une vérification de bogue 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 :

  1. Le numéro NCRN (Next Core Register Number) est défini sur r0.

  2. Les registres VFP sont marqués comme non alloués.

  3. L’adresse NSAA (Next Stacked Argument Address) est définie sur le pointeur de pile (SP) actif.

  4. 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 :

  1. 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.

  2. 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.

  3. 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é :

  1. 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.

  2. 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.

  3. Si l’argument nécessite un alignement de 8 octets, le numéro NCRN est arrondi au numéro de registre pair supérieur.

  4. 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.

  5. 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.

  6. Si l’argument nécessite un alignement de 8 octets, l’adresse NSAA est arrondie à l’adresse alignée de 8 octets supérieure.

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