LdB - Modes d'adressages

De Ensiwiki
(Redirigé depuis LdB Modes d'adressages)
Aller à : navigation, rechercher

Mode d'adressage : Le principe

En langage d'assemblage, la notion de base est la notion d'instruction. Une instruction contient un code opération, et des opérandes. Par exemple, dans

movl x, %eax

Le code opération est « movl », et les deux opérandes sont « x » et « %eax ». Bien sûr, on ne peut pas mettre n'importe quoi à la place des opérandes. Par exemple, en C, on peut écrire :

y = (x + 2) * z;

mais on ne pourra pas traduire ça naïvement par « movl (x + 2) * z, y » : les opérandes ne peuvent pas être des expressions quelconques, on n'a le choix qu'entre un nombre limités de types d'opérandes possible. C'est ce qu'on appelle les modes d'adressages.

Les modes d'adressages de l'Assembleur Pentium

Mode d'adressage « Immédiat »

Nous avons déjà vu un mode d'adressage très simple : dans l'instruction

movl $15, %eax

L'opérande $15 est un opérande immédiat, c'est à dire qu'on considère la valeur 15 comme une valeur, et cette valeur est incluse dans l'instruction.

On pourrait traduire cette instruction dans une syntaxe C par

<le registre eax> = 15;

Syntaxe : Opérande qui commence par $ => Adressage immédiat

Mode d'adressage « Registre » ou « Registre Direct »

Dans l'exemple ci-dessus, le deuxième opérande "%eax" désigne un registre. Le nom du registre est inclu dans l'instruction, et on considère directement la valeur du registre.

Syntaxe : Opérande qui commence par % => Adressage Registre

Mode d'adressage « Direct »

On peut parler d'une « case mémoire » en donnant directement son adresse dans l'instruction.

Exemple :

movl 0, %eax  # Segmentation Fault plus que probable

Cette instruction va tenter de charger la valeur contenue à l'adresse 0 dans le registre %eax (ce qui a priori va déclencher une Segmentation Fault). En pratique, le programmeur connaît rarement l'adresse exacte où il veut écrire, et il utilise des étiquettes :

.data
x:	.long 42
y:	.long 54
.text
       ...
       movl x, %eax
       movl %eax, y
       ...

Dans cet exemple, « x » et « y » sont des étiquettes, donc chaque utilisation sera remplacée par l'adresse de l'endroit où ces étiquettes sont déclarées. La première instruction lit la valeur qui se trouve à l'adresse de x (donc, 42 sauf si quelqu'un d'autre a écrit dessus entre temps), et la stocker dans %eax. La seconde écrit la valeur de %eax dans la case mémoire suivant l'endroit où l'étiquette « y » est déclarée.

Syntaxe : Valeur numérique ou étiquette sans % ni $ => Adressage Direct.

Exercice : On cherche à faire la somme des éléments d'un tableau de 5 entiers. Pour l'instant, on n'a pas de bon moyen de parcourir le tableau, on va déclarer une étiquette par case de tableau (mais les entiers 1, 12, 3, ... sont bien rangés côte à côte en mémoire, comme dans un tableau).

Récupérer le squelette suivant dans l'arborescence Git, ou bien recopier et compléter :

Fichier : asm/2-modes-adressages/tableau.S |raw |historique |rafraichir

<include select="" linesXXX="" fromXXX="" toXXX="" beforeXXX="" afterXXX="" linestartXXX="" linenumsXXX="" src="http://github.com/moy/cours-ldb/raw/master/asm/2-modes-adressages/tableau.S" highlight="c" style="border: 0px none white"> </include>

Si besoin, les solutions se trouvent dans le répertoire asm/2-modes-adressages/solutions/.

Vérifier dans GDB que le programme fait bien ce qu'on lui demande. Ça devrait donner quelque chose comme ceci :

(gdb) b main
Breakpoint 1 at 0x8048328: file tab_direct.S, line 10.
(gdb) r
Starting program: a.out 

Breakpoint 1, main () at tab_direct.S:12
Current language:  auto
The current source language is "auto; currently asm".
(gdb) display $eax
1: $eax = -1073746940
(gdb) n
1: $eax = 0
(gdb) 
1: $eax = 1
(gdb) 
1: $eax = 13
(gdb) 
1: $eax = 16
(gdb) 
1: $eax = 21
(gdb)
fin_main () at tab_direct.S:21
1: $eax = 46

Mode d'adressage « Indirect Registre »

Quand un registre contient une adresse, on peut vouloir aller chercher la donnée qui se trouve en mémoire à cette adresse. La syntaxe est la suivante :

movl (%eax), %ebx

Si %eax contient une adresse A, cette instruction va chercher la donnée à l'adresse A, et la place dans le registre %ebx.

Attention, les parenthèses changent tout, pas comme en C où « x » et « (x) » sont équivalents. Ici, les parenthèses indiquent une indirection, un peu comme * en C.

Syntaxe : Un registre entre parenthèses => Adressage Indirect Registre

Exercice

Compléter le squelette chaine.S :

Fichier : asm/2-modes-adressages/chaine.S |raw |historique |rafraichir

<include select="" linesXXX="" fromXXX="" toXXX="" beforeXXX="" afterXXX="" linestartXXX="" linenumsXXX="" src="http://github.com/moy/cours-ldb/raw/master/asm/2-modes-adressages/chaine.S" highlight="" style="border: 0px none white"> </include>

Implémenter le calcul de la longueur de la chaîne en utilisant le mode d'adressage Indirect Registre. L'algorithme en C serait :

while (*p != '\0') {
	p++;
	i++;
}
// Résultat dans i.

Mode d'adressage « Indirect Registre avec Déplacement »

On peut aussi accéder aux cases mémoire un peu avant ou un peu après l'endroit pointé par un registre. Typiquement, si en C, un programme déclare une structure comme ceci :

struct cellule {
	int val;
	struct cellule *suiv;
};

et qu'un pointeur sur une cellule est contenu dans %eax, alors on peut accéder aux champs de la cellule pointée avec :

	movl 0(%eax), %ecx // mettre l->val dans %ecx
	movl 4(%eax), %ecx // mettre l->suiv dans %ecx

La première instruction est en fait strictement équivalente à « movl (%eax), ... ». La seconde signifie « mettre la valeur se trouvant 4 octets derrière la valeur pointée par %eax dans %ecx ».

Syntaxe : Un nombre suivi d'un registre entre parenthèses => Adressage Indirect Registre

Exercice :

Pour cet exercice, on fourni une section .data qui contient une liste chainée :

Fichier : asm/2-modes-adressages/liste.S |raw |historique |rafraichir

<include select="" linesXXX="" fromXXX="" toXXX="" beforeXXX="" afterXXX="" linestartXXX="" linenumsXXX="" src="http://github.com/moy/cours-ldb/raw/master/asm/2-modes-adressages/liste.S" highlight="c" style="border: 0px none white"> </include>

Remplissez le corps de la boucle while pour parcourir la liste avec le registre %eax, et stocker les valeurs successives dans %ecx. On devrait pouvoir observer ceci dans GDB :

(gdb) b debut_while 
Breakpoint 4 at 0x804832d: file liste.S, line 8.
(gdb) display $ecx
(gdb) r
Starting program: liste

Breakpoint 4, debut_while () at liste.S:8
2: $ecx = -1208098828
(gdb) c
Continuing.

Breakpoint 4, debut_while () at liste.S:8
2: $ecx = 42
(gdb) 
Continuing.

Breakpoint 4, debut_while () at liste.S:8
2: $ecx = 12
(gdb) 
Continuing.

Breakpoint 4, debut_while () at liste.S:8
2: $ecx = 666
(gdb) 
Continuing.

Breakpoint 4, debut_while () at liste.S:8
2: $ecx = 1234
(gdb)
Continuing.

Program exited normally.

Mode d'adressage « Indirect avec Base et Index »

Ce mode d'adressage permet de faire la somme de deux registres, et désigne la case mémoire pointée par le résultat.

Exemple : Pour accéder à la i-ème case d'un tableau d'octets t, on a besoin d'accéder à la case d'adresse i+t. Si les valeurs de i et t sont dans des registres %eax et %ebx, on peut écrire :

movb (%eax, %ebx), %ecx

Cette instruction recopie la valeur se trouvant à l'adresse « %eax + %ebx » et place le résultat dans %ecx. En syntaxe pseudo-C, on pourrait l'écrire :

ecx = *(eax + ebx)
movl $42, 12(%eax, %ebx)

Cette instruction place la valeur 42 en mémoire, à l'adresse « %eax + %ebx + 12 ».

Syntaxe : VAL(%reg, %reg) => adressage Indirect avec Base et Index (et Déplacement)

Exercice : Reprendre le calcul de la longueur d'une chaîne, et implémenter le même calcul en utilisant le mode d'adressage indirect avec base. L'algorithme en C serait :

while(chaine[i] != '\0')
	i++;

On stockera chaine dans %eax (%eax ne bouge plus par la suite), et i dans %ecx. %eax sera la base, et i l'index.

Mode d'adressage « Indirect avec Base et Index Typé »

Pour manipuler des tableaux d'éléments plus grands que des octets, la règle change un peu. Par exemple, pour un tableau d'entiers t, accéder à la i-ème case nécessite d'accéder à la case d'adresse (t + 4*i). Si l'adresse de la première case de t est dans %eax et que la valeur de i est dans %ebx, on peut le faire avec :

movl (%eax, %ebx, 4), %ecx

En syntaxe pseudo-C, ceci s'écrirait :

ecx = *(eax + ebx * 4);

Syntaxe : VAL(%reg, %reg, VAL) => adressage Indirect avec Base et Index Typé (et Déplacement)

Exercice :

On va maintenant travailler sur des tableaux d'entiers strictement positifs.

On part du squelette de code ci-dessous :

Fichier : asm/2-modes-adressages/max.S |raw |historique |rafraichir

<include select="" linesXXX="" fromXXX="" toXXX="" beforeXXX="" afterXXX="" linestartXXX="" linenumsXXX="" src="http://github.com/moy/cours-ldb/raw/master/asm/2-modes-adressages/max.S" highlight="c" style="border: 0px none white"> </include>

Écrire le code assembleur cherchant dans un registre l'entier maximum du tableau tab. On note que le tableau ne contient que des entiers strictement positifs et qu'il est terminé par l'entier 0.

Le code C correspondant pourrait être :

unsigned max, i;
for (max = i = 0; tab[i] != 0; i++) {
    if (tab[i] > tab[max]) {
        max = i;
    }
}

On n'utilisera dans cette question que l'adressage indirect avec index typé et déplacement : par exemple

movl %eax, tab(, %edx, 4)

On demande d'écrire le code de façon naïve, en accédant autant de fois que nécessaire à la case courante du tableau. Tester le code avec gdb, et vérifier aussi qu'il fonctionne avec un tableau vide et un tableau contenant un seul élément.

Adressage mémoire programme

En langage C, on peut stocker l'adresse d'une fonction dans une variable. On parle de pointeur sur fonction. Par exemple :

Fichier : asm/2-modes-adressages/pointeur.c |raw |historique |rafraichir

<include select="" linesXXX="" fromXXX="" toXXX="" beforeXXX="" afterXXX="" linestartXXX="" linenumsXXX="" src="http://github.com/moy/cours-ldb/raw/master/asm/2-modes-adressages/pointeur.c" highlight="c" style="border: 0px none white"> </include>

Pour traduire ceci, on peut utiliser les instructions assembleur :

call *%registre
call *variable_globale

Qui veulent dire « appeler la fonction dont l'adresse est contenue dans le registre/la variable globale ».

Exercice Complétez le code ci-dessous, en utilisant les deux modes possibles.

Fichier : asm/2-modes-adressages/star.S |raw |historique |rafraichir

<include select="" linesXXX="" fromXXX="" toXXX="" beforeXXX="" afterXXX="" linestartXXX="" linenumsXXX="" src="http://github.com/moy/cours-ldb/raw/master/asm/2-modes-adressages/star.S" highlight="c" style="border: 0px none white"> </include>

Quelques mots sur le codage des instructions

Le codage détaillé des instructions du pentium est assez complexe et dépasse le cadre de cette séance. On peut néanmoins se faire une idée du mécanisme en regardant le code produit par l'assembleur sur quelques exemples.

Pour regarder le binaire produit, deux solutions : générer un listing d'assemblage pendant l'assemblage (avec l'option -Wa,-a de gcc), ou bien désassembler le binaire généré (avec objdump -d).

Fichier : asm/2-modes-adressages/codage.S |raw |historique |rafraichir

<include select="" linesXXX="" fromXXX="" toXXX="" beforeXXX="" afterXXX="" linestartXXX="" linenumsXXX="" src="http://github.com/moy/cours-ldb/raw/master/asm/2-modes-adressages/codage.S" highlight="c" style="border: 0px none white"> </include>

Compilez ce fichier avec la commande :

gcc -Wa,-a -m32 codage.S

(ce programme n'a aucune chance de produire quelque chose d'intéressant à l'exécution)

On peut remarquer :

  • Le codage de la valeur numérique en little-endian (i.e. « à l'envers »)
  • L'instruction contient la valeur numérique, plus quelques bits pour spécifier le code opération et le mode d'adressage
  • Le codage des instructions contenant une étiquette identique au codage avec la valeur numérique écrite « en dur » (en réalité, l'utilisation d'une étiquette génère un binaire différent : le codage de l'instruction ne change pas mais la présence de l'étiquette est codée dans la table de relocation)