Les
instructions issues du langage d’assemblage et directement exécutables par le
processeur sont stockées dans la section .text.
Pour notre assembleur arm, il s’agit d’instructions de type RISC d’une
longueur de 32 bits (4 octets) quelle que soit la nature de l’instruction. Cela
entraine quelques limitations.
Le détail de
la composition de chaque instruction présente peu d’intérêt pour la
programmation : code condition, code opération, bit maj état, codes des
registres utilisés etc, qui utilisent une partie des 32 bits. Il ne reste donc
que quelques bits pour le reste de l’instruction ce qui explique que pour
stocker une valeur immédiate il ne reste plus que 12 bits (dont 8 bits pour la
valeur et 4 bits pour indiquer un multiple).
Lors du
lancement du programme, le système d’exploitation charge les différentes
sections en mémoire de l’ordinateur suivant les directives établies par le
linker et donc charge les instructions dans la section .text puis initialise un
registre particulier avec la première instruction à exécuter. Le registre est le 15ième registre
et porte le nom de pc (program counter) et il contient toujours l’adresse de la
prochaine instruction à exécuter (pas tout à fait en réalité car le processeur
peut anticiper l’exécution : voir pour plus de précision….).
L’adresse de
la première instruction est indiquée par la directive entry du linker. Il
s’agit le plus souvent de l’adresse de la procédure principale du programme.
Cette procédure doit donc être accessible par le système d’exploitation et doit
être déclarée avec la pseudo instruction .globale
Les adresses
du code qui doivent être utilisées pour des sauts doivent être déclarées par
des labels comme tache1 : toto:
ou addition: (avec les 2 points
en fin).
Pour
effectuer un saut à un label ou étiquette nous utilisons l’instruction b
(branch en anglais) par exemple b tache1. Pour effectuer des sauts
conditionnels après un test, il suffit d’ajouter les codes conditions comme
déjà vus dans le chapitre un registre.
Par
exemple :
Cmp r1,#0
Beq egal
@ si r1= 0 saut au label egal
Bne
pasegal @ sinon saut au label pasegal
Et bien sûr
le label peur être placé avant ce qui permet d’effectuer des boucles :
Mov r0,#0 @ init du
compteur de boucle
Debut : @ label de début de boucle
Add r0,#1 @
incrémentation du compteur
Cmp r0,#5 @
comparaison à la valeur 5
Ble debut @ si
plus petit ou égal ->boucle
….puis suite du programme
L’assembleur
as autorise aussi des labels numériques locaux à une sous routines. On peut
accéder au label précèdent en mettant un b (pour before) ou au label suivant
par un f (pour forward). Exemple autre boucle :
Mov r0,#0
1 : @ label début de boucle
Cmp r0,#5
Bgt 1f @ saut au label 1 suivant si r0 supérieur à
5
Add r0,#1
B 1b @ saut au label 1 précèdent
1 : … suite du programme
Mais je
déconseille l’utilisation du même N° car cela me parait peu lisible.
Appel d’une
sous procédure :
Il
s’effectue avec l’instruction bl label
(branch and link en anglais). A la rencontre de cette instruction, le
processeur stocke l’adresse de l’instruction suivante dans le registre N°14 (lr
pour Link return) et met l’adresse du label appelé dans le compteur de
programme, ce qui permet le saut à l’instruction de la sous routine.
Le retour de
la sous procédure s’effectue par l’instruction bx lr qui va mettre l’adresse
contenu dans le registre lr dans le compteur de programme ce qui permet le
retour à l’instruction qui suivait l’appel.
Ce mécanisme
simple ne fait pas appel à la pile (comme dans le call d’autres assembleurs) et
il faudra donc veiller à ce que l’adresse de retour contenue dans le registre
lr ne soit pas perdue. Et c’est le cas quand une routine va elle-même appeler
une sous-routine car le processeur va stocker dans lr l’adresse de retour de la
sous-routine et écraser l’adresse de retour de la routine !! C’est
pourquoi , vous verrez dans de nombreuses routines que la première instruction
est push {lr] et l’avant dernière pop {lr) puis bx lr pour être sûr que
l’adresse de retour n’a pas été perdue.
Pour passer
des paramètres à la routine, nous pouvons utiliser des registres ou stocker des
valeurs sur la pile. Comme souvent, les programmes assembleurs peuvent faire
appel à des routines de la librairie du langageC , il est fréquent de
respecter la norme de ce langage :
Les 4
premiers paramètres sont passés dans les registres r0 à r3, et si nécessaire
les autres sont passés sur la pile. La valeur de retour est passée dans le
registre r0 et éventuellement les autres valeurs dans les autres registres r1 à
r3. Si la routine utilise les autres registres (r4-r12) en interne, elle doit
les sauvegarder et restaurer leur valeur en fin.
Attention :
les registres r4 à r11 sont bien sauvegardés par les routines du C mais le
registre r12 pas toujours (car il peut servir pour des appels longs)
Voyons de
plus près le mécanisme de passage des paramètres par la pile. Supposons que
l’adresse de la pile contenue dans le registre sp soit 1000
Dans le
programme appelant nous stockons les valeurs r4 et r5 sur la pile par
Push
{r4} @ donc r4 est stockée à l’adresse
1000 puis la pile est décrémentée de 4 octets soit 996
Push {r5} @
donc r5 est stockée à l’adresse 996 et la pile est décrémentée de 4 octets soit
992.
Dans la
sous-routine, nous commençons par sauvegarder lr ce qui décrémente l’adresse de la pile de 4 octets soit 988 puis
nous récupérons le deuxième paramètre par ldr r5,[sp,#4] et le premier par ldr r4,[sp,#8]
Mais il est
aussi possible de stocker l’adresse de la pile sp dans le registre fp (frame
pointer) et d’utiliser cette adresse quelle que soit l’évolution de la pile
dans la routine. Exemple :
Push {fp,lr}
add fp,sp,#8
Et donc le
deuxième paramètre est récupéré par ldr r5,[fP] et le premier par ldr
r4,[fp,#4]
Attention,
si vous utilisez l’instruction push {r0,r1}, r1 est stocké sur la pile avant
r0 !!
Il reste une
dernière intervention à effectuer dans la routine (ou dans le programme
appelant), c’est de remettre la pile à son état initial, et pour éviter de
modifier des registres, nous ajoutons simplement le nombre d’octets utilisés
par le ou les push. Ici il y a 2 push donc nous ajoutons l’instruction add
sp,#8. Si vous ne le faites pas, votre programme ne fonctionnera plus en cas
d’appel imbriqué de routines.
L’instruction
bx peut aussi servir à l’appel d’une routine dont l’adresse a été stockée dans
un registre.
Et maintenant je peux vous la vérité sur l'instruction de chargement de l'adresse d'une variable. Je vous avais dit que la seule instruction possible de lecture de la mémoire était l'instruction ldr rn,[rm] mais que l'on pouvait charger une adresse (toto) par l'instruction ldr rn,iAdrtoto avec iAdrtoto déclarée en fin de routine par l'instruction iAdrtoto: .int toto . Mais iAdrtoto n'est pas de la forme [rm] donc hiatus !!!!
En fait, ldr rn,iAdrtoto est une pseudo instruction que le compilateur va remplacer par l'instruction ldr rn,[pc,#ecart] pc étant le registre 15 (program counter) et #ecart, la différence entre l'adresse de l'instruction et l'adresse du label iAdrtoto, différence que le compilateur calcule très bien !!!
Et maintenant je peux vous la vérité sur l'instruction de chargement de l'adresse d'une variable. Je vous avais dit que la seule instruction possible de lecture de la mémoire était l'instruction ldr rn,[rm] mais que l'on pouvait charger une adresse (toto) par l'instruction ldr rn,iAdrtoto avec iAdrtoto déclarée en fin de routine par l'instruction iAdrtoto: .int toto . Mais iAdrtoto n'est pas de la forme [rm] donc hiatus !!!!
En fait, ldr rn,iAdrtoto est une pseudo instruction que le compilateur va remplacer par l'instruction ldr rn,[pc,#ecart] pc étant le registre 15 (program counter) et #ecart, la différence entre l'adresse de l'instruction et l'adresse du label iAdrtoto, différence que le compilateur calcule très bien !!!
Aucun commentaire:
Enregistrer un commentaire