Débogage avancé/Travail pratique/Double libération

Leçons de niveau 17
Une page de Wikiversité, la communauté pédagogique libre.
Début de la boite de navigation du travail pratique
Double libération
Image logo représentative de la faculté
T.P. no 3
Leçon : Débogage avancé

TP de niveau 17.

Précédent :Débordement de pile
Suivant :Allocation: 0, Libération: 1
En raison de limitations techniques, la typographie souhaitable du titre, « Travail pratique : Double libération
Débogage avancé/Travail pratique/Double libération
 », n'a pu être restituée correctement ci-dessus.


But de ce TP[modifier | modifier le wikicode]

Il est compliqué de suivre dans l'exécution d'un programme si une zone de mémoire allouée dynamiquement dans le tas (avec malloc) est encore utilisée. Mais il faut pourtant bien la libérer un jour pour pouvoir la recycler et ne pas exploser en utilisation de la mémoire. La libération est donc sujette à deux bugs courants: libérer trop tôt une zone encore utilisée, et la libérer deux fois dans deux endroits différents du code. Ce TP tourne autour de ce deuxième bug.

Les langages interprétés (Python, Perl, Ruby, Julia, etc.) et certains langages compilés (Java, Go, Haskell, etc.) font cette libération en exécutant un ramasse-miettes, c'est-à-dire du code dédié à la détection de ces libérations. D'autres méthodes existent comme le compteur de référence d'Objective-C ou des règles strictes d'emprunt d'un pointeur, en Rust (Elles sont vérifiées et validés à la compilation).

Le code à déboguer[modifier | modifier le wikicode]

Créer un fichier bug_doublefree.c contenant le code suivant.

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>

const unsigned int SIZE = 100;

// Double libération dans et après fibon
// NB : argument de type C11 plus précis que uint *p
void fibon(const unsigned int size, unsigned int p[static size]) {
  for (unsigned int i = 0; i < size; i++)
    if (i < 2)
      p[i] = i;
    else
      p[i] = p[i - 1] + p[i - 2];
  free(p); // Première libération
}

int main() {
  assert(SIZE > 2);

  unsigned int *p = malloc(sizeof(int[SIZE]));
  assert(p != NULL);

  fibon(SIZE, p);

  free(p); // Seconde libération
  return EXIT_SUCCESS;
}

Les consignes[modifier | modifier le wikicode]

Le code bug_doublefree.c libère deux fois le tableau alloué, une première fois juste avant la fin de la fonction fibon() et une fois juste après.

  1. Compiler le code. L'analyseur statique de votre compilateur détecte le bug.
  2. Lancer le programme bug_doublefree. C'est votre bibliothèque C qui détecte le problème à l'exécution au moment du second free. Mais elle n'indique pas où était le premier free.
  3. Lancer le programme avec valgrind. Il détecte également le problème. Valgrind donne les différentes lignes du programme ayant faite l'allocation et les deux libérations. Valgrind donne aussi en toute fin un résumé de ce qu'il a observé (1 allocation et deux libérations).
  4. Recompiler en utilisant AddressSanitizer et exécuter le programme. Cette fois la détection interne de ASan (et pas de la librairie C) est capable d'indiquer le code faisant la première libération et celui ayant fait l'allocation.