Personal tools
You are here: Home Calcul Technique Documentation IBM cluster p575 (Power5) Déboguage
Document Actions

Déboguage

Outils et options de compilation pour éliminer certaines erreurs de programmation

1. Débogueurs

2. Déboguage


Introduction

Les débogueurs sont des auxiliaires précieux pour éliminer les erreurs dans les programmes.

Au premier abord, déboguer un programme consiste à l'arrêter sous certaines conditions pour examiner l'état de la pile d'appels et les valeurs stockées dans les variables.
On arrête l'exécution en insérant des points d'arrêt (breakpoints). Ces points d'arrêt peuvent être inconditionnels (ils engendrent toujours un arrêt du programme lorsqu'ils sont rencontrés) ou conditionnels (ils arrêtent le programme seulement si une condition est vérifiée, condition spécifiée par l'utilisateur).
Ils permettent aussi d'analyser les fichiers core générés lors d'un plantage. Pour cela on autorise la création de tels fichiers avec la commande ulimit coredumpsize.
L'utilisation d'un débogueur nécessite au moins l'ajout des l'options -qnooptimize et -qdbg à la compilation des fichiers sources. La première option inhibe les optimisations et la seconde entraine l'insertion d'informations dans l'exécutable qui permettent au débogueur de suivre les variables locales, les numéros des lignes, etc ...
Le déboguage d'un code est préliminaire et incompatible à toute optimisation.
Pour une liste détaillée des options conseillées pour le déboguage, consultez la page des compilateurs XLF ou XLC.

dbx est un débogueur symbolique textuel.
pdbx est un débogueur parallèle, une extension du débogueur standard, dbx.


flechehaut

dbx

dbx est un débogueur symbolique textuel.
Il permet de déterminer l'origine d'un plantage en étudiant le fichier core pour analyser la pile d'appel du programme :
dbx mon_prog core
On utilise la commande where au prompt qui apparait. En retour s'affiche la file d'appels des fonctions employées ainsi que le nom des fichiers sources et les numéros des lignes ou ont eu lieu ces appels :
(dbx) where
dbx travaille de manière optimale sur un code compilé avec les options -qnooptimize et -qdbg. Cela a pour effet d'inhiber les optimisations et de rajouter des informations de déboguage dans le code objet. Ces informations permettent à dbx de voir les variables locales et de déterminer les numéros des lignes du source. Autrement dbx ne pourra fournir que peu d'informations.

Session de déboguage en interactif


Pour déboguer son code en interactif sur une frontale, il faut privilégier un petit cas test qui prend peu de ressources et permet d'arriver rapidement à la zone suspecte. Prenons par exemple le petit exemple suivant:
  1. le fichier source ...
    program divzero
    implicit none
    real :: x, y, z
    data X /5.0/, Y/0.0/

    Z = X / Y
    write (6,*) 'Z = ', Z
    stop
    end program divzero
  2. Il faut (re)compiler tout son programme avec des options adéquates :
    xlf90_r -qnooptimize -qcheck -qdbg -qextchk -qflttrap=:ov:und:zero:inv:en -qfullpath -qinitauto=FF div_zero.f
    ** divzero === End of Compilation 1 ===
    1501-510 Compilation successful for file div_zero.f.
  3. On autorise la création des fichiers core :
    ulimit -c unlimited
  4. On lance le code :
    ./a.out
    Trace/BPT trap (core dumped)
  5. On applique le débogueur sur le fichier core :
    dbx ./a.out core
    Type 'help' for help.
    [using memory image in core]
    reading symbolic information ...

    Trace/BPT trap in divzero at line 6 ($t1)
       6  Z = X / Y
    (dbx) print X, Y
    5.0 0.0
    (dbx)
Remarque 1 :
On peut déboguer un code sans créer de fichier core en lançant le code directement dans dbx :
dbx a.out
(dbx) run

Trace/BPT trap in divzero at line 6 ($t1)
6 Z = X / Y
(dbx) quit

Remarque 2 :
Dans le cas où un fichier d'entrée est nécessaire, par exemple pour le programme suivant :
program divzero
implicit none
integer :: i
real :: x, y, z
data X /5.0/, Y/0.0/
read (5,*) i
write (6,*) 'i = ', i
Z = X / Y
write (6,*) 'Z = ', Z
stop
end program divzero
on peut le préciser lors du lancement :
dbx a.out
(dbx) run < input

Trace/BPT trap in divzero at line 8 ($t1)
8 Z = X / Y
(dbx) quit

Remarque 3 :
dbx est un débogueur symbolique dont l'exécutable est un binaire compilé en 32 bits. Cela impose un certain nombre de contraintes, notamment sur les ressources utilisées à l'exécution. En particulier, il impose des limites( commande ulimit) quel que soit le programme que l'on débogue. Dans le cas d'un programme compilé en 64 bits qui a un gros bloc BSS (données) les limites imposées par dbx empêche l'exécutable de démarrer en session de débogage. On a un message de la forme :
dbx programme
Type 'help' for help.
warning: cannot execute programme
reading symbolic information ...program is not active

Pour résoudre cela, il faut modifier la commande de lancement de dbx en ajoutant une option. Cela devient :
dbx -E LDR_CNTRL=MAXDATA=0x900000000 programme
On donne à dbx une plus grande limite pour stocker les données du programme en mémoire et cela fonctionne. Si la valeur donnée en exemple (ici en hexadécimal) est trop petite, on peut en mettre plus plus grande.

Procédure de déboguage en soumission de travaux


Pour déboguer son code à l'aide du traitement par lots, on procède de manière analogue au déboguage en interactif. Il y a toutefois deux différences importantes :
  • il faut autoriser la création de fichiers core dans le script de soumission ;
  • il faut récupérer le fichier core dans le cri_finaldir pour l'exploiter en interactif
il faut privilégier un petit cas test qui prend peu de ressources et permet d'arriver rapidement à la zone suspecte. Prenons par exemple le petit exemple suivant:
  1. le fichier source ...
    program divzero
    implicit none
    real :: x, y, z
    data X /5.0/, Y/0.0/

    Z = X / Y
    write (6,*) 'Z = ', Z
    stop
    end program divzero
  2. Il faut (re)compiler tout son programme avec des options adéquates :
    xlf90_r -qnooptimize -qcheck -qdbg -qextchk -qflttrap=:ov:und:zero:inv:en -qfullpath -qinitauto=FF div_zero.f
    ** divzero === End of Compilation 1 ===
    1501-510 Compilation successful for file div_zero.f.
  3. On autorise la création des fichiers core dans le script de soumission:
    # @ core_limit = 100 mb

    cette valeur (par processus) doit être supérieure à la somme de la data_limit et de la stack_limit (par processus)
  4. Dans le script de soumission, on récupère le fichier core :
    ./a.out
    mv core $LOCAL_SPOOL_DIR
  5. Après la fin du job, on applique le débogueur sur le fichier core :
    dbx ./a.out core
    Type 'help' for help.
    [using memory image in core]
    reading symbolic information ...

    Trace/BPT trap in divzero at line 6 ($t1)
       6  Z = X / Y
    (dbx) print X, Y
    5.0 0.0
    (dbx)

Lien vers la page dbx


flechehaut

ddd

ddd (Data Display Debugger) est une interface graphique reposant sur un débogueur symbolique sous-jacent comme dbx ou gdb.

Il s'utilise de manière analogue à dbx que ce soit pour le mode interactif ou le mode batch. On peut aussi l'utiliser pour analyser les fichiers core. Le manuel d'utilisation est ici.

La séquence de démarrage est
ddd mon_programme
Par défaut, ddd travaille avec gdb.

On peut consulter ce site qui présente l'exemple d'introduction traduit en français.


flechehaut

pdbx

Le débogueur parallèle est une extension du débogueur standard, dbx. Il permet de définir un "groupe de tâches parallèles" et d'utiliser les fonctions habituelles de déboguage sur une tâche, sur un ensemble de tâches prénommées (all, slaves, master) ou sur un groupe défini par l'utilisateur.

Il fonctionne en mode NORMAL, où l'utilisateur donne le nom du programme à déboguer, ou en mode ATTACH, où l'utilisateur décide en cours d'exécution d'un programme de lancer le débogueur sur une tâche précise.
Il supporte le déboguage de threads : variables, stack, breakpoints, tracepoints et code source.

Session de déboguage en interactif


Pour déboguer son code en interactif sur une frontale, il faut privilégier un petit cas test qui prend peu de ressources et permet d'arriver rapidement à la zone suspecte. Il ne faut pas oublier que les frontales sont communes à tous les utilisateurs et il est donc souhaitable de limiter le nombre de processus pour le test au strict minimum, 2. Selon que l'on produit (ou non) des fichiers core, on utilise dbx (ou pdbx) sur un d'entre-eux (par exemple celui correspondant à la tâche qui a planté). Prenons par exemple le petit exemple suivant:
  1. le fichier source ...
    program divzero
    implicit none
    integer :: rang, procs, ierr

    real :: x, y, z
    data X /5.0/, Y/0.0/

    call mpi_init (ierr)
    call mpi_comm_rank(mpi_comm_world,rang,ierr)
    call mpi_comm_size(mpi_comm_world,procs,ierr)
    write (6,*) 'je suis le processus ', rang, ' parmi ', procs, ' processus'
    Z = X / Y
    write (6,*) 'Z = ', Z
    stop
    end program divzero
  2. Il faut (re)compiler tout son programme avec des options adéquates :
    mpxlf90_r -qnooptimize -qcheck -qdbg -qextchk -qflttrap=:ov:und:zero:inv:en -qfullpath -qinitauto=FF div_zero.f -o a_mpi.out
    ** divzero === End of Compilation 1 ===
    1501-510 Compilation successful for file mpi_div_zero.f.
  3. On autorise la création des fichiers core :
    ulimit -c unlimited
  4. On lance le code :
    poe ./a_mpi.out -procs 2 -hfile ./hostfile
    je suis le processus 0 parmi 2 processus
    je suis le processus 1 parmi 2 processus
    ERROR: 0031-250 task 1: Trace/BPT trap
    ERROR: 0031-250 task 0: Trace/BPT trap
  5. On applique le débogueur dbx sur le fichier core du processus de rang 1. On a un ensemble de répertoire de la forme coredir.X/core où X est le rang du processus. Ici cela donne :
    dbx a_mpi.out coredir.1/core
    Type 'help' for help.
    [using memory image in coredir.1/core]
    reading symbolic information ...

    Trace/BPT trap in divzero at line 17 in file "/home/crihan/gm/tmp/mpi_div_zero.f" ($t1)
        17   Z = X / Y
    (dbx) print X, Y
    5.0 0.0
    (dbx)

On peut déboguer un code sans créer de fichier core en lançant le code directement dans pdbx, en mode normal :

pdbx a_mpi.out
pdbx Version 4, Release 1 -- Nov 8 2005 16:03:20

    0:Core file "
    0:" is not a valid core file (ignored)
    0:reading symbolic information ...
    0:[1] stopped in divzero at line 9 in file "/home/crihan/gm/tmp/mpi_div_zero.f" ($t1)
    0: 9 call mpi_init (ierr)
0031-504 Partition loaded ...

pdbx(all) cont
   0: je suis le processus 0 parmi 1 processus
   0:Non-breakpoint trap instruction with handler encountered
   0:(set $ignorenonbptrap to disable future notification of such events)
   0:
   0:Trace/BPT trap in divzero at line 17 in file "/home/crihan/gm/tmp/mpi_div_zero.f" ($t1)
   0: 17 Z = X / Y

pdbx(all) print X,Y    0:5.0 0.0

Remarque :
Dans le cas où un fichier d'entrée est nécessaire, par exemple en ajoutant les lignes suivantes :
if (rang == 0) then
   read (5,*) i
   write (6,*) 'i = ', i
end if

On peut directement taper les valeurs dans la session de pdbx :

pdbx a_mpi.out
pdbx Version 4, Release 1 -- Nov 8 2005 16:03:20

   0:Core file "
   0:" is not a valid core file (ignored)
   0:reading symbolic information ...
   0:[1] stopped in divzero at line 9 in file "/home/crihan/gm/tmp/mpi_div_zero.f" ($t1)
   0: 9 call mpi_init (ierr)
0031-504 Partition loaded ...

pdbx(all) cont
   0: je suis le processus 0 parmi 1 processus
3
   0: i = 3
   0:Non-breakpoint trap instruction with handler encountered
   0:(set $ignorenonbptrap to disable future notification of such events)
   0:
   0:Trace/BPT trap in divzero at line 17 in file "/home/crihan/gm/tmp/mpi_div_zero.f" ($t1)
   0: 17 Z = X / Y

Procédure de déboguage en soumission de travaux

Pour déboguer son code à l'aide du traitement par lots, on procède de manière analogue au déboguage en interactif. Il y a toutefois deux différences importantes :
  • il faut autoriser la création de fichiers core dans le script de soumission ;
  • il faut récupérer le fichier core dans le cri_finaldir pour l'exploiter en interactif
il faut privilégier un petit cas test qui prend peu de ressources et permet d'arriver rapidement à la zone suspecte. On procède de même qu'en interactif :
  1. Un petit cas test ;
  2. Il faut (re)compiler tout son programme avec des options adéquates :
    mpxlf90_r -qnooptimize -qcheck -qdbg -qextchk -qflttrap=:ov:und:zero:inv:en -qfullpath -qinitauto=FF mpi_div_zero.f -o a_mpi.out
    ** divzero === End of Compilation 1 ===
    1501-510 Compilation successful for file mpi_div_zero.f.
  3. On autorise la création des fichiers core dans le script de soumission:
    # @ core_limit = 100 mb

    cette valeur (par processus) doit être supérieure à la somme de la data_limit et de la stack_limit (par processus)
  4. Dans le script de soumission, on récupère le fichier core :
    poe ./a.out
    mv coredir.* $LOCAL_SPOOL_DIR
  5. Après la fin du job, on applique le débogueur dbx sur le fichier core d'un processus, par exemple celui qui s'est planté :
    dbx a_mpi.out coredir.1/core
    Type 'help' for help.
    [using memory image in coredir.1/core]
    reading symbolic information ...

    Trace/BPT trap in divzero at line 17 in file "/home/crihan/gm/tmp/mpi_div_zero.f" ($t1)
        17   Z = X / Y
    (dbx) print X, Y
    5.0 0.0
    (dbx)
Lien vers la page pdbx


flechehaut

Options de déboguage

La liste suivante fournit quelques conseils d'écriture de code pour avoir un code portable, lisible et réutilisable :
  • faire un code aéré avec des commentaires ;
  • indenter les lignes du code ;
  • mettre un sous-programme ou un module (ou bloc common) par fichier ;
  • déclarer explicitement l'ensemble des variables (locales et globales), des fonctions externes appelées, avec leur nom générique ;
  • utiliser les types génériques pour les variables (REAL, INTEGER), voir les conseils pratiques (type de données des flottants) ;
  • construire des modules Fortran90 par thème (des blocs common par type de variables et par thème) : CHARACTER et LOGICAL à part ;
  • déclarer explicitement les dimensions des tableaux en Fortran (pas de * fourre-tout) et transmettre ces dimensions comme arguments lors des appels ;
  • utiliser les clauses INTENT pour les programmes en Fortran 90.
Ensuite, les principales options de déboguage :
  • aucune optimisation de code : option -qnooptimize ;
  • enrichir la table des symboles pour permettre la localisation de certaines erreurs ou/et l'utilisation efficace d'un débogueur symbolique (par exemple dbx) : option -qdbg ;
  • recherche des variables non initialisées avant utilisation : option -qinitauto=ff, cette option initialise à NaN toutes les variables locales et automatiques mais ne détecte rien, pour cela il faut utiliser l'option -qflttrap (ci-dessous) ;
  • recherche des débordements de tableau : option -qcheck ;
  • vérification des séquences d'appels de sous-programmes : option -qextchk ;
  • capture des signaux des exceptions : option -qsigtrap (empêche la création de fichier core) ;
  • traçage des exceptions flottantes (opérations non conformes sur les nombres réels) : option -qflttrap=ov:und:zero:inv:en ;
  • affichage de tous les messages de la compilation : option -qflag=i:i; on peut éliminer certains messages (warning) qui inondent les sorties à l'aide de l'option -qsuppress=nnnn-mmm. nnnn-mmm est le numéro du message et on peut préciser un seul numéro, soit plusieurs, intercalés avec des double-points ":" .
Il faut ensuite valider à part les différents morceaux d'un code : écrire un petit programme pour tester la subroutine qui effectue le produit matrice vecteur, la factorisation LU, le gradient conjugué, ... au cas ou il n'existe pas déjà la routine adéquate dans une bibliothèque scientifique (comme BLAS, LAPACK, ...)

Résumé :

Une séquence d'options pour le déboguage pourrait donc être :

-qnooptimize -qcheck -qdbg -qextchk -qflttrap=:ov:und:zero:inv:en -qfullpath -qinitauto=FF


flechehaut

Exceptions flottantes

Les exceptions flottantes sont des résultats incorrects qui entrainent un surcoût cpu pour le traitement de l'opération. Ce surcoût peut être très important selon l'ampleur du phénomène. Cela peut se produire même si le code ne plante pas ou si les résultats semblent corrects (présence d'underflows).
Il y a cinq types d'exception : underflow, overflow, divide-by-zero, invalid operation, inexact operation. Il y a aussi une sixième exception concernant les entiers : l'integer overflow.
De manière générale, la présence d'exceptions flottantes correspond à des erreurs dans le programme et il est impératif de les corriger avant de passer à l'exploitation du code. La rubrique précédente propose des outils pour détecter ces exceptions.

  • underflow
Les underflows sont générés lorsqu'une opération arithmétique produit un résultat trop petit pour être représenté comme un nombre flottant normalisé (le premier chiffre de la mantisse est différent de zéro). La valeur renvoyée peut être zéro ou la valeur la plus petite représentable selon le type de la variable.

  • overflow
Les overflows sont générés lorsqu'une opération arithmétique produit un résultat trop grand pour être représenté comme un nombre flottant normalisé (le premier chiffre de la mantisse est différent de zéro). La valeur renvoyée est alors l'infini (Inf), signée.

  • divide-by-zero
Une division par zéro se produit lorsqu'un nombre non nul est divisé par un nombre nul. La valeur renvoyée est alors l'infini (Inf), signée.

  • invalid operation
Une opération invalide se produit lorsqu'au moins l'un ou plusieurs opérandes ne sont pas valides pour l'opération implémentée. La valeur renvoyée est une valeur particulière appelée Not a Number (NaN). Cela peut survenir en divisant deux valeurs infinies, en multipliant une valeur infinie par zéro, en additionnant deux infinis de signe opposé, en prenant la racine carrée (ou le logarithme) d'un nombre négatif (ou nul), etc ...

  • inexact operation
Une opération inexacte se produit lorsqu'une perte importante de précision a lieu ou s'il se produit un overflow non détecté, par exemple dans le cas ou la valeur en sortie diffère de la véritable valeur si le calcul des exposants ou des mantisses ne sont pas bornés. La valeur en sortie est la valeur ainsi calculée.

  • integer overflow
Une opération sur les entiers produit une valeur supérieure au plus grand entier représentable dans le format de la variable.

  • tableau récapitulatif

Calcul résultat par défaut type d'exception
grand * (- grand) - inf overflow, inexact
inf / inf NaNQ invalid, inexact
nombre / 0.0 + inf divide-by-zero/td>
- nombre / 0.0 - inf divide-by-zero
0.0 / 0.0 NaNQ invalid
petit * petit 0.0 underflow, inexact
inf * 0.0 NaNQ invalid
petit / grand nombre subnormal underflow, inexact
inf / 0.0 inf oveflow, inexact
0.0 / inf 0.0 underflow, inexact
2.0 / 3.0 2/3 (arrondi) inexact

  • représentation des nombres
La représentations des nombres se fait en base 2 en distinguant la mantisse de l'exposant (pour les nombre flottants), chacun ayant un nombre de bits permettant son codage. Cette représentation est architecture-dépendant, il s'agit de la représentation interne des données en mémoire, ce qui signifie aussi que les fichiers non formatés (qui reprennent cette représentation interne) sont généralement inexploitables sur une machine différente de celle qui les a générés si le codage employé n'est pas reconnu.
Par exemple, sous AIX les intervalles de valeurs pour les différents types de données sont :

Type des données base 2 base 10
integer(*4) -2^31 < I < 2^31 -10^9 < I < 10^9
integer(*8) -2^63 < I < 2^63 -10^18 < I < 10^18
real(*4) (i.e. real) 2^(-125) < |R| < 2^128 10^(-38) < |R| < 10^38
real(*8) (i.e. double precision) 2^(-1021) < |R| < 2^1024 10^(-308) < |R| < 10^308

Sur un IBM p690, les underflows surviennent par exemple lorsque |R| < 10^(-38) en simple précision (soit 4 octets) ou 10^(-308) en double précision (soit 8 octets) et les overflows lorsque |R| > 10^38 en simple précision ou 10^308 en double précision.

  • précision des nombres
La précision des nombres concerne uniquement les nombres flottants. En effet, pour un nombre entier soit la valeur que l'on veut affecter à une variable de type entier est dans l'intervalle de valeurs du type considéré soit elle est en dehors de cet intervalle, ce qui est un overflow.
Un nombre réel a comme particularité que son développement décimal peut être infini. Hors on ne peut pas garder toutes ses décimales : on ne peut en garder qu'un nombre fini, on fait donc un arrondi en tronquant son développement. En général, la simple précision correspond à environ 6-7 décimales, la double précision à environ 14-15 décimales et la quadruple à environ 30-31; à l'exception (liste non exhaustive) des machines vectorielles Cray où la simple précision se situe vers 14-15 décimales et la double précision vers 30-31 décimales.
Lorsque l'on fait un calcul il est important de connaitre la précision employée, cela permet de relativiser le nombre de décimales significatives des résultats et de déterminer si une "petite" valeur a un sens ou bien s'il peut s'agir d'une erreur d'arrondi ou de troncature.


flechehaut

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: