Débogage avancé/Travail pratique/Allocation dans la pile
But de ce TP
modifierPour éviter le tourbillon de Charybde des allocations dynamique dans le tas (heap), certains programmeurs aventureux ou programmeuses aventureuses sont prêts à se jeter dans la gueule du monstre Scylla des allocations dynamique dans la pile (stack).
Ils doivent alors de garantir que l'adresse de l'allocation ne sera jamais utilisée après la fin de l'exécution de la fonction, puisque cette fin va entrainer la libération automatique de l'allocation.
Côté débogage, le problème est que la pile est librement manipulable. Il est possible d'y lire et d'y écrire partout ! Et du coup tous les outils deviennent beaucoup plus difficiles à exploiter.
Le code à déboguer
modifierCréer un fichier bug_allocinstack.c contenant le code suivant.
#include <stdio.h>
#include <stdlib.h>
typedef struct elem {
struct elem *next;
} Elem;
// Patron commun en Python mais délétère en C
// NB: l'arithmétique sur les pointeurs suffit empêcher l'apparition de
// l'avertissement par le compilateur.
Elem *Elem_new() {
Elem a = {.next = NULL}; // BUG: Elem *a = malloc(sizeof(Elem));
Elem *p = &a;
p++; // pour tromper le linter de votre IDE
p--; // pour tromper le linter de votre IDE
return p; // BUG: return a;
}
int list_length(Elem *h) {
long long int l = 0;
while (h != NULL) {
h = h->next;
l++;
}
return l;
}
int main() {
Elem *a = Elem_new();
Elem *b = Elem_new();
a->next = b; // the code should fail here
Elem *head = a;
printf("list length: %d", list_length(head));
return EXIT_SUCCESS;
}
Les consignes
modifierLe code bug_allocinstack.c
reproduit un schéma
classique de la programmation en Python. Mais, pour allouer tous ses
objets, Python utilise le tas (malloc) ! Même si la syntaxe est
identique, Python ne fait pas du tout la même chose que ce code qui
crée son objet dans la pile de la fonction.
- Compiler le programme. Les deux lignes
p++,p--
sont peut-être suffisantes pour désorienter votre linter, mais probablement pas l'analyseur statique d'un compilateur récent. - Exécuter le programme. Suivant le code généré, il peut boucler à l'infini.
- Exécuter le programme avec Valgrind. Vous n'aurez pas beaucoup plus d'indications.
- Compiler et exécuter le programme avec ASan.
- TODO: Exécuter le programme avec gdb et détecter avec un watchpoint qui modifie dans le champ next.
Vous devriez avoir tapé les commandes suivantes (Certains affichages sont omis):
$ gcc -o bug_allocinstack bug_allockinstack.c -g -Wall -Wextra -fanalyzer
[... détection du bug par l'analyseur statique ...]
$ ./bug_allocinstack
Erreur de segmentation (core dumped)
you@ensipc$ valgrind ./bug_allocinstack
[...] # rien de bien précis, même s'il détecte le problème
==21326== Invalid write of size 8
==21326== at 0x1091A4: main (bug_allocinstack.c:28)
==21326== Address 0x0 is not stack'd, malloc'd or (recently) free'd
[...]
$ gcc -o titi titi.c -Wall -Wextra -fsanitize=address
[... ASan détecte le problème ...]