TP6: Compilation

Les étapes de la compilation

Le pré-processeur

Comme vous l’avez vu au TP précédent, un fichier contenant toutes les sources ce n’est pas très pratique. Imaginez que le code d’un OS (~8*10^6) soit regroupé dans un seul fichier. Cela devient impossible à gérer! Nous allons donc séparer le code en plusieurs fichiers, et c’est ce que vous allez faire avec votre code.

Pour commencer, répartissez le code contenu dans le fichier main.c:

  • mettez les signatures des fonctions (new_link, chain, display_linked_list, free_list, sort, search, insert, read_file_content) dans un fichier linked-list.h
  • mettez la définition des types de données (Link_t et Student_t) dans un fichier link-types.h
  • mettez le code des fonctions (new_link, chain, display_linked_list, free_list, sort, search, insert, read_file_content) dans un fichier linked-list.c
  • laisser le reste du code (définition des variables globales et fonction main) dans le fichier main.c: supprimer les sections de code que vous avez copiées dans d’autres fichiers.

Vous allez maintenant compiler un programme dont le code C a été réparti sur plusieurs fichiers « .c ». Pour la compilation de trois fichiers fic1.c, fic2.c, et fic3.c, vous devrez exécuter la commande:

$gcc -Wall fic1.c fic2.c fic3.c -o mon_prog

L’exécution de cette ligne de commande avec les fichiers main.c et linked-list.c devrait vous renvoyer des erreurs et warning du type:

error: unknown type name 'Link_t'
warning: implicit definition of function 'read_file_content'

L’erreur vient du fait que le type Link_t est utilisé, mais le compilateur ne trouve pas sa définition. Utilisez la directive #include pour remédier à ce type problème.

Le warning vient du fait que la fonction read_file_content est appelée, mais le compilateur ne connait pas la signature de cette fonction.

Exercice 1: Utilisez la directive #include pour remédier à ce type problème.

Maintenant, vous pouvez avoir des erreurs de redéfinition de type. Pour être sûr de mettre en évidence le problème, incluez linked-list.h puis link-types.h dans le fichier linked-list.c.

error: typedef redefinition with different types ('struct Link_t' vs 'struct link')

Ceci est du au fait que vous incluez plusieurs fois le même fichier .h, et donc vous définissez plusieurs fois le même type de données. Tapez la commande suivante pour vous en convaincre:

gcc -E linked-list.c

Exercice 2: Utilisez les directives #ifndef /#endif et #define pour remédier à ce type problème.

Des erreurs de compilation, encore? Et bien oui: vous devriez avoir des erreurs du type:

error: use of undeclared identifier 'number_of_students'

Ceci est dû au fait que lors de la compilation dans linked-list.c, vous utilisez une variable qui n’a pas été déclarée.

Exercice 3: déclarez la variable number_of_students avant de l’utiliser dans le fichier linked-list.c pour remédier à ce type de problèmes. Attention cependant à ne pas DEFINIR cette variable deux fois! (Utilisez le mot clé extern pour vous en assurer).

Normalement, plus d’erreur ni de warning, merci gcc ! 😉

La compilation séparée

Imaginez que le code d’un OS (~8*10^6) soit regroupé dans un seul fichier. Cela devient très (très!) long à compiler! Nous allons donc compiler les fichiers « .c » séparément. et c’est ce que vous allez faire avec votre programme.

Essayez pour commencer:

gcc -Wall linked-list.c

Mille milliards de milles sabord, encore des erreurs de compilation!

Undefined symbols for architecture x86_64:
  "_main", referenced from:
     implicit entry/start for main executable
ld: symbol(s) not found for architecture x86_64

La raison est simple: vous avez essayé de produire un fichier executable à partir d’un fichier « .c » qui ne contient pas d’implémentation de la fonction main. gcc ne sait pas par où commencer l’exécution du programme…

Exercice 4: utiliser l’option -c pour produire des fichiers objets:

  • gcc -Wall -c main.c L’effet de cette commande: le fichier main.c est compilé, et un fichier objet, main.o, est produit.
  • gcc -Wall -c linked-list.c L’effet de cette commande: le fichier linked-list.c est compilé, et un fichier objet, linked-list.o, est produit.

Exercice 5: vérifiez le format des fichiers objets en utilisant la commande file :

$ file linked-list.o 
foncs.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped
$ file main.o
main.o:  ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), not stripped

Exercice 6: visualisez quels sont symboles connus dans les fichiers objets en utilisant la commande nm

$ nm main.o
$ nm linked-list.o

Interprétation du résultat:
T —–> le symbole est défini dans le fichier, c’est une fonction,
B —–> le symbole est défini dans le fichier, c’est une variable globale, non initialisée
C —–> le symbole est défini dans le fichier, c’est une variable globale, initialisée à une valeur différent de 0 à la définition
U —–> le symbole n’est pas défini dans ce fichier, il doit se trouver dans une bibliothèque, ou dans un autre fichier objet.

Vérifier que :

  1. main et number_of_students sont définis dans main.o;
  2. chain, display_linked_list, free_list, insert, etc. sont NON définis dans main.o
  3. chain, display_linked_list, free_list, insert, etc. sont définis dans linked-list.o;
  4. plusieurs symboles ne sont connus dans aucun des deux, par exemple : strcmp, scanf, printf.

L’édition de lien

  • gcc linked-list.o main.o -o linked-list-exe L’effet de cette commande: un fichier executable, linked-list-exe, est produit. Vérifiez son bon fonctionnement.

Exercice 7: vérifiez le format du fichier exécutable en utilisant la commande file :

$file linked-list-exe
appli: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs),
for GNU/Linux 2.6.32, BuildID[sha1]=0x294d2160a3f70a1b0467e926454fdaa64a5c5016, not stripped

Exercice 8: visualisez quels sont symboles connus dans les fichiers objets en utilisant la commande nm:

$nm linked-list-exe

La sortie de nm montre que certains symboles ne sont toujours pas définis dans cet exécutable, par exemple: printf et strcmp.

Pour avoir la liste des bibliothèques dites « dynamiques » nécessaires pour exécuter appli, exécuter la commande :

$ ldd linked-list-exe

On obtient une liste du type :

       linux-vdso.so.1
    libc.so.6 => /lib64/libc.so.6
    /lib64/ld-linux-x86-64.so.2

L’utilitaire strip retire la table des symbole, l’exécutable est plus petitt :

$ ls -l linked-list-exe
-rwx------ 1 domas ir_domas 13572  6 déc.  13:57 linked-list-exe

$ strip linked-list-exe

$ ls -l linked-list-exe
-rwx------ 1 domas ir_domas 13308 6 déc. 13:57 linked-list-exe

Par défaut, l’édition de lien est dynamique. Pour réaliser une édition de lien statique, nous devons utiliser l’option -static de gcc.

Création et utilisation de bibliothèque (library en anglais)

Certaines fonctions peuvent être utilisées, ou ré-utilisées par plusieurs program (printf par exemple). Ces fonctions sont alors regroupées dans des bibliothèques.

Une bibliothèque (statique) n’est rien d’autre qu’une archive qui regroupe plusieurs fichier objets en un seul. Pour créer une librairie, on utilisera l’outil « ar » (ar comme archive). La principale utilisation de ar est la construction de bibliothèques. On va reprendre l’exercice précédent et créer une bibliothèque pour y ranger les fonctions se trouvant dans le fichier linked-list.c.

Création d’une bibliothèque

  1. Bien sûr, il faut d’abord créer les fichiers objets, par exemple, ici : gcc -Wall -c linked-list.c
  2. Création de l’archive en utilisant la commande ar : ar -r libmy.a fic1.o fic2.o
  • L’option -r permet d’inclure les fichiers objets fic1.o et fic2.o dans l’archive nommée libmy.a. Notez que les bibliothèques commencent le plus souvent par le préfix lib.
  • L’option -t affiche la liste des fichiers contenus dans l’archive
$ ar -r liblinked_list_utils.a linked-list.o
ar: creating archive liblinked_list_utils.a

$ ar -t liblinked_list_utils.a
linked-list.o
  • Gestion de la bibliothèque.
    • La commande nm permet de vérifier quels sont les symboles définis et indéfinis dans l’archive.
    • Pour ajouter d’autres objets dans une bibliothèque , on utilise la commande ar avec l’option -r.

Exercice 9: créez la bibliothèque liblinked_list_utils.a à partir du fichier objet linked-list.o

Nous n’avons plus besoin du fichier objet, et nous supprimons aussi le fichier executable; faites :

$rm linked-list.o linked-list-exe

Utilisation d’une bibliothèque

Vous allez maintenant faire l’édition de lien en utilisant la bibliothèque:

$ gcc main.o liblinked_list_utils.a -o linked-list-exe
  • On peut aussi utiliser l’option -l de l’éditeur de liens pour lui demander d’aller chercher une bibliothèque. Cette option s’utilise ainsi : -lx où x est une abréviation pour libx.a. C’est pour cette raison que nous avons utilisé le nom liblinked_list_utils.a dans l’exemple. On peut donc utiliser cette bibliothèque en faisant :
$gcc main.o -L. -llinked_list_utils -o linked-list-exe
  • Si l’éditeur de liens ne trouve pas la bibliothèque, il faut lui indiquer qu’elle est dans le répertoire courant en utilisant l’option -L (suivi du chemin vers le répertoire contenant la bibliothèque) :
$gcc main.o -L. -llinked_list_utils -o linked-list-exe

Exercice 10: créez l’exécutable linked-list-exe par édition de lien entre main.o et liblinked-list-utils.a

  • On va maintenant mettre la bibliothèque libma_bib.a dans le répertoire lib : $mkdir lib $mv liblinked_list_utils.a ./lib/liblinked_list_utils.a L’option -Lrep_bib de gcc indique qu’il faut chercher les bibliothèques dans le répertoire rep_bib. La commande à exécuter est alors : $gcc main.o -L./lib -llinked_list_utils -o appli

Exercice 11: Après avoir déplacé la bibliothèque dans le répertoire lib, re-créez l’exécutable linked-list-exe par édition de lien entre main.o et liblinked-list-utils.a

Informations complémentaires sur les bibliothèques:

Les bibliothèques présentées dans ce TP sont des bibliothèques statiques. Les bibliothèques dynamiques sont construites par gcc (en utilisant l’option -shared). Les bibliothèques dynamique ont une extension « .so » et exposent plus de symboles et d’informations qu’une bibliothèques statique pour pouvoir être chargé dynamiquement.

Certaines bibliothèques, en particulier la « libc », sont ajoutées par défaut lors de l’édition de lien (voir utilisation de la commande ldd plus haut dans ce TP). C’est ainsi que sont résolus les symboles scanf, printf, etc.

Lorsque vous utilisez des opérations mathématiques (e.g. sqrt), vous pouvez utiliser la librairie mathématique. Pour cela, il vous suffit d’utiliser l’option -lm, ce qui signifie que gcc trouvera un fichier libm.a (ou libm.so). Où? … Dans /usr/lib

Pour information : annexe, illustrée sur un autre exemple que celui du TP

Pour aller plus loin, faites les exercices qui suivent; il n’est pas obligatoire de finir la partie du TP ci-dessous.

Outil de debug (GDB)

Pour les mises au point de programmes (i.e. le debug), il est conseillé d’utiliser des outils tels que gdb qui permettent de suivre l’exécution du programme en pas à pas et de vérifier ou de modifier le contenu de leurs variables.
Souvent plus efficace que le mise au point faite en utilisant printf !
Si vous ne l’avez pas déja fait, ajoutez l’option -g aux paramétres CFLAGS et LDFLAGS.
Faites make clean puis make all, puis utilisez gdb en vous inspirant de l’exemple ci-dessous.

Exemple :
Pour utiliser gdb sur le programme appli, on entre la commande suivante :

gdb appli

On peut maintenant exécuter ce programme appli sous le contrôle de gdb, et essayer les commandes suivantes :

  • list affiche 10 lignes à partir de la position courante,
  • break positionne un point d’arrêt,
  • run démarre l’exécution
  • continue relance l’exécution jusqu’au point d’arrêt suivant,
  • step fait passer à l’instruction suivante,
  • print visualise la valeur des variables,
  • set permet de modifier la valeur d’une variable,
  • help donne le liste des commandes,

Par exemple :

(gdb) list 25
20        
21        printf ("nom du fichier ?\n");
22        scanf( "%s", le_fichier);
23        
24        nb_mots = lire_fichier(tab_mots, le_fichier);
25        
26        /*-- visualisation du tableau a l ecran  ---*/
27        for (i=0; i<=nb_mots; i++){
28            printf("tab_mots[%d] = %s %d\n", i, tab_mots[i].texte,
29                   tab_mots[i].info);       
(gdb) break 27
Breakpoint 1 at 0x400758: file main.c, line 27.
(gdb) run
Starting program: appli 
nom du fichier ?
main.c
Breakpoint 1, main (argc=1, argv=0x7fffffffde98) at main.c:27
27        for (i=0; i<=nb_mots; i++){

(gdb) print nb_mots
$1 = 98
(gdb) set nb_mots=10
(gdb) print nb_mots
$3 = 10

Structures des fichiers objets et exécutables

Un premier fichier objet

Exécuter la commande suivante :

  • objdump -h main.o (si objdump n’existe pas, essayer gobjdump).

On obtient des informations sur les différentes sections du fichier objet main.o, en particulier :

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000148  0000000000000000  0000000000000000  00000040  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  00000188  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  00000188  2**2
                  ALLOC
  3 .rodata       00000068  0000000000000000  0000000000000000  00000188  2**0

Commentaires :

  • size et File off : taille de la section et son adresse de début dans le fichier objet,
  • Algn : alignement, en puissance de deux,
  • la section text contient le programme,
  • la section data contient les données modifiables ,
  • la section rodata contient les données non modifiables (read only!), dans notre cas les chaines de caractères des formats utilisés dans les printf.

Exécuter la commande suivante :

  • objdump -t main.o

Résultat :

main.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l    df *ABS*    0000000000000000 main.c
0000000000000000 l    d  .text    0000000000000000 .text
0000000000000000 l    d  .data    0000000000000000 .data
0000000000000000 l    d  .bss    0000000000000000 .bss
0000000000000000 l    d  .rodata    0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame    0000000000000000 .eh_frame
0000000000000000 l    d  .comment    0000000000000000 .comment
0000000000000e10       O *COM*    0000000000000020 tab_mots
0000000000000000 g     F .text    0000000000000148 main
0000000000000000         *UND*    0000000000000000 puts
0000000000000000         *UND*    0000000000000000 __isoc99_scanf
0000000000000000         *UND*    0000000000000000 lire_fichier
0000000000000000         *UND*    0000000000000000 printf
0000000000000000         *UND*    0000000000000000 strncmp
0000000000000000         *UND*    0000000000000000 chercher

Commentaires sur le résultat de cette commande :

  • COM : variable globale non initialisée,
  • data : variable globale initialisée,
  • UND : référence non satisfaite,
  • la première colonne indique l’adresse de la variable dans sa section, la troisième sa taille, par exemple : 0000000000000e10 O *COM* 0000000000000020 tab_mots veut dire que la variable tab_mots est implantée sur 20 octets à partir de l’adresse e10 dans la zone COM.

Si on exécuter la commande  :

  • objdump -s main.o

On peut voir le contenu de cette section rodata:

Contents of section .rodata:
 0000 6e6f6d20 64752066 69636869 6572203f  nom du fichier ?
 0010 00257300 7461625f 6d6f7473 5b25645d  .%s.tab_mots[%d]
 0020 203d2025 73202564 0a007175 656c206d   = %s %d..quel m
 0030 6f742063 68657263 68657220 3f006669  ot chercher ?.fi
 0040 6e007472 6f757665 20202573 20286d6f  n.trouve  %s (mo
 0050 74202564 2920210a 00706173 2074726f  t %d) !..pas tro
 0060 75766520 25730a00                    uve %s..        

Un second fichier objet

Exécuter la commande suivante :

  • objdump -h foncs.o

On obtient des informations sur les différentes sections du fichier objet foncs.o:

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000139  0000000000000000  0000000000000000  00000040  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  0000017c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  0000017c  2**2
                  ALLOC
  3 .rodata       00000040  0000000000000000  0000000000000000  0000017c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA

Exécuter la commande suivante :

  • objdump -t foncs.o

Extrait du résultat :

SYMBOL TABLE:
0000000000000000 l    df *ABS*    0000000000000000 foncs.c
0000000000000000 l    d  .text    0000000000000000 .text
0000000000000000 l    d  .data    0000000000000000 .data
0000000000000000 l    d  .bss    0000000000000000 .bss
0000000000000000 l    d  .rodata    0000000000000000 .rodata
0000000000000000 l    d  .note.GNU-stack    0000000000000000 .note.GNU-stack
0000000000000000 l    d  .eh_frame    0000000000000000 .eh_frame
0000000000000000 l    d  .comment    0000000000000000 .comment
0000000000000000 g     F .text    00000000000000c2 lire_fichier
0000000000000000         *UND*    0000000000000000 fopen
0000000000000000         *UND*    0000000000000000 printf
0000000000000000         *UND*    0000000000000000 exit
0000000000000000         *UND*    0000000000000000 __isoc99_fscanf
00000000000000c2 g     F .text    0000000000000077 chercher
0000000000000000         *UND*    0000000000000000 tab_mots
0000000000000000         *UND*    0000000000000000 strcmp

Commentaires sur le résultat de cette commande :

  • text contient lire_fichier et chercher qui sont UND dans main.o,
  • toutes les autres variables sont UND .

Exécuter la commande suivante :

  • objdump -s foncs.o

Extrait du résultat :

Contents of section .rodata:
 0000 72006c65 20666963 68696572 20257320  r.le fichier %s 
 0010 6e276578 69737465 20706173 0a002573  n'existe pas..%s
 0020 006c6967 6e652020 2564203a 20657272  .ligne  %d : err
 0030 65757220 0a000000 00000000 0000803f  eur ...........?

Commentaires sur le résultat de cette commande :

  • pas de data
  • dans rodata on trouve touts les constantes chaines de caractères des formats utilisés dans les printf.

Le fichier exécutable

Produire un fichier exécutable en utilisant la commande suivante :

gcc main.o foncs.o -o exo

Exécuter ensuite la commande suivante :

  • objdump -h exo

On obtient des informations sur les différentes sections du fichier exécutable exo, on s’intéresse aux suivantes:

Sections:
 14 .rodata       000000b8  0000000000400a30  0000000000400a30  00000a30  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 23 .data         00000004  0000000000600e60  0000000000600e60  00000e60  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 24 .bss          00000e30  0000000000600e80  0000000000600e80  00000e64  2**5

Commentaires :

  • la section rodata a une taille égale à la somme de celles de main.o et foncs.o, elle commanece en a30 dans l’exécutable.
  • la section data contient toutes las variables globales initialisées,
  • la section bss contient toutes les variables globales non initialisées.

La pile

  • La pile croit dans le sens des adresses décroissantes.
  • Rôle de %esp et %ebp : Les registres 32 bits %esp et %ebp définissent, dans la pile, l’espace de la fenêtre (stack frame) associée à une fonction :
  • %ebp : pointeur sur la base de la pile,
  • %esp : pointeur sur le sommet de la pile, modifié par les instructions pushl, popl, call, ret.
  • Sauver/Restaurer : Sauver les valeurs de %esp et %ebp mémorise un contexte dans la pile. Restaurer les valeurs de ces registres permet de se retrouver dans le contexte sauvegardé.
  • Mécanisme push/pull :
    • pushl var : décrémente %esp et copie 4 octets,
    • popl var : copie 4 octets dans var et incrémente %esp,
    Exemple : une stack frame pour 4 variables : pushl %ebp sauvegarde de la base de la pile movl %esp, %ebp deplacement de la base de la pile subl $16, %esp 16 octets dans la nouvelle pile
  • call, ret et leave  : Les trois instructions spécifiques aux appels de fonction sont:
  • call Fonc call est l’enchainement d’un empilement de l’adresse qui la suit et d’un jmp à l’adresse fournie en opérande.
  • leave restaure la fenetre de pile précédente, c.a.d :
    movl %ebp,%esp
    popl %ebp
  • ret retour de fonction (c.a.d : popl %eip). L’instruction ret combine dépilement et branchement à l’adresse dépilée.
  • valeur de retour  : L’éventuelle valeur de retour est rangee dans %eax.
  • Etat de la pile dans la fonction : 4(%ebp) adresse de retour 8(%ebp) argument 1 12(%ebp) argument 2 …

Génération de code

On donne ici un fichier source écrit en langage C et le code assembleur produit par le compilateur gcc (3.4.2) sur un système Linux/PC Intel.
Remarquer les optimisations pour les cas factorielle(0) et factorielle(1).

/*
 *  factorielle.c
 */
#include 
int main(void){
    int factorielle (int n);
    static int n, i =4;
    n =  factorielle(i);
    printf("Factorielle(%d) = %d\n", i, n);
    return 0;
}

int factorielle(int n){
    int i;
    if  ( (n == 1) || (n == 0) )  i= 1; 
    else i = n * factorielle(n-1);
    return i;
}


Le fichier assembleur (simplifié et commenté) produit par la commande : gcc -Wall -O4 -s factorielle.c

////////////////////////////////////////////////////
    .comm   n.0,4,4
i.1:
    .long   4
.LC0:
    .string "Factorielle(%d) = %d\n"

    .text
main:
    pushl   %ebp
    movl    %esp, %ebp
    pushl   %ebx
    pushl   %ebx
    andl    $-16, %esp
    movl    i.1, %ebx          mettre i dans ebx
    subl    $16, %esp
    cmpl    $1, %ebx           comparer ebx avec 1
    movl    $1, %eax           mettre 1 dans eax (valeur de retour)
    ja  .L11                   aller a L11 SEULEMENT SI i > 1 
    movl    %eax, n.0          initialiser n
    pushl   %edx
    pushl   %eax               mettre n sur la pile
    pushl   i.1                mettre i sur la pile
    pushl   $.LC0              mettre adresse chaine de carc. sur la pile
    call    printf             optimisation : appel direct a printf
    xorl    %eax, %eax
    movl    -4(%ebp), %ebx
    leave
    ret

.L11:
    leal    -1(%ebx), %eax      decrementer ebx et le mettre dans eax
                                (eax est aussi valeur de retour)
    pushl   %eax                mettre eax sur la pile comme argument
    call    factorielle       
    imull   %ebx, %eax
    popl    %ecx
    movl    %eax, n.0
    pushl   %edx
    pushl   %eax                mettre n sur la pile
    pushl   i.1                 mettre i sur la pile
    pushl   $.LC0               mettre adresse chaine de carc. sur la pile
    call    printf
    xorl    %eax, %eax
    movl    -4(%ebp), %ebx
    leave
    ret

factorielle:
    pushl   %ebp                  decrementer esp et sauver ebp sur la pile 
    movl    %esp, %ebp            mettre le esp courant dans ebp (nouvelle fenetre)
    pushl   %ebx                  decrementer esp et sauver ebx sur la pile
    movl    8(%ebp), %ebx         ebx = valeur du premier argument dans la
                                  fenetre precedente
    cmpl    $1, %ebx              comparaison avec 1
    movl    $1, %eax              toujours mettre 1 dans eax 
    ja  .L6                   si argument > 1 : aller a L6
    movl    -4(%ebp), %ebx        (1) restaurer ebx 
    leave                         restaurer la fenetre de pile précédente 
                                  (c.a.d movl %ebp,%esp puis popl %ebp)
    ret                           depiler le compeur ordinal sauve (c.a.d : popl %eip)

.L6:
    subl    $12, %esp              decrementer esp de 12 pour preparer une nouvelle fenetre
    leal    -1(%ebx), %eax         decrementer ebx et le mettre dans eax (valeur de retour)
    pushl   %eax                   mettre eax sur la pile comme argument
    call    factorielle            appel avec (n-1) en argument
    imull   %ebx, %eax             ebx a ici la valeur deposee en (1)     
    addl    $16, %esp              faire pointer esp sur la fenetre precedente
    movl    -4(%ebp), %ebx         restaurer ebx
    leave                          restaurer la fenetre de pile précédente
    ret