Assembleur ARM
Instructions
Rappel architecture K. Rhofir @2015/16
Instructions Assembleur
• Nous pouvons écrire un programme en binaire si on sait
comment représenter les instructions avec des “0” et des
“1”. Toutefois, le processus est très pénible: qui voudrait
écrire 0x23AB435F4EE7832FFA pour effectuer l’opération
“a = b + c”?
• Un assembleur est un programme qui traduit des mots, en
anglais, dans un fichier texte, en instructions.
• Exemple (a = b + c):
MOV R0, #0x00 ; Adresse de la variable b
LDR R0, [R0] ; Lire la variable b dans le registre R0
MOV R1, #0x01 ; Adresse de la variable c
LDR R1, [R1] ; Lire la variable c dans le registre R1
ADD R2, R0, R1 ; R2 = R0 + R1
MOV R0, #0x02 ; Adresse de la variable a
STR R2, [R0] ; Écrire le registre R2 dans la variable a
Rappel architecture K. Rhofir @2015/16
Revenons à notre exemple d’addition
MOV R0, #0x00 ; Adresse de la variable b
LDR R0, [R0] ; Lire la variable b dans le registre R0
MOV R1, #0x01 ; Adresse de la variable c
LDR R1, [R1] ; Lire la variable c dans le registre R1
ADD R2, R0, R1 ; R2 = R0 + R1
MOV R0, #0x02 ; Adresse de la variable a
STR R2, [R0] ; Écrire le registre R2 dans la variable a
• Pas pratique:
• d’avoir à connaître les adresses de a, b, et c
• d’avoir à utiliser deux instructions (MOV puis LDR) pour les charger
• Solution?
• L’assembleur nous permet de donner un nom à des adresses
mémoires: ce sont les constantes et les variables!
• constante = ne change pas
• variable = peut changer
Rappel architecture K. Rhofir @2015/16
Constantes — syntaxe
• Déclarer une constante (réserve de l’espace en ROM)
nom DCss valeur
• “nom” est le nom de la constante
• “DCss": C=constante, ss indique la taille. Par exemple:
• DC8: constante de 8 bits
• DC32: constante de 32 bits
• “valeur”: la valeur de la constante
• exemple:
a DC32 0xAB
b DC32 0xF2
Rappel architecture K. Rhofir @2015/16
Variables — syntaxe
• Déclarer une variable (réserve de l’espace en mémoire
RAM):
nom DSss nombre
• “nom” est le nom de la variable
• “DSss": S=variable, ss indique la taille. Par exemple:
• DS8: variable de 8 bits
• DS32: variable de 32 bits
• “nombre”: le nombre d’éléments à réserver
• exemple:
; dans notre exemple d’addition, la valeur initiale importe
peu,
; car nous allons la remplacer, mais en général on peut
initialiser
; les variables de la même façon que les constantes
c DS32 1
Rappel architecture K. Rhofir @2015/16
Tableaux (constantes et variables)
• Pour déclarer un tableau:
nom DCss el1 el2 el3 ... ; Constante
nom DSss nombreElements ; Variable
• “nom” est le nom de la variable/constante
• “D*ss”: ss indique la taille
• “el1 el2 el3”: la valeur des éléments du tableau s’il s’agit d’une
constante
• “nombreElements”: le nombre d’éléments dans le tableau s’il s’agit
d’une variable
• exemple:
a DS32 3 ; tableau de 3 mots de 32
bits chacun
b DC8 0x01 0x02 0x03 ; tableau de 3 octets
Rappel architecture K. Rhofir @2015/16
Tableaux
• Les tableaux peuvent être vus comme des chaînes de
variables.
• Une chaîne texte est un exemple de tableau d'octets,
chaque caractère est présenté comme un élément de code
ASCII (0 à 255).
• Par exemple:
strA DC8 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x00
strB DC8 'Hello', 0
• strA est la copie exacte de strB. Lorsque le compilateur
voit une chaîne entourée par des ' ', il la convertit
automatiquement en un ensemble d'octets.
Rappel architecture K. Rhofir @2015/16
Variables et constantes
• Une variable/constante est un nom donné à une adresse de
mémoire.
• Ce sont des créations du langage assembleur pour faciliter la
création d’un programme: il est plus facile de retenir un nom
qu’une adresse de mémoire!
• La directive DC sert à insérer des octets dans la mémoire
ROM du système. L’adresse de ces octets est l’adresse de la
constante.
• La directive DS sert à nommer des octets dans la mémoire
RAM du système. L’adresse de ces octets est l’adresse de la
variable.
• L’assembleur (le programme) s’occupera de remplacer tous
les noms des variables par les adresses correspondant à ces
noms.
Rappel architecture K. Rhofir @2015/16
Exemple d’addition—avec variables & constantes
• Comment représenter notre programme d’addition en utilisant des
variables et constantes?
; Définissons les constantes b et c
b DC32 0xAB
c DC32 0xF2
; Définissons la variable c (pour stocker le résultat)
a DS32 1
; Programme principal
LDR R0, b ; Lire la constante b dans le registre R0
LDR R1, c ; Lire la constante c dans le registre R1
ADD R2, R0, R1 ; Registre R2 = R0 + R1
LDR R3, =a ; Lecture de l’adresse de la variable a
STR R2, [R3] ; Écrire le registre R2 dans la variable a
Rappel architecture K. Rhofir @2015/16
Plus que de la “traduction” d’instructions
• En plus de traduire des mots/mnémoniques en binaire, l’assembleur
interprète aussi le texte de plusieurs façons. Il permet:
• d’associer des mots du programmeur à des adresses de mémoire.
• au programmeur de déclarer des variables et il gère l’adresse de ces
variables à travers les instructions du programme.
• au programmeur d’identifier des fonctions ou des sections de codes avec
des étiquettes (labels). Lorsque l’assembleur décode un appel de
fonction ou un branchement (saut) dans le programme, il remplace les
étiquettes par les adresses ou déplacements (offset) appropriées.
• L’assembleur supporte des directives qui lui disent comment placer le code
en mémoire, comment gérer plusieurs fichiers, comment précompiler le
code—modifier le code avant de le traduire en binaire—et plus. Les
directives sont des mots réservés qui ne génèrent pas de code en binaire,
mais qui dirigent la création du code exécuté.
• L’assembleur permet aussi d’insérer des commentaires dans le code!
Rappel architecture K. Rhofir @2015/16
Exemple de programme sur IAR Workbench
NAME main Nom du fichier/module
PUBLIC __iar_program_start Rend l’étiquette __iar_program_start disponible
pour d’autres fichiers
SECTION .text : CODE (2) Le texte qui suit doit être assemblé en mémoire FLASH
CODE32
Étiquette __iar_program_start: indique à
__iar_program_start
IAR de commencer le programme ici
; a = b + c
main
LDR R0, b
LDR R1, c
ADD R2, R0, R1 Code principal
LDR R3, =a
STR R2, [R3]
B main Saute à l’étiquette main: boucle infinie!
DATA Le texte qui suit doit être assemblé en mémoire FLASH
b DC32 0xAB
c DC32 0xF2
SECTION `.noinit`:DATA (2) Variables en mémoire RAM
a DS32 1
END Fin du fichier/module
Rappel architecture K. Rhofir @2015/16
6 types d’instructions en ARM
Déplacement de données: Transfert de données ou de constante impliquant des
registres seulement.
Accès à la mémoire: l’instruction, un load ou store, lit ou écrit la mémoire. La
valeur lue est mise dans un registre. La valeur écrite provient d’un registre.
Opération arithmétique: addition, soustraction, multiplication, division et plus. Les
calculs s’effectuent sur des registres et peuvent changer les drapeaux de l’unité
d’arithmétique et de logique (ALU).
Opération logique: ET, OU, NON-OU, OU EXCLUSIF et plus. Les calculs
s’effectuent sur des registres et peuvent changer les drapeaux de l’unité
d’arithmétique et de logique (ALU).
Gestion de la séquence d’instructions: saut et branchements. Peuvent être
conditionnels ou inconditionnels, à des adresses directes ou indirectes. Comprend
les appels de fonctions.
Contrôle du système: Comprend toutes les autres instructions contrôlant le
microprocesseur. Permet de gérer le temps, le format des instructions, l’exécution
en pipeline, les interruptions et plus.
Rappel architecture K. Rhofir @2015/16
Déplacement de Données: MOV
• L’instruction
MOV Rn Op1
met l’opérande de type 1 Op1 dans le registre Rn
• Opérande de type 1:
• Constante (valeur immédiate): toujours précédée du symbole #
• Registre
• Registre décalé
• Le décalage est fait avant l’opération. Cinq opérations sont possibles: LSL, LSR, ASR,
ROR et RRX. Ces opérations sont détaillées en Annexe A.
• Exemples:
MOV R0, #1234 ; R0 = 1234
MOV R0, R1 ; R0 = R1
MOV R0, R1, ASR #2 ; R0 = R1 / 4
Rappel architecture K. Rhofir @2015/16
Accès Mémoire: Load/Store
• Les accès à la mémoire se font avec deux instructions:
• LDR (LoaD Register) lit la mémoire et met la valeur lue dans un
registre.
• STR (STore Register) met la valeur d’un registre dans la mémoire.
• Ces instructions ont le format
LDR Rd, Op2
STR Rs, Op2
• Rd et Rs décrivent le registre de destination ou de source
• Op2 est une opérande de type 2
Rappel architecture K. Rhofir @2015/16
Opérande de type 2
• Symbolise tous les modes d’adressage du microprocesseur:
toutes les façons permises pour désigner une adresse de la
mémoire.
• Se découpe ainsi
LDR Rd, [Rb, Offset]
• Rb est le registre de base
• Offset est une opérande de type 1
LDR R0, [R2] ; R0 = Mémoire[R2]
LDR R0, [R2, #4] ; R0 = Mémoire[R2+4]
LDR R0, [R2, R3] ; R0 = Mémoire[R2+R3]
LDR R0, [R1, R2 LSL #2] ; R0 = Mémoire[R1 + (R2 * 4)]
• Pour calculer l’adresse, on additionne Rb et Offset
Rappel architecture K. Rhofir @2015/16
Opérande de type 2
• Pour faciliter les accès aux tableaux, on peut modifier Rb:
• avant le calcul d’accès mémoire (pre-indexing)
• symbole “!”
• après le calcul d’accès à la mémoire (post-indexing).
LDR R0, [R1, #4]! ; R1 = R1 + 4, suivi de R0 = Memoire[R1]
• en dehors des [ ].
LDR R0, [R1], #4 ; R0 = Memoire[R1], suivi de R1
= R1 + 4
Rappel architecture K. Rhofir @2015/16
Accès Mémoire: Load/Store Multiple
• Les instructions LDM (LoaD Multiple) et STM (Store Multiple) permettent
de lire des mots de mémoire contigus et de mettre les valeurs lues dans
plusieurs registres. Une seule instruction LDM peut remplacer plusieurs
instructions LDR si les adresses visées se suivent.
• Les instructions LDM et STM sont utilisées pour lire/écrire des données
de tableaux ou pour sauvegarder/récupérer de l’information sur la pile
(voir les prochains cours).
• Syntaxe:
LDMmm Ra{!}, {Liste de registres}
• mm = DB (Decrement Before), ou IA (Increment After)
• Exemples:
STMDB SP!, {R0, R1} ; R0 = Mem[SP-4], R1 = Mem[SP-8], SP = SP-8
LDMIA SP!, {R0, R1} ; R0 = Mem[SP], R1 = Mem[SP+4], SP = SP+8
Rappel architecture K. Rhofir @2015/16
Récapitulation: MOV vs LDR/STR
• MOV: déplacements entre des registres seulement
MOV R0, #0xFF ; R0 <- 0xFF
MOV R0, R1 ; R0 <- R1
MOV R0, R1 ASR #2 ; R0 <- (R1 / 4)
• LDR/STR: déplacements entre le CPU et la mémoire
LDR R0, [R1] ; R0 <- Memoire[R1]
LDR R0, [R1, #4] ; R0 <- Memoire[R1 + 4]
LDR R0, [R1, R2] ; R0 <- Memoire[R1 + R2]
LDR R0, [R1] #4 ; R0 <- Memoire[R1], R1 <- R1 + 4
STR R0, [R1] ; Memoire[R1] <- R0
STR R0, [R1, #4] ; Memoire[R1 + 4] <- R0
STR R0, [R1, R2] ; Memoire[R1 + R2] <- R0
STR R0, [R1] #4 ; Memoire[R1] <- 0, R1 <- R1 + 4
Rappel architecture K. Rhofir @2015/16
Accès mémoire avec variables
• Les instructions LDR et STR sont utilisées avec la syntaxe suivante pour
accéder aux variables:
• L’assembleur traduit les lignes en plusieurs instructions du processeur
LDR Rd, MaVariable ; Met la valeur de la
variable dans Rd
LDR Rd, =MaVariable ; Met l’adresse de la
variable dans Rd
Rappel architecture K. Rhofir @2015/16
Accès mémoire avec PC
• On peut aussi se servir de PC pour accéder à la mémoire
LDR Rd, [PC #16] ; Rd = Memoire[PC + 16]
• Particularité importante de ARM:
• PC contient l’adresse de l’instruction courante + 8
• PC est “en avance”: il pointe 2 instructions plus loin. Cela est dû à
une optimization de l’architecture ARM nommée “pre-fetching”.
• Donc, dans l’exemple ci-haut, si l’instruction courante est à l’adresse
0x80, nous aurons
0x80 LDR Rd, [PC #16] ; Rd = Memoire[(0x80+8) + 16]
Rappel architecture K. Rhofir @2015/16
Accès mémoire avec variables
LDR Rd, MaVariable ; Met la valeur de la
variable dans Rd
LDR Rd, =MaVariable ; Met l’adresse de la
variable dans Rd
• Toutes les adresses sont sur 32 bits et toutes les instructions sont
sur 32 bits. Une instruction 32 bits ne peut pas contenir un opcode,
un numéro de registre et une adresse de 32 bits…
• Lors d’un LDR R0, MaVariable, l’assembleur placera les items
suivants en mémoire:
• L’adresse de MaVariable sera en mémoire code, définie comme
une constante
• Une instruction LDR Rd, [PC+Offset] mettra l’adresse de
MaVariable dans Rd. Ici, Offset est une constante qui dépend de
la distance entre l’instruction LDR et l’adresse de MaVariable
(constante en code).
• Une autre instruction LDR R0, [Rd] mettra la valeur de
MaVariable dans R0
Rappel architecture K. Rhofir @2015/16
Exercices
Rappel architecture K. Rhofir @2015/16
Annexe 1: Décalage de bits
• LSL, Logical Shift Left, décale les bits vers la gauche et met des zéros à droite. Décaler un bit
vers la gauche équivaut à multiplier par 2. Carry devient égal au bit le plus significatif.
• LSR, Logical Shift Right, décale les bits vers la droite et met des 0 à gauche. Décaler un bit
vers la droite équivaut à diviser un nombre non-signé par 2. Carry devient égal au bit le moins
significatif.
• ASR, Arithmetical Shift Right, décale les bits vers la droite et copie le bit le plus significatif à
gauche. Décaler un bit vers la droite en conservant le bit de signe équivaut à diviser un nombre
signé par 2. Carry devient égal au bit le moins significatif.
• ROR, Rotate Right, décale les bits vers la droite et met le Carry à gauche. Carry devient égal
au bit le moins significatif.
• RRX, Rotate Right eXtended équivaut à ROR #1.
GIF-1001
Cours 8, p.18
Rappel architecture K. Rhofir @2015/16
Annexe 2: Qu'est-ce qu'une variable?
• Pour le microprocesseur, les variables n'existent pas: le microprocesseur
lit et exécute des instructions. Certaines instructions (LOAD et STORE)
lui demandent d'accéder à certaines adresses de la mémoire. La plupart
des instructions lui demandent de modifier ses registres internes.
Pour le programmeur et le mathématicien, une variable est un objet ayant
une certaine valeur qui peut changer dans le temps. Pour le
programmeur, une variable a un type, c'est-à-dire un format et une taille
(exemple: un entier sur 32 bits) et une portée (la variable peut être
utilisée dans la fonction seulement, dans le fichier seulement ou dans
tout le programme).
Le compilateur (ou l'assembleur) et l'éditeur de liens font la relation entre
les variables du programmeur et le monde du microprocesseur. Ces
programmes associent une adresse de mémoire (ou un registre) aux
variables que le programmeur déclare. Lorsque le programme du
programmeur lit ou écrit une variable, le compilateur transforme cette
lecture ou écriture en instructions qui accèderont aux adresses de
mémoires (ou aux registres) allouées aux variables...
Rappel architecture K. Rhofir @2015/16
Rappel: instructions
• De quoi une instruction est-elle constituée?
• “Opcode” (ou code d’opération): code identifiant quelle instruction est
effectuée (MOV, LDR, etc.)
• Paramètres: un ou plusieurs, dépendent de l’opcode.
Rappel architecture K. Rhofir @2015/16
Rappel: ARM
• Les instructions sont encodées sur combien de bits?
• 32!
• Quelle est la valeur de PC?
• L’adresse de l’instruction courante + 8
• Toujours 2 instructions “en avance”
• Quelle est la différence entre MOV et LDR/STR?
• MOV: entre les registres, à l’intérieur du microprocesseur
• LDR/STR: entre le microprocesseur et la mémoire
Rappel architecture K. Rhofir @2015/16
Instructions arithmétiques et logiques
• Les opérations mathématiques et logiques ont la forme
INSTRUCTION Rd, Rs, Op1
• Où
• Rd est le registre de destination
• Rs est un registre source
• Op1 est une opérande de type 1
• Le format de l’instruction ADD, par exemple, est:
ADD Rd, Rs, Op1 ; Rd = Rs + Op1
ADD R0, R0, #1 ; R0 = R0 + 1
ADD R0, R0, R1 ; R0 = R0 + R1
ADD R0, R0, R1, LSL #1 ; R0 = R0 + (R1 * 2)
Rappel architecture K. Rhofir @2015/16
Exemples
• Soustraction
SUB R0, R0, #1 ; R0 = R0 - 1
SUB R0, R0, R1 ; R0 = R0 - R1
• Décalage
LSL R0, R0, #1 ; R0 = R0 * 2
ASR R0, R0, #2 ; R0 = R0 / 4 (préserve le signe)
• “Et” logique
AND R0, R0, #1 ; R0 = R0 ET 1
AND R0, R0, R1 ; R0 = R0 ET R1
• Prendre le négatif
RSB R0, R0, #0 ; R0 = 0 - R0, donc R0 = -R0
Rappel architecture K. Rhofir @2015/16
Problème à résoudre
• But: comparer deux nombres placés dans R1 et R2
• Si R1 > R2, mettre R3 dans R0
• Si R2 >= R1, mettre R4 dans R0
• Comment faire?
• Nous allons avoir besoin de trois mécanismes:
• Une instruction pour comparer R1 et R2
• Un endroit pour stocker le résultat de la comparaison
• Des instructions pouvant être activées si la comparaison répond à
certains critères
Rappel architecture K. Rhofir @2015/16
Rappel: registre de statut (CPSR)
• Un registre de statut décrit l’état du processeur
Dépassement (overflow)
Retenue (carry/borrow )
Valeur nulle (zero)
Nombre négatif (negative )
Rappel architecture K. Rhofir @2015/16
Instructions avec drapeaux
• Les instructions arithmétiques et logiques changent les drapeaux de
l’ALU, lorsque l’option “S” est rajoutée après le nom de l’instruction
INSTRUCTIONS Rd, Rs, Op1 ; exécute l’instruction,
; et met à jour les drapeaux
• Exemple:
SUBS R0, R1, R2 ; R0 = R1 - R2
; et met à jour les drapeaux
Quels seront les drapeaux N et Z du CPSR?
Rappel architecture K. Rhofir @2015/16
CPSR: détection de conditions
• N: Détection de signe négatif
• 1 si résultat < 0, 0 autrement
• Z: Détection de zéro
• 1 si résultat = 0, 0 autrement
• Souvent utilisé pour détecter les égalités
• C: Détection de retenue (“carry)” ou d’emprunt (“borrow”)
• 1 si l’opération a impliqué une retenue, 0 autrement
• Ex. retenue d’addition de nombres positifs
• V: Détection de dépassements (overflow)
• 1 si l’opération a impliqué un dépassement, 0 autrement
• Ex. dépassement signé lors d’une addition
Rappel architecture K. Rhofir @2015/16
Instructions conditionnelles
• L’instruction
met l’opérande de type 1 Op1 dans le registre Rn, si la condition cc est
vraie
MOVcc Rn Op1
• Exemple:
MOVEQ R3, R1 ; R3 = R1 seulement si
le drapeau Z est 1
ADDNE R2, R2, R1 ; R2 = R2 + R1 seulement si
le drapeau Z est 0
Rappel architecture K. Rhofir @2015/16
Instructions conditionnelles
Code assembleur:
MOVEQ R3, R1 ; R3 = R1 seulement si
le drapeau Z est 1
Équivalent, en C, à:
if (Z == 1) {
R3 = R1;
}
Code assembleur:
ADDNE R2, R2, R1 ; R2 = R2 + R1 seulement si
le drapeau Z est 0
Équivalent, en C, à:
if (Z == 0) {
R2 = R2 + R1;
}
Rappel architecture K. Rhofir @2015/16
Codes de condition (CC)
• Plusieurs instructions s’exécutent si une condition est
rencontrée.
• Toutes les conditions sont évaluées à partir des
drapeaux de L’ALU et assument que ceux-ci ont été
déterminés auparavant.
• Par exemple, la condition EQ (equal) assume qu’une
soustraction ou comparaison a été faite avant: si le résultat de
l’opération est 0, le drapeau Z sera à 1 et la condition EQ sera
rencontrée.
• Les drapeaux N (Negative), Z (Zero), C (Carry) et V
(Overflow) servent à évaluer toutes les conditions.
• Les drapeaux et les conditions à évaluer changent si les
nombres comparés sont signés ou s’ils ne le sont pas.
Rappel architecture K. Rhofir @2015/16
Codes de condition
Manuel de référence ARM, p. A8-8
Rappel architecture K. Rhofir @2015/16
Problème à résoudre
• But: comparer deux nombres placés dans R1 et
R2
Code Symbole
• Si R1 > R2, mettre R3 dans R0
GT >
• Si R2 >= R1, mettre R4 dans R0 GE >=
LT <
• Comment faire?
LE <=
CMP R1, R2 ; calcule R1 - R2, change
les drapeaux
MOVGT R0, R3 ; si R1 > R2, R0 = R3
MOVLE R0, R4 ; si R2 >= R1, R0 = R4
Rappel architecture K. Rhofir @2015/16
Problème à résoudre
• But: Code Symbole
GT >
• R0 = abs(R1 - R2) ; valeur absolue
GE >=
• Comment faire? Indices:
LT <
• R0 = R1 - R2 si R1 > R2
LE <=
• R0 = R2 - R1 sinon
• l’instruction RSB peut être utilisée pour calculer le négatif d’un registre
RSB R0, R0, #0 ; R0 = -R0
• Solution (à 3 instructions):
CMP R1, R2 ; calcule R1 - R2, change les drapeaux
SUBGT R0, R1, R2 ; si R1 > R2, R0 = R1 - R2
SUBLE R0, R2, R1 ; si R1 <= R2, R0 = R2 - R1
• Solution (à 2 instructions):
SUBS R0, R1, R2 ; calcule R1 - R2, change les drapeaux
RSBLE R0, R0, #0 ; si R1 <= R2, R0 = -R0 (donc R0 = R2 -
R1)
Rappel architecture K. Rhofir @2015/16
Exemple: Addition de variables sur 64 bits
NAME Additionne64Bits ; Nom du fichier/module; Addition sur 64 bits en little endian
PUBLIC Additionne64Bits ; Rend l'étiquette/fonction Additionne64Bits disponible pour d'autres
fichiers
SECTION .intvec : CODE (2) ; Le texte qui suit doit être assemblé dans la mémoire FLASH
CODE32 ; Les instructions qui suivent sont sur 32 bits
Additionne64Bits
LDR R0, =Op1 ; Met l'adresse de Op1 dans R0
LDR R1, [R0] ; Met les 4 octets les moins significatifs d'Op1 dans R1
LDR R2, [R0,#4] ; Met les 4 octets les plus significatifs d'Op1 dans R2
LDR R0, =Op2 ; Met l'adresse de Op2 dans R0
LDR R3, [R0] ; Met les 4 octets les moins significatifs d'Op2 dans R3
LDR R4, [R0,#4] ; Met les 4 octets les plus significatifs d'Op2 dans R4
ADDS R5, R1,R3 ; R5 = R1+R3, additionne les octets les moins significatifs, met à jour Carry
ADC R6, R2,R4 ; Additionne les octets les plus significatifs avec la retenue
LDR R0, =Res ; Met l'adresse du résultat dans R0
STR R5, [R0] ; Sauvegarde le résultat: bit les moins significatifs
STR R6, [R0,#4] ; Sauvegarde le résultat: bit les plus significatifs
Fin
B Fin ; Terminé
DATA ; Le texte qui suit doit être assemblé dans la mémoire FLASH, constantes
Op1 DC32 0x11111111, 0x22222222 ; Déclaration de l'opérande 1 sur 2 fois 32bits
Op2 DC32 0x33333333, 0x44444444 ; Déclaration de l'opérande 2 sur 2 fois 32bits
SECTION `no_init`: DATA (2) ; Le texte qui suit doit être assemblé dans la mémoire RAM
Res DS32 2 ; Déclaration du résultat sur 2 fois 32 bits
END ; Fin du fichier/module
Rappel architecture K. Rhofir @2015/16
Annexe 1: Décalage de bits
• LSL, Logical Shift Left, décale les bits vers la gauche et met des zéros à droite. Décaler un bit
vers la gauche équivaut à multiplier par 2. Carry devient égal au bit le plus significatif.
• LSR, Logical Shift Right, décale les bits vers la droite et met des 0 à gauche. Décaler un bit
vers la droite équivaut à diviser un nombre non-signé par 2. Carry devient égal au bit le moins
significatif.
• ASR, Arithmetical Shift Right, décale les bits vers la droite et copie le bit le plus significatif à
gauche. Décaler un bit vers la droite en conservant le bit de signe équivaut à diviser un nombre
signé par 2. Carry devient égal au bit le moins significatif.
• ROR, Rotate Right, décale les bits vers la droite et met le Carry à gauche. Carry devient égal
au bit le moins significatif.
• RRX, Rotate Right eXtended équivaut à ROR #1.
GIF-1001
Cours 8, p.18
Rappel architecture K. Rhofir @2015/16
Annexe 2: Qu'est-ce qu'une variable?
• Pour le microprocesseur, les variables n'existent pas: le microprocesseur
lit et exécute des instructions. Certaines instructions (LOAD et STORE)
lui demandent d'accéder à certaines adresses de la mémoire. La plupart
des instructions lui demandent de modifier ses registres internes.
Pour le programmeur et le mathématicien, une variable est un objet ayant
une certaine valeur qui peut changer dans le temps. Pour le
programmeur, une variable a un type, c'est-à-dire un format et une taille
(exemple: un entier sur 32 bits) et une portée (la variable peut être
utilisée dans la fonction seulement, dans le fichier seulement ou dans
tout le programme).
Le compilateur (ou l'assembleur) et l'éditeur de liens font la relation entre
les variables du programmeur et le monde du microprocesseur. Ces
programmes associent une adresse de mémoire (ou un registre) aux
variables que le programmeur déclare. Lorsque le programme du
programmeur lit ou écrit une variable, le compilateur transforme cette
lecture ou écriture en instructions qui accèderont aux adresses de
mémoires (ou aux registres) allouées aux variables...
Rappel architecture K. Rhofir @2015/16
Modification de la séquence d’exécution
• Par défaut, les instructions s’exécutent séquentiellement et PC
est incrémenté automatiquement par le microprocesseur entre
chaque instruction
• PC = PC + 4 (taille d’une instruction)
• Dans nos programme, il arrive que l’on veuille exécuter autre
chose que la prochaine instruction:
• Saut direct à une instruction
• Énoncé conditionnel “si”
• Boucle: “répète N fois” ou “répète tant que”
• Appel de fonction
• Il est possible de contrôler la séquence d’exécution, en
assembleur, avec des instructions qui modifient PC.
Rappel architecture K. Rhofir @2015/16
Sauts Absolus
• PC est un registre et peut être modifié comme les autres registres, avec
la plupart des instructions
• Modifier la valeur de PC correspond à effectuer un saut absolu à une
adresse.
• Exemples:
MOV PC, 0x80 ; PC = 0x80
MOV PC, R0 ; PC = R0
LDR PC, [R0] ; PC = Memoire[R0]
ADD PC, R0, #0 ; PC = R0 + 0
Rappel architecture K. Rhofir @2015/16
Sauts relatifs
• Dans plusieurs cas, on ne veut pas exécuter l’instruction à l’adresse X, mais
exécuter l’instruction qui se trouve à N octets de l’instruction courante: on
veut « déplacer » PC par rapport à sa valeur actuelle
• L’instruction B (Branch) modifie PC relativement à sa valeur actuelle:
B Offset
• Offset est la valeur du déplacement, signé. L’adresse de la prochaine instruction
est calculée comme suit
• PC = Adresse de l’instruction B + Offset + 8*
• Rappelez-vous: PC contient l’adresse de la prochaine instruction + 8
• Comme le programmeur ne veut pas être obligé de compter l’offset, on peut
remplacer « Offset » par une étiquette: l’assembleur calculera la valeur pour
nous.
B #12 ; Saute à l’adresse de
l’instruction + 20
MonEtiquette B MonEtiquette ; Boucle infinie!
Rappel architecture K. Rhofir @2015/16
Branchements conditionnels
• Un énoncé conditionnel se code habituellement avec au moins deux instructions:
• une pour faire le test ou la comparaison
• une pour effectuer (ou pas) le branchement en fonction de la comparaison.
• Le résultat du test ou de la comparaison est entreposé dans les drapeaux de l’ALU
et l’instruction de branchement est conditionnelle. Si la condition est rencontrée, on
effectue le branchement. Sinon, on exécute la ligne suivante… Pour cette raison,
les conditions de branchement sont souvent inversées.
• Exemple: Si MaVar vaut 4, exécute une tâche:
if (MaVar == 4) {
// exécute une tâche…
exemple en C
}
// le programme continue…
LDR R0, MaVar ; Met la valeur de MaVar dans R0
CMP R0, 4 ; Change les drapeaux comme R0-4
BNE PasEgal
; execute une tâche…
PasEgal
; le programme continue… assembleur
Rappel architecture K. Rhofir @2015/16
Branchements conditionnels
• Autre exemple: if/else
if (MaVar == 4) { exemple en C
// exécute une tâche…
} else {
// exécute une autre tâche…
}
// le programme continue…
LDR R0, MaVar ; Met la valeur de MaVar dans R0 assembleur
CMP R0, 4 ; Change les drapeaux comme R0-4
BNE PasEgal
; execute une tâche…
B Continue
PasEgal
; exécute une autre tâche…
Continue
; le programme continue...
Rappel architecture K. Rhofir @2015/16
Branchements conditionnels
• Afin d’éviter de changer les drapeaux et pour faire un énoncé
conditionnel avec une seule instruction:
• CBZ (Conditional Branch Zero)
• CBNZ (Conditional Branch Non-Zero).
CBZ Rn, Offset ; Branchement à l’Offset si
Rn est égal à 0
CBNZ Rn, Offset ; Branchement à l’Offset si
Rn n’est pas égal à 0
Rappel architecture K. Rhofir @2015/16
Boucles
• Une boucle (répète N fois ou tant que) est constituée de trois opérations:
• initialiser la boucle
• condition d’arrêt
• opération mathématique qui mènera à la réalisation de la condition d’arrêt.
• En assembleur, le début de toutes les boucles est identifié par une étiquette qui
permet de revenir au début de la boucle
• Voici un exemple de boucle qui se répète cinq fois:
for (int i = 0; i < 5; ++i) { exemple en C
// tâche à l’intérieur de la boucle
}
// le programme continue…
InitDeBoucle assembleur
MOV R4,#5
DebutDeBoucle
; tâche à l’intérieur de la boucle
SUBS R4, R4, #1 ; R4 = R4 - 1, change les
drapeaux
BNE DebutDeBoucle ; Condition d’arrêt
; le programme continue…
Rappel architecture K. Rhofir @2015/16
Appel de Fonction et Adresse de Retour
• Appeler une fonction signifie mettre PC au début de la fonction pour exécuter
les instructions constituant la fonction.
• Retourner d’une fonction signifie reprendre l’exécution d’où l’appel s’est fait. Il
faut revenir à l’endroit où nous étions: c’est l’adresse de retour.
• Une fonction est un ensemble d’instructions à une adresse donnée. Cette
adresse est identifiée par une étiquette.
• Mauvais exemple d’appel de fonction:
Main Fonction
B MaFonction
; Tâche dans la fonction
AdresseDeRetour
B AdresseDeRetour
MOV R0, 0
• Pourquoi est-ce un mauvais exemple?
• Il est impossible d’appeler la fonction de deux endroits différents…
Rappel architecture K. Rhofir @2015/16
Appel de Fonction et Adresse de Retour
• Pour pouvoir revenir à plus d’un endroit, il faut sauvegarder l’adresse de
retour avant de faire le branchement.
• Instruction BL (Branch and Link):
BLcc Adresse ; met l’adresse de retour dans LR
• Après l’exécution de la fonction, on place PC = LR pour continuer
l’exécution après l’endroit où la fonction a été appelée, avec l’instruction
BX (Branch and eXchange):
BX Rm ; PC = Rm (Rm peut être n’importe
quel registre)
Main Fonction
BL MaFonction ; LR = PC-4
MOV R0, 0 ; Tâche dans la fonction
BL MaFonction ; LR = PC-4 BX LR ; PC = LR
ADD R0, R0, 1
Rappel architecture K. Rhofir @2015/16
Appel de Fonction et Adresse de Retour
• Que ce passe-t-il si une fonction appelle une autre fonction?
Main Fonction1 Fonction2
; Debut Fonction1
BL Fonction2
; Debut Main ; Code Fonction 2
; Suite 1 Fonction1
BL Fonction1 BX LR
BL Fonction2
; Suite Main
; Suite 2 Fonction1
BX LR
• La séquence de gestion des adresses de retour devient:
• LR = “Suite Main”
• LR = “Suite 1 Fonction1”
• PC = LR, donc PC = “Suite 1 Fonction1”
• LR = Suite 2 Fonction1
• PC = LR, donc PC = “Suite 2 Fonction1”
• PC = LR, donc PC = “Suite 2 Fonction1”… Le BX LR de Fonction1 “retourne" au
mauvais endroit!
Rappel architecture K. Rhofir @2015/16
La pile
• La pile est une structure de données qui permet d’empiler et de dépiler
des données. Le pile est un espace de la mémoire qui est géré comme
une pile de feuilles: mettre ou retirer une donnée se fait toujours à partir
du dessus de la pile.
• Le registre SP (Stack Pointer) devrait indiquer le dessus de la pile en tout
temps.
• PUSH
PUSH {Rs} ; Place le contenu de Rs sur la pile, SP = SP - 4
PUSH {R0, R1, R2} ; Place le contenu de R0, R1, et R2 sur la pile,
; SP = SP - 12
• Permet de mettre une (ou plusieurs) donnée(s) sur la pile.
• POP
POP {Rd} ; Place le contenu sur la pile dans Rd, SP = SP + 4
POP {R0, R1, R2} ; Place le contenu sur la pile dans R2, R1, et R0
; (dans l’ordre), SP = SP + 12
• Permet de récupérer une (ou plusieurs) donnée(s) de la pile.
Rappel architecture K. Rhofir @2015/16
Préparation d’une pile
• La pile est habituellement:
• descendante: lors d’un PUSH, SP diminue de 4 octets
(pourquoi 4?)
• placée après les données en RAM, donc à une adresse
supérieure
• Exemple:
SECTION .text : CODE (2)
CODE32
main
LDR SP, =MaPile ; Préparons une pile (de 64 octets)
ADD SP, SP, #64 ; La pile descend, donc il faut commencer à la
fin
SECTION `.noinit`:DATA(2)
MaVar DS32 1 ; Variable
MaPile DS32 16 ; Pile de 64 octets (16*4 = 64)
Rappel architecture K. Rhofir @2015/16
Appel de fonction et adresse de retour, avec pile
• Que ce passe-t-il si une fonction appelle une autre fonction?
Main Fonction1 Fonction2
; Debut Fonction1
BL Fonction2
; Debut Main ; Code Fonction 2
; Suite 1 Fonction1
BL Fonction1 BX LR
BL Fonction2
; Suite Main
; Suite 2 Fonction1
BX LR
Main Fonction1 Fonction2
PUSH {LR}
; Debut Fonction1
BL Fonction2 PUSH {LR}
; Debut Main
; Suite 1 Fonction1 ; Code Fonction 2
BL Fonction1
BL Fonction2 POP {LR}
; Suite Main
; Suite 2 Fonction1 BX LR
POP {LR}
BX LR
Rappel architecture K. Rhofir @2015/16
Exemple d’appel de fonction
• L’instruction BL commande un branchement
• Le nom de la fonction suit l’instruction BL
• L’éditeur de lien convertira le nom de la fonction en adresse
Code C
Retour = FonctionSansParametre();
BL FonctionSansParametre Code Assembleur
Rappel architecture K. Rhofir @2015/16
Appel de fonctions — conventions
• Paramètres:
• On se sert de R0 à R3 lorsqu’il y en a 4 ou moins
• S’il y en a plus?
• On utilise la pile… et/ou la mémoire
• Valeur de retour:
• On se sert de R0 lorsqu’il y en a 1 ou moins
• S’il y en a plus?
• On utilise la pile… et/ou la mémoire
Rappel architecture K. Rhofir @2015/16
Appel de fonction: 1 paramètre et 1 retour
• On place la valeur du paramètre dans R0 juste avant l’appel
• L’instruction BL commande un branchement
• R0 contient la valeur de retour une fois la fonction exécutée
Retour = FonctionAUnParametre(0x12);
Code C
Code Assembleur
MOV R0, #0x12 ; R0 contient le paramètre de la fonction
BL FonctionAUnParametre ; Appel de la fonction
MOV R3, R0 ; Récupère le résultat de la fonction
int FonctionAUnParametre (int param) Code C
{ return param + 1; }
Code Assembleur
FonctionAUnParametre
ADD R0, R0, #1 ; R0 contient le paramètre de la fonction
BX LR ; R0 contient le résultat de la fonction
Rappel architecture K. Rhofir @2015/16
Exemple
• Exemple de fonction
MOV R0, #8 ; Nous voulons que R5 = Fonction(8)
BL MaFonction
MOV R5, R0
MaFonction ; R0 = paramètre, R0 = valeur de retour
PUSH {LR} ; Préserve LR
ADD R4, R0, R0 ; Calcule le double du paramètre passé en entrée
MOV R0, R4 ; R0 = valeur de retour
POP {LR} ; Restaure R4 à sa valeur initiale
BX LR
• Quel est le problème?
• Qu’arrive-t-il à R4?
Rappel architecture K. Rhofir @2015/16
Préservation de l’environnement
• Le nombre de registres étant limité, on ne veut pas qu’une fonction
remplace le contenu des registres
• Problème:
• La fonction ne connait pas le nom des registres qui sont utilisés par le code
qui fait l’appel de la fonction
• Le code qui fait appel à la fonction ne connait pas le nom des registres qui
sont utilisés par la fonction
• Solution:
• La fonction “protège” le contenu des registres qu’elle utilise
• Méthode utilisée:
• On protège le contenu d’un registre en le sauvegardant sur la pile avec
PUSH
• On récupère ce contenu avec POP
Rappel architecture K. Rhofir @2015/16
Retour sur l’exemple
• Le code qui fait l’appel préserve le contenu de R0 pour ne pas le
perdre lors du retour de la fonction
• La fonction préserve le contenu de R4 pour ne pas le corrompre
en faisant ses calculs
• La valeur de retour est placée dans R5
PUSH {R0}
MOV R0, #8 ; Nous voulons appeler FonctionQuiPreserve(8)
BL FonctionQuiPreserve
MOV R5, R0
POP {R0}
FonctionQuiPreserve ; R0 = paramètre, R0 = valeur de retour
PUSH {R4, LR} ; Préserve LR et R4 -- nous allons nous en servir
ADD R4, R0, R0 ; Calcule le double du paramètre passé en entrée
MOV R0, R4 ; R0 = valeur de retour
POP {R4, LR} ; Restaure R4 et LR à leur valeur initiale
BX LR
Rappel architecture K. Rhofir @2015/16
Appel de fonction: 1—4 paramètres et 1 retour
• R0, R1, R2 et R3 servent à passer les paramètres
• R0 sert à retourner la valeur de retour
Retour = FonctionAMoinsDe5Parametres(0x12, 0x23, 0x34, 0x45); Code C
MOV R0, #0x12 ; Paramètre 1 Code Assembleur
MOV R1, #0x23 ; Paramètre 2
MOV R2, #0x34 ; Paramètre 3
MOV R3, #0x45 ; Paramètre 4
BL FonctionAUnParametre ; Appel de fonction
MOV R4, R0 ; Valeur de sortie
int FonctionAMoinsDe5Parametres(int P0, int P1, int P2, int P3) Code C
{ return P0 + P1 + P2 + P3; }
FonctionAMoinsDe5Parametres Code Assembleur
ADD R0, R0, R1 ; Calcul de la somme
ADD R0, R0, R2
ADD R0, R0, R3
BX LR ; Fonction terminée
Rappel architecture K. Rhofir @2015/16
Cas à plus de 4 paramètres: par la pile
• On utilise R0 à R3
• Si on en veut plus, on utilise la pile
• Il faut tenir compte des registres qu’on préserve en début de fonction avant
de lire des valeurs dans la pile
MOV R0, #1
MOV R1, #2
MOV R2, #3
MOV R3, #4
MOV R5, #5 ; Plaçons le 5e paramètre dans R5
PUSH {R5}
BL FonctionA5Parametres
MOV R8, R0
POP {R5}
FonctionsA5Parametres
PUSH {R9, LR} ; Sauvegarde LR et R9 (nous l’utiliserons)
LDR R9, [SP, #8] ; R9 = paramètre 5
ADD R0, R0, R9 ; retour = paramètre 0 + paramètre 5
POP {R9, LR} ; Restaure LR et R9
BX LR
Rappel architecture K. Rhofir @2015/16
Autres cas à plus de 4 paramètres
• Les paramètres sont indépendants les uns des autres:
• On les place un par un sur la pile et ils sont lus un par un
• Les paramètres font partie d’une chaîne ou d’un vecteur:
• On place l’adresse du début des valeurs sur la pile
• On place le nombre de valeurs sur la pile
• La fonction peut lire en boucle
• Les paramètres font partie d’une structure dont les éléments n’occupent pas tous
le même nombre d’octets:
• On place l’adresse du début de la structure sur la pile
• On place le nombre de mots utilisés pour mémoriser la structure
• La fonction doit lire la pile en tenant compte des longueurs variées des valeurs de la
structure
• Ça correspond à passer un pointeur ou une référence en langage plus évolué que
l’assembleur (natif ou évolué)
Rappel architecture K. Rhofir @2015/16
Cas à plusieurs retours
• R0 peut quand même servir à retourner une valeur
• Le code qui fait appel à la fonction passe des valeurs
d’adresse en paramètre (par R0 jusqu’à R3 et/ou par la pile)
• Les adresses passées pointent sur des espaces mémoires où
la fonction pourra écrire sans causer de problème au code qui
lui fait appel
• La fonction fait ses calculs et elle utilise les valeurs d’adresses
pour savoir où sauvegarder les résultats à retourner
• Le code qui a fait l’appel retrouve les valeurs retournées aux
adresses qu’il a passé en paramètre
• Le principe fonctionne tant pour des variables indépendantes
que pour des chaînes, des vecteurs, des structures, etc.
Rappel architecture K. Rhofir @2015/16
Annexe: La pile, plus que pour les retours
• La pile sert à entreposer les adresses de retours comme vu
précédemment.
• La pile sert aussi à passer des paramètres
• La pile sert à sauvegarder les registres utilisés dans une fonction.
• Souvent, les variables locales (dont la portée se limite à une fonction),
sont de l’espace mémoire sur la pile allouée dynamiquement (le
compilateur modifie SP au début de la fonction et à la fin pour réserver
de l’espace mémoire). Sinon les variables locales sont des registres.
• La pile sert à effectuer des calculs mathématiques comme réaliser une
chaînes d’additions et de multiplications
• La pile sert à sauvegarder le contexte d’une tâche lors d’interruptions
(voir prochain cours!)
• La pile est une structure de donnée fondamentale pour les ordinateurs.
Tous les processeurs ont un pointeur de pile…
Rappel architecture K. Rhofir @2015/16