BCI INF Module ARSE
TP chaine de production
B. Dupouy, L. Pautet et S. Gadret


SOMMAIRE

  1. Présentation du programme à étudier
    1. Question 1
    2. Question 2
  2. Edition de liens statique et dynamique
  3. Fichier Makefile
    1. Question 1
    2. Question 2
    3. Question 3
    4. Question 4
    5. Question 5
  4. Gestion de bibliothèque
    1. Création
    2. Utilisation
  5. Annexe 1 : Structures des fichiers objets et exécutable
    1. Le premier fichier objet
    2. Le second fichier objet
    3. Le fichier exécutable
  6. Annexe 2 : La pile. Génération de code (Intel)
    1. La pile
    2. Génération de code

Exercices à rendre :
  1. Celui de la première partie
  2. Le Makefile de la troisième partie
Les envoyer par mail à l'enseignant qui vous fait les cours, par exemple :
dupouy@enst.fr

Le texte du sujet ( subject ) doit contenir le mot :
TPAST pour les AST (INF 201)
TP1A pour les éléves de 1ère année


1. Présentation du programme à étudier

On travaillera sur le programme contenu dans starwars.c (Pour le récupérer, cliquer ici. )
Il s'agit de la simulation d'un combat au sabre entre SkyWalker (le bon) et DarkVador (le méchant, attention !) , héros de la célèbre saga...

L'envoi d'un coup de sabre est simulé par l'émission d'un signal vers l'adversaire. Chacun des combattants se protége en ignorant le signal qui lui est destiné. Des qu'ils attaquent leur adversaire, les protagonistes deviennent sensibles aux signaux, donc peuvent recevoir des coups. Le programme est divise en trois fonctions :

Pour travailler, créez un répertoire, recopiez-y starwars.c et travaillez dans ce répertoire :
mkdir tp_chprod
cd    tp_chprod

1.1 Question 1

Rappels:

while (PointsDeVie[Moi]  > 0) {
   signal (Sabre[Lui], SIG_IGN);           /*  Commentaire 30 */
   sleep (random() % 3);
   sleep (TempsDefense[Moi] - 1);          /*  Commentaire 35 */

   signal (Sabre[Lui], Touche);            /*  Commentaire 40 */
   sleep (TempsAttaque[Moi]);
   ik = kill (Pids[Lui], Sabre[Moi]);
   if (ik < 0)  break;                     /*  Commentaire 45 */
  }

1.2 Question 2

Nous allons découper le programme en plusieurs fichiers :

  1. Faites ce découpage
  2. Incluez les fichiers .h nécessaires dans les fichiers .c.
  3. Recompilez chaque fichier comme indiqué ci-dessous, il ne doit pas y avoir de "warnings" si il y en a, retournez à l'étape 2.
Pour éviter les inclusions multiples, utilisez #ifndef et #define.
Pour créer un exécutable dont le nom est starwars, faites :
gcc main.o foncs.o -o starwars

2. Edition de liens statique et dynamique


Attention :
Il se peut que les bibliothèques statiques ne soient pas disponibles (message d'erreur à à l'exécution de la première commande de la question 1 suivante), dans ce cas passez au paragraphe 3 (Fichier Makefile).
Nous allons voir les différences entre édition de liens statique et dynamique.
  1. Exécuter les deux commandes suivantes, la première donne un exécutable complet (édition de liens statique), la seconde un fichier qui contient des références non satisfaites qui sont des pointeurs vers des bibliothèques d'exécutables.
      
    gcc -static main.o foncs.o -o exo-s
    gcc main.o foncs.o -o exo
    

  2. Vérifier que le fichier construit en édition de liens statique est plus volumineux que celui construit en édition de liens dynamique en utilisant les deux commandes suivantes :
      
    ls -l exo-s
    ls -l exo
    
  3. Pour vérifier qu'il n'y a plus de symboles non définis dans le second fichier mais qu'il en reste dans le premier, exécuter les deux commandes suivantes :
    nm (ou /usr/local/bin/nm) exo
    nm exo-s
    

    La sortie de nm exo montre que les sympoles suivants ne sont pas définis dans exo :
    U printf
    U random
    U signal
    U sleep

  4. Pour avoir la liste des bibliothèques dites "dynamiques" utilisées, faire :
    ldd exo. On obtient une liste du type :
    
       libc.so.1 =>     /usr/lib/libc.so.1
       libm.so.2 =>     /usr/local/lib/libm.so.2
    


3. Fichier Makefile

Nous allons maintenant écrire le fichier Makefile correspondant à cette application, pour en obtenir le canevas cliquer ici.)

(Pour en savoir plus sur make on consultera le man et/ou la page de Philippe Dax.)

3.1 Question 1

Compléter le fichier Makefile et exécuter la commande make, ou gmake, puis le fichier starwars :

gmake all
starwars

pour en vérifier le bon fonctionnement.

3.2 Question 2

Créer un répertoire include et y déplacer tous les fichiers .h.
Faire :
gmake all
Modifier le Makefile pour que ce Makefile retrouve les fichiers include sans que vous ayez à modifier le code des fichiers sources.
Indications :

3.3 Question 3

Dans main.c, changer la ligne:
sleep (random() % 3);
en
sleep (sqrt (random() % 3) );

Faire : gmake starwars
Quelles modifications faut-il faire pour remédier aux erreurs ?
TOUS les warnings doivent disparaitre.

3.4 Question 4


Faire : gmake starwars

Dans utils.h, changer la ligne:
typedef short POINTS;
en
typedef float POINTS;

  1. Faire à nouveau gmake starwars
    Si gmake recompile c'est que vous avez anticipé la question ! Passez au point suivant.
    Sinon, si il ne se passe rien, c'est que quelque chose manque puisque ce fichier utils.h concerne foncs.c et main.c. Pourquoi rien n'a été refait ?
    Modifier le fichier Makefile pour résoudre ce problème.
    Remarque :
    On peut automatiser les inclusions de fichiers de type .h dans les règles de make en utilisant makedepend. Nous n'en parlons pas dans cette introduction.

  2. Faire à nouveau gmake starwars, il y a sans doute un warning.
    Nous allons provisoirement l'ignorer.
    Exécuter la commande gmake starwars
    Les valeurs affichées concernant les points de vie dans main et dans la fonction touche ( ... Pts DV : ... ) sont étonnantes.
    programme correct, erreur dans le printf !!!!
  3. Corriger la cause du warning.
    Attention à l'utilisation de printf pour les mises au point.

3.5 Question 5


Pour les mises au point de programmes, il est conseillé d'utiliser des outils tels que gdb ou xxgdb qui permettent de suivre l'exécution du programme en pas à pas et de vérifier ou de modifier le contenu de leurs variables.
Si vous ne l'avez pas déja fait, ajoutez l'option -g aux paramétres CFLAGS et LDFLAGS.
Faites gmake all, puis utilisez gdb comme indiqué ci-dessous.

Si on veut utiliser gdb sur le programme starwars, on entre la commande suivante :

gdb starwars

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


(gdb) break main
Breakpoint 1 at 0x10900: file main.c, line 36.
(gdb) run
Starting program: starwars 
Breakpoint 1, main () at main.c:36
36        int TempsDefense[] = {5, 3};
(gdb) step
37        int TempsAttaque[] = {3, 1};
(gdb) step
41         printf ("PointsDeVie : SkyWalker %d DarkVador : %d\n",
(gdb) step
PointsDeVie : SkyWalker 1076101120 DarkVador : 0
44        pid = fork();
(gdb) print PointsDeVie[0]
$1 = 10
(gdb) print PointsDeVie[1]
$2 = 10
(gdb) 

Essayez également xxgdb starwars, xxgdb est une version graphique de gdb, plus ergonomique que ce dernier, à utiliser pour mettre au point les programmes sans avoir à les remplir de printf !


4. Création de bibliothèque

4.1 Création

Nous allons utiliser la commande ar qui permet de regrouper plusieurs fichiers en un seul, appelé archive, sa principale utilisation 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 foncs.c.
  1. Bien sûr, il faut d'abord créer les fichiers objets, par exemple, ici :
    gcc -Wall -c foncs.c -I./include
  2. Création de l'archive en utilisant la commande ar :
    adour$ar r libma_bib.a foncs.o
    ar: creating archive libma_bib.a
    adour$ ar t libma_bib.a
    foncs.o
    
    On peut maintenant faire :
    adour$rm foncs.o
    
  3. Création d'un index.
    La commande ranlib génère l'index pour l'archive libma_bib.a et range cet index dans l'archive. L'utilisation de nm (/usr/local/bin/nm) permet de vérifier quels sont les symboles définis et indéfinis dans l'archive.
    Par exemple :
    
    adour$ranlib libma_bib.a
    adour$nm libma_bib.a
    libma_bib.a(foncs.o):
             U _DegatSabre
    00000148 T _Fin
             U _Lui
             U _Moi
             U _Noms
             U _PointsDeVie
             U _Sabre
    00000000 T _Touche
             U _exit
             U _printf
             U _signal
    

Remarque :
Faire la différence entre la commande ar t qui donne la liste des fichiers contenus dans l'archive et la commande nm qui donne la liste des symboles figurant dans cette archive : On constate ainsi que l'archive contient bien Touche et Fin.

4.2 Utilisation

Utilisation de la bibliothèque.
  1. Première version :
    
    adour$ gcc -Wall -c main.c -I./include
    adour$ gcc main.o libma_bib.a -o fic
    
    Dans la première ligne, gcc agit en compilateur, dans la seconde en éditeur de liens.

  2. Seconde version :
Remarques :


Annexe 1 - Structures des fichiers objets et exécutables

Annexe 1.1 Le premier fichier objet

Exécuter la commande suivante : On obtient des informations sur les différentes sections du fichier objet main.o:
Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         00000544  00000000  00000000  00000084  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
                  
  1 .data         00000018  00000000  00000000  000005c8  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, DATA
                  
  2 .rodata       000000b2  00000000  00000000  000005e0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
                  
  3 .comment      00000070  00000000  00000000  0000
Commentaires : Exécuter la commande suivante : Extrait du résultat :
00000000 l    df *ABS*  00000000 main.c
00000000 l    d  .data  00000000 
00000000 l       .text  00000000 gcc2_compiled.
00000000 l    d  .text  00000000 
00000000 l    d  .rodata        00000000 
00000000         *UND*  00000000 Touche
00000000         *UND*  00000000 getpid
00000000         *UND*  00000000 exit
00000008       O *COM*  00000004 Pids
00000004       O *COM*  00000004 Lui
00000004       O *COM*  00000004 Moi
00000000         *UND*  00000000 printf
00000000         *UND*  00000000 kill
00000000         *UND*  00000000 random
00000000 g     F .text  00000544 main
00000000         *UND*  00000000 sleep
00000000         *UND*  00000000 fork
00000000 g     O .data  00000004 PointsDeVie
00000000         *UND*  00000000 getppid
00000000         *UND*  00000000 .rem
00000004 g     O .data  00000004 DegatSabre
00000000         *UND*  00000000 Fin
00000000         *UND*  00000000 signal
00000010 g     O .data  00000008 Noms
00000008 g     O .data  00000008 Sabre
Commentaires sur le résultat de cette commande : Exécuter la commande suivante : Extrait du résultat :
   
Contents of section .data:
 0000 000a000a 00030002 00000010 00000011  ................
 0010 00000000 00000000                    ........   
      
Contents of section .rodata:
 0000 536b7957 616c6b65 72000000 00000000  SkyWalker.......
 0010 4461726b 5661646f 72000000 00000000  DarkVador.......
 0020 506f696e 74734465 56696520 3a20536b  PointsDeVie : Sk
 0030 7957616c 6b657220 25642044 61726b56  yWalker %d DarkV
 0040 61646f72 203a2025 640a0000 00000000  ador : %d.......
 0050 2573202d 3e207069 64202564 2c202573  %s -> pid %d, %s
 0060 202d3e20 70696420 25640a00 00000000   -> pid %d......
 0070 25732064 6566656e 640a0000 00000000  %s defend.......
 0080 25732061 74746171 75652025 730a0000  %s attaque %s...
 0090 25732028 70696420 25642920 61207661  %s (pid %d) a va
 00a0 696e6375 20257320 28706964 20256429  incu %s (pid %d)
 00b0 0a00               
Commentaires sur le résultat de cette commande :

Annexe 1.2 Le second fichier objet

Exécuter la commande suivante : 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         0000017c  00000000  00000000  00000074  2**2
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .rodata       00000050  00000000  00000000  000001f0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .comment      00000070  00000000  00000000  000005d4  2**0
                  CONTENTS, READONLY
Exécuter la commande suivante : Extrait du résultat :
SYMBOL TABLE:
00000000 l    df *ABS*  00000000 foncs.c
00000000 l       .text  00000000 gcc2_compiled.
00000000 l    d  .text  00000000 
00000000 l    d  .rodata        00000000 
00000000 g     F .text  00000110 Touche
00000000         *UND*  00000000 exit
00000000         *UND*  00000000 Lui
00000000         *UND*  00000000 Moi
00000000         *UND*  00000000 printf
00000000         *UND*  00000000 PointsDeVie
00000000         *UND*  00000000 DegatSabre
00000110 g     F .text  0000006c Fin
00000000         *UND*  00000000 signal
00000000         *UND*  00000000 Noms
00000000         *UND*  00000000 Sabre
Commentaires sur le résultat de cette commande : Exécuter la commande suivante : Extrait du résultat :
Contents of section .rodata:
 0000 25732074 6f756368 65202573 20285074  %s touche %s (Pt
 0010 73205357 203a2025 64290a00 00000000  s SW : %d)......
 0020 25732074 6f756368 65202573 20285074  %s touche %s (Pt
 0030 73204456 203a2025 64290a00 00000000  s DV : %d)......
 0040 25732061 20766169 6e637520 25730a00  %s a vaincu %s..
Commentaires sur le résultat de cette commande :

Annexe 1.3 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 : On obtient des informations sur les différentes sections du fichier exécutable exo, on s'intéresse aux suivantes:
Sections:

 11 .rodata       00000110  00010f78  00010f78  00000f78  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 15 .data         00000020  0002123c  0002123c  0000123c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 19 .bss          0000002c  00021270  00021270  00001270  2**2
                  ALLOC
Commentaires :
  
   Contents of section .data:
 2123c 00021268 00000000 000a000a 00030002  ...h............
 2124c 00000010 00000011 00010f90 00010f80  ................
Pour retrouver les variables initialisées (data) et non initialisées (bss), faire :
  1.   
    objdump -t exo | grep .data  
    
    Extrait du résultat :
      
    0002123c l    d  .data  00000000 
    00021244 l     O .data  00000000 force_to_data
    00021240 l     O .data  00000000 completed.4
    0002123c l     O .data  00000000 p.3
    0002123c l     O .data  00000000 force_to_data
    00021254 g     O .data  00000008 Noms
    0002124c g     O .data  00000008 Sabre
    00021248 g     O .data  00000004 DegatSabre
    00021244 g     O .data  00000004 PointsDeVie
    
  2.   
    objdump -t exo | grep bss  
    
    Extrait du résultat :
      
    00021270 l    d  .bss   00000000 
    0002129c l     O .bss   00000000 _END_
    00021270 l     O .bss   00000018 object.11
    00021298 g     O .bss   00000004 _environ
    00021290 g     O .bss   00000008 Pids
    00021288 g     O .bss   00000004 Lui
    0002128c g     O .bss   00000004 Moi
    00021298  w    O .bss   00000004 environ
    0002129c g     O .bss   00000000 _end
    

Annexe 2- La pile. Génération de code

Annexe 2.1 La pile


Annexe 2.1 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


©(Copyright) dupouy@inf.enst.fr