Valgrind ======== Présentation ------------ Valgrind est un exécutable permettant de détecter certaines erreurs de programmation lors de l’exécution d’un programme. Le principe de fonctionnement de Valgrind est de surcharger certaines fonctions systèmes. Ceci est fait au travers d’une bibliothèque dynamique et des fonctions telles que malloc *,* free, memcpy sont ainsi remplacées par des équivalents instrumentés par Valgrind. Pour en savoir plus: http://valgrind.org/docs/manual/quick-start.html ou bien: .. code-block:: text man valgrind valgrind --help Utilisation ----------- Pour analyser un calcul avec Valgrind, l’exécutable *Aster* doit être compilé avec les symboles de débogage (version “debug” à cocher dans ASTK). Exemple d’usage (pour vérifier le programme unix “ls”) : .. code-block:: text valgrind --tool=memcheck --error-limit=no ls Plus généralement, une bonne ligne de commande Valgrind ressemble à: .. code-block:: text valgrind --tool=memcheck --error-limit=no --leak-check=full \ --suppressions=python.supp --track-origins=yes Le fichier python.supp permet de supprimer les erreurs Python non justifiées (Python dispose de son propre gestionnaire de mémoire qui se permet des manipulations non standards). On trouve en général un exemplaire de ce fichier dans les distributions Linux. Sur Calibre, celui-ci se trouve dans /usr/lib/valgrind/python.supp. Pour utiliser Valgrind avec *Aster* , il faut pouvoir “encapsuler” l’appel à l’exécutable. Cette technique “d’encapsulation” peut se faire de plusieurs façons mais on ne détaille ici que la plus simple (et celle qui est recommandée). On utilise pour cela la fonctionnalité “exectool” de ASTK. On commence par renseigner dans son fichier local de configuration (situé dans $HOME/.astkrc/prefs) des alias vers des lignes de commandes qui préfixeront la ligne de lancement d’ *Aster:* *desoza@claut621:~$ echo 'memcheck : valgrind --tool=memcheck --error-limit=no --leak-check=full --suppressions=/chemin/vers/python.supp --track-origins=yes' >> ~/.astkrc/prefs* Ensuite dans le menus “Options”, on déclare exectool=memcheck. Puis on lance le calcul normalement. Un message s'affiche alors pour confirmer que l'on veut lancer le calcul avec l'outil sélectionné. .. image:: images/10000201000001E20000025A1EC3802D8B26D234.png :width: 3.4445in :height: 4.3362in .. _RefImage_10000201000001E20000025A1EC3802D8B26D234.png: L'équivalent avec wafest obtenu en exécutant: waf test_debug --name=zzzz000a --exectool=memcheck --time_limit=7200 Plusieurs remarques peuvent être faites: * L’exécution sous valgrind peut être beaucoup plus longue (30 fois plus parfois). Il est préférable d’utiliser un exécutable “debug” pour que le diagnostic valgrind soit plus précis (numéro de ligne dans les sources). Penser donc à allouer suffisamment de temps dans ASTKou utiliser --time_limit avec waf. Il est aussi parfois nécessaire d'augmenter la limite mémoire sous peine d'obtenir un arrêt brutal en cours d'exécution sans information claire. * Avec l’option –-num-callers=n, on choisit la profondeur n de l’arbre d’appel affiché par Valgrind. * L'option –- *track-origins=yes* n'est disponible qu'à partir des versions de Valgrind supérieures à 3.4.0. Décryptage ---------- Une fois le calcul lancé, les messages d’erreur détectés par Valgrind se retrouveront alors mélangés à l’ *output* d’ *Aster* . Ils sont signalés par des “==NumeroDeProcessus==” et on a en général 3 types d'erreurs possibles: * Utilisation d’une variable non initialisée * Lecture invalide en dehors d’un segment mémoire * Écriture invalide en dehors d’un segment mémoire **Variable non initialisée** .. code-block:: text ==8906== ==8906== Conditional jump or move depends on uninitialised value(s) ==8906== at 0x9167E47: nbsuco_ (nbsuco.F90:124) ==8906== by 0x90459F2: poinco_ (poinco.F90:130) ==8906== by 0x8E5CB3F: limaco_ (limaco.F90:120) ==8906== by 0x8C0AAC7: calico_ (calico.F90:284) ==8906== by 0x8BEE78F: charme_ (charme.F90:194) ==8906== by 0x833047F: op0007_ (op0007.F90:66) ==8906== by 0x81D82B9: ex0000_ (ex0000.F90:69) ==8906== by 0x8175A10: execop_ (execop.F90:83) ==8906== by 0x81028F6: expass_ (expass.F90:82) ==8906== by 0x80CDBDD: aster_oper (astermodule.c:2621) ==8906== by 0x408288C: PyCFunction_Call (in /usr/lib/libpython2.5.so.1.0) ==8906== by 0x40D05E8: PyEval_EvalFrameEx (in /usr/lib/libpython2.5.so.1.0) Dans le cas de variables initialisées, il est possible si le problème ne saute pas aux yeux de demander à Valgrind de remonter la chaîne et d’indiquer dans quelle routine la variable non initialisée a été créée. Il faut pour cela rajouter l’option “–track-origins=yes“. Cette option est disponible à partir de la version 3.4.0. **Lecture ou écriture invalide** .. code-block:: text ==11092== ==11092== Invalid write of size 4 ==11092== at 0x94894EE: ajellt_ (ajellt.F90:327) ==11092== by 0x9426F37: cazocc_ (cazocc.F90:552) ==11092== by 0x93863C2: cazoco_ (cazoco.F90:170) ==11092== by 0x90DC5A3: caraco_ (caraco.F90:93) ==11092== by 0x8C0DF43: calico_ (calico.F90:279) ==11092== by 0x8BF2FB7: charme_ (charme.F90:194) ==11092== by 0x832CE8B: op0007_ (op0007.F90:66) ==11092== by 0x81D942D: ex0000_ (ex0000.F90:69) ==11092== by 0x8176114: execop_ (execop.F90:83) ==11092== by 0x81031F2: expass_ (expass.F90:82) ==11092== by 0x80CE40D: aster_oper (astermodule.c:2621) ==11092== by 0x408288C: PyCFunction_Call (in /usr/lib/libpython2.5.so.1.0) ==11092== Address 0x5D3A8E4 is 0 bytes after a block of size 40,036 alloc'd ==11092== at 0x4022765: malloc (vg_replace_malloc.c:149) ==11092== by 0x816B470: hpalloc_ (hpalloc.c:30) ==11092== by 0x80FAA8B: jjalls_ (jjalls.F90:113) ==11092== by 0x8126D61: jxveuo_ (jxveuo.F90:231) ==11092== by 0x80FDE70: jjalty_ (jjalty.F90:59) ==11092== by 0x8104CE1: jeveuo_ (jeveuo.F90:142) ==11092== by 0x94876F6: ajellt_ (ajellt.F90:114) ==11092== by 0x9426F37: cazocc_ (cazocc.F90:552) ==11092== by 0x93863C2: cazoco_ (cazoco.F90:170) ==11092== by 0x90DC5A3: caraco_ (caraco.F90:93) ==11092== by 0x8C0DF43: calico_ (calico.F90:279) ==11092== by 0x8BF2FB7: charme_ (charme.F90:194) Ce bloc se présente en deux parties. La partie haute donne la description de l’erreur et sa localisation dans le source. Ici dans ajellt.F90 à la ligne 327, on fait une écriture de 4 octets en dehors du segment mémoire qui avait été alloué. Pour information la ligne ressemblait à cela : .. code-block:: text ZI(IDLITY+ZI(IDPOMA+ZI(IDAPMA)-1)+I-1) = ITYP La partie basse donne l’origine du problème. En effet on apprend que l’on se situe à l’adresse 0x5D3A8E4 avec un décalage de 0 octets par rapport au segment mémoire dans lequel on est en train d’écrire (autrement dit on est au bout de ce segment). On comprend donc bien que si l’on fait une écriture de taille 4 octets, on sort du segment mémoire. L’information la plus précieuse du bloc Valgrind est que l’objet en dehors duquel on écrit a été alloué dans ajellt.F90 à la ligne 114. .. code-block:: text CALL JEVEUO(LIGRET//'.LITY','E',IDLITY) En regardant les attributs de l’objet LIGRET.LITY, on s’aperçoit qu’il était dimensionné en dur à une longueur 1000, d’où le problème. .. _RefNumPara__9622_1383662992: Erreurs détectées par valgrind mais que l'on peut « oublier » ------------------------------------------------------------- Il est admis que les erreurs de type «Conditional jump or move depends on uninitialised value(s)» détectées sur les routines suivantes ne sont pas problématiques: * jjcrec.F90 * codree.F90 Valgrind pour les nuls ---------------------- Pour lancer une étude avec valgrind 1. vérifier que as_run –showme param memcheckretourne bien une ligne pour exécuter valgrind. 2. Multiplier le temps de l'étude par 100 dans astk ou utliser --time_limit avec waf. 3. dans astk/options/exectool écrire memcheck 4. lancer l'étude en debug Analyse du fichier .mess 1. rechercher les occurrences de «conditional jump» 2. si la dernière routine fortran dans la remontée ne fait pas partie de la liste des routines exemptées (voir § :ref:`2.4 ` ) alors il y a un vrai problème: une variable non initialisée est déclarée dans cette routine. Pour pister cette variable VAR, on peut rajouter des IF (VAR.EQ.XX) dans le source.