Valgrind

De Ensiwiki
Aller à : navigation, rechercher

Valgrind est un debogueur mémoire, disponible pour Linux. Il permet d'exécuter un programme en vérifiant les accès mémoires, et permet donc d'identifier des erreurs de programmations qui seraient passées inaperçues « par chance ».

Utilisation

Tout simplement, pour trouver des bugs dans l'exécutable « toto », lancer :

valgrind ./toto

Pour que les messages d'erreurs soient pertinents, il faut que « toto » ait été compilé avec l'option -g, comme pour utiliser un débogueur.

Valgrind est particulièrement appréciable pour :

  • Vérifier qu'un programme qui marche marche vraiment (typiquement, avant de le rendre à vos enseignants ...)
  • Mieux comprendre pourquoi un programme qui ne marche pas ne marche pas, vu qu'il donne souvent plus d'informations que Gdb.

Exemple

Voici un petit programme, qui contient une erreur classique de débutant en langage C (on a oublié d'allouer un caractère pour le '\0' de fin de chaine) :

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

char * s1 = "hello\n";

int main(void) {
	char * s2 = malloc(strlen(s1));
	strcpy(s2, s1);
	printf("%s", s2);
	return 0;
}

On peut compiler et exécuter le programme sans se rendre compte de rien :

$ gcc -g expl-valgrind.c -o expl-valgrind
$ ./expl-valgrind                        
hello

Ça marche par chance aujourd'hui, mais on n'a aucune garantie que ça continuera à marcher dans toutes les circonstances. Valgrind trouve le bug immédiatement :

$ valgrind ./expl-valgrind
==13815== Memcheck, a memory error detector.
==13815== Copyright (C) 2002-2006, and GNU GPL'd, by Julian Seward et al.
==13815== Using LibVEX rev 1658, a library for dynamic binary translation.
==13815== Copyright (C) 2004-2006, and GNU GPL'd, by OpenWorks LLP.
==13815== Using valgrind-3.2.1-Debian, a dynamic binary instrumentation framework.
==13815== Copyright (C) 2000-2006, and GNU GPL'd, by Julian Seward et al.
==13815== For more details, rerun with: -v
==13815== 
==13815== Invalid write of size 1
==13815==    at 0x401E9AD: strcpy (mc_replace_strmem.c:272)
==13815==    by 0x804840E: main (expl-valgrind.c:9)
==13815==  Address 0x415302E is 0 bytes after a block of size 6 alloc'd
==13815==    at 0x401D38B: malloc (vg_replace_malloc.c:149)
==13815==    by 0x80483F7: main (expl-valgrind.c:8)
==13815== 
==13815== Invalid read of size 1
==13815==    at 0x401E211: strlen (mc_replace_strmem.c:246)
==13815==    by 0x4060163: vfprintf (in /lib/tls/i686/cmov/libc-2.3.6.so)
==13815==    by 0x4065322: printf (in /lib/tls/i686/cmov/libc-2.3.6.so)
==13815==    by 0x8048421: main (expl-valgrind.c:10)
==13815==  Address 0x415302E is 0 bytes after a block of size 6 alloc'd
==13815==    at 0x401D38B: malloc (vg_replace_malloc.c:149)
==13815==    by 0x80483F7: main (expl-valgrind.c:8)
hello
==13815== 
==13815== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 13 from 1)
==13815== malloc/free: in use at exit: 6 bytes in 1 blocks.
==13815== malloc/free: 1 allocs, 0 frees, 6 bytes allocated.
==13815== For counts of detected errors, rerun with: -v
==13815== searching for pointers to 1 not-freed blocks.
==13815== checked 61,016 bytes.
==13815== 
==13815== LEAK SUMMARY:
==13815==    definitely lost: 6 bytes in 1 blocks.
==13815==      possibly lost: 0 bytes in 0 blocks.
==13815==    still reachable: 0 bytes in 0 blocks.
==13815==         suppressed: 0 bytes in 0 blocks.
==13815== Use --leak-check=full to see details of leaked memory.

Le premier bloc d'erreur explique ce qu'il se passe :

==13815== Invalid write of size 1
==13815==    at 0x401E9AD: strcpy (mc_replace_strmem.c:272)
==13815==    by 0x804840E: main (expl-valgrind.c:9)
==13815==  Address 0x415302E is 0 bytes after a block of size 6
alloc'd
==13815==    at 0x401D38B: malloc (vg_replace_malloc.c:149)
==13815==    by 0x80483F7: main (expl-valgrind.c:8)

On vient de faire une écriture invalide d'un octet (« of size 1 ») à la ligne 9 de expl-valgrind.c, et l'adresse à laquelle on vient d'écrire se trouve just derrière un bloc alloué par malloc à la ligne 8 de expl-valgrind.c.


Autre exemple

L'exemple précédent comporte une autre erreur classique : la variable s2 a été allouée (avec malloc), mais pas libérée (free). Dans cet exemple, ça ne pose pas vraiment de problèmes autre que la propreté du code (puisque le programme va se terminer, et donc libérer toute sa mémoire juste après), mais en cas d'allocations répétitives pour un programme qui doit tourner longtemps, on peut finir par utiliser l'intégralité de la mémoire sans raisons.

valgrind permet de détecter ces fuites de mémoire. La fin du rapport de valgrind nous indique :

==13815== LEAK SUMMARY:
==13815==    definitely lost: 6 bytes in 1 blocks.

Pour avoir plus d'informations sur cet 6 octets perdus, on peut lancer valgrind avec l'option --leak-check=full :

valgrind --leak-check=full ./expl-valgrind

Le début du rapport de valgrind reste le même, mais la fin nous indique à quels endroits se situent les zones mémoires allouées mais non libérées :

==4954== 
==4954== 6 bytes in 1 blocks are definitely lost in loss record 1 of 1
==4954==    at 0x4A05809: malloc (vg_replace_malloc.c:149)
==4954==    by 0x40055C: main (expl-valgrind.c:8)
==4954== 
==4954== LEAK SUMMARY:
==4954==    definitely lost: 6 bytes in 1 blocks.
==4954==      possibly lost: 0 bytes in 0 blocks.
==4954==    still reachable: 0 bytes in 0 blocks.
==4954==         suppressed: 0 bytes in 0 blocks.
==4954== Reachable blocks (those to which a pointer was found) are not shown.
==4954== To see them, rerun with: --show-reachable=yes

Ainsi on sait que les 6 octets "perdus" viennent d'un malloc, appelé à la ligne 8 de expl-valgrind.c.

Callgrind

carte des appels

La commande valgrind --tool=callgrind ./prgm permet de la création d'un fichier de profiling qui vous aidera dans l'optimisation de votre programme.

En appelant kcachegrind sur votre fichier créer par la commande précédente vous obtiendrais la carte des appels:


Aller plus loin

Valgrind permet bien plus que cela. On peut par exemple optimiser son code pour la gestion du cache, faire travailler valgrind avec un débogueur, ou vérifier la bonne utilisation de la mémoire en générale (variables non initialisées par exemple) ... Pour plus d'informations, voir :

Alternatives à Valgrind

Valgrind n'est aujourd'hui disponible que sous Linux et MacOSX = 10.5.x. Les portages pour d'autres plateformes sont en cours, mais ne sont pas la priorité des développeurs.

À moins d'être prêt à utiliser des versions expérimentales, les utilisateurs d'autres systèmes devront donc se tourner vers des alternatives :

Pour Windows, Solaris et quelques autres unices, on citera notamment Purify, propriétaire et payant.

Les programmeurs sous BSD et MacOSX antérieurs à 10.5 devront se contenter de dmalloc ou Electric Fence (à trouver dans les paquetages de votre système). Ceux-ci sont notablement plus difficiles à utiliser (et se combinent avec GDB), et quelque peu moins précis.

Valgrind sous MacOSX SnowLeopard

Valgrind ne supporte pas officiellement Snow Leopard (MacOSX 10.6) dû notamment au support du 64 bits. Il est toutefois possible d'effectuer quelques patchs que la communauté a publié pour obtenir une version utilisable ([details https://bugs.kde.org/show_bug.cgi?id=205241]). Ci-dessous, l'ensemble des commandes à faire pour installer valgrind sur votre machine.

svn co -r 11104 svn://svn.valgrind.org/valgrind/trunk valgrind
cd valgrind
wget http://bugs.kde.org/attachment.cgi?id=40091 -O snow-leopard.patch
wget http://bugsfiles.kde.org/attachment.cgi?id=40900 -O arc4random.patch
wget http://bugsfiles.kde.org/attachment.cgi?id=42530 -O sidt.patch 
wget http://bugsfiles.kde.org/attachment.cgi?id=42892 -O signal.patch
patch -p0 < snow-leopard.patch
patch -p0 < arc4random.patch
patch -p1 < signal.patch
cd VEX; patch -p0 < ../sidt.patch ; cd ..
touch darwin10-drd.supp
touch darwin10.supp
./autogen.sh || autoreconf -fvi
./configure 
make -j 8 && make install

Notez que valgrind ne fonctionnera qu'en mode 32 bits (arch i386). Ainsi, il vous sera impossible de debugger un programme compilé normalement sous SL au risque de vous prendre un message tel que : cannot execute binary file. Pour palier à ce problème, ajoutez dans les options de compilation ET du link :

-m32

Ainsi tous les .o et exécutables seront en architecture i386. L'inconvénient étant que si vous utilisez des librairies annexes, il sera peut-être nécessaire de les recompiler.