This is an old revision of the document!
Table of Contents
Architecture des Ordinateurs
Supports : http://dept-info.labri.fr/ENSEIGNEMENT/archi/
Emploi du Temps : Groupe A3
Assembleur y86
y86 est un langage assembleur simplifié basé sur l'architecture des processeurs x86 (IA-32). Les mots mémoires sont alignés sur 32 bits et utilise la convention little endian.
Le y86 utilise 8 registres généraux de 32 bits, dont certains ont des rôles historiques.
- %eax : le registre d'accummulation
- %ecx : le registre de compteur utilisé dans les boucles
- %edx : un registre auxiliaire
- %ebx : le registre base index utilisé comme pointeur sur la base d'un tableau
- %esp : le registre stack pointer qui pointe sur le sommet du cadre d'appel de la fonction courante, c''est-à-dire le sommet de la pile
- %ebp : le registre frame base pointer qui pointe la base du cadre d'appel de la fonction courante
- %esi : le registre source index utilisé lors des copies de suite d'octets
- %edi : le registre destination index utilisé lors des copies de suite d'octets
En outre, le registre %eip (instruction pointer) pointe sur l'adresse de la prochaîne instruction à exécuter. Ce registre est modifié automatiquement à chaque exécution et peut être manipulé par des instruction du type jmp, call, ret.
If then
Pour effectuer un branchement conditionnel qui dépend d'un test entre deux registres %eax et %ebx, on commence typiquement à comparer ces deux registres par soustraction, ce qui a pour effet de modifier les codes de condition (Z pour zéro, O pour overflow et S pour strictement négatif). Il suffit alors d'effectuer le branchement approprié jle, jl, je, jne, jge, jg (l = lower, g = greater, e = equal).
long a = 2, b = 3, c; if (a < b) c = 1;
- ifthen.ys
.pos 0 mrmovl a,%eax mrmovl b,%ebx subl %ebx,%eax # on calcule a-b (dans ce sens) jge endif # on teste la condition de sortie : a-b >= 0 ? then: irmovl 1,%ecx rmmovl %ecx,c # c = 1 endif: halt .pos 0x100 a: .long 2 b: .long 3 c: .long 0
Astuce : L'instruction subl modifie la registre cible, ce qui présente un inconvénient. Dans le cas où l'on souhaite tester si un registre %eax est égal à zéro (ou non) avec je (ou jne), une astuce consiste à effectuer l'opération andl %eax,%eax
If then Else
long a = 2, b = 3, c; if (b <= a) c = 1; else c = 2;
- ifthenelse.ys
.pos 0 mrmovl a,%eax mrmovl b,%ebx subl %eax,%ebx # on calcule b-a (dans ce sens) jg else # on teste la condition du else : b-a > 0 ? then: irmovl 1,%ecx rmmovl %ecx,c # c = 1 jmp endif else: irmovl 2,%ecx rmmovl %ecx,c # c = 2 endif: halt .pos 0x100 a: .long 2 b: .long 3 c: .long 0
Boucle For
long sum = 0; for(i = 1 ; i <= 10 ; i++) sum += i;
- sum.ys
.pos 0 mrmovl sum,%edx # %edx: sum irmovl 1,%eax # %eax: i = 1 loop: rrmovl %eax,%ebx isubl 10,%ebx # on calcule i-10 jg end # condition arrêt si i-10 > 0 addl %eax,%edx # sum += i iaddl 1,%eax # i++ jmp loop end: rmmovl %edx,sum halt .pos 0x100 sum: .long 0
Note Bene : On peut un peu simplifier ce code en parcourant la boucle à l'envers… la comparaison à zéro est toujours plus facile à écrire
Pointeurs
long a = 1, b = 0; long * p = &a; b = *p; /* b = 1 */ *p = 2; /* a = 2 */
Dans le code suivant, on utilisera le registre %esi pour désigner le pointeur p.
irmovl a,%esi # p = &a mrmovl (%esi),%eax # b = *p rmmovl %eax,b irmovl 2,%eax # *p = 2 rmmovl %eax,(%esi) .pos 0x100 a: .long 1 b: .long 0
Les tableaux
On rappelle que l'étiquette t représente une adresse immédiate. La notation (%ebx) désigne l'adresse indirecte en mémoire pointée par le registre %ebx. La notation 4(%ebx) correspond à l'adresse (%ebx)+4. Notons qu'un long compte pour 4 octets.
irmovl t,%ebx # l'adresse t est mis dans %ebx, qui joue le rôle de pointeur mrmovl (%ebx),%eax # load: %eax = t[0] ... rmmovl %ecx,(%ebx) # t[0] <- %ecx rmmovl %ecx,4(%ebx) # t[1] <- %ecx .pos 0x100 t: .long 1 # tableau t d'adresse 0x100 et de taille 4 long .long 2 .long 3 .long 4
expliquer la variante d'adressage indexé : t(%eax)
Décalage des valeurs dans un tableau
for(i = 0 ; i < n-1; i++) t[i] = t[i+1];
- decalage.ys
.pos 0 mrmovl n,%ecx irmovl t,%ebx loop: isubl 1,%ecx # n-- je end mrmovl 4(%ebx),%eax # t[0] <- t[1] rmmovl %eax,(%ebx) iaddl 4,%ebx # t += 4 jmp loop end: halt .pos 0x100 n: .long 5 t: .long 1 .long 2 .long 3 .long 4 .long 5
Appel de fonction et Pile
long f(long i, long j) { return (i+j); } long a = f(1,2);
- function.ys
.pos 0 irmovl stack,%esp # initialisation du pointeur de pile (stack pointer) jmp main f: mrmovl 4(%esp),%eax # on récupère le premier paramètre (a) mrmovl 8(%esp),%ecx # on récupère le second paramètre (b) addl %ecx,%eax # variable de retour dans %eax ret main: mrmovl b,%eax # on empile les paramètres en sens inverse pushl %eax mrmovl a,%eax pushl %eax call f # appel de la fonction f() aret: popl %ecx # on dépile les 2 paramètres avec popl popl %ecx # ou iaddl 8,%esp rmmovl %eax,res halt .pos 0x100 a: .long 1 b: .long 2 res: .long 0 .pos 0x200 # adresse de base de la pile stack:
Tout d'abord, il faut noter que la pile stocke les données dans l'ordre décroissant des adresses mémoire, à partir de l'adresse stack (0x200) vers la zone des variables (0x100) ! Le registre %esp est le pointeur de pile (ou stack pointer) qui désigne le sommet de la pile à l'adresse (%esp).
Avant l'appel de fonction f(), on commençe par empiler ces paramètres avec pushl dans l'ordre inverse ! Au moment de l'appel de fonction avec l'instruction call, l'adresse de retour de la fonction (notée aret) est automatiquement empilée et se trouve donc au sommet de la pile. Le retour de fonction avec l'instruction ret dépile automatiquement cette adresse et fait sauter le programme sur aret. Il faut encore dépiler les paramètres avec popl ou en ajoutant explicitement à %esp la taille nécessaire (2 long ici, soit 8 octets).
Par convention, on place la valeur de retour de la fonction dans le registre %eax.
Nota Bene : Il ne faut pas oublier d'initialiser la pile au départ du programme ! A la fin du programme, on peut vérifier que %esp est revenu à sa valeur initiale stack.
Sauvegarde des registres lors d'un appel de fonction
Lors d'un appel de fonction, certains registres utilisés par l'appelant peuvent être modifié par l'appelé ! Il convient donc de les sauvegarder sur la pile. En principe, on distingue deux types de registres :
- les registres caller-saved (%eax, %ecx, %edx) qui doivent être sauvegardés par l'appellant si nécessaire ;
- les registres callee-saved (%ebx, %esi, %edi, %ebp), qui doivent être sauvegardés par l'appelé.
main: # sauvegarde des registres caller pushl %eax pushl %ecx pushl %edx # empilement des paramètres de f ... call f # dépilement des paramètres de f ... # restauration des registres caller popl %edx popl %ecx popl %eax halt f: # récupération des paramètres ... # sauvegarde des registres callee pushl %ebp pushl %esi pushl %edi pushl %ebx ... ... ... # restauration des registres callee popl %ebx popl %edi popl %esi popl %ebp ret
Nota Bene : En pratique, on sauvegardera uniquement les registres utiles pour éviter d'allourdir le code. Attention à la modification de %esp !
Appel de fonction avec des variables locales
Les variables locales sont définies dans la pile après l'adresse de retour. On accède donc aux variables à partir de (%esp), mais du coup les paramètres sont décalés ! Le code de la fonction précédente est modifié pour utiliser une variable locale !
- funcloc.ys
.pos 0 main: irmovl stack,%esp # initialisation du pointeur de pile (stack pointer) mrmovl b,%eax # on empile le second param b pushl %eax mrmovl a,%eax # on empile le premier param a pushl %eax call f # appel de la fonction f() aret: popl %ecx # on depile le premier param popl %ecx # on depile le second param rmmovl %eax,res # recup resultat halt f: isubl 4,%esp # alloc variable locale x sur la pile mrmovl 8(%esp),%eax # recup premier param a mrmovl 12(%esp),%ebx # recup second param b ... # compute x mrmovl (%esp),%ecx # load x pushl %ecx # save x ... # do something else mrmovl (%esp),%eax # restore x (variable de retour dans %eax) iaddl 4,%esp # liberation variable locale x ret .pos 0x100 a: .long 1 b: .long 1 res: .long 0 .pos 0x200 # adresse de base de la pile stack:
Nota Bene : Dans un soucis de simplification, il est choisi ici (et plus tard) de ne pas utiliser le registre %ebp !
Passage par référence dans les fonctions
Dans les exemples précédent de fonction, nous avons utiliser le passage de paramètres par valleur. Ici, nous donnons l'exemple d'une fonction swap(&a,&b) qui utilise le passage par référence. Il s'agit d'échanger les valeurs de deux entiers.
- swap.ys
.pos 0 main: irmovl stack,%esp # init de la pile irmovl b,%eax pushl %eax # empiler &b irmovl a,%eax pushl %eax # empiler &a call swap iaddl 8,%esp # depiler halt swap: mrmovl 4(%esp),%eax # param pa=&a mrmovl 8(%esp),%ebx # param pb=&b mrmovl (%eax),%ecx # valeur a=*pa mrmovl (%ebx),%edx # valeur b=*pb rmmovl %ecx,(%ebx) # swap *pb = a rmmovl %edx,(%eax) # swap *pa = b ret .pos 0x100 a: .long 1 b: .long 2 .pos 0x200 stack:
Fonction manipulant un tableau
todo
Appel récursif de fonction
f(n) { if(n == 0) return 0 ; else return (f(n-1)+n) ; } main() { res = f(10); }
- recursif.ys
.pos 0 main: irmovl stack,%esp mrmovl n,%eax pushl %eax # empiler param n call f iaddl 4,%esp # depiler param rmmovl %eax,res # resultat dans %eax halt f: mrmovl 4(%esp),%ecx # param n andl %ecx,%ecx # eval condition code je f0 # si n = 0 ? fn: isubl 1,%ecx # n-1 pushl %ecx # empiler param n-1 call f # retour dans %eax iaddl 4,%esp # depiler param mrmovl 4(%esp),%ecx # restore param n addl %ecx,%eax # retour dans %eax = n + f(n-1) ret f0: irmovl 0,%eax ret # valeur de retour dans %eax end: halt .pos 0x100 n: .long 10 res: .long 0 # res = 55 = 0x37 .pos 0x200 stack:
Structure Liste Chaînée
struct list { long x; struct list * next; }; long length(struct list * l) { long n = 0; while(l) { n++; l = l->next; } return n; }
- linkedlist.ys
main: .pos 0 irmovl stack,%esp irmovl l1,%eax pushl %eax call length # retour dans %eax iaddl 4,%esp halt length: mrmovl 4(%esp),%esi # param l xorl %eax,%eax # n = 0 loop: andl %esi,%esi # test si l est NULL ? je end iaddl 1,%eax # n++ mrmovl 4(%esi),%esi # l = l->next jmp loop end: ret # retour n dans eax .pos 0x100 # linked list l1: .long 1 .long l2 # next = l2 l2: .long 2 .long l3 # next = l1 l3: .long 3 .long 0 # next = NULL .pos 0x200 stack: