d1.05.01 Pour déboguer Code_Aster#

Résumé :

Ce document a pour but de recenser les principaux outils qu’a à sa disposition le développeur Code_A ster pour:

  • Déboguer un plantage ou un comportement anormal

  • Détecter et éradiquer les écrasements, fuites et autres problèmes mémoires

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:


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”) :

valgrind --tool=memcheck --error-limit=no ls

Plus généralement, une bonne ligne de commande Valgrind ressemble à:

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é.

../../../../_images/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

==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

==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 :

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.

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.

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 § 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.

Débogage JEVEUX#

L’usage de JEVEUX peut conduire à certaines erreurs particulières.

Deux outils permettent au développeur de détecter ces erreurs:

  • le mode d’exécution en “debug jeveux”

  • la routine jxveri.F90

« debug jeveux »#

Le mode d’exécution “debug jeveux” s’active dans ASTK en cochant Options/Paramètres/dbgjeveux avant de lancer l’exécution.

Le code s’exécute alors (plus lentement) en provoquant systématiquement la lecture et/ou l’écriture des objets JEVEUX lorsqu’ils sont demandés ou libérés de la mémoire (routines jeveuo, jelibe, jedema). De plus, quand un objet jeveux est détruit (jedetr, jedetc), la zone mémoire qu’il occupait est mise à “undef”. Ce comportement du code permet de provoquer une erreur d’exécution quand:

  • On continue à utiliser un objet après sa destruction

  • On continue à utiliser un objet qui a été “libéré”

  • On écrit dans un objet alors qu’on a demandé un accès en “lecture”.

JXVERI#

jxveri.F90 est la subroutine de Code_Aster permettant de détecter un écrasement dans la mémoire statique de JEVEUX.

Elle est utile lorsque le code s’arrête avec l’un des messages d’erreur suivants:

JEVEUX_15 : Ecrasement amont ...

JEVEUX_16 : Ecrasement aval ...

JEVEUX_17 : Chainage cassé …

L’objet du débogage est alors de localiser l’instruction qui provoque l’écrasement de la mémoire JEVEUX. Pour cela, on agit par itérations successives.

1ère étape

On localise la commande coupable en utilisant DEBUT(DEBUG=_F(JXVERI=’OUI’) )

2ème étape

On surcharge (en mode debug) la routine op00ij correspondant à la commande coupable en la “truffant” de call jxveri(’ ‘,’ ‘) :

subroutineop00ij(...)

...

call jxveri(' ',' ')

bloc 1

call jxveri(' ',' ')

bloc 2

call jxveri(' ',' ')

bloc 3

call jxveri(' ',' ')

end

Lors de l’exécution du code ainsi surchargé, le code s’arrêtera en erreur fatale au 1er appel à jxveri coupable. Si par exemple, il a lieu à la fin du bloc 2 (on connait la ligne grâce au “traceback” imprimé par le débogueur “ post-mortem ”), alors, on réitère le processus en ajoutant des “call jxveri” entre les différentes instructions du bloc 2. Et ainsi de suite …

Dans la pratique, le processus converge assez rapidement vers l’instruction fautive.

Autres outils#

Dans cette partie, on décrit quelques outils qui permettent aussi de trouver des bugs ou de débusquer des comportements anormaux:

  • Option “-Checkbounds” des compilateurs

  • Comparaison de 2 versions différentes de Code_Aster

Dépassement de tableaux ( -CheckBounds)#

Les compilateurs disposent d’outils permettant d’instrumenter le code pour détecter des dépassements de tableaux statiques (un des types de bugs difficiles à trouver en Fortran).

Pour utiliser ces fonctionnalités, il faut recompiler les routines suspectes avec ces options (il faut pour cela modifier le config.txt et le mettre en Donnée dans l’onglet Surcharge) puis exécuter le code. Si un dépassement survient, une erreur fatale avec un message se produira.

Syntaxe :Gcc (g77) : -fbounds-check Intel (ifort) : -CB

Remarques :

Les routines utilisant les COMMONJEVEUX (ZI, ZR, …)ne peuvent pas être compilées avec -CBcar alors l’exécution s’arrête rapidement du fait du débordement du tableau ZI(1).

Du coup, l’utilisation de -CBest un peu compliquée : il faut jongler avec 2 fichiers config.txtet conserver les fichiers .o.

L’intérêt de -CBn’est pas énorme car ce mécanisme ne détecte pas tous les écrasements de tableaux. Pour que l’écrasement soit détecté, il faut que le tableau soit local (donc dimensionné en “dur”), ou bien que ce soit un tableau argument déclaré avec sa longueur exacte (et nom pas TAB()). L’autre intérêt de -CBest la détection des écrasements des chaines de caractères car en fortran la longueur d’une chaine est attachée à la chaine. C’est pour cela que l’on peut faire len(chaine)sur une chaine que l’on a reçue en argument (alors que l’on ne peut pas faire len(TAB)).*

Gcc (g77) : -fbounds-check


-fbounds-check
-ffortran-bounds-check

Enable generation of run-time checks for array subscripts and substring start and end points against the (locally) declared minimum and maximum values.

The current implementation uses the « libf2c » library routine « s_rnge » to print the diagnostic.

However, whereas f2c generates a single check per reference for a multi-dimensional array, of the computed offset against the valid offset range (0 through the size of the array), g77 generates a single check per sub‐ script expression. This catches some cases of potential bugs that f2c does not, such as references to below the beginning of an assumed-size array.

g77 also generates checks for « CHARACTER » substring references, something f2c currently does not do.

Use the new -ffortran-bounds-check option to specify bounds-checking for only the Fortran code you are compiling, not necessarily for code written in other languages.

Note: To provide more detailed information on the offending subscript, g77 provides the « libg2c » run-time library routine « s_rnge » with somewhat differently-formatted information. Here’s a sample diagnostic:

Subscript out of range on file line 4, procedure rnge.F90/bf.

Attempt to access the -6-th element of variable b[subscript-2-of-2].

Aborted

The above message indicates that the offending source line is line 4 of the file rnge.F90, within the program unit (or statement function) named bf. The offended array is named b. The offended array dimension is the second for a two-dimensional array, and the offending, computed subscript expression was -6.

For a « CHARACTER » substring reference, the second line has this appearance:Attempt to access the 11-th element of variable a[start-substring].

This indicates that the offended « CHARACTER » variable or array is named a, the offended substring position is the starting (leftmost) position, and the offending substring expression is 11.

(Though the verbage of « s_rnge » is not ideal for the purpose of the g77 compiler, the above information should provide adequate diagnostic abilities to it users.)

Some of these do not work when compiling programs written in Fortran:

Intel (ifort) : -CB

-CB Performs run-time checks on whether array subscript and

substring references are within declared bounds

(same as the -check bounds option).

Exemple de détection d’erreur :

forrtl: severe (408): fort: (2): Subscript #1 of the array RESU

has value 4 which is greater than the upper bound of 3


Image PC Routine Line Source

asteru_jpl 0000000001F9E956 Unknown Unknown Unknown

asteru_jpl 0000000001F9DB56 Unknown Unknown Unknown

asteru_jpl 0000000001F11232 Unknown Unknown Unknown

asteru_jpl 0000000001EDA0E2 Unknown Unknown Unknown

asteru_jpl 0000000001ED9068 Unknown Unknown Unknown

asteru_jpl 00000000004933AE mkkvec_ 52 mkkvec.F90

asteru_jpl 0000000000493AD0 mmmab2_ 40 mmmab2_jpl.F90

asteru_jpl 0000000000E6A639 te0364_ 370 te0364.F90

asteru_jpl 0000000000910304 te0000_ 1261 te0000.F90

asteru_jpl 0000000000613998 calcul_ 472 calcul.F90

asteru_jpl 0000000000EE74A0 mmcmat_ 149 mmcmat.F90

asteru_jpl 0000000000D9F12F mmcmem_ 69 mmcmem.F90

asteru_jpl 00000000009D308F nmdepl_ 300 nmdepl.F90

asteru_jpl 00000000007A4EA8 op0070_ 304 op0070.F90

asteru_jpl 0000000000599772 ex0000_ 258 ex0000.F90

asteru_jpl 00000000004E6750 execop_ 90 execop.F90

asteru_jpl 00000000004D26EE expass_ 82 expass.F90

asteru_jpl 0000000000499BD7 aster_oper 2635 astermodule.c

Comparaison de 2 versions différentes de Code_Aster#

Il arrive parfois que deux exécutions différentes de Code_Aster conduisent à des résultats différents.

Cela peut se produire :

  • Avec la même version du code sur deux plateformes différentes.

  • Avec deux versions différentes (N et N+1) sur la même plateforme

  • Avec la même version mais avec les deux exécutables “debug” et “nodebug”

Le problème à résoudre est alors d’identifier le morceau de code qui a un comportement différent pour les deux exécutions. Pour localiser le problème, on peut déclencher des impressions intermédiaires à quelques endroits “stratégiques” du code :

  • lors de chaque appel aux calculs élémentaires (routine calcul.F90)

  • lors de chaque appel à la routine de résolution de système linéaire (routine resoud.F90)

En faisant un diff (ou un tkdiff) sur les 2 fichiers message produits, on peut localiser l’endroit où les 2 versions divergent.


Mise en œuvre

Pour déclencher ces impressions, il faut surcharger la routine calcul.F90 et/ou resoud.F90.On modifie alors le source en forçant la variable : DBG=.TRUE..

Cela entraine alors des impressions supplémentaires dans le fichier message.


routine calcul.F90

Par exemple, les impressions de la routine calcul.F90 lors du calcul de l’option AMOR_ACOU sont :

1 &&CALCUL|IN |PGEOMER | MAIL .COORDO .VALE | LONMAX= ...| SOMMR= 0.58898033E+03

2 &&CALCUL|IN |PIMPEDC | IMPEACOU.CHAC.IMPED.VALE | LONMAX= ...| SOMMR= 0.13370000E+04

3 &&CALCUL|IN |PMATERC | CHAMPMAT.MATE_CODE .VALE | LONMAX= ...| SOMMI= 743107436

4 &&CALCUL OPTION=AMOR_ACOU ACOU_FACE8 182

5 &&CALCUL|OUTG|PMATTTC | _9000024.ME001 .RESL | LONMAX= ...| SOMMR= 0.74828831E-04

6 &&CALCUL|OUTF|PMATTTC | _9000024.ME001 .RESL | LONMAX= ...| SOMMR= 0.74828831E-04

Les lignes 1,2,3 correspondent aux 3 paramètres “in” de cette option. Pour chaque paramètre, on imprime des informations sur le champ associé à ce paramètre : nom du champ, LONMAX de l’objet contenant les valeurs du champ, … et “résumé” (colonne SOMMR ou SOMMI) des valeurs du champ.

La ligne 4 indique que le ligrel sur lequel est fait le calcul contient un grel d’éléments de type ACOU_FACE8 et que la routine te00ij.F90 appelée est le te0182.F90.

La ligne 5 renseigne sur le champ “out” PMATTTC après les calculs élémentaires du grel ACOU_FACE8 (donc du te0182.F90).

Les lignes 4 et 5 peuvent être répétées s’il y a plusieurs grel dans le ligrel.

La ligne 6 renseigne sur le champ “out” après le calcul de tous les grel.

Il peut arriver que les impressions montrent que bien que les champs “in” d’un calcul élémentaire soient identiques, les champs “out” diffèrent. On sait alors que le problème concerne un calcul élémentaire précis : OPTION type_element et numéro de la routine te00ij.F90.


Remarques:

Lorsqu’un champ est de type entier, réel ou complexe, le nombre résumant le champ ( SOMMI ou SOMMR ) est un nombre obtenu en “sommant” les valeurs du champ. En réalité, un léger “biais” est introduit pour permettre la détection d’une permutation des valeurs : le vecteur (1 2 3 4) conduira en général à un SOMMI différent de (2 3 1 4).

Pour les champs de type CHARACTER, on fait une somme entière (SOMMI) en transformant chaque caractère en entier (fonction ICHAR).

Attention: un champ “in” est presque toujours différent entre deux exécutions, c’est le champ de “matériau codé” (‘PMATERC’) : il contient des adresses JEVEUXqui n’ont aucune raison d’être identiques. D’autres objets JEVEUXont également presque toujours un contenu différent à chaque exécution, ce sont les objets .TITRqui contiennent en général la date de l’exécution.

Détail : Pour chaque objet JEVEUX“résumé”, on imprime : son nom, sa “somme” (SOMMIou SOMMR) son LONMAX, son LONUTI, son TYPE (R/C/I/K8/…), un code_retour IRET (si iret /= 0, l’objet JEVEUXest dans un état douteux) ainsi qu’un nombre IGNORE qui compte les valeurs “ignorées” dans la somme (SOMMIou SOMMR). Les valeurs ignorées sont les valeurs ‘Nan’ ou invalides (pour faire la somme) : R8MAEM(), R8VIDE(), ISMAEM(), …

routine resoud.F90

Les impressions de la routine resoud.F90 sont :

1 &&RESOUD 2ND MEMBRE | &&MESTAT.2NDMBR_ASS.VALE | LONMAX= ... | SOMMR= -0.20000000000E+06

2 &&RESOUD CHCINE | &&ASCAVC.VCI .VALE | LONMAX= ... | SOMMR= 0.00000000000E+00

3 &&RESOUD MATR.VALM | &&MESTAT_MATR_ASSEM.VALM | LONMAX= ... | SOMMR= 0.13926619473E+13

4 &&RESOUD MATR.VALF | &&MESTAT_MATR_ASSEM.VALF | LONMAX= ... | SOMMR= 0.12301036782E+13

5 &&RESOUD MATR.CONL | &&MESTAT_MATR_ASSEM.CONL | LONMAX= ... | SOMMR= 0.55076923235E+12

6 &&RESOUD SOLU | &&MERESO_SOLUTION .VALE | LONMAX= ... | SOMMR= -0.37488024534E+06

Ligne 1 : second membre du système linéaire

Ligne 2 : valeurs des degrés de liberté imposés éliminés (char_cine)

Ligne 3 : valeurs de la matrice initiale (avant factorisation)

Ligne 4 : valeurs de la matrice factorisée

Ligne 5 : valeur du coefficient de conditionnement des Lagranges (ddls imposés dualisés)

Ligne 6 : valeurs de la solution

Si la ligne 1 diffère, le problème provient de la fabrication du second membre du système.

Si la seule ligne 4 diffère, cela traduit un problème de factorisation (routine preres.F90)

Si la seule ligne 6 diffère, le problème vient de la résolution (routine resoud.F90).