Débogage avancé/Travail pratique/Allocation: 0, Libération: 1
But de ce TP
modifierIl existe deux principales façons d'allouer de la mémoire dans un programme: dans la pile (les variables locales) et dans le tas (malloc/free). Un pointeur dans un langage comme C ou C++ peut donc pointer de la mémoire valide mais qui n'a jamais été allouée avec un malloc.
La fonction free()
sert exclusivement à libérer de la
mémoire allouée dans le tas avec malloc
,
calloc
ou realloc
.
Le code dans ce TP utilise un tableau alloué dynamiquement dans la pile. Ce tableau ne doit surtout pas être libéré. Cela n'a pas de sens du point de vue de la mémoire.
Le code à déboguer
modifierCréer un fichier bug_stackallocthenfree.c contenant le code suivant.
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
unsigned int SIZE = 100;
// le tableau en argument a été alloué dans la pile, pas avec
// malloc. free ne peut pas être appellé dessus. p est passé avec des
// argument de type C11+
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); // BUG !
}
// Notation C11+ pour les arguments du main
int main(int argc, char *argv[static argc + 1]) {
if (argc == 2)
SIZE = atoi(argv[1]);
assert(SIZE > 2);
unsigned int p[SIZE];
assert(p != NULL);
fibon(SIZE, p);
return EXIT_SUCCESS;
}
Les consignes
modifier- Compiler le programme. Un analyseur statique récent détecte le bug
- Lancer le programme
bug_stackallocthenfree
- Lancer le programme avec Valgrind. Valgrind vous indique que votre variable a été allouée dans la frame de la fonction main (les variables locales de main) mais il ne vous indique pas laquelle.
- Relancer le programme avec Valgrind en demandant à ce dernier de s'arrêter avant le début de l'exécution pour vous laisser vous connecter avec GDB. Connecter GDB. Continuer l'exécution. Remonter dans la pile d'appel et afficher l'adresse de la variable
p
. - Recompiler avec ASan. Lancer le programme. ASan indique que ce qui est libéré vient de la pile
- Lancer la commande
ulimit -s
pour obtenir la taille maximum de votre pile (probablement 8192 Kio, soit 8 Mio). Lancer le programme avec comme argument une valeur au moins 256 fois plus grandes (2097152 = 8192 * 1024/4 car *1024 pour avoir la taille en octet /4 car un entier est sur 4 octets). ASan détecte cette fois un stack overflow. le tableau en variable local est trop grand pour rentrer dans la pile.
Solution
Vous devriez avoir tapé les commandes suivantes (certains affichages de GCC et Valgrind sont omis):
$ gcc -o bug_stackallocthenfree bug_stackallocthenfree.c -g -Wall -Wextra -fanalyzer
[... analyseur statique détecte le bug ...]
$ ./bug_stackallocthenfree
free(): invalid size
Abandon (core dumped)
$ valgrind ./bug_stackallocthenfree
[...]
==11348== Invalid free() / delete / delete[] / realloc()
==11348== at 0x484217B: free (vg_replace_malloc.c:872)
==11348== by 0x1091DF: fibon (bug_stackallocthenfree.c:13)
==11348== by 0x1092A6: main (bug_stackallocthenfree.c:22)
==11348== Address 0x1fff0002e0 is on thread 1's stack
==11348== in frame #2, created by main (bug_stackallocthenfree.c:16)
[...]
$ valgrind --vgdb=full --vgdb-error=0 ./bug_stackallocthenfree &
[...] # copy gdb command: "target remote | vgdb --pid=<NUMÉRO DU PID DU PROCESSUS>"
$ gdb ./bug_stackallocthenfree
[...]
(gdb) target remote | /usr/bin/vgdb --pid=<LE PID INDIQUÉ TROIS LIGNES AVANT>
(gdb) cont # valgrind s'arrête au bug du free(0x1fff0002e0)
(gdb) where # afficher la pile d'appel
(gdb) up 2 # remonter de deux frames
(gdb) print &p
$1 = (unsigned int (*)[100]) 0x1fff0002e0
(gdb) quit # confirmer
$ gcc -o bug_stackallocthenfree bug_stackallocthenfree.c -g -Wall -Wextra -fanalyzer -fsanitize=address
$ ./bug_stackallocthenfree
[... Explications du bug par ASan ...]