Avant de commencer ce TP, téléchargez l'archive TP6.tgz et extrayez la.
GDB
Pour cet exercice, vous aurez à débugger des programmes avec gdb. Vous pourriez bien sûr utiliser d'autres méthodes (ajouter des printf, ou lire et comprendre tous les détails du code, etc.), mais:
- Dans la vraie vie (sur de gros projets), ce n'est pas toujours faisable;
- Vous rêvez d'apprendre à utiliser un des outils de base du développeur C.
Pour cet exercice, nous vous conseillons de vous référer abondamment à l'annexe gdb.
Le programme à débugger est basé sur le corrigé de l'exercice sur les listes vu lors du CI4. Quelques erreurs plus ou moins subtiles se sont néanmoins glissées dans le programme.
Donc, cur_node vaut NULL car le paramètre list est NULL.
En analysant la fonction insert, on voit que cas de l'insertion dans une liste vide n'est pas traité.
Maintenant que l'affichage de la liste fonctionne, la suite du programme s'exécute, mais l'application semble bloquée dans une boucle infinie. Avec Ctrl+C, arrêtez le programme, puis utilisez bt pour examiner la pile d'appels.
Utilisez la commande frame <x> pour vous positionner sur la frame contenant la fonction destroy.
Utilisez la commande b pour ajouter un point d'arrêt à l'entrée de la boucle. Continuez l'exécution du programme avec c.
Vous remarquerez que le programme s'arrête rapidement sur le point d'arrêt. En continuant l'exécution du programme plusieurs fois, vous remarquerez que la boucle est infinie !
Afin de comprendre pourquoi la boucle est infinie, nous allons observer le contenu des variables cur_node et next_node. Pour cela, utilisez la commande display afin d'afficher les valeurs de cur_node, *cur_node, *next_node. Continuez l'exécution du programme, et voyez comment les variables évoluent.
Une fois le problème identifié, il ne vous reste plus qu'à corriger le programme !
Pointeurs de fonction
Avant de commencer cet exercice, placez vous dans le répertoire 3-pointeurs
Dans le fichier tri_tabs.c, on définit deux tableaux non triés : un tableau de short et un tableau de double. Pour trier chacun de ces tableaux, nous allons utiliser la fonction qsort de la bibliothèque C. Elle est générique, dans le sens où elle est capable de trier des tableaux de n'importe quel type de données.
Faites man qsort pour comprendre comment travaille cette fonction, et écrivez la fonction compar_short requise pour trier votre tableau de short avec qsort.
Écrivez la fonction compar_double requise pour trier votre tableau de double avec qsort.
Faites l'appel à qsort sur votre tableau de short.
Faites l'appel à qsort sur votre tableau de double.
Compilez votre programme et vérifiez son bon fonctionnement.
Recopiez le fichier tri_tabs.c que vous avez obtenu à la question précédente en un fichier tri_tabs_bubble.c.
Écrivez une fonction bubble_sort qui a exactement les mêmes paramètres que qsort et dont l'algorithme implémente un tri à bulles.
Remplacez les appels à qsort par des appels à votre fonction bubble_sort.
Compilez votre programme et vérifiez son bon fonctionnement.
Valgrind
On observe que value=17 la plupart du temps. Toutefois, lorsque le paramètre vaut 6, 10, 14, ou 18, la valeur de value est 5.
L'initialisation de buffer semble donc avoir affecté value.
On remarque le message d'erreur de valgrind: Invalid write of size 4.
Ce message apparaît lorsque le programme écrit en dehors d'une zone allouée. La ligne de code responsable de l'erreur se trouve dans init_tab à la ligne 7: tab[i] = 5;
Au passage, on remarque que lorsqu'on exécute le programme avec valgrind, le bug n'apparaît plus (value est bien égal à 17). Ceci est dû à la méthode qu'utilise valgrind pour détecter les accès mémoires incorrects.
Pour un cas non problématique (par exemple, 2), valgrind détecte également un accès incorrect:
Il semble dont que le bug soit présent quelle que soit la valeur du paramètre.
Maintenant que vous avez identifié la portion de code responsable du problème, utilisez maintenant gdb pour observer la valeur pointée par value. Pour cela, ajoutez un breakpoint au début de la fonction buggée, puis ajoutez un watchpoint sur la zone pointée par value.
Une fois le problème identifié, vous pouvez corriger le problème et vérifier avec valgrind qu'il n'y a plus d'accès mémoire incorrect.
Le bug est donc causé par la fonction init_tab lorsqu'on modifie la valeur d'indice 8 de buffer. Comme le buffer est de taille 6, la case d'indice 8 est en dehors du tableau. Il se trouve que cette case correspond à l'emplacement mémoire pointé par value.
Ce type de bug s'appelle buffer overflow (en français: débordement de tampon). Il s'agit d'un bug pouvant causer des failles de sécurités importantes. Cette technique est fréquemment utilisée par les pirates à des fins malveillantes.
Un attaquant peut par exemple modifier une valeur stratégique (par exemple, l'identifiant de l'utilisateur, afin de se faire passer pour un administrateur).
Si la zone mémoire de laquelle on déborde est située sur la pile, il devient possible pour l'attaquant de changer le flot d'exécution du programme (en changeant l'adresse de retour de la fonction). Il s'agit alors d'un stack overflow, responsable d'un grand nombre d'attaques logicielles.
Pour cet exercice, nous allons étudier le programme nbody.c. Il s'agit d'un simulation NBody: le programme simule les interactions gravitationnelles d'un ensemble de corps. C'est à l'aide de ce type de programmes qu'on tente de comprendre la formation de galaxies (par exemple).
Le programme nbody.c alloue un ensemble de particules (des planètes, des étoiles, etc.), chacune caractérisée par une position, une vitesse, et une masse. La simulation consiste, pour chaque pas de temps, à calculer l'interaction gravitationnelle que chaque particule fait subir à chaque autre particule. Une fois la force appliquée à chaque particule calculée, on calcule ses nouvelles position et vitesse avant de passer au pas de temps suivant.
Les données allouées à la ligne 44 (particle->pos = malloc(sizeof(struct vector)); ne sont pas toutes initialisées.
Le problème se résout en initialisant pos et vel:
Lorsque le programme libère le tableau particles, les zones correspondant aux pos, vel et force des particules restent allouées, mais ne sont plus accessibles (puisqu'on "oublie" leur adresse).
Pour corriger le problème, il est nécessaire d'appeler free() pour chacun des buffers alloués: