TD2 : Assembleur

TD2 de Programmation 1

EX 1

Programme 1

La factorielle.

  • cmpl src dest : compare la src et la destination, puis place le résultat dans un autre registre (flag).
  • je .end : aller au label .end (label = adresse symbolique)

Point important : call VS ret

  • call : fait 2 choses : il empile sur la pile l’adresse de l’instruction qui vient juste après (adresse de retour).
  • ret : dépile la dernière adresse, et y va.

Programme 2

Calcule la somme des carrés.

L’OS impose des conventions sur le rôle des registres :

  • les callee save (fonction appelée) ⟶ Si la fonction appelée veut modifier ce registre, elle doit mémoriser sa valeur avant, et la restaurer après.
  • les caller save (fonction appelante) ⟶ Le contraire : il faut que le “caller” (l’appelant) le sauvegarde.
  • les temps ⟶ là pour être modifiés par tout le monde : pas faire d’hypothèse ni sur l’appelant, ni sur l’appelé.

%edx : un “callee save”.

MIPS : ont créé des processeurs avec des instructions “totalement orthogonales” (i.e toutes de même taille) ⟶ BUT : aller plus vite, moins de confusion L’adresse de retour : pas mise sur la pile, dans $ra


Que fait vraiment cmp ?

eflags : contient CF (Carry Flag), ZF (Zero Flag), …

cmpl %eax, %ebx ⟶ calcule %eax-%ebx, et met la retenue dans le CF

À quoi sert le Carry Flag ?

jae (jump if above or equal) saute au label ssi

- En vrai: saute au label si le `CF` vaut 0
- Intuition : `%eax >= %ebx` en NON signé.

jb (if below) saute au label ssi CF=1

- En vrai: saute au label si le `CF` vaut 1
- Intuition : `%eax < %ebx` en NON signé.

jge (jump if greater than or equal) saute au label ssi

  - En vrai: saute au label si le `SF` vaut 0
  - Intuition : `%eax >= %ebx` en NON signé.

jl (jump if less) saute au label ssi

  - En vrai: saute au label si le `SF` vaut 1
  - Intuition : `%eax < %ebx` en NON signé.

Idem pour je (equal), jne (not equal) avec ZF

3)

popl src =

movl %esp, dest
addl $4, %esp

MAIS : marche pas toujours :

  • Attention à popl %esp

4)

NB: movl : pour une adresse : va dans le contenu de l’adresse contenue dans cette adresse.

VS

leal : pour une adresse : va dans le contenu de l’adresse directement.

a) leal (%eax, %esi, 4) %ebx : met l’adresse de %eax + 4$%esi dans %ebx b) car les modes d’adressage immédiat et registre n’ont pas d’adresse. c) leal

5)

a)

  movl $0x8aa0e022, %eax  
  pushl %eax
  jmp 0x7fe976a0

Pas bon : ça décale tout le code après

NB :

  • call : 1 octet
  • adresse du call : 4 octets
  • jmp … : 5 octets

b)

ret :

popl %eax
jmp *%eax

NB: *%eax : au lieu de (eax), car c’est un jmp

c)

  • Utilise beaucoup de mémoire (un registre dans le vide)

6)

int main(){
  int i;
  i =  2*4+3;
  printf("%d\n", i*i);
  return 0;
}

a)

int main(){
  int i; int j;
  i = 2;
  j = 4;
  i = i*j;
  j = 3;
  i = i+j;
  i = i*i;
  printf("%d\n", i);
  return 0;
}

b)

li "$"a, 2
li "$"b, 4
li "$"c, 3
mul a, $a, $b
add a, $a, $c
mul a, $a, $a

c)

movl $2 %eax
movl $4 %ebx
mull %ebx %eax
movl $3 %ebx
addl %ebx %eax
mull %eax %eax

movl format .asciz "%d\n" %edx

Pourquoi du code à deux valeurs en x86 ?

Parce que les instructions sont de la forme “faire opération sur i, avec j puis mettre le contenu dans i”.

8) But du MIPS : aller très vite, en utilisant aussi peu de transistors que possible.


TD 2

1)

a) int * b) int (*) (char *) c) void (*)(intk, ___siginfo *, void *); d) void (*(int, void (*)(int)))(int);

NB : null est de type void * (n’a rien à voir avec void : pointeur sur n’importe quoi) ⟶ void : pas un vrai type.

void * : peut-être associé à n’importe quel pointeur.

2)

3)

a) a[i] et *(a+i) : pareil b)

e) p[i] et i[p] : c’est un addition de pointeurs, et l’addition est commutative ⟶ égalité. f) (&p[i])[j] et p[i+j] : égalité car, addition associative (dans arithmétique des pointeurs).

4)

a) int p[30][40];

  • p[0] : 40 int
  • p[1] : 40 int etc…
int *p[30];
int i;
for (i=0; i<30; i++) p[i] = malloc(sizeof(int [40]));

Les zones de “40 int” sont éparpillées dans la mémoire. ⟶ moins rapide car deux accès à la mémoire successifs pour accéder à p[i][j]

Pas la même classe de stockage, mais même typage.

b)

int **p;
int i;
p = malloc(sizeof(int *[30]));
if (p==NULL) abort();
for (i=0; i<30; i++) p[i] = malloc(sizeof(int [40]));

P : double pointeur,

p[0], p[1], ⋯, p[29] dans la même zone ⟶ pointent chacun vers des zones de “40 bit”

c) accès : p[40*i+j]

d)

Leave a comment