samedi 16 novembre 2019

Chapitre 72 : Assembleur ARM 64 : tests instructions, nombres aléatoires, saisie chaine et nombre


Dans ce petit programme, testRegSup.s, je vérifie les instructions add avec le mot clé supplémentaire UXTW ou SXTW qui permettent d’additionner des registres de tailles différentes avec en plus la possibilité d’effectuer une multiplication du registre avant son addition.
Je teste aussi le même mot clé pour les accès mémoire. Et enfin je réecit en 64 bits la routine de génération de nombre aléatoires. Je ne me souviens plus d’où j’ai tiré la première série de nombres servant au calcul !! mais j’ai mis en commentaire une deuxième série tiré d’un livre sur les algorithme en langage C de Robert Sedgewick.

Dans le programme suivant saisie.s: https://github.com/vincentARM/ARMassembly64/blob/master/projet64_003/saisie.s, je teste la saisie d’une chaine de caractère en utilisant le call système Linux Read. Comme il faut un buffer de réception de la chaine saisie et à la place d’une zone réservée dans la .bss, je reprends la procédure d’allocation sur le tas avec le call system Linux BRK. Cette procédure effectue 2 appels, le premier pour récupérer l’adresse du tas et qui sera donc l’adresse de début de notre zone réceptrice et l’autre qui alloue la place demandée. Vous remarquerez que la procédure alloue une place de telle façon que la fin soit sur une frontière de 8 octets suivante. En effet lorsque je mettrais cette sous-routine dans le fichier routinesarm64, il faudra qu’elle fonctionne pour tous les appels futurs et en particulier pour des utilisations du tas pour des tables de double mots alignées sur une frontière de 8 octets.
Le test de la saisie chaine étant ok, je teste la conversion d’une chaine en un nombre positif ou négatif.
Petite curiosité : quand j’ai écrit cette routine en 32 bits au début de ma découverte de l’assembleur arm, j’effectuais la conversion de la saisie en nombre négatif en multipliant le résultat par – 1 . ensuite j’ai découvert que l’on pouvait utiliser les 2 instructions mvn r1,r1  et add r1,#1 et maintenant en 64 bits la seule instruction :    cneg x0,x0,eq
Vous remarquerez qu’au fur et à mesure de ma découverte de l’assembleur 64 bits, j’adapte les routines pour utiliser les nouvelles instructions : par exemple pour tester un registre à zéro : l’instruction cbz ou son contraire l’instruction cbnz. Pour gérer éventuelle erreur de calcul, je positionne le carry avec les instructions cmp x0,0 et cmn x0,0. Vous pouvez vérifier que quel que soit la valeur de x0, le carry est ou non positionné dans tous les cas : astucieux non !!

jeudi 14 novembre 2019

Chapitre 71 : Assembleur ARM 64 : accès mémoire


Dans les précédents programmes, nous avons déjà rencontrés les instructions d’accès à la mémoire (par exemple avec les sauvegardes de registres sur la pile, l’adresse de la pile n’étant qu’une adresse d’une zone mémoire).
Dans le programme instLdrStr, nous commençons avec les instructions de stockage d’un octet, demi mot mot et double mot. On remarque que c’est la même instruction str qui sert pour un mot et un double mot mais c’est le registre d’origine (x ou w) qui fait la différence. Puis nous lisons les mêmes informations avec l’instruction ldr. J’ai teste aussi l’instruction ldur mais je n’ai pas vu de différence avec ldr. C’est à approfondir.
Pour les accès avec déplacement, j’ai eu une erreur de compilation avec l’instruction str x0,[x1,x2,lsl 2] que j’ai eu du mal à résoudre. En fait la valeur pour un double mot ne peut être que supérieure à 2 (mais pour un mot, elle peut être égale à 2). Je teste aussi la possibilité d’avoir un déplacement avec un indice contenu dans un registre 32 bits grâce au mot clé supplémentaire UXTW (voir la documentation pour d’autres possibilités).
Ensuite nous restons les lectures d’octet négatif avec recopie du bit de signe. Il reste encore des instructions particulières à tester.
Nous terminons le programme par un exemple simple de manipulation d’une table avec des enregistrements comportant plusieurs champs. Nous décrivons les champs d’un enregistrement à l’aide d’une structure (.struct), nous déclarons dans la section .bss la place nécessaire pour gérer 10 enregistrements.
Dans le code nous écrivons une routine pour insérer un nouvel enregistrement dans la table et une routine pour afficher un enregistrement. L’adresse de chaque enregistrement est calculée grâce à l’instruction madd qui permet d’ajouter à l’adresse de la table, la longueur d’un enregistrement * numéro de l’enregistrement.
Ici, pour chaque insertion, nous balayons toute la table pour trouver un emplacement vide, mais nous aurions pu stocker l’enregistrement directement dans le dernier poste vide puisque nous avons le registre x28 qui contient le nombre d’enregistrements déjà stockés.
Vous remarquerez que j’utilise les instructions ldrsb x0, ldrsh x0 et ldrsw x0 à la place de ldrb w0,ldrh w0 et ldrw w0. Ces instructions complètent le registre 64 bits en dupliquant le signe.

dimanche 10 novembre 2019

Chapitre 70 : Assembleur ARM 64 : tests de quelques instructions 64 bits


Maintenant, que les outils précédents sont prêts, nous allons regarder plus en détail les principales instructions disponibles en 64 bits. Le programme instrBits.s va tester les instructions de manipulations de bits. Nous commençons par les tables de vérité and, oor, eor, orn, eon et bic. Cette dernière est intéressante pour remettre des bits à zéro.
Par exemple dans la routine d’affichage mémoire, l’affichage commence toujours en début d’un bloc de 16 octets soit une adresse qui se termine par 0 hexa soit 0b0000  binaire. Pour remettre ces 4 bits à zéro j’utiliser les instructions mov x1, x2, ASR #4 puis mov x1, x1, LSL #4 alors que l’on peut avoir une seule instruction :
Bic x1,x2,0b1111     (voir l’exemple bic_bis dans le programme instrBits.s)

Ensuite nous testons les instructions lsr,lsl,ror et asr. Asr permet de diviser un nombre négatif par une puissance de 2 à faible côut. Ce test m’a perturbé car l’affichage du résultat en nombre décimal était incorrect. Après recherche je me suis aperçu que je passais dans x1 l’adresse de la zone réceptrice avec l’instruction mov x1, qAdrszZoneRec1 à la place de ldr x1, qAdrszZoneRec1. En 32 bits, le compilateur signalait une erreur mais pas en 64 bits. Dans ce cas, il charge bien une valeur dans x0 mais je n’ai pas encore trouvé à quoi elle correspond. Cela va être une source d’erreur pas facile à trouver.
Nous testons les comptages de bits à zéro ou de un  à gauche avec les instructions clz et cls puis les inversions de bits, d’octets, de demi mot et de mot et nous terminons par les remplacements de bits.
J’ai mis un exemple de calcul de valeur absolue sans utilisation d’un test !! et je termine par un test d’une routine dans laquelle il n’y une instruction qui désaligne la pile. Si vous enlevez le commentaire vous verrez apparaitre l’erreur erreur du bus !!!

Dans le second programme instMovArith.s, nous allons tester les instructions arithmétiques et move. Je commence par refaire le test de l’instruction mov x0,label pour voir à quoi correspond le résultat. Il s’avère que x0 contient maintenant le déplacement du label à partir du début de la section .text.  Si le label fait partie de la section .data, le déplacement est celui du début de cette section. Cela peut être utile de le savoir et il faudra faire quand même attention à ne pas confondre avec ldr x0,label.

Dans la suite de programme, je teste la valeur immédiate maximum acceptable dans un mov. La longueur maxi est de 16 bits soit une valeur égale à 0xFFFF soit 65535 décimal. S’il faut manipuler une valeur immédiate supérieure il faut utiliser l’instruction movk avec un déplacement de 16,32 et 48 pour positionner des tranches de 16 bits au bon endroit.
Puis je reteste l’instruction movn qui ne donne pas la valeur d’un complément à 2 mais qui est en fait l’operateur not. Par contre dans la documentation je trouve l’instruction neg qui donne bien le résultat attendu : neg x0,-5 donne 5
Ensuite, je teste l’instruction addition en vérifiant la mise à jour du flag overflow pour une valeur supérieure à 2puissance 63. Puis je teste la soustraction et la multiplication.
Curieusement pour la multiplication, il n’est pas possible d’ajouter le s pour demander la mise à jour du registre d’état et donc de vérifier l’overflow. Il faut donc recourir à une astuce : utiliser l’instruction smulh pour multiplier les parties hautes des registres et si le résultat est non nul c’est qu’il y a dépassement de capacité.
Dans le programme instrCond.s, nous allons vérifier quelques instructions conditionnelles. Tout d’abord on teste l’instruction csel qui permet de mettre dans un registre la valeur d’un deuxième ou d’un 3ième registre suivant la condition. En fait c’est un if then else mais concernant qu’une seule possibilité.
La 2ième instruction testée est cinc qui incrémente ou non un registre suivant la condition.
Puis je teste l’instruction csinc qui soit incrémente un registre soit met la valeur d’un autre.
Puis l’instruction cneg qui permet de calculer une valeur absolue en 2 instructions seulement.
Et je termine par l’instruction ccmp plus compliquée : ici je montre en 2 exemples comment vérifier si une valeur est comprise entre 2 bornes incluses puis entre 2 bornes exclues.  J’ai mis zéro pour la 3ième donnée de l’instruction car je n’ai pas bien compris son rôle.
Les sources des programmes se trouvent dans ce répertoire : 

jeudi 7 novembre 2019

Chapitre 69 : Assembleur Arm 64 : macro affichage de plusieurs registres et affichage mémoire


Après réflexion et un premier test, je décide d’afficher le contenu de 6 registres sur 2 lignes et d’indiquer dans la macro le N° du premier registre à afficher. Ainsi en indiquant affregtit Libellé 0, la macro affichera le texte Libellé puis le contenu des registres x0 à x5. Si nous voulons d’autres registres, nous indiquerons affregtit toto1 10 et la macro affichera les contenus des registres x10 à x15. Il est inutile de prévoir une macro pour les registres w0 à w30 puisqu’ils sont inclus dans les registres 64 bits.
Un petit problème se pose : il faut afficher les registres x0 et x1 et il faut passer à la routine d’affichage l’adresse du libellé et le N° du premier registre à afficher : Je décide de les passer dans x0 et x1 et de passer les valeurs de xo x1 précédentes sur la pile (à la réflexion c’était peut être mieux de faire le contraire).
Tout d’abord, j’écris dans le fichier routinesARM64.s, les routines pour afficher le libellé, le nom des registres et leur contenu : affRegistres16, prepRegistre16 et prepNumRegistre. Pour calculer la position d’affichage il faut utiliser les instructions de multiplication et de division. Elles sont identiques à celles en 32 bits et donc ne posent aucun problème. Pour convertir le N° de registre en caractères ascii, je fais simple : une seule division par 10 et calcul du reste grâce à l’instruction msub x4,x3,x5,x0 qui soustrait de x0 le résultat de la multiplication de x3 * x5 et qui met le résultat dans x4.
Et tout d’un coup, je m’aperçois que je commence les libellés des adresses par iAdr i pour integer alors que maintenant elles sont décrites en .quad et donc doivent commencer par qAdr. Et reste une question : faut-il bien les déclarer avec le mot clé .quad comme c’était le cas en 32 bits ? : il faudra le vérifier.
Quand ces routines sont prêtes, j’écris la macro pour appeler la routine principale : il faut manipuler la pile pour passer correctement les valeurs (et sauvegarder aussi le registre d’état). Après quelques tests, la macro fonctionne bien.
Il reste 2 petits problèmes : la macro n’affiche pas le contenu de la pile et le contenu du registre 30 (lr) affiche le contenu qu’il a dans la routine d’affichage et pas la valeur qu’il a avant l’appel de la macro. Si on veut avoir les contenus de ces 2 registres, il faut les copier avant l’appel dans 2 registres inutilisés mais l’expérience du 32 bits m’a montré que les contenus de ces 2 registres sont rarement utiles !!
Tant que je suis sur l’écriture des macros, j’écris aussi celle qui affiche les drapeaux du registre d’état en utilisant les instructions mrs x5,nzcv et msr nzcv,x5. Vous noterez que le nom du registre n’est pas le même qu’en 32 bits.
Pour l’utilisation de ces nouvelles macros, je crée le programme testaffreg.s qui verifie le contenu des registres et pour vérifier aussi que l’adresse de la pile en fin de routine est égale à celle du début.
Je crée aussi un autre programme verifinst.s pour vérifier un certain nombre d’instructions en 64 bits : les mov, cbz cbnz tbz et tbnz. Je remarque que l’instruction teq n’existe plus en 64 bits.
Je teste aussi l’écriture de registres sur la pile en diminuant l’adresse de la pile à chaque instruction. Cela me parait plus satisfaisant car on peut ajouter des écritures de paires de registres plus facilement.

Je poursuis l’écriture des macros en recopiant et modifiant la macro de vidage des zones de la mémoire. Comme il faut aussi afficher en premier un libellé, je réorganise les autres macros pour appeler la même sous-routine d’affichage.
Je décide aussi de dupliquer la routine d’affichage d’une chaine pour permettre le passage de la longueur de la chaine dans le registre x1. En effet, je me suis aperçu que la longueur des chaines à afficher étaient très souvent connue et donc qu’il est inutile de passer par une boucle de calcul de cette longueur.
Je teste ces améliorations et l’affichage des zones mémoires dans le programme debutaffmem1.

AFFICHAGE D'UN REGISTRE EN DECIMAL SIGNE ET NON SIGNE


J’ai réussi à trouver une synthèse des instructions arm64  au format pdf :
https://courses.cs.washington.edu/courses/cse469/18wi/Materials/arm64.pdf
Pour terminer cette première partie, j’écris les 2 routines qui permettent de convertir la valeur d’un registre en un nombre décimal signé et non signé. Le résultat sera une chaine de caractères ascii cadrée à gauche et se terminera donc par un zéro final. Cette solution est un peu différente de celle que j’avais proposée pour le 32 bits. Suivant le besoin, c’est donc le programme appelant qui utilisera la chaine comme il conviendra.
La chaine contiendra donc par exemple « 12345 » en non signé et « +12345 » ou « -12345 » en signé.
Remarque : c’est le programme appelant qui doit fournir la place de la zone réceptrice soit 21 caractères. Quand j’aurais testé les allocations de place sur le tas, c’est la routine qui allouera la place nécessaire (ce qui me semple mieux). La routine retourne dans le registre x0, la longueur de la chaine (hors zéro final)
La conversion s’effectue en utilisant les instructions udiv et msub dans une boucle.
Le programme de test fait appel à ces routines et affiche les résultats en appelant la nouvelle version de l’affichage de chaine donc en passant la longueur.
Quelques remarques : contrairement à ce que je pensais au début, les instructions de l’assembleur 64 bits ne font pas 64 bits de long mais seulement 32. La manipulation des zones en mémoire peut se faire en 8bits,16 bits 32 bits et 64 bits et il est conseillé que ces zones soient alignées sur leur frontière(si ce n’est pas le cas, il n’y a pas d’erreur mais une augmentation du nombre de cycles internes). La pile doit être toujours alignée sur une frontière de double mot (donc se terminer par 4 bits à zéro).
 
Tous les sources se trouvent ici : https://github.com/vincentARM/ARMassembly64 
 dans le projet64_001.