EX05 - Assembleur des processeurs ARM – avec solutions
Exercice 1 : Utilisation des registres
Décrivez succinctement le rôle des 4 registres spéciaux (SP, LR, PC et APSR).
Solution
Ces registres requièrent une attention particulière:
- R13/SP : le “stack pointer” permet de gérer la pile. Il pointe sur le sommet de la pile (Full Descending Stack). La pile implémente le principe LIFO (Last In / First Out).
- R14/LR : le “link register” contient l’adresse de retour. Il est utilisé lors d’appel de fonction avec les instructions
BL
etBLX
. En cas d’appel successif, son contenu doit impérativement être sauvé sur la pile. - R15/PC : le “program counter” pointe sur la prochaine instruction à exécuter.
- APSR : le “application program status register” contient les fanions de conditions (Z, C, V et N) résultants d’opérations arithmétiques et logiques. Il sert aux branchements conditionnels.
Exercice 2 : Développement croisé vs natif
Quelles différences existent-ils entre un développement croisé et un développement natif ?
Solution
Dans un développement croisé, l’édition du code source, l’assemblage/compilation et l’édition de liens s’effectuent sur machine différente (machine hôte - host) que l’exécution de l’application (machine cible - Target).
Dans un développement natif, l’ensemble des opérations s’effectue sur la même machine.
Exercice 3 : Structure d’un module d’assemblage
Quelles sont les sections principales d’un module en langage assembleur ?
Que contiennent ces sections ?
Quelle est l’utilité de la directive .align
?
Solution
Les principales sections:
.section .text
: section pour le code.section .rodata
: section pour les constantes.section .data
: section pour les données avec une valeur initiale non nulle.section .bss
: section pour les données avec une valeur initiale nulle
La directive .align <x>
permet d’aligner le début d’une section afin que son adresse soit un multiple de \(2^x\).
Il est impératif d’utiliser cette directive au début d’une section afin d’éviter que du code, des constantes ou des données soient mal alignés et dégradent les performances du µP.
Exercice 4 : Fonction assembleur et interface C/C++
Implémentez en assembleur la fonction C “factorial”. Implémentez également le fichier d’en-tête permettant d’appeler la fonction depuis du code C/C++. La récursivité doit impérativement être conservée et le fichier doit être compilable pour la cible des TPs.
unsigned long factorial (unsigned n) {
if (n == 0) {
return 1;
}
return n * factorial (n-1);
}
Solution
Interface C permettant son utilisation par des fonctions et méthodes C/C++.
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
extern unsigned long factorial(unsigned n);
#ifdef __cplusplus
}
#endif
Code assembleur implémentant la fonction.
.text
.thumb
.syntax unified
.align 2
.global factorial
.type factorial, %function
factorial:
cmp r0, #0
itt eq
moveq r0, #1
bxeq lr
push {r4, lr}
movs r4, r0
subs r0, #1
bl factorial
muls r0, r4
pop {r4, pc}
.size factorial, .-factorial
Exercice 5 : Boucle “for”
Implémentez en assembleur la fonction C ci-dessous.
struct CheckSum {
unsigned cks1;
unsigned cks2;
};
struct CheckSum checksum (const char* msg, unsigned n) {
struct CheckSum cks = {0, 0};
for (unsigned i=0; i<n; i++) {
cks.cks1 += msg[i];
cks.cks2 += msg[i] * (i+1);
}
return cks;
}
Trouvez l’algorithme le plus performant et réalisez-le.
Solution
Version original:
.text
.thumb
.syntax unified
.align 2
.global checksum
.type checksum, %function
checksum:
push {r4-r6}
movs r3, #0 // cks.cks1 = 0
movs r4, #0 // cks.cks2 = 0
movs r5, #0 // i = 0
b 2f
1: ldrb r6, [r1, r5] // r6 = msg[i]
adds r5, #1 // i++
adds r3, r6 // cks.cks1 += msg[i]
muls r6, r5 // r6 = msg[i] * (i+1)
adds r4, r6 // cks.cks2 += r6
2: cmp r5, r2 // i < n
blt 1b // si i plus petit que -> entre dans la boucle
str r3, [r0] // sinon met cks.cks1 dans la structure passée par r0
str r4, [r0, #4] // et cks.cks2 dans cette même structure (avec offset
// correspondant - cks1 et cks2 étant de type unsigned)
pop {r4-r6}
bx lr
.size checksum, .-checksum
Version optimisée:
struct CheckSum checksum (const char* msg, unsigned n) {
struct CheckSum cks = {0, 0};
for (int i=n-1; i>=0; i--) {
cks.cks1 += msg[i];
cks.cks2 += cks.cks1;
}
return cks;
}
.text
.thumb
.syntax unified
.align 2
.global checksum
.type checksum, %function
checksum:
push {r4-r5}
movs r3, #0
movs r4, #0
b 2f
1: ldrb r5, [r1, r2]
adds r3, r5
adds r4, r3
2: subs r2, #1
bhs 1b
str r3, [r0]
str r4, [r0, #4]
pop {r4-r5}
bx lr
.size checksum, .-checksum
Exercice 6 : Boucle “while”
Implémentez en assembleur la fonction C ci-dessous.
char* strcpy (char* dest, const char* src) {
char* d = dest;
while (*dest++ = *src++);
return d;
}
Solution
.text
.thumb
.syntax unified
.align 2
.global strcpy
.type strcpy, %function
strcpy:
movs r2, r0
1: ldrb r3, [r1]
strb r3, [r2]
adds r2, #1
adds r1, #1
cmp r3, #0
bne 1b
bx lr
.size strcpy, .-strcpy
Exercice 7 : Calcul de parité verticale
Implémentez en assembleur la fonction C ci-dessous.
uint8_t parity (const uint8_t* msg, unsigned n) {
char parity = 0;
while (n>0) {
n--;
parity ^=msg[n];
}
return parity;
}
Solution
.text
.thumb
.syntax unified
.align 2
.global parity
.type parity, %function
parity:
movs r2, #0
b 2f
1: ldrb r3, [r0, r1]
eor r2, r3
2: subs r1, #1
bhs 1b
movs r0, r2
bx lr
.size parity, .-parity