Débuguer facilement avec Valgrind

Formation

En Ligne

Prix sur demande

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Description

  • Typologie

    Formation

  • Méthodologie

    En ligne

Grâce à cette formation vous pourrez acquérir les connaissances nécessaires qui vous permettrons d’ajouter des compétences à votre profil et obtenir de solides aptitude qui vous offriront de nombreuses opportunités professionnelles.

Questions / Réponses

Ajoutez votre question

Nos conseillers et autres utilisateurs pourront vous répondre

À qui souhaitez-vous addresser votre question?

Saisissez vos coordonnées pour recevoir une réponse

Nous ne publierons que votre nom et votre question

Les Avis

Le programme

Introduction du cours

Valgrind est un programme créé en 2000 par Julian Seward qui a depuis été rejoint par d'autres programmeurs. Il a été conçu principalement pour les programmes écrits en C et C++ et ne fonctionne pas sur Windows. Vous pouvez avoir la liste des plateformes supportées ici.

Valgrind possède plusieurs outils dont memcheck qui permet de :

  • vérifier les accès en lecture et en écriture ;

  • contrôler les fuites de mémoire ;

  • vérifier que l'on n'utilise aucune variable non initialisée.

Tout ceci permet notamment de débusquer très facilement les fameuses erreurs de segmentation qui en ont terrorisés plus d'un. C'est alléchant n'est-ce pas ? :)

Valgrind permet aussi de faire du profilage de code (callgrind), du cache (cachegrind) et du tas (massif), ainsi que du débugage d'application multi-threadée (helgrind). Toutefois ce tutoriel ce concentre sur sa fonctionnalité première qui est de contrôler l'utilisation de la mémoire avec memcheck.

Comment utiliser valgrind ?

Comme je l'ai dit en introduction, valgrind a été principalement conçu pour les programmes écrits en C ou en C++. C'est particulièrement le cas pour l'utilisation que l'on va en faire dans ce tutoriel. Je vais quant à moi utiliser le langage C.

Hello, world !

Prenons un premier programme juste pour comprendre comment on utilise valgrind :

#include<stdio.h> int main(void) { printf("Hello, world !\n"); return 0; }

Simple comme bonjour me direz-vous ! Et bien oui, mais le but ici n'est pas de faire compliqué.

Compilons-le :

gcc -o test0 test0.c

Voila, maintenant exécutons-le avec valgrind :

valgrind ./test0

Voila ce que j'obtiens chez moi :

==9029== Memcheck, a memory error detector ==9029== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al. ==9029== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info ==9029== Command: ./test ==9029==  Hello, world ! ==9029==  ==9029== HEAP SUMMARY: ==9029==     in use at exit: 0 bytes in 0 blocks ==9029==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==9029==  ==9029== All heap blocks were freed -- no leaks are possible ==9029==  ==9029== For counts of detected and suppressed errors, rerun with: -v ==9029== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 12 from 7)

Ça, c'est exactement ce que vous voudrez obtenir quand vous testerez vos propres programmes. :)
Le numéro à gauche indique le numéro du processus, vous n'en aurez probablement pas besoin.
On remarque également la dernière ligne :

==9029== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 12 from 7)

Cette ligne est un résumé du nombre d'erreurs qui ont été détectées. Ne faites pas attention aux erreurs « supprimées », elle ne nous concernent pas. ;)

HEAP summary==9029== HEAP SUMMARY: ==9029==     in use at exit: 0 bytes in 0 blocks ==9029==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==9029==  ==9029== All heap blocks were freed -- no leaks are possible

Il s'agit d'un résumé de l'usage du tas. Lorsque vous utilisez l'allocation dynamique, vous allouez sur le tas.

Fuite de mémoire==9029==     in use at exit: 0 bytes in 0 blocks

Cette ligne nous informe sur la quantité de mémoire allouée dynamiquement et non libérée à la fin du programme. Ici, bien évidemment, comme rien n'a été alloué, il ne peut pas vraiment y avoir de fuite de mémoire... :p

Usage de la mémoire==9029==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated

Cette ligne informe de l'utilisation qui a été faite de l'allocation dynamique. Le nombre d'allocations qui on été faites, le nombre de libérations, et la quantité totale de mémoire qui a été demandée en tout au cours de l'exécution du programme.

Invalid read/write, partie 1

À présent nous allons essayer des programmes volontairement erronés pour voir quelles sortes de messages peut nous donner valgrind. :)

Déréférençons NULL#include<stdlib.h> int main(void) { int *p = NULL; *p = 0; return 0; }

En voila un très joli bug. Compilons vite ça !

gcc -o test02 test02.c

Voila, maintenant exécutons-le, mais sans valgrind :

tados@tados-laptop:valgrind_tests$ ./test02 Erreur de segmentation

Et PAF ! Erreur de segmentation ! Ça plante et on n'est pas avancé.
Mais essayons avec valgrind :

tados@tados-laptop:valgrind_tests$ valgrind ./test02 ==9239== Memcheck, a memory error detector ==9239== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al. ==9239== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info ==9239== Command: ./test02 ==9239==  ==9239== Invalid write of size 4 ==9239==    at 0x80483F9: main (in /home/tados/Documents/SdZ/valgrind_tests/test02) ==9239==  Address 0x0 is not stack'd, malloc'd or (recently) free'd ==9239==  ==9239==  ==9239== Process terminating with default action of signal 11 (SIGSEGV) ==9239==  Access not within mapped region at address 0x0 ==9239==    at 0x80483F9: main (in /home/tados/Documents/SdZ/valgrind_tests/test02) ==9239==  If you believe this happened as a result of a stack ==9239==  overflow in your program's main thread (unlikely but ==9239==  possible), you can try to increase the size of the ==9239==  main thread stack using the --main-stacksize= flag. ==9239==  The main thread stack size used in this run was 8388608. ==9239==  ==9239== HEAP SUMMARY: ==9239==     in use at exit: 0 bytes in 0 blocks ==9239==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==9239==  ==9239== All heap blocks were freed -- no leaks are possible ==9239==  ==9239== For counts of detected and suppressed errors, rerun with: -v ==9239== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 7) Erreur de segmentation

N'ayez pas peur ! On retrouve tout ce qu'il y avait avant, on a juste une petite tartine au milieu mais c'est pas pire que ce que l'on peut avoir en compilant. De plus, comme avec les erreurs de compilation, il faut commencer par la première.

==9239== Invalid write of size 4 ==9239==    at 0x80483F9: main (in /home/tados/Documents/SdZ/valgrind_tests/test02) ==9239==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

Isolée, cette erreur ne fait plus peur du tout : le programme a simplement essayé d'écrire à un endroit où il n'avait pas le droit depuis la fonction main.

OK, c'est un bon indice mais, me direz-vous :
« Mais si ma fonction main fait 400 lignes de long ? »
Et bien je vous dirais qu'il ne faut pas faire des fonctions de 400 lignes de long !
« Mais si c'est pas moi qui l'ai écrite cette fonction de 400 lignes de long ? »
Ha ! alors là c'est différent. C'est vrai, j'avoue, c'est un indice plutôt maigre, alors voila ce qu'on va faire...

Compiler en débug

Oui, il faut compiler en débug, avec l'option -g.

Compiler en débug permet de garder ce que l'on appelle les informations de débugage. Et ce n'est pas pour rien ! Essayons-donc :

tados@tados-laptop:valgrind_tests$ gcc -o test02 test02.c -g tados@tados-laptop:valgrind_tests$ valgrind ./test02 ==9283== Memcheck, a memory error detector ==9283== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al. ==9283== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info ==9283== Command: ./test02 ==9283==  ==9283== Invalid write of size 4 ==9283==    at 0x80483F9: main (test02.c:6) ==9283==  Address 0x0 is not stack'd, malloc'd or (recently) free'd ==9283==  ==9283==  ==9283== Process terminating with default action of signal 11 (SIGSEGV) ==9283==  Access not within mapped region at address 0x0 ==9283==    at 0x80483F9: main (test02.c:6) ==9283==  If you believe this happened as a result of a stack ==9283==  overflow in your program's main thread (unlikely but ==9283==  possible), you can try to increase the size of the ==9283==  main thread stack using the --main-stacksize= flag. ==9283==  The main thread stack size used in this run was 8388608. ==9283==  ==9283== HEAP SUMMARY: ==9283==     in use at exit: 0 bytes in 0 blocks ==9283==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==9283==  ==9283== All heap blocks were freed -- no leaks are possible ==9283==  ==9283== For counts of detected and suppressed errors, rerun with: -v ==9283== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 7) Erreur de segmentation

Là encore, isolons l'erreur du reste de la trace :

==9283== Invalid write of size 4 ==9283==    at 0x80483F9: main (test02.c:6) ==9283==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

Voila qui est mieux ! On sait à présent que l'erreur de segmentation a lieu dans la fonction main, à la 6° ligne du fichier test02.c ! Jetons un petit coup d'œil :

#include<stdlib.h> int main(void) { int *p = NULL; *p = 0; return 0; }

C'est pas merveilleux ça ? :p

Essayons en lecture

On va maintenant faire la même erreur, déréférencer le pointeur NULL, mais pour essayer de lire cette fois. Et hop, un autre petit programme foireux : :p

#include<stdlib.h> int main(void) { int i; int *p = NULL; i = *p; return 0; } tados@tados-laptop:valgrind_tests$ gcc -o test03 test03.c -g tados@tados-laptop:valgrind_tests$ valgrind ./test03 ==9459== Memcheck, a memory error detector ==9459== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al. ==9459== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info ==9459== Command: ./test03 ==9459==  ==9459== Invalid read of size 4 ==9459==    at 0x80483CE: main (test03.c:7) ==9459==  Address 0x0 is not stack'd, malloc'd or (recently) free'd ==9459==  ==9459==  ==9459== Process terminating with default action of signal 11 (SIGSEGV) ==9459==  Access not within mapped region at address 0x0 ==9459==    at 0x80483CE: main (test03.c:7) ==9459==  If you believe this happened as a result of a stack ==9459==  overflow in your program's main thread (unlikely but ==9459==  possible), you can try to increase the size of the ==9459==  main thread stack using the --main-stacksize= flag. ==9459==  The main thread stack size used in this run was 8388608. ==9459==  ==9459== HEAP SUMMARY: ==9459==     in use at exit: 0 bytes in 0 blocks ==9459==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated ==9459==  ==9459== All heap blocks were freed -- no leaks are possible ==9459==  ==9459== For counts of detected and suppressed errors, rerun with: -v ==9459== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 7) Erreur de segmentation

Et voila !

==9459== Invalid read of size 4 ==9459==    at 0x80483CE: main (test03.c:7) ==9459==  Address 0x0 is not stack'd, malloc'd or (recently) free'd

Lecture invalide à la 7° ligne.

À votre prochaine erreur de segmentation, pas de panique ! Un petit coup de valgrind et le tour est joué ! :D
Et ce n'est pas fini, valgrind a plus d'un tour dans son sac !

Invalid read/write, partie 2

On va continuer à faire des accès interdits pour voir différents messages d'erreur. Vous allez voir, il suffit de lire ce que nous écrit valgrind, tout est limpide. Je ne prendrai d'ailleurs plus la peine d'isoler les erreurs, les surligner suffit amplement. ;)

Dépassons, dépassons.

Allez ! Un petit dépassement de tableau pour voir ! :)

#include<stdlib.h> int main(void) { int *p = malloc(3 * sizeof *p); if (p != NULL) { p[3] = 0; free(p); } return 0; } tados@tados-laptop:valgrind_tests$ gcc -o test04 test04.c -g tados@tados-laptop:valgrind_tests$ valgrind ./test04 ==12044== Memcheck, a memory error detector ==12044== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al. ==12044== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info ==12044== Command: ./test04 ==12044==  ==12044== Invalid write of size 4 ==12044==    at 0x804843B: main (test04.c:8) ==12044==  Address 0x4185034 is 0 bytes after a block of size 12 alloc'd ==12044==    at 0x4023C1C: malloc (vg_replace_malloc.c:195) ==12044==    by 0x8048428: main (test04.c:5) ==12044==  ==12044==  ==12044== HEAP SUMMARY: ==12044==     in use at exit: 0 bytes in 0 blocks ==12044==   total heap usage: 1 allocs, 1 frees, 12 bytes allocated ==12044==  ==12044== All heap blocks were freed -- no leaks are possible ==12044==  ==12044== For counts of detected and suppressed errors, rerun with: -v ==12044== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 7)

On a écrit, à la 8° ligne du fichier test04.c, à un endroit situé juste après un bloc de 12 bytes alloué à la 5° ligne du fichier test04.c. Et oui, lorsque l'on essaye d'accéder à une zone mémoire proche d'une zone allouée, valgrind s'en rend compte et nous le signale. Oui j'ai dit « proche », pas « au-delà ». Regardez :

#include<stdlib.h> int main(void) { int *p = malloc(3 * sizeof *p); if (p != NULL) { p[-1] = 0; free(p); } return 0; } tados@tados-laptop:valgrind_tests$ gcc -o test05 test05.c -g tados@tados-laptop:valgrind_tests$ valgrind ./test05 ==12073== Memcheck, a memory error detector ==12073== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al. ==12073== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info ==12073== Command: ./test05 ==12073==  ==12073== Invalid write of size 4 ==12073==    at 0x804843B: main (test05.c:8) ==12073==  Address 0x4185024 is 4 bytes before a block of size 12 alloc'd ==12073==    at 0x4023C1C: malloc (vg_replace_malloc.c:195) ==12073==    by 0x8048428: main (test05.c:5) ==12073==  ==12073==  ==12073== HEAP SUMMARY: ==12073==     in use at exit: 0 bytes in 0 blocks ==12073==   total heap usage: 1 allocs, 1 frees, 12 bytes allocated ==12073==  ==12073== All heap blocks were freed -- no leaks are possible ==12073==  ==12073== For counts of detected and suppressed errors, rerun with: -v ==12073== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 12 from 7)

On a écrit, à la 8° ligne du fichier test05.c, à un endroit situé juste avant un bloc de 12 bytes alloué à la 5° ligne du fichier test05.c. Limpide, je vous dis ! :D

Utilisons un pointeur libéré#include<stdlib.h> int main(void) { int *p = malloc(sizeof *p); if (p != NULL) { free(p); *p = 1; } return 0; } tados@tados-laptop:valgrind_tests$ gcc -o test06 test06.c -g tados@tados-laptop:valgrind_tests$ valgrind ./test06 ==11890== Memcheck, a memory error detector ==11890== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al. ==11890== Using Valgrind-3.5.0-Debian and LibVEX; rerun with -h for copyright info ==11890== Command: ./test06 ==11890== ...

Appeler le centre

Avez-vous besoin d'un coach de formation?

Il vous aidera à comparer différents cours et à trouver la solution la plus abordable.

Débuguer facilement avec Valgrind

Prix sur demande