Eléments du langage Assembleur ARM

 Il ne s'agit que d'un rappel des principaux éléments. Pour des précisions plus poussées reportez vous à la documentation officielle. Suivant le modèle du Raspberry et du processeur ARM associé, les instructions peuvent être légèrement différentes.

Les registres :

r0 à r3 : registres de travail, ou servant pour passer les paramètres : ne sont pas sauvegardés
r4 à r10 : registres de travail. Ils sont sauvegardés
r11 ou fp : pointeur de contexte (ou frame pointer) : peut servir de registre de travail localement : normalement il est sauvegardé.
r12 :pointeur pour les sauts longs : peut servir de registre de travail localement. Il n’est pas toujours sauvegardé.
r13 ou sp : registre de pile où seront sauvegardés les registres ou stockées des données temporaires.
r14 ou lr: contient l’adresse de retour d’une fonction, d’un sous programme.
r15 ou pc : compteur d’instructions

Les registres sont mouvementés avec les instructions mov rd,rs  (avec rd registre destination et rs registre source) et mvn rd,rs   (avec inversion du signe).
mov r0,r1      @  r0 contiendra la valeur de r1
 
Remarque : pour les calculs en virgule flottante, d'autres registres sont disponibles

Registre d’état ( ou de flags ou CPSR) : évolue à tout moment lors des instructions de test. N’est jamais sauvegardé.  Il peut être manipulé avec les instructions
                     mrs r0,cpsr  /* copie du registre d'état  dans r0 */
                     msr cpsr,r0  /* opération inverse */
Le registre d'état contient aussi un indicateur sur le mode de fonctionnement du processeur (User, superviseur, IRQ, abort etc). Dans ce blog nous ne documenterons que le mode User.
  

Les conditions :

Le registre d'état contient 4 drapeaux :
N  mis à 1 si le résultat d'une opération est négatif
Z  mis à 1 si le résultat d'une opération est égal à zéro
C (carry) mis à 1 si une opération entraine une retenue
V mis à 1 si une opération entraine un dépassement de capacité

Codes pour tester l'état de ces drapeaux :
eq   si égalité  (z=1)
ne   inégal (z=0)
vs  dépassement de capacité (v=1)
vc  pas de dépassement (v=0)
mi  négatif (n=1)
pl  positif ou égal à 0  (n=0)
cs carry positionné (c=1)
cc carry non positionné (c=0)
gt plus grand
ge plus grand ou égal
lt plus petit
le plus petit ou égal
hi plus grand pour des nombres non signés (c=1 et z=0) 
ls plus petit ou égal pour des nombres non signés (c=0 et z=1)

S'utilise dans les branchements :
    cmp r0,r1
    beq  suite      @ branchement à l'étiquette suite si les 2 registres sont égaux
mais aussi au niveau de chaque instruction
   cmp r0,r1
   movlt   r1,r0   @ on met r0 dans r1 que si r0 est plus petit que r1

Pour cmp, teq et tst, le registre d'état est toujours mis à jour. Pour les autres instructions il faut ajouter s à l'instruction. Par exemple
  subs r0,#1    @ si r0 se retrouve à zéro, le flag z sera mis à 1

La pile :

La pile est une zone de mémoire dont l'adresse est contenue dans le registre r13(sp). Par convention, cette adresse est décrémentée avant chaque stockage de données.
Par exemple :
  sub sp,#4
  str r1,[sp]     @ stockage du registre r1 sur la pile
Il est possible d'utiliser les instructions de mouvements multiples :
stmdb  sp!,{fr,lr}   @ stockage de fr et lr sur la pile après décrementation ( db = decrement before )
ldmia sp!,{fr,lr}    @  chargement des 2 registres depuis la pile  avant incrémentation (ia = increment after)
Remarque : le ! indique que le registre sp sera modifié après ces opérations par l'incrémentation et la décrémentation.

Mais il est préférable d'utiliser les macros instructions push et pop.
push {r1}
pop {r1}
ou
push {r1,r2,r3}
pop {r1,r2,r3}
ou
push {r1-r5}    @ les registres r1, r2, r3 , r4 et r5 sont stockés sur la pile
pop {r1-r5}

Remarque : la pile doit toujours être rendue en son état initial après utilisation (donc il faut avoir toujours le même nombre de pop que de push ou remettre l'adresse de la pile par add  sp,#nb de push * 4).
Il n'est pas possible de stocker des valeurs immédiates ou des adresses mémoires, il faut toujours passer par les registres.
Il faut stocker les registres sur la pile en nombre pair si utilisation de fonctions externes (voir la convention AAPCS ).

Les accès à la  mémoire :

Pour charger une valeur de 4 octets (un mot ou word) dans un registre, l'instruction est ldr rd,<label>.
Pour une valeur de 2 octets, l'instruction est ldrh rd,<label>  et pour un octet c'est ldrb rd,<label>.
Mais attention, si l'adresse de <label> est trop éloigné de l'instruction, il y aura une erreur de compilation et il faudra charger la valeur en 2 temps :
ldr r0,=<label>   @ chargement de l'adresse
ou ldr r0,adresse_zone
ldr r0,[r0]           @ chargement de la valeur située à l'adresse contenue dans le registre r0
Remarque importante, le chargement de l'adresse par ldr r0,=label fonctionne bien pour les petits programmes. Pour les gros programmes (environ plus de 4K mots) vous aurez un curieux message :


constante littéral invalide: le bassin doit être plus près
Dans  ce cas, il faudra utiliser l'autre manière bien décrite sur le site thinkingeek.
 


Il est possible de charger une valeur en fonction de son déplacement :
ldr r0,[r0,#8]        @  chargement de la valeur situé à l'adresse contenue dans r0 + 8 octets
ldr r0,[r0,#-4]        @  chargement de la valeur situé à l'adresse contenue dans r0 - 4 octets
ldr r0,[r0,r1]        @  chargement de la valeur situé à l'adresse contenue dans r0 + r1
ldr r0,[r0,r1,#4]        @  chargement de la valeur situé à l'adresse contenue dans r0 + r1+4 octets
ldr r0,[r0,r1,lsl #2]    @  chargement de la valeur situé à l'adresse contenue dans r0 + (r1 * 4 octets)

Remarque : ldr r0,[r0] ,#8       @  chargement de la valeur situé à l'adresse contenue dans r0 + 8 octets mais dans ce cas r0 sera incrémenté de 8 octets après le chargement.

Pour stocker une valeur, c'est l'instruction str avec les mêmes modalités que ldr.
strb r0,[r0,#1]    @ stockage de l'octet de poids faible de r0 à l'adresse contenue dans r0 + 1 octet

Remarque : le stockage des 4 octets d'un registre en mémoire par str r0,[r0] est effectué dans l'ordre inverse (convention big endian), poids faible dans le premier octet de la mémoire, poids fort dans le 4 ième octet.

Il est possible d'utiliser les instructions de  mouvements multiples :
ldr r0,=toto
stm r0,{r1,r2,r3}     @ r1 sera stocké à l'adresse r0, r2 à l'adresse r0 + 4 octets et r3  à l'adresse r0 + 8 octets. A la fin de l'opération r0 n'est pas incrémenté (pas le signe ! à la suite). Attention, l'adresse toto doit être alignée sur une frontière d'un mot.


Les branchements :

b <label>      :   saut à l'étiquette <label>

bl <label>    :  saut à l'étiquette <label> et l'adresse suivante est stocké dans le registre lr

blx <registre>  : appel de la routine dont l'adresse est contenue dans registre

bx lr            : saut à l'adresse contenue dans le registre lr, ce qui permet de revenir de la routine appelée.
 

Comme vu dans les conditions , le branchement peut être beq (si égalité) bne (si inégal) etc..

Les labels ou étiquettes peuvent être alphanumérique comme boucle:  ou positif1:   mais ces labels sont valables pour tout le programme. Pour avoir des labels locaux à chaque sous-routine, il faut utiliser des labels numériques comme 1: ou 2: . Pour sauter à un de ces labels on ajoute soit b pour before (saut en avant) ou f pour forward  (saut en arrière).
Exemple d'une boucle
     mov r1,#0   @ compteur de boucle
1:                      @ début de boucle
    cmp r1,#10   @ test du compteur
    bge 1f           @ saut à l"etiquette 1: ci dessous (forward)
   /* instructions à executer dans la boucle */
   b 1b               @ branchement en début de boucle (before).
:1
Pour plus de clarté, il est préférable (à mon gout) de remplacer la 2ième étiquette 1: par 2:

Les instructions de calcul :


add  : addition       add  r0,r1,r2    add r0,r1   add r0,#1
adc  : addition plus addition du carry
sub  : soustraction
sbc   : soustraction plus soustraction du carry
mul  : multiplication     mul r0,r1,r0    (mul r0,r0,r1 entraine une erreur)
rsb   : inversion    rsb r0,r1,r2     @ r0 contiendra la valeur de r2 - r1
          (intéressant pour effectuer une soustraction d'une valeur immédiate  exemple rsb r0,r1,#32)
sxtb  : report d'un octet    sxtb r0,r1     @ r0 contiendra l'octet de poids faible de r1 avec extension du signe

Les manipulations de bits :

and   : Et    exemple and r0,r1  
eor    : Ou
orr    : ou exclusif (à vérifier)

lsl    : déplacement de bits vers la gauche :  lsl r0,#1   @ déplacement d'un bit
lsr   : déplacement de bits vers la droite :  lsr r0,#5   @ déplacement de 5 bits vers la droite

Remarque 1  : le bit éjecté est stocké dans le flag carry. Si vous voulez l'utiliser il faut  mettre lsls
          exemple  lsls r0,#1
                          bcs  ok      @  saut à l'étiquette ok si le bit le plus à gauche de r0 est 1

remarque 2  : lsl sert à multiplier rapidement par un multiple de 2 et lsr à diviser par un multiple de 2
           exemple lsl r0,#2   @  déplacement de 2 bits à gauche équivalent à multiplier par 4


ror  :  rotation des bits dans le registre

Il existe d'autres instructions suivant le processeur.


Les comparaisons :

cmp r0,r1      compare 2 registres avec mise à jour des flags

tst   r0,r1       opération r0 and r1  avec mise à jour des flags

teq r0,r1       opération  r0 or r1  avec mise à jour des flags


Valeurs immédiates :

 Dans beaucoup d'instructions précédentes, le registre source peut être remplacé par une valeur immédiate (un nombre précédé du signe #) .  Mais compte tenu de la longueur des instructions qui font 32 bits, il y a des limitations aux valeurs possibles. Toutes les valeurs jusqu'à 255 sont possibles. ensuite seules les valeurs possibles à partir de ces 255 et leurs rotations sont admises : voir les magnifiques explications mais en anglais sur le site : https://alisdair.mcdiarmid.org/arm-immediate-value-encoding/
Exemple :   add r0,#1    @ pour incrémenter le registre r0 de 1
                   mov r1,#2000   @ init de r1 avec la valeur 2000
il est aussi possible d'utiliser pour de grandes valeurs   ldr  r0,=50000000
sinon il faut déclarer la valeur en mémoire et la charger par
        ldr r0,=adressevaleur
        ldr r0,[r0]













Aucun commentaire:

Enregistrer un commentaire