ift3912 : développement & maintenance de logiciels
TRANSCRIPT
Bruno DufourUniversité de Montré[email protected]
Débogage
IFT3912 – Développement et maintenance de logiciels
Matériel adapté de : Andreas Zeller, “Why Programs Fail ”, 2nd ed, Morgan Kaufmann, 2009
Thursday, March 31, 2011
Quelques staEsEques…
■ Les bogues coûtent ~ 60 milliards $ annuellement■ Des amélioraJons pourraient réduire ce coût de 30%■ La validaJon (incluant le débogage) peuvent facilement prendre jusqu’à 50-‐75% du temps de développement
■ Certaines personnes sont trois fois plus efficace que d’autres pour le débogage
2
Thursday, March 31, 2011
Le premier bogue
3
9 septembre 1947
Thursday, March 31, 2011
Plus de bogues...
4
Thursday, March 31, 2011
DéfiniEons (rappel)
§ Erreur (error): commise par un développeur• Erreur de programmaEon• Erreur de logique
§ Faute ou défault (fault, defect): état interne invalide d’un logiciel après l’acEvaEon d’une erreur
§ Défaillance (failure): manifestaEon externe d’une faute• Observable (ex: le programme dévie de sa spécificaEon)
5
Thursday, March 31, 2011
Erreurs, Fautes & Défaillances (rappel)
6
Erreur Faute Défaillance
ImpliqueImplique
peut causer peut causer
Thursday, March 31, 2011
✘✘
✘
De l’erreur à la défaillance
■ Le programmeur commet une erreur dans le code
■ Lorsqu’exécutée, l’erreur provoque une faute (un état erroné du programme)
■ Ce^e faute ce propage■ La faute cause une défaillance
■ Il faut retracer la chaîne et la briser
7
✘
✘ ✘
t
variables
Thursday, March 31, 2011
8
■ Les erreurs ne causent pas toutes des défaillances!
■ Tester ne peut que démontrer la présence d’erreurs – et non pas leur absence.(Dijkstra 1972)
Erreurs et tests
✘✘
✘
✘
✘ ✘
t
variables
Thursday, March 31, 2011
9
■ On peut remonter à une faute depuis chaque défaillance, et chaque faute est due à une erreur.
■ Le débogage consiste à faire le lien entre une défaillance et une erreur, et à supprimer l’erreur.
Débogage
✘✘
✘
✘
✘ ✘
t
variables
Thursday, March 31, 2011
■ L’état iniJal est correct.■ Eventuellement, l’état est erroné.■ C’est dans ce^e région qu’on doit chercher.
10
Recherche dans l’espace et le temps
✔✔✔✔✔✔✔✔✔✔
✘
t?
Thursday, March 31, 2011
■ La faute est la transiJon de correct à incorrect■ Comment isoler la faute ?
•Milliers de variables•Millions d’instrucEons
11
Isoler la faute
✔
✘
✘
t
Thursday, March 31, 2011
L’état d’un programme
12
44 000 variables et 42 000 liens entre elles !
Thursday, March 31, 2011
Étapes du débogage
■ Enregistrer le bogue■ Reproduire la défaillance■ Automa:ser et simplifier le cas de test■ Trouver les origines possibles de la faute■ Isoler l’origine de la faute (l’erreur)■ Corriger l’erreur et tester le programme à nouveau
13
Thursday, March 31, 2011
1. Enregistrer le bogue
■ Le débogage débute lorsqu’un problème est constaté■ Souvent, un uJlisateur observe et rapporte le problème■ Quoi inclure ?
•Version du logiciel• Environnement•Démarche à suivre pour reproduire le bogue• Comportement a`endu et observé•Un court résumé
■ Les rapports de bogues sont souvent enregistrés et gérés par un logiciel spécialisé
14
Thursday, March 31, 2011
2. Reproduire la défaillance
■ La première tâche du débogage (et souvent la plus difficile)
■ Pourquoi reproduire le problème ?• Pour pouvoir faire de nouvelles observaEons et trouver de nouveaux faits à propos du problème
• Pour perme`re de déterminer si le problème est réglé■ Il faut :
• recréer la démarche qui mène au problème• recréer l’environnement qui dans lequel le problème est survenu
15
Thursday, March 31, 2011
Reproduire l’environnement
■ Débuter avec votre environnement■ Tant que le problème n’est pas reproduit, adapter d’autres circonstances de l’environnement de l’usager
■ L’itéraJon se termine• lorsque le problème est reproduit• lorsque les deux environnements sont idenEques
■ Permet par la même occasion d’apprendre des faits au sujet des circonstances qui causent le problème• Circonstance = un aspect de l’environnement ou une étape de la démarche qui influence le problème
16
Thursday, March 31, 2011
Reproduire l’exécuEon
■ En plus de reproduire l’environnement, il faut reproduire la démarche (l’exécuJon) qui cause le problème
■ L’exécuJon est déterminée par les entrées (généralement)
■ Reproduire l’entrée permet donc de reproduire l’exécuJon
17
Thursday, March 31, 2011
18
Les entrées d’un programme
Program
Données
UJlisateur
CommunicaJon
Hasard Système d’exploitaJon
Ordonnancement
Physique
OuJls de débogage
Thursday, March 31, 2011
19
“Heisenbug”
■ Le code suivant ne cause pas de problème en mode débogage
int f() { int i; return i;}
ExécuJon normale:retourne une valeur non-‐définie
Dans le débogueurretourne 0
Thursday, March 31, 2011
3. AutomaEser et simplifier
■ AutomaJser : écrire un test automaJsé (ex: JUnit) qui reproduit et détecte l’échec à l’exécuJon
■ Simplifier : pour chaque circonstance du problème, vérifier à l’aide d’expériences si elle est vraiment nécessaire• Le problème dépend-‐t’il vraiment des milliers de lignes d’entrées ?
• La défaillance requiert-‐elle vraiment cet ordonnancement précis ?
•A-‐t’on besoin de ce`e séquence d’appels ?■ Les circonstances non-‐essenJelles sont éliminées
20
Thursday, March 31, 2011
Pourquoi simplifier ?
■ Pour faciliter la communicaJon•Un cas simple est plus facile à communiquer, à expliquer et à comprendre
■ Pour faciliter le débogage•Un cas simple réduit la taille de l’état à considérer et produit des exécuEons plus courtes
■ Pour idenJfier les doubles•Un cas simple peut remplacer plusieurs cas plus complexes qui illustrent le même problème
21
Thursday, March 31, 2011
4. Trouver les origines
■ Consiste à suivre les dépendances dans le programme pour idenJfier les causes possibles d’une faute
■ 2 types de dépendances• dépendances de données
• dépendances de contrôle
22
x = y * y + z * z; // x dépend de y et z
if (y == z) { x = 0;} else { x = 1;}
// x dépend de y et z
Thursday, March 31, 2011
23
Un exemple
sort 9 8 7$
Sortie: 7 8 9
sort 11 14$
Sortie: 0 11
Thursday, March 31, 2011
24
main()int main(int argc, char *argv[]) { int *a; int i;
a = (int *)malloc((argc - 1) * sizeof(int)); for (i = 0; i < argc - 1; i++) a[i] = atoi(argv[i + 1]);
shell_sort(a, argc);
printf("Output: "); for (i = 0; i < argc - 1; i++) printf("%d ", a[i]); printf("\n");
free(a);
return 0;}
Thursday, March 31, 2011
variables
time!
!
!
!
!
25
Trouver les origines
■ La valeur 0 affichée est la valeur de a[0]. D’où provient-‐elle ?
■ But : Suivre ou déduire l’origine des valeurs
■ Sépare les valeurs perJnentes de celles qui sont inuJles
■ On peut suivre a[0] jusqu’à shell_sort
Thursday, March 31, 2011
26
shell_sort()
static void shell_sort(int a[], int size) { int i, j; int h = 1; do { h = h * 3 + 1; } while (h <= size); do { h /= 3; for (i = h; i < size; i++) { int v = a[i]; for (j = i; j >= h && a[j - h] > v; j -= h) a[j] = a[j - h]; if (i != j) a[j] = v; } } while (h != 1);}
Thursday, March 31, 2011
27
Recherche dans le temps
■ Dans shell_sort, l’état doit être devenu corrompu.
■ But : Observer une transiJon de correct à incorrect
✔✔✔✔✔✔✔✔✔✔
✘✘✘✘✘✘✘✘✘✘
✘
t
Thursday, March 31, 2011
28
ObservaEon spécifique
static void shell_sort(int a[], int size) {
int i, j; int h = 1; ...}
fprintf(stderr, “At shell_sort”); for (i = 0; i < size; i++) fprintf(stderr, “a[%d] = %d\n”, i, a[i]); fprintf(stderr, “size = %d\n”, size);
L’état est erroné au moment de l’appel à shell_sort!
sort 11 14$a[0] = 11a[1] = 14a[2] = 0size = 3Sortie: 0 11
Thursday, March 31, 2011
29
main()
int main(int argc, char *argv[]) { int *a; // Input array a = (int *)malloc((argc - 1) * sizeof(int)); for (int i = 0; i < argc - 1; i++) a[i] = atoi(argv[i + 1]);
// Sort array shell_sort(a, argc);
// Output array printf("Output: "); for (int i = 0; i < argc - 1; i++) printf("%d ", a[i]); printf("\n");
free(a); return 0;}
Devrait être argc -‐ 1
Thursday, March 31, 2011
shell_sort(a, argc - 1);
int main(int argc, char *argv[]){ int *a; int i;
a = (int *)malloc((argc - 1) * sizeof(int)); for (i = 0; i < argc - 1; i++) a[i] = atoi(argv[i + 1]);
...}
30
Corriger le programme
shell_sort(a, argc); shell_sort(a, argc);sort 11 14$
Sortie: 11 14
Thursday, March 31, 2011
31
Débogage scienJfique
Thursday, March 31, 2011
Erreur
■ Une erreur est une déviaJon de ce qui est correct, juste ou vrai.
■ Pour prouver que quelque chose est une erreur, il faut démontrer la déviaJon• Peut être difficile pour certains programmes
32
Thursday, March 31, 2011
Causes et effets
■ Quelle est la cause de la défaillance observée ?• La cause d’un événement est un événement précédent sans lequel l’effet ne serait pas produit
■ Pour prouver la causalité, il faut montrer que• l’effet se produit lorsque la cause se produit• l’effet ne se produit pas lorsque la cause ne se produit pas
■ En général, la causalité est difficile à établir• Il faut pouvoir répéter les événements passés• En informaEque, les programmes peuvent être contrôlés et exécutés à volonté
33
Thursday, March 31, 2011
Vérifier les causes
34
a = compute_value();printf("a = %d\n", a);
a = 0
(a ne devrait jamais être égal à zéro...)
Thursday, March 31, 2011
Vérifier les causes
35
a = compute_value();a = 1;printf("a = %d\n", a);
a = 0
?
Thursday, March 31, 2011
Vérifier les causes
36
double a;a = compute_value();a = 1;printf("a = %d\n", a);
a = 0
Thursday, March 31, 2011
Vérifier les causes
37
double a;a = compute_value();a = 1;printf("a = %f\n", a);
a = 3.1415...
Nous avons isolé le format (“%d”) comme cause de la défaillance.
Thursday, March 31, 2011
“Voilà le bogue”
■ Certaines personnes sont vraiment douées à deviner les causes
■Malheureusement, l’intuiJon est difficile à saisir :• Requiert des connaissances préalables•Ne foncEonne pas d’une manière systémaEque et reproducEble
•Ne peut pas être enseignée
38
Thursday, March 31, 2011
La méthode scienEfique
■ La méthode scienJfique décrit comment trouver une théorie qui explique (et prédit) certains aspects de notre univers
■ Ce^e méthode est appliquée dans toutes les branches de science expérimentale.
39
Thursday, March 31, 2011
Étapes de la méthode scienEfique
1.Observer un aspect de l’univers2.Formuler une hypothèse qui est compaJble avec les observaJons
3.UJliser l’hypothèse pour faire une prédicJon4.Tester la prédicJon par des expériences ou observaJons.5.Si nécessaire, modifier l’hypothèse si nécessaire et retourner à l’étape 3.
40
Thursday, March 31, 2011
Une théorie
■ Lorsque l’hypothèse explique toutes les expériences et observaJons, elle devient une théorie
■ Une théorie est une hypothèse qui• Explique toutes les observaEons antérieures• Prédit les observaEons ultérieures
■ Dans le contexte du débogage, une théorie est un diagnos(c
41
Thursday, March 31, 2011
MasterMind
■MasterMind est un exemple typique d’applicaJon de la méthode scienJfique
■ Des hypothèses sont formulées jusqu’à ce que le secret soit révélé.
42
Thursday, March 31, 2011
43
Débogage scienEfique
Hypothèse PrédicEon Expérience ObservaEons
DiagnosEc
Hypothèse supportée : raffiner l’hypothèse
Hypothèse rejetée : formuler une nouvelle hypothèse
Thursday, March 31, 2011
44
Un exemple
sort 9 8 7$
Sortie: 7 8 9
sort 11 14$
Sortie: 0 11
Déboguer à nouveau en uElisant le débogage scienEfique
Thursday, March 31, 2011
45
Hypothèse iniEale
Hypothèse
Prédic:on
Expérience
Observa:on
Conclusion
“sort 11 14” fonctionne
Sortie est “11 14”
Exécuter “sort” avec l’entrée spécifiée
Sortie est “0 11”
L’hypothèse est rejetée.
Thursday, March 31, 2011
46
int main(int argc, char *argv[]){ int *a; int i;
a = (int *)malloc((argc - 1) * sizeof(int)); for (i = 0; i < argc - 1; i++) a[i] = atoi(argv[i + 1]);
shell_sort(a, argc);
printf("Output: "); for (i = 0; i < argc - 1; i++) printf("%d ", a[i]); printf("\n");
free(a);
return 0;}
Est-‐ce que a[0] = 0 est vrai?
Thursday, March 31, 2011
47
Hypothèse 1: a[]
Hypothèse
Prédic:on
Expérience
Observa:on
Conclusion
L’exécution cause a[0] = 0.
À la ligne 37, a[0] = 0 est vrai.
Observer a[0] à la ligne 37.
a[0] = 0 est vrai, tel que prédit.
L’hypothesis est confirmée.
Thursday, March 31, 2011
48
static void shell_sort(int a[], int size){ int i, j; int h = 1; do { h = h * 3 + 1; } while (h <= size); do { h /= 3; for (i = h; i < size; i++) { int v = a[i]; for (j = i; j >= h && a[j - h] > v; j -= h) a[j] = a[j - h]; if (i != j) a[j] = v; } } while (h != 1);}
L’état est-‐il correct ici?
Thursday, March 31, 2011
49
Hypothèse 2: shell_sort()
Hypothèse
Prédic:on
Expérience
Observa:on
Conclusion
La faute ne se produit pas avant shell_sort.
À la ligne 6, a[] = [11, 14]; size = 2
Observer a[] et sa taille à la ligne 6.
a[] = [11, 14, 0]; taille = 3.
L’hypothèse est rejetée.
Thursday, March 31, 2011
50
Hypothèse 3: size
Hypothèse
Prédic:on
Expérience
Observa:on
Conclusion
size = 3 cause la défaillance
Changer la valeur de ‘size’ à 2 rend la sortie correcte.Changer la valeur de size dans un débogueur.
Tel que prédit.
L’hypothèse est confirmée.
Thursday, March 31, 2011
shell_sort(a, argc - 1);
int main(int argc, char *argv[]){ int *a; int i;
a = (int *)malloc((argc - 1) * sizeof(int)); for (i = 0; i < argc - 1; i++) a[i] = atoi(argv[i + 1]);
...}
51
Corriger le programme
shell_sort(a, argc); shell_sort(a, argc);sort 11 14$
Sortie: 11 14
Thursday, March 31, 2011
52
Hypothèse 4: argc
Hypothèse
Prédic:on
Expérience
Observa:on
Conclusion
L’invocation de shell_sort avec size = argc cause la défaillance.Changer argc pour argc - 1 devrait faire réussir l’exécution.Changer argc pour argc - 1 et recompiler.
Tel que prédit.
L’hypothèse est confirmée.
Thursday, March 31, 2011
53
Le diagnosEc
■ La cause est “Invoquer shell_sort() avec argc”■ Prouvé par deux expériences :
• Invoqué avec argc, la défaillance se produit• Invoqué avec argc -‐ 1, la défaillance ne se produit pas
■ Effet de bord: nous avons obtenu une soluJon■ NB: nous n’avons pas encore obtenu la validité
•D’autres erreurs peuvent être présentes• La soluEon peut engendrer d’autres problèmes
Thursday, March 31, 2011
54
Garder la trace...
■ Dans le jeu Mastermind, toutes les hypothèses et observaJons sont explicites.
■ Rend le jeu beaucoup plus facile
Thursday, March 31, 2011
Débogage implicite vs explicite
■ Débogage implicite :•Garde tout en mémoire• Très difficile, comme jouer à Mastermind sans le plateau!
■ Débogage explicite : uJlise un carnet pour toujours connaître :•Où on est présentement• Par où on est passé•Où l’on va•Où on veut aller
55
Thursday, March 31, 2011
Carnet de débogage
■ Quoi noter ?
56
Hypothèse
Prédic:on
Expérience
Observa:on
Conclusion
Thursday, March 31, 2011
Débogage rapide
■ Pas toutes les situaJons demandes des carnets et des procédures complexes
■ SuggesJon :•Déboguer de façon rapide et ad hoc pendant une courte période (10 minutes)
•UEliser le débogage scienEfique si le problème n’est pas encore idenEfié
57
Thursday, March 31, 2011
Sources d’hypothèses
58
0 exécuEon
1 exécuEon
n exécuEons
n exécuEons contrôléesExpérimentaEon
InducEon
ObservaEon
DéducEon
UJlise
UJlise
UJlise
Thursday, March 31, 2011
OBSERVATIONSObservaJons
59
Thursday, March 31, 2011
Principes d’observaEon
■ Ne pas interférer• Éviter que les faits observés découlent de l’observaEon elle-‐même
■ Savoir quoi et quand observer• L’état d’un programme est trop grand pour être observé à chaque instrucEon
• Trop d’observaEons peuvent ralenEr le programme■ Procéder de façon systémaJque
• Les hypothèses doivent guider les observaEons plutôt que de procéder à tâtons
60
Thursday, March 31, 2011
JournalisaEon (logging)
■ Idée principale : insérer des instrucJons de sorJe à des endroits stratégiques dans le code•Débogage par printf / println
■ Problèmes• Code encombré• SorEe encombrée• RalenEssement• Perte de données possible (buffering)
61
Thursday, March 31, 2011
Au-‐delà de print
■ UJliser un format standard• Permet la recherche et les filtres
■ Rendre l’enregistrement opJonnel• Pour améliorer la performance lorsque déployé
■ Perme^re une granularité variable• Pour la performance• Pour une meilleure compréhension
■ Favoriser la persistence• Pour recommencer si un problème semblable fait surface
62
Thursday, March 31, 2011
Au-‐delà de print
■ FoncJons de journalisaJon• FoncEons spécifiques qui impriment les messages sur une sorEe spécifique
•UEliser des macros pour perme`re d’acEver ou de désacEver les messages rapidement
• Exemple :
63
#define LOG(msg) fprintf(stderr, “[DEBUG] %s at %s:%d\n”, \ msg, __FILE__, __LINE__)
Thursday, March 31, 2011
Au-‐delà de print
■ Bibliothèques spécialisées :• LOG4J populaire pour Java• Est conçu pour la persistence et la flexibilité• Exemple :
64
public class TestLogging { // Initialize a logger. final ULogger logger = LoggerFactory.getLogger(TestLogging.class);
public static void main(String args[]) { logger.debug("Start of main()"); logger.info ("A log message with level set to INFO"); logger.warn ("A log message with level set to WARN"); logger.error("A log message with level set to ERROR"); logger.fatal("A log message with level set to FATAL"); }}
Thursday, March 31, 2011
Débogueurs
■ Débogueur : un ouJl conçu pour s’a^acher au programme durant l’exécuJon et perme^re d’effectuer des observaJons
■ Avantages :• Permet de débuter rapidement, sans modificaEon du code• Permet l’observaEon flexible d’événements arbitraires• Sessions éphémères (aucun code à écrire)
65
Thursday, March 31, 2011
Débogueurs
■ FoncJonnalités des débogueurs modernes :• Exécuter un programme et l’arrêter lorsqu’une condiEon est remplie (breakpoint -‐ emplacement, watchpoint -‐ condiEon)
•Observe l’état du programme arrêté (valeurs des variables, pile d’exécuEon, etc.)
•Modifier l’état du programme arrêté•Débogage post-‐mortem (core dump)• Logging (print, display, etc.)• “Fix and conEnue” (permet de modifier le code durant l’exécuEon)
66
Thursday, March 31, 2011
Techniques d’observaEons avancées
■ IntégraJon avec l’interpréteur• Plus interpréteurs de code (ex: python, java) perme`ent à des ouEls arbitraires de s’intégrer dynamiquement pour observer l’exécuEon
■ModificaJon du code• Il est possible d’effectuer des observaEons en modifiant directement le code binaire (code machine ou interprété)
■ Débogage à rebours• Session de débogage typique : suivant, suivant, suivant, ..., trop loin!
• Certains ouEls perme`ent de remonter dans le temps (ex: Whyline)
67
Thursday, March 31, 2011
Techniques d’observaEons avancées
■ VisualisaJon• Plusieurs ouEls perme`ent de visualiser une ou plusieurs exécuEons• Données• Code exécuté
68
Thursday, March 31, 2011
AutomaEser les observaEons
■ Il est difficile pour un humain de vérifier tout l’état d’un programme
■ Pourquoi ne pas demander à la machine de vérifier son propre état ?
69
Thursday, March 31, 2011
AsserEons de base
70
if (divisor == 0) { printf("Division by zero!"); abort();}
Thursday, March 31, 2011
AsserEons spécialisées
71
assert (divisor != 0);
Thursday, March 31, 2011
ImplémentaEon
72
void assert (int x) { if (!x) { printf("Assertion failed!\n"); abort(); }}
$ my-‐programAssertion failed!Abort (core dumped)$
Thursday, March 31, 2011
ImplémentaEon avec macro
73
#ifndef NDEBUG#define assert(ex) \((ex) ? 1 : (cerr << __FILE__ << ":" << __LINE__ \ << ": assertion ‘" #ex "’ failed\n", \ abort(), 0))#else#define assert(x) ((void) 0)#endif
Thursday, March 31, 2011
Meilleurs diagnosEcs
74
$ my-‐programdivide.c:37: assertion ‘divisor != 0’ failedAbort (core dumped)$
Thursday, March 31, 2011
AsserEons et Java
75
private void divide(int divisor) { assert divisor != 0; assert divisor > numerator : divisor;}
$ java -‐ea Divide 0[...AssertionError...]$
Passé au constructeur pour AsserEonError
Thursday, March 31, 2011
Quoi observer ?
76
■ PrécondiJons•ViolaEon = faute avant la méthode
■ PostcondiJons•ViolaEon = faute à l’intérieur de la méthode
■ Invariants•Devrait toujours être vrai (pré-‐ et post-‐condiEon à la fois)
Thursday, March 31, 2011