TP ‐ Environnement de Travail - vbridonneau/CoursSysteme GitHub Wiki
Utiliser la commande man
pour obtenir le manuel d'utilisation de
- la commande
gcc
- Chercher dans le manuel de cette commande le rôle jouer par les options
-Wall
et-Werr
. Vous ne devez pas utiliser les flèches directionnelles.
- Chercher dans le manuel de cette commande le rôle jouer par les options
- la commande
man
(oui le manuel a aussi un manuel d'utilisation). - la fonction de la bibliothèque standard C
printf
.- Que se passe-t-il si l'on se contente d'exécuter la commande suivante ?
man printf
- Que faut-il faire pour pallier ce problème ?
Utilisez la commande top
afin que celle-ci montre :
- Les programmes lancés par vous.
- Les performances de votre machine.
Vous pouvez obtenir ces informations en utilisant la commande btop
qui propose une interface plus moderne.-
L'objectif de cet exercice est d'utiliser un programme de débogage afin de trouver l'origine d'un dysfonctionnement. Avec un tel programme, il est possible de :
- Marquer des points d'arrêts dans un programme. Ces points sont aussi appelés breakpoints dans la littérature.
- Afficher l'état d'une variable (sa valeur).
- Connaître l'état de la pile. Ceci est utile lorsque l'on veut savoir où l'on est dans un programme. On peut également connaître l'ensemble des fonctions appelées à un instant t.
- Avancer instruction par instruction dans un programme.
Le programme que l'on va utiliser pour déboguer nos programmes C se nomme gdb
.
Il s'utilise comme suit :
gdb [--args] ./programme [arguments] # En supposant que le programme se nomme `programme` dans le répertoire courant.
Prenez le programme C suivant:
#include <stdio.h>
#include <stdlib.h>
#define SIZE 10
void init(int* tab, int size) {
for (int i = 0; i <= size; i++) {
tab[i] = 0;
}
}
void print(int* tab, int size) {
for (int i = 0; i <= size; i++) {
printf("%d\n", tab[i]);
}
}
int main(int argc, char const** agrv) {
int *tab = malloc(SIZE * sizeof(int));
init(tab, SIZE);
print(tab, SIZE);
return 0;
}
Pour compiler un programme C afin de pouvoir le déboguer, il faut indiquer à gcc
quelques options supplémentaires.
En effet, gcc
ne fait pas que traduire votre code, il se permet également de l'optimiser s'il pense que certaines séquences d'instructions sont inutiles ou s'il pense pouvoir optimiser votre code.
Il faut donc demander à gcc
de ne pas faire cela.
- Cherchez dans le manuel quelles options fournir à
gcc
pour que le programme soit débogable.
Dans ce qui suit, nous allons utiliser gdb
afin de chercher des potentielles erreurs dans le code et naviguer dans celui-ci.
- Exécutez le programme sans le modifier. D'où vient l'erreur ?
Nous allons maintenant apprendre à naviguer dans un programme lors de son exécution ainsi qu'à lire le contenu de variables.
Pour cela reprenez le programme précédent et relancez le avec gdb
.
- Nous allons commencer par la navigation:
- Mettez un breakpoint à l'instruction précédant l'appel de la fonction
init
(breakpoint
oub
). - Mettez un breakpoint à l'entrée de la fonction
print
. - Lancez le programme (
run
). - Avancez d'une instruction pour arriver sur l'appel à la fonction
init
. - Entrez dans la fonction (
step
ous
). - Avancez de quelques instructions (
next
oun
). - Observez votre position dans le code (
where
) - Continuez l'exécution du programme (
continue
ouc
).
- Mettez un breakpoint à l'instruction précédant l'appel de la fonction
Une chose importante à remarquer est que les commandes step
et next
ont un comportement proche. Leur seul différence réside dans le fait qu'avec step
on peut rentrer à l'intérieur des fonctions.
- Nous allons maintenant apprendre à regarder le contenu de variables et à les modifiées. Relancez
gdb
comme précédemment puis:- Mettez un
breakpoint
au début de la fonctioninit
. - Avancez de quelques instructions afin d'être dans la boucle
for
. - Affichez le contenu de la variable
i
(print
oup
). - Changez le contenu de cette variable à 9 (
set variable i = 9
). - Continuer l'exécution du programme. Que se passe-t-il ?
- Mettez un
Notre objectif ici est d'automatiser la compilation d'un projet C ainsi que l'exécution et le débogage de ce projet. Pour cela nous allons créer un fichier appelé Makefile qui contiendra toutes les directives nécessaires à cette fin. Pour illustrer l'utilisation d'un Makefile, nous allons reprendre l'exemple réalisé lors du TP sur la création d'une bibliothèque. Pour ce faire, commencez par récuperer le projet dans les ressources github et placez les dans un répertoire de travail. L'objectif d'un Makefile est de pouvoir réaliser la compilation, l'exécution ainsi que le débogage avec des commandes courtes et faciles à manipuler. Voici quelques exemples.
La compilation d'un projet se fera en lançant la commande :
make
L'exécution du projet se fera en lançant la commande :
make run
Le débogage du projet se fera en lançant la commande :
make debug
La création d'une bibliothèque
Dans cet exercice nous allons nous concentrer sur des Makefile pour des projets écrits en C.
De manière général un fichier Makefile pour des projets C commence très souvent par contenir les informations relatives au sources du projet (fichiers contenant le code de notre projet) ainsi que le nom de l'exécutable, le compilateur et ses options, les bibliothèques (-lm
pour les fonctions mathématiques).
SRC= # Les sources.
OBJ= # Les fichiers objets (fichiers compilés).
TEST= # Le nom de l'exécutable pour tester la bibliothèque.
CC=gcc # Le compilateur.
FLAGS=-std=c99 # Les options du compilateur.
LIBS= # Les librairies (s'il y en a).
- Reprendre le code ci-dessus et le compléter pour qu'il contienne toutes les informations relatives au projet
-
Note : pour récupérer en une fois tous les fichiers sources possibles il est possible d'utiliser la commande
wildcard
. De la même manière pour convertir tous les fichiers d'extensions .c en .o on pourra utiliser la commandepatsubst
(ou sa cousinesubst
).
-
Note : pour récupérer en une fois tous les fichiers sources possibles il est possible d'utiliser la commande
Une fois que ces informations sont remplies, on peut reprendre notre Makefile pour écrire les différentes règles associées aux commandes que l'on souhaite voir implémentées. Dans notre cas il s'agit de la compilation, l'exécution et le débogage.
Dans un Makefile, une règle définit comment créer un ou plusieurs fichiers cibles à partir de fichiers sources. Une règle de base a la forme suivante :
cible: dependances
commandes
Si l'on souhaite créer le fichier cible à partir d'un terminal, il suffit de lancer make cible
et la cible sera créée.
Dans notre projet, toutes les sources sont des fichiers .c contenus dans le répertorie src. Les sources associées seront les mêmes fichiers mais avec l'extension .o. Il existe une façon simple de faire correspondre un nom du fichier .c avec un fichier .o avec l'aide du caractère %. C'est ce caractère qui permet d'être générique et de ne pas avoir à faire une règle par nom de fichier objet. Par exemple, le code suivant utilise ces concepts pour convertir des fichiers sources C en objet :
%.o: %.c
$(CC) ...
- Compléter et adapter ce code pour créer une règle générique permettant de créer un ficher objet à partir du fichier source qui lui correspond. Note : les fichiers sources sont dans un répertoire source et les objets seront placés dans un répertoire objet.
Maintenant qu'une règle existe pour convertir les fichiers sources en fichier objets, il est temps de créer la bibliothèque.
- Donner la règle permettant de créer une bibliothèque statique. Note: on oubliera pas de mettre les dépendances vers les fichiers objets.
Maintenant que l'on a créé la bibliothèque, il va nous falloir créer la règle principale du Makefile.
Pour cela on veut créer une règle qui, quand la commande make
sans argument est lancée compile notre projet.
Pour identifier ce cas précis, on définit une règle spéciale appelée all
.
- Écrire la règle
all
afin qu'elle puisse créer la bibliothèque.
Pouvoir créer une bibliothèque, c'est bien, mais pouvoir la tester, c'est encore mieux. Nous allons donc créer une règle permettant de créer et d'exécuter des tests. Ces tests sont situés dans le répertoire test/.
- Écrire une règle permettant de créer et d'exécuter les tests contenus dans le répertoire test.
Une dernière chose que l'on veut pouvoir réaliser avec un Makefile c'est de supprimer les fichiers créés à partir des règles précédantes, à savoir les tests, la bibliothèque ainsi que les fichiers objets.
Pour cela, on va écrire une règle clean
faisant le ménage dans les fichiers.
- Écrire la règle
clean
supprimant les fichiers objets ainsi que la bibliothèque et les tests.
Dans ce petit exercice, on se propose d'utiliser un outil de détection de fuite mémoire afin de s'assurer que toute la mémoire que l'on alloue est bien désallouée. Le second objectif de cet exercice sera d'utiliser ce même outil afin de détecter de potentiel accès (lecture/écriture) dans des zones non allouées dans la mémoire. Cet outil se nomme valgrind. Nous allons l'utiliser pour détecter les fuites mémoires dans un programme ainsi que les accès interdits dans la mémoire. Le programme que l'on va étudier est le suivant :
#include <stdlib.h>
void f(void) {
int* x = malloc(10 * sizeof(int));
x[10] = 0;
}
int main(void) {
f();
return 0;
}
On va essayer de comprendre ce que valgrind peut nous dire d'un tel programme. Pour commencer on va compiler notre programme comme on compilerait un programme classique :
gcc program.c -o program
L'idée est ensuite d'utiliser valgrind pour obtenir les informations suivantes:
- Les fuites de mémoires (mémoire non désallouées) et leurs origines: pour cela on cherchera dans le manuel de valgrind l'option indiquant les appels à
malloc
non désallouées. Que dit valgrind concernant ces fuites ? - Les accès interdits. Il n'y a rien à faire de particulier pour ces cas, valgrind les donnes lui même. Comment sont-ils indiqués par valgrind ?
Maintenant que l'on a pu observé comment valgrind nous indique les erreurs présentent dans notre programme, nous allons voir comment l'on peut combiner l'utilisation de gdb
avec valgrind
pour traquer les erreurs dans notre programme.
L'objectif ici est d'utiliser gdb en parallèle de valgrind pour s'arrêter dès que valgrind détecte une erreur à l'exécution. Pour cela on va lancer notre programme avec valgrind avec l'option suivante :
valgrind --vgdb=yes --vgdb-error=0 ./program
Valgrind va alors se lancer, mais notre programme sera mis en pause avant même de rentré dans la fonction main. Dans les quelques lignes que nous montre valgrind, on trouve une série d'instructions expliquant comment utiliser gdb avec valgrind pour que notre programme se lance.
- Cherchez dans la sortie de valgrind les commandes à rentré dans gdb pour pouvoir lancer notre programme. Lancez les.
En utilisant valgrind et gdb, corrigez le programme précédent pour qu'il n'y ait plus d'erreurs.