jeudi 3 octobre 2019

Chapitre 64 : instructions ldr et str en détail


Voici un petit chapitre pour revenir en détail sur les possibilités de ces 2 instructions : ldr sert à charger dans un registre des données stockées en mémoire et str à stocker la valeur d’un registre dans une zone mémoire. Les possibilités d’utilisation sont identiques dans les 2 cas.
Voir l'exemple du programme ici.
Je rappelle qu’avec l’assembleur arm, les seules instructions valides du processeur sont celles qui accèdent à la mémoire avec une adresse contenue dans un registre car les instructions font toutes 32 bits de long et donc il n’est pas possible d’avoir le code opération, le code condition et une adresse de 32 bits dans une instruction. Il n’est donc pas possible d’écrire ldr r0,[zonememoire] comme dans d’autres assembleurs. Les autres instructions comme ldr r0,iAdrzoneMemoire ou ldr r0,=variable ne sont que des pseudo instructions.
Prenons le premier cas ldr r0,iAdrzonememoire qui nous permet de charger une adresse ou une variable dans le registre r0. Le label ne peut être que dans la zone du code (.text) sous la forme :
iAdrzonememoire :      .int zonememoire
Le label zonememoire pointe sur une zone mémoire soit dans la .data soit dans la .bss comme par exemple :
Zonememoire :     .asciz « Bonjour »
Lors de la compilation, le compilateur remplace le label iAdrzonememoire par l’utilisation du registre 15 (le compteur d’instruction ou pc) et calcule le déplacement entre l’adresse de l’instruction et l’adresse du label.
Ainsi l’instruction pour le processeur devient ldr r0,[pc,+30] et à l’exécution r0 contiendra l’adresse de Zonememoire. Puis l’instruction ldr r1,[r0] chargera dans r1 les données de cette adresse.
Dans le deuxième cas, ldr r0,=variable, le label variable se trouve dans la zone data ou bss sous la forme :
Variable :     .int 50
Et que fait le compilateur ? il va créer en fin de la partie code une zone contenant l’adresse du label Variable puis il va calculer le déplacement entre l’adresse de l’instruction et cette adresse pour remplacer l’instruction comme dans le premier cas par ldr r0,[pc,+860].
Ceci pose 2 problèmes : le déplacement ne peut pas dépasser 4096 octets en plus ou en moins, ce qui entraine une anomalie du compilateur pour les gros programmes de plus de 1000 lignes (4 octets * 1024 = 4096). Dans ce cas il ne faut plus utiliser la forme ldr r0,=label (erreur : constante littéral invalide: le bassin doit être plus près) et dans le premier cas, il faudra dédoubler la définition du label situé dans la partie code si une erreur de compilation est détectée (Erreur: valeur immédiate erronée pour l'offset).

Maintenant stockons la constante 0x31323334 correspondants aux caractères 1234, et affichons le contenu du buffer. Nous remarquons que les octets sont stockés dans l’ordre 4321 car il existe plusieurs type de stockage d’un mot de 32 bits en mémoire : Little endian configuration et Big endian configuration.  En français c’est gros boutiste et petit boutiste (voir les explications amusantes de l’origine de ces termes sur wikipedia). En big endian, les octets d’un mot de 32 bits sont stockés dans l’ordre inverse des poids et en little endian dans l’ordre des poids. Pour la version Linux du Raspberry c’est en little endian par défaut mais on doit pouvoir modifier cette option car le bit qui indique le type est un bit du registre d’état. Mais je vous déconseille de le faire car les conséquences peuvent être imprévisible en cas d’oubli de remise à sa valeur d’origine. L’ordre des octets peut avoir une importance lors de la récupération d’une valeur 32 bits issue d’un fichier externe ou d’un périphérique. Dans ce cas si l’ordre ne vous convient pas il suffit d’ajouter l’instruction rev rn, rn qui inverse les octets d’un registre.
Vous remarquerez que le label de la constante est iConstante1 pour indiquer un type entier (integer en anglais) mais que le label d’une donnée se trouvant dans la section data ou bss commence par iAdr pour indiquer qu’il s’agit d’une adresse pointant sur la donnée. Vous remarquerez aussi que j’ai appelé cette donnée Constante car en effet se trouvant dans la partie code, elle ne peut pas être modifiée par le programme (essayez pour voir !!).
Nous vérifions aussi l’impact de données en mémoire non alignées sur fa frontière d’un mot. Ici il n’y a pas d’anomalie mais il est toujours préférable d’aligner les données d’une taille d’un mot sur une frontière de mot. La documentation indique la possibilité d’anomalie (et je pense que c’est en fonction du processeur) et/ou une augmentation du nombre de cycles pour effectuer l’opération.

Après le registre de base indiquant l’adresse mémoire, nous pouvons ajouter un déplacement positif ou négatif et dans la limite de 4096 caractères. L’instruction str r1,[r0,#+8] stockera le contenu du registre r1 à l’emplacement mémoire r0 + 8 octets. La valeur peut être stockée dans un registre ce qui permet d’effectuer un déplacement de plus de 4096 caractères : par exemple str r1,[r0,r2]. Et bien entendu il est possible d’effectuer sur le registre r2 des opérations de décalage bien pratique pour effectuer des accès à des tables. Par exemple pour accéder au 4 ième poste d’une table contenant des entiers de 32 bits ;

Ldr r0,iAdrTableEntiers
Ldr r1,#3           @ car le premier poste commence à l’adresse de la table
Ldr r2,[r0,r1,lsl #2]    @ accès à l’adresse r0 +  ( r1 * 4) soit r0 + 12


Il est possible d’effectuer automatiquement l’incrémentation du registre de base après le stockage en mémoire par

Str r2,[r0],#4          @ r2 sera stocké à l’adresse r0 puis r0 sera incrémenté de 4 octets

Ou aussi

Str r2,[r0],r1       @ r2 sera stocké à l’adresse r0 puis r0 sera incrémenté du contenu de r1

Voir l’exemple dans le programme qui recopie une chaine dans une autre zone.
Mais il est aussi possible d’effectuer d’abord l’incrémentation avant le stockage par l’instruction :

Str r2,[r0],#4!     @   r0 sera incrémenté de 4 puis r2 sera stocké à cette nouvelle adresse.

Remarque : Lors de la définition d’une table en mémoire (voit la pseudo instruction .struct), il est intéressant d’avoir des longueurs puissance de 2 quite à ajouter quelques octets inutiles.
En effet pour balayer une table de longueur 16 et retourner le N° du poste il suffit :

Ldr r0,iAdrTable
Mov r1,#0
1 : Ldr r2,[r0,r1,lsl #4]
Cmp r2,#valeurcherchée
Beq 2f
Add r1,#1
Cmp r1,#nbMaxiPoste
Ble 1b
@instructions si non trouvée
B suite
2 :  @ instructions si trouvée

Nous terminons ce tour d’horizon avec les instructions travaillant sur un octet (ldrb et strb) et les instructions travaillant sur un demi mot (16 bits) ldrh et strh.
Bien sûr il est possible d’ajouter à ces instructions, le code condition ce qui peut donner des instructions du type :

Ldreqb r0,[r1,r2]

Par contre, il n’est pas possible d’indiquer de mettre à jour les drapeaux d’état, ce qui nécessite d’effectuer une comparaison après un ldr pour tester si la valeur récupérée est à zéro.

Aucun commentaire:

Enregistrer un commentaire