TP 3 : Manipulation de la pile



L'objectif

Le but de ce tp est de vous montrer le principe d’une pile, sa gestion et de voir où se trouvent les différentes données d’un programme. En même temps, on va voir la taille mémoire occupée par différentes données déclarées initialisées ou non.
Vous pourrez aussi remarquer le codage des nombres négatifs, et voir que le microprocesseur utilise le complément à 2, comme nous l'avons vu lors d'un Td du premier semestre.

Le temps imparti

1h30

Le sujet

La pile est une zone mémoire dans laquelle il est possible de stocker des données temporaires. Dans les deux Tp précédents, vous avez dû remarquer dans les différents sources des programmes que les mêmes lignes de code se trouvaient toujours en début de code source :

  1. pushl %ebp
  2. movl %esp,%ebp

En fin de source, on trouve également :

  1. leave
  2. ret

Vous allez voir dans ce TP, et surtout comprendre, à quoi servent ces lignes.

Attention : Le cadre de pile de la fonction main est toujours différent et plus complexe que celui des autres fonctions. Je vous propose donc de le laisser de côté et d'utiliser une fonction auxiliaire main2.

A faire

Saisir le programme suivant, écrit en langage C, que vous devez comprendre assez facilement.

Pile.c
  1. /* Déclaration des variables globales */
  2. char a,b;
  3. unsigned char c,d;
  4. short e;
  5. unsigned short f;
  6. short g=;
  7. int h,i;
  8. long j;
  9. double k;
  10. int main2()
  11. {
  12. /* Déclaration des variables locales */
  13. char ma,mb;
  14. unsigned char mc;md;
  15. short me = 12;
  16. unsigned short mf;
  17. short mg;
  18. int mh,mi;
  19. long mj;
  20. double mk=2,5;
  21. /* Quelques instructions quand même... */
  22. a=ma; b=mb; c=mc; d=md; e=me; f=mf;
  23. g=mg; h=mh; i=mi; j=mj; k=mk;
  24. mg=-5
  25. return 0;
  26. }
  27. int main()
  28. {
  29. return main2();
  30. }

Traduisez-le en langage d’assemblage en tapant la commande:

$gcc -Wall -S pile.c

Notez l'utilisation de -Wall qui demande au compilateur C de vous donner des avertissements supplémentaires.
Utilisation recommandée pour TOUTE compilation d'un code C

Visualiser le fichier source en langage d’assemblage. Attention le compilateur génère cette année des directives .cfi* (Call Frame Information). Ces directives ne servent que pour le débogage, et en particulier pour l'affichage de la pile en cas d'erreur. Vous pouvez donc les supprimer ou les ignorer sans vergogne.
Segmenter le programme et Faire le dessin de la pile et de l'occupation mémoire.
Conclure sur la taille des différents types.


Changer l’ordre des déclarations de variables (par exemple mettre mh entre mb et mc) de façon à ne pas les avoir déclarées en ordre de taille croissante.
Le traduire en langage d’assemblage.
Visualiser le fichier source en langage d’assemblage.
Faire le dessin de la pile et de l'occupation mémoire. Conclure sur l'intelligence de GCC.


Desassembler le fichier objet :

$objdump -d pile.s > pile.dis

Constater le codage des nombres négatifs. (voir variable mg, ligne 24).
Vous pouvez aussi jeter un oeil sur le codage des nombres flottantes (variable mk, ligne 20).


Remplacer les lignes int h et int mh respectivement par static int h et static int mh.
Recommencer l’étude précédente et conclure.



Nous allons approfondir un peu notre connaissance sur la pile.
Pour cela je vous invite à saisir le programme suivant :

Pile2.c
  1. int appel_fonction(int c)
  2. {
  3. int d;
  4. d = c;
  5. return(d);
  6. }
  7. int main()
  8. {
  9. return main2;
  10. }
  11. int a;
  12. int main2()
  13. {
  14. int b = 1;
  15. a = appel_fonction(b);
  16. return 0;
  17. }

Ce programme contient trois fonctions, main, main2 et appel_fonction.
Sachant que le compilateur C lit votre programme de haut en bas (dans ce sens), expliquer l'erreur obtenue pendant la compilation.

Après traduction en langage d’assemblage, on récupère le source suivant :

Pile2.s
  1. .text
  2. main:
  3. /* On n'analysera pas le cadre de pile de la fonction main... */
  4. leal 4(%esp), %ecx
  5. andl $-16, %esp
  6. pushl -4(%ecx)
  7. pushl %ebp
  8. movl %esp, %ebp
  9. pushl %ecx
  10. subl $4, %esp
  11. /* Ici commence votre programme... */
  12. call main2
  13. /* Ici termine votre programme... */
  14. /* On n'analysera pas la destruction du cadre de pile de la fonction main... */
  15. addl $4, %esp
  16. popl %ecx
  17. popl %ebp
  18. leal -4(%ecx), %esp
  19. ret
  20. main2:
  21. pushl %ebp
  22. movl %esp, %ebp
  23. subl $24, %esp
  24. movl -4(%ebp), %eax
  25. movl %eax, (%esp)
  26. call appel_fonction
  27. movl %eax, a
  28. movl $0, %eax
  29. leave
  30. ret
  31. appel_fonction:
  32. pushl %ebp
  33. movl %esp, %ebp
  34. subl $16, %esp
  35. movl 8(%ebp), %eax
  36. movl %eax, -4(%ebp)
  37. movl -4(%ebp), %eax
  38. leave
  39. ret

Analyser le programme pile2.c de façon à comprendre pile2.s.
Donner le nombre de cadres de piles que le programme pile2.s contient,
Expliquer chaque ligne du source assembleur.
Dessiner la zone mémoire de ce programme.

Pourquoi le return de la fonction main n'est-il pas traduit en assembleur ? Que devriez vous écrire pour le faire ? Conclure...


Optimiser le programme pile2.s (option O3) en tapant la commande :

$gcc S -O3 pile2.c -o pile2.opt.s

Vous devez obtenir un source optimisé, et donc beaucoup plus simple, donc plus facile à analyser.
Attention, dans ce mode, le compilateur peut ne pas respecter tout à fait l'ordre du programme C...
Expliquer chaque ligne du source assembleur.
Dessiner la zone mémoire de ce programme.


Retour à la liste des TP