User Tools

Site Tools


archi:index

This is an old revision of the document!


Architecture des Ordinateurs

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:
archi/index.1455346289.txt.gz · Last modified: 2024/03/18 15:05 (external edit)