[Visual Studio] Déboguer avec Visual Studio 2005/2008

Ce tutoriel (destiné surtout aux débutants) vous expliquera comment utiliser un débogueur et en particulier celui de Visual Studio 2005.

Depuis que je participe énormément sur les Forums de Microsoft consacrés à .NET, je suis impressionné de voir le nombre de programmeurs débutants qui ne savent pas se servir d’un débogueur. Tout simplement parce qu’ils n’ont pas connaissance de cet outil fort utile (voir même indispensable !) ou alors ils ne savent pas bien s’en servir.

Ce tutoriel porte exclusivement sur l’utilisation du débogueur de Visual Studio 2005. Cependant les concepts sont identiques sur les autres débogueurs quel que soit le langage utilisé !

Avant de continuer un minimum de connaissance est requis au niveau du langage C# ou VB .NET, il faut comprendre au moins ce qu’est une méthode, une variable, un paramètre… etc.

Qu’est ce qu’un débogueur ?

Lorsque vous exécutez une application, des milliers d’instructions .NET sont exécutées à la seconde (selon la puissance de calcul du processeur). Comme l’exécution des instructions est beaucoup trop rapide, pour que l’homme puisse voir leur déroulement ainsi que les valeurs des différentes variables mis en jeu (un peu comme si l’homme voulait voir comment roule une voiture à 100 000 km/h), il a fallu créer un outil qui permet de contrôler l’avancement pas-à-pas d’un programme.
Cet outil s’appelle le débogueur, et une fois que l’on y a touché, on ne peut plus s’en passer !!!

Pour mettre les choses au clair avant de continuer, le mot « instruction » (dans ce tutoriel et donc au niveau .NET) sont :

  • Sous C# et C++ séparées par des points-virgules
  • Sous VB .NET séparées par « le retour à la ligne ».

Souvent le débogueur est fourni et intégré à l’environnement de développement que vous êtes en train d’utiliser. C’est le cas de Visual Studio 2005.

Quand utiliser un débogueur ?

La réponse est assez simple : dès que vous rencontrez un bogue ! En fait, on utilise un débogueur la plus-part du temps pour essayer de comprendre le déroulement pas-à-pas d’un bout de code qui pose problème (par exemple, le clic d’un bouton).

Comme exemple à déboguer nous allons utiliser ce code source « bogué » dans une application Console :

using System;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  class ProgrammeBug
  {
    public static void Main(string[] args)
    {
      int maVariable;

      maVariable = 16;
      maVariable =  64;

      Console.WriteLine(maVariable);
      Console.ReadKey();
    }
  }
}
Namespace Tourreau.Gilles.VisualStudio.Debogage

  Module ProgrammeBug

    Sub Main(ByVal args() As  String)
      Dim maVariable As Integer

      maVariable =  16
      maVariable = 64

      Console.WriteLine(maVariable)
      Console.ReadKey()
    End  Sub

  End Module

End Namespace

Le but fonctionnel recherché par ce programme est d’affecter une valeur « 16 » à la variable « maVariable » et de l’afficher sur la Console.
J’ai inséré volontairement une instruction qui affecte « 64 » à cette variable avant de l’afficher sur la Console. Cette instruction représente un bogue (anodin je le reconnais) que nous devons essayer de corriger.

Avancer pas-à-pas une application

Pour lancer cette application rien de bien compliqué, cliquez sur la jolie flèche verte intitulé « Démarrer le débogage (F5) ».

Bouton permettant de lancer le débogueur

Bouton permettant de lancer le débogueur

Après le lancement de l’application, le résultat obtenu est le suivant :

Affichage du bogue...

Affichage du bogue...

Il y a donc bel et bien la présence d’un bogue !!! On voulait afficher « 16 » et « 64 » est apparu…

Nous allons donc déboguer notre application pour rechercher l’erreur, pour ce faire, il faut dans un premier temps afficher la barre d’outils « Déboguer » (Faites un clic-droit à coté d’un emplacement vide au niveau des barres d’outils et cochez – si ce n’est pas déjà fait – « Déboguer »).
La barre d’outils suivante devrait apparaitre :

Barre d'outils du débogueur

Barre d'outils du débogueur

Il suffit maintenant de cliquez sur le bouton « Pas à pas détaillé » (F11). (6ème bouton en partant de la gauche).

A ce stade vous devriez voir Visual Studio en premier plan avec le code du programme, une flèche jaune et une accolade surligné en jaune (pour C#) ou au niveau du nom de la méthode Main pour VB .NET :

Ligne de code où se trouve stoppée l'application (en C#)

Ligne de code où se trouve stoppée l'application (en C#)

Ligne de code où se trouve stoppée l'application (en VB .NET)

Ligne de code où se trouve stoppée l'application (en VB .NET)

Avant de continuer, regardez dans la barre des tâches, votre console est ouverte, mais rien n’est encore affiché.
En fait votre programme est « mis en pause » à la ligne qui se trouve surlignée en jaune. Le code (et plus exactement l’instruction) qui est surligné en jaune représente « l’instruction en cours ».
En débogage, l’instruction en cours correspond à la prochaine instruction qui sera exécutée au pas suivant. Il est très important de bien assimiler cette notion, et de comprendre que l’instruction en cours ne signifie en aucun cas qu’elle est en cours d’exécution !

La flèche en jaune à gauche permet uniquement de spécifier sur quelle ligne se situe l’instruction en cours.

Nous allons maintenant faire avancer notre programme pas à pas, il suffit pour cela de cliquez sur : « Pas à pas détaillé (F11) ». (J’expliquerai plus-tard en détail les différents boutons présents dans la barre d’outils).
Vous remarquez que vous faites avancer votre programme pas à pas et cela à votre rythme afin de voir l’évolution de votre programme :

Avancement de l'exécution du programme (en C#)
Avancement de l'exécution du programme (en VB .NET)

Pour pouvoir arrêter le débogueur (et votre application en cours d’exécution) cliquez sur le bouton « Arrêter le débogage (Maj + F5) ».

Bouton permettant d'arrêter le débogage

Bouton permettant d'arrêter le débogage

Sachez cependant que cette méthode est brutale. Votre application est automatiquement tuée comme si vous l’aviez fait par le gestionnaire des tâches ! (Certaines ressources risquent de ne pas d’être libérée proprement…).

Afficher le contenu des variables

La deuxième grande fonction d’un débogueur est d’afficher les valeurs des variables en cours au moment où votre programme est mis en pause.
Pour cela il existe plusieurs possibilités :

  • En regardant dans une des fenêtres de liste de variable. Il en existe deux sortes dans Visual Studio : « Les variables locales » et les « Variables automatiques ».
    La première se contente d’afficher uniquement les variables qui sont locales (dans la portée) de l’instruction en cours (Accessible en bas à gauche ou via le menu Déboguer -> Fenêtres -> Variables locales durant le débogage).
    La seconde affiche les variables au fur et à mesure de l’avancement de votre code. A noter que la fenêtre des « Variables automatiques » affiche les variables aussi bien locales que membres de la classe où se trouve l’instruction en cours (Accessible en bas à gauche durant le débogage, ou via le menu Déboguer -> Fenêtres -> Automatiques)..
  • En passant la souris sur la variable. Il suffit tout simplement de survoler la variable dans votre code source via le pointeur de la souris.
  • En utilisant les espions. Les espions fonctionnent exactement comme les fenêtres de liste de variable, à la différence que vous pouvez ajouter manuellement vous-même (par saisie clavier ou drag’n’drop) les variables dont vous souhaitez obtenir les valeurs (Pour cela cliquez dans la cellule blanche de la colonne « Nom » et saisissez « maVariable » sans les guillemets).
Affichage des variables locales

Affichage des variables locales

Affichage de la valeur d'une variable avec le survol de la souris

Affichage de la valeur d'une variable avec le survol de la souris

Affichage de la valeur d'une variable avec un espion

Affichage de la valeur d'une variable avec un espion

Le survol du pointeur de la souris et les espions sont (à mon goût) les plus utiles !

Si nous revenons maintenant dans notre exemple précédent, relancez le débogueur et avancez pas à pas jusqu’à la ligne « maVariable = 16; ». Consultez la valeur de la variable « maVariable », vous devez voir apparaitre « 0 ».
Avancez maintenant d’un pas, re-consultez la variable, celle-ci est passée à « 16 ».

Avant d'affecter "16" à "maVariable", celle-ci est initialisé à "0"

Avant d'affecter "16" à "maVariable", celle-ci est initialisé à "0"

Après affectation, "maVariable" contient "16"

Après affectation, "maVariable" contient "16"

Avant d’affecter « 16 » à « maVariable », celle-ci est initialisé à « 0 ». Après affectation, « maVariable » contient « 16 ».

A noter que, si vous utilisez les fenêtres de listes de variables ou les espions, Visual Studio marque la valeur de la variable en rouge, tout simplement pour vous signaler qu’entre l’étape précédente et celle que vous venez d’exécuter, la variable à changé de valeur.

Immédiatement après son changement de valeur, le contenu de "maVariable" est mis en rouge

Immédiatement après son changement de valeur, le contenu de "maVariable" est mis en rouge

Avancez maintenant encore d’un pas, re-consultez la variable, celle-ci est passée à « 64 ». Automatiquement votre cerveau doit vous dire : « Le bug est sur cette ligne, car cette instruction a changé la valeur de maVariable en une valeur non désirée ». Dans notre cas, il suffira juste de supprimer cette ligne.

Afficher le contenu des variables non-primitives et des tableaux.

Nous avons vu, juste avant comment afficher la valeur d’une variable primitive (un int). Il faut savoir que c’est le même principe pour tous les autres types de variables primitive comme les byte, char, …etc.
Pour rappel : Les types primitifs sous .NET sont tous les types de base du langage .NET (bool, byte, char, int, long, …etc). Attention, « string » n’est pas un type primitif !

Pour les variables qui référencent une instance d’une classe, Visual Studio (contrairement à beaucoup d’autres débogueurs) permet d’afficher les variables membres contenu dans cet objet et cela de façon hiérarchique !

Voici un exemple pour mettre tout ceci en œuvre :

using System;
using System.Globalization;

namespace Tourreau.Gilles.VisualStudio.Debogage
{
  class  ProgrammeBugPlus
  {
    public static void Main(string[]  args)
    {
       DateTimeFormatInfo formatInfo;
       string[] technos;

       formatInfo = new  DateTimeFormatInfo();
       technos = new string[] { "WPF", "ADO .NET",  "Windows Forms", "WCF", "Code DOM" };

       Console.ReadKey();
    }
  }
}
Imports System.Globalization

Namespace  Tourreau.Gilles.VisualStudio.Debogage
  Module ProgrammeBugPlus

    Sub Main(ByVal args() As String)
      Dim formatInfo As DateTimeFormatInfo
      Dim technos() As  String

      formatInfo = New DateTimeFormatInfo()
      technos = New String() {"WPF", "ADO .NET", "Windows Forms", "WCF", "Code  DOM"}

      Console.ReadKey()
    End Sub

  End  Module
End Namespace

Lancez le débogage et arrêtez vous sur la ligne « technos = … ».
Observez le contenu de la variable « formatInfo », Visual Studio affiche son type entre accolade :

Affichage du nom du type d'une variable

Affichage du nom du type d'une variable

Je vous l’accorde, cette information n’est pas très intéressante ! Cependant vous avez la possibilité en cliquant sur le « + » à gauche d’obtenir le contenu de l’objet « formatInfo » :

Affichage des valeurs de la propriété de l'instance sélectionnée

Affichage des valeurs de la propriété de l'instance sélectionnée

Vous pouvez consulter toutes les propriétés et cela de façon récursive :

Affichage des valeurs de la propriété de l'instance sélectionnée de manière hierarchique

Affichage des valeurs de la propriété de l'instance sélectionnée de manière hierarchique

Maintenant nous allons voir comment sont affichés le contenu des tableaux, mais avant consultez le contenu de la variable « technos » :

Affichage d'une référence null

Affichage d'une référence null

Le débogueur vous affiche que l’instance technos est défini à null, c’est à dire que la variable ne référence rien du tout !

Maintenant, avancez d’un pas pour initialiser le tableau et affichez celui-ci, vous constatez que Visual Studio indique la dimension du tableau (ici « 5 »), cliquez sur le petit « + » pour dérouler et afficher le contenu de ce tableau :

Affichage des valeurs contenu dans un tableau

Affichage des valeurs contenu dans un tableau

Vous pouvez constater que Visual Studio affiche chaque élément du tableau en précisant à gauche son indice.

Petite conclusion de cette partie :
Visual Studio (ou plus généralement les débogueurs) offre un nombre incroyable d’informations sur le contenu des variables, n’hésitez donc surtout pas à les utiliser pour comprendre et déboguer votre code !!!

Modifier le contenu d’une variable/propriétés.

Durant le débogage, nous avons vu qu’il était possible d’afficher le contenu d’une variable. Visual Studio permet aussi de modifier le contenu de celle-ci (primitive ou non !).
Pour mettre en pratique cela, relancez le programme précédent et arrêtez vous au niveau de l’affectation de « technos = … ».
Consultez le contenu de la variable « formatInfo » et cliquez sur la valeur de la propriété « DateSeparator ». Un curseur clignote indiquant qu’il est possible de saisir quelque chose. Effacer le « / » et saisissez « toto », prêtez attention à saisir « toto » entre des guillemets ! Appuyez ensuite sur la toucher entrée de votre clavier. Vous constatez que vous venez de changer la valeur d’une propriété d’une variable.

Modification d'une valeur d'une propriété

Modification d'une valeur d'une propriété

En fait, plus techniquement, vous venez de déclencher le « setter » de la propriété « DateSeparator » avec comme valeur « toto ».

En d’autres mots, lorsque vous modifier une propriété le code du « setter » est automatiquement exécuté…
Pour vous le prouver, consultons la documentation de Microsoft concernant la propriété DateSeparator. Microsoft précise que si vous passez une valeur null (Nothing en VB .NET) à cette propriété une exception de type System.ArgumentNullException sera déclenchée :

Je n'invente rien ! Microsoft le dit !

Je n'invente rien ! Microsoft le dit !

Affectez donc la valeur null (Nothing sous VB .NET) sans les guillemets à la propriété DateSeparator…

Mise à null de la propriété DateSeparator

Mise à null de la propriété DateSeparator

…et validez ! Vous obtenez un message d’insulte de la part de Visual Studio vous indiquant qu’une exception a été levée :

Message d'erreur de Visual Studio suite au modification de la valeur de la propriété

Message d'erreur de Visual Studio suite au modification de la valeur de la propriété

La modification d’une propriété peut donc avoir des effets secondaires sur le débogage de votre code… Gardez ceci en tête !

Avant de clore cette petite partie une petite précision :
Lorsque vous modifiez la valeur d’une variable, vous pouvez placer dans la saisie de cette valeur n’importe quel code qui renvoi un objet (de bon type bien évidemment) que vous souhaitez mettre dans cette variable.

Par exemple :
Remplacez dans le code précédent l’appel au constructeur de « DateTimeFormatInfo() » par l’affectation de null. Déboguer l’application en vous arrêtant sur l’instruction « technos = … ».
Affichez le contenu de la variable « formatInfo », le débogueur vous indique qu’il n’y a rien (null) :

"formatInfo" est défini à null

"formatInfo" est défini à null

Modifiez cette valeur en saisissant sans les guillemets « new DateTimeFormatInfo() » :

Création d'une nouvelle instance de DateTimeFormatInfo

Création d'une nouvelle instance de DateTimeFormatInfo

Vous constatez maintenant qu’un objet DateTimeFormatInfo a été instancié et référencé par la variable « formatInfo ».

Instance DateTimeFormatInfo crée pendant le débogage

Instance DateTimeFormatInfo crée pendant le débogage

Il est bien sûr possible d’appeler une méthode ou une propriété pour affecter la valeur d’une variable. Les constructeurs, méthodes et propriétés que vous appelez pour affecter une valeur à une propriété ou une variable, doivent bien évidemment être de même type !

Les points d’arrêts (Breakpoints).

Depuis le début de cet article, vous déboguez pas à pas depuis le point d’entrée de votre application (méthode Main). Vous vous rendez bien compte que dans un gros projet, ne serait-ce que de 1000 lignes, il devient difficile de faire du pas-à-pas en partant du début… Vous risqueriez d’exploser le bouton gauche de votre souris ou la touche F10 de votre clavier…

Pour remédier à ce genre d’inconvénient, on utilise des points d’arrêts. Un point d’arrêt est attaché sur une instruction et permet de mettre votre programme en pause dès que celui-ci arrive sur cette instruction.

Pour mettre un point d’arrêt cliquez à gauche (dans la partie grise) de la ligne qui contient votre instruction où vous souhaitez arrêter votre programme.
L’autre solution étant de mettre le curseur du clavier sur la ligne contenant l’instruction et de choisir l’option « Basculer le point d’arrêt » (F9) dans le menu « Déboguer ».
Pour mettre en pratique l’utilisation d’un point d’arrêt, reprenez le tout premier exemple, ajoutez un point d’arrêt au niveau de la ligne « maVariable = 64 ».

Le cercle rouge correspond à la zone où l'on doit cliquer pour insérer un point d'arrêt.

Le cercle rouge correspond à la zone où l'on doit cliquer pour insérer un point d'arrêt.

Une fois le point d'arrêt inséré...

Une fois le point d'arrêt inséré...

Remarquez que l’instruction, est surlignée en rouge et un petit rond rouge plein apparait. Le surlignage de code permet au développeur de bien localiser de façon immédiate sur quelle instruction se situe un point d’arrêt. (Beaucoup de débogueurs ne proposent pas ce genre « d’effet visuel » !).

Lancez le débogage, non plus en utilisant le bouton « Pas à pas détaillé », mais tout simplement en utilisant notre jolie flèche verte du début : « Démarrer le débogage (F9) ».
Vous remarquez que le programme se lance, et s’arrête au niveau de la ligne où le point d’arrêt a été crée.

L'exécution s'est arrêté au niveau du point d'arrêt

L'exécution s'est arrêté au niveau du point d'arrêt

Prêtez attention au niveau du surlignage de code ! En effet, lorsque que le débogueur s’arrête sur un point d’arrêt, le surlignage jaune (de l’instruction en cours) prend le dessus sur le surlignage rouge du point d’arrêt… Il est donc difficile à ce moment de savoir si l’instruction en cours est sur un point d’arrêt, pour cela regardez à gauche des numéros de ligne. Vous remarquez qu’un cercle rouge plein est sous la flèche jaune de l’instruction en cours. Cela indique tout simplement que l’instruction en cours est sur le point d’arrêt !

Maintenant, vous pouvez déboguer votre application comme d’habitude en consultant les variables et en déroulant votre application pas-à-pas…

Dans de gros projets, il est utile de savoir où se trouvent tous les points d’arrêt que vous avez définie.
Visual Studio propose une fenêtre vous permettant d’afficher la liste de tous les points d’arrêts présents dans la solution en cours ainsi que lors emplacement. Cette fenêtre est disponible dans le menu « Déboguer -> Fenêtres -> Points d’arrêt » :

Affichage de la liste des points d'arrêts

Affichage de la liste des points d'arrêts

Voici la fenêtre des points d’arrêt :

Liste des points d'arrêts

Liste des points d'arrêts

Cette fenêtre offre un vue d’ensemble de tous vos points d’arrêt dans la solution en cours et vous permet de les supprimer.

La pile des appels et les différents modes de pas-à-pas.

Nous avons vu comment se servir d’un débogueur dans une méthode assez simple. Nous allons maintenant voir un aspect un peu plus complexe pour les débutants mais fondamental pour bien déboguer une application : La pile des appels.

Qu’est ce que la pile des appels ?

Le code que vous avez programmé, s’exécute de façon séquentielle, et de temps en temps réalise « des sauts » d’un bout de code à un autre.
Cela est dû tout simplement au fait que vous faites appel à des fonctions se situant à des endroits différents de votre code.

Ordre d'exécution des méthodes

Ordre d'exécution des méthodes

La question que l’on peut se poser ici (du moins si vous êtes curieux…), comment après avoir exécuté une méthode, .NET sait-il où il doit revenir dans le code ?

On introduit pour cela une pile des appels de méthodes (ou de fonctions si l’on n’est pas dans un environnement orienté objets).
A chaque fois que vous appelez une méthode, .NET ajoute dans la pile, l’emplacement de l’instruction qui est en cours d’exécution avancé d’un pas (son adresse + 1). Il effectue ensuite le saut et exécute le contenu de la méthode. Et cela de façon récursive…
A la sortie de la méthode, .NET retire l’élément se trouvant au sommet de la pile contenant l’emplacement où .NET doit reprendre son exécution.

Pour mettre en évidence ce concept, nous allons exécuter le code précédent (mentalement…) et regarder le contenu de la pile des appels des méthodes. On a besoin pour cela d’affecter des adresses aux méthodes et aux appels de méthode. (Les numéros sont complètement arbitraires…)

Numéro des lignes associés au code précédent

Numéro des lignes associés au code précédent

Exécutons manuellement ce code. Au lancement du programme, on a la méthode Main qui est empilé. On se retrouve avec la pile suivante :

Méthode associée Adresse de retour
Main() Aucune

La pile ne contient pas le « nom de la méthode associée », mais uniquement une adresse de retour.
J’ai ajouté une colonne « méthode associée » pour que vous puissiez vous rendre compte plus rapidement, quel sont les appels de méthodes qui sont en cours d’exécution.
La méthode Main() ne possède pas d’adresse de retour, tout simplement parce que si l’on dépile la méthode Main() cela signifie la fin de l’application.

On avance maintenant notre code et l’on se trouve au niveau de l’instruction n°10 (appelle de la méthode « Méthode1 »). A ce moment là, .NET ajoute dans la pile, l’adresse de l’instruction en cours incrémenté de 1 :

Méthode associée Adresse de retour
Méthode1() 11
Main() Aucune

On avance maintenant notre code et on arrive à la fin de l’exécution de la méthode « Méthode1 », à ce moment là .NET dépile l’élément et récupère l’adresse de retour qui est ici de « 11 ». Il continue donc l’exécution à partir de cette adresse. On se retrouve de nouveau avec la pile suivante :

Méthode associée Adresse de retour
Main() Aucune

On arrive à l’instruction n°20 qui est l’appel de la méthode « Méthode2 ». .NET empile alors l’adresse de l’instruction en cours incrémenté de 1 :

Méthode associée Adresse de retour
Méthode2() 21
Main() Aucune

On exécute le contenu de la méthode « Méthode2 » et l’on arrive à l’appel de la méthode « Méthode3 ». .NET empile l’instruction en cours incrémenté de 1 :

Méthode associée Adresse de retour
Méthode3() 251
Méthode2() 21
Main() Aucune

Arrivé à la fin de la méthode « Méthode3 », .NET dépile l’élément du contenu de la pile le plus haut et exécute l’instruction contenu dans l’adresse de retour (251). On se retrouve maintenant avec la pile suivante :

Méthode associée Adresse de retour
Méthode2() 21
Main() Aucune

On continue et on arrive à la fin de la méthode « Méthode2 ». Même punition :

Méthode associée Adresse de retour
Main() Aucune

Arrivé à la fin de la méthode Main, la pile est détruite et signifie la fin de l’application.

Il faut cependant ajouter une chose importante que je n’ai pas précisée avant (afin d’éviter d’embrouiller les débutants) : La pile contient en plus toutes les variables locales à une fonction. Pour expliquer plus clairement imaginez que la méthode « Méthode1 » contient le code suivant :

static void Méthode1(int entierParam1, int  entierParam2)
{
  int entierLocal1;
  int entierLocal2;
  ...
}

Lorsque vous appelez la méthode « Méthode1 » voilà ce que vous avez exactement dans la pile :

Méthode associée Adresse de retour Variables locales
Méthode1 11 entierParam1, entierParam2, entierLocal1, entierLocal2
Main() Aucune args[]

Cela signifie que lorsque vous dépilez la fonction associée « Méthode1 », les variables locales sont automatiquement supprimées ! Vous comprenez mieux maintenant pourquoi j’ai mis la méthode Main() dans la pile : on n’empile pas d’adresse de retour (car il n’en n’existe pas), mais uniquement ses variables locales (ici le classique tableau args[]).

Niveau vocabulaire, la pile se dit en Anglais : « Stack », et un élément (une ligne) de la pile : « Frame ». C’est utile de vous le précisez si vous souhaitez rechercher des classes permettant la manipulation de celle-ci.

La pile possède une propriété très intéressante :
Si l’on regarde tous les éléments de la pile à un instant donné du début jusqu’au sommet, alors on obtient le chemin (plutôt la succession des méthodes) empruntée par l’instruction en cours. C’est ce chemin que l’on retrouve affiché sur une page dans une application ASP .NET en cours de développement lors d’une levée d’exception.

Exemple d'une trace d'une pile

Exemple d'une trace d'une pile

Lorsqu’on utilise le débogueur, il est très utile de connaitre l’état de la pile afin de savoir qui a appelé la méthode où l’on se trouve. Il est donc très important (voir fondamental) de connaitre ce principe.

Utiliser la pile des appels avec un débogueur

Pour mettre en pratique l’utilisation de la pile, je vous propose d’utiliser ce code :

using System;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  class ProgramPiPile
  {
    static void Main(string[] args)
    {
      int  biere;

      biere = 1664;

      Méthode1(biere);
      Méthode2(biere);
   }

   static void Méthode1(int param)
   {
     int  local;

     local = param + 1;

     Console.Write(local);
   }

   static void Méthode2(int  entier)
   {
     Méthode3(entier);
   }

   static void Méthode3(int param)
   {
     int  entier;

     entier = param + 1;
     Console.Write(entier);
   }
 }
}
Namespace Tourreau.Gilles.VisualStudio.Debogage
  Module  ProgrammePiPile

    Sub Main(ByVal args() As String)
      Dim biere As Integer

      biere = 1664

      Méthode1(biere)
      Méthode2(biere)

      Console.ReadKey()
    End Sub

    Sub Méthode1(ByVal param As  Integer)
      Dim local As Integer

      local = param +  1

      Console.WriteLine(local)
    End Sub

    Sub Méthode2(ByVal entier As Integer)
      Méthode3(entier)
    End Sub

    Sub Méthode3(ByVal param As Integer)
      Dim entier As Integer

      entier = param + 1

      Console.WriteLine(entier)
    End Sub

  End Module
End  Namespace

Ce programme n’apporte rien « de manière fonctionnel », comprenez juste l’enchainement des différents appels de méthodes.

Maintenant, placez un point d’arrêt sur l’instruction « local = param + 1 » de la méthode « Méthode1 ». Lancez le débogueur, vous devez vous trouver dans cette situation :

Affichage des méthodes en cours d'exécution dans le débogueur

Affichage des méthodes en cours d'exécution dans le débogueur

Vous pouvez déjà constater que l’appel à la méthode « Méthode1 » est surligné en gris, cela indique tout simplement que votre code est entré dans la méthode « Méthode1 » et qu’il n’est toujours pas ressorti. Cela signifie aussi que la méthode « Méthode1 » est « en cours d’exécution ».

Maintenant, arrêtez le débogueur, enlevez le point d’arrêt crée précédemment, et ajoutez-en un au niveau de l’instruction « entier = param + 1 » de la méthode « Méthode3 ». Lancez le débogage :

Autre affichage des méthodes en cours d'exécution dans le débogueur

Autre affichage des méthodes en cours d'exécution dans le débogueur

Vous constatez maintenant que les appels aux méthodes « Méthode2 » et « Méthode3 » sont surlignés en gris.

Affichons maintenant le contenu de la pile. Pour cela si ce n’est pas encore fait, allez dans Déboguer / Fenêtres / Pile des appels. Vous devez voir apparaitre la fenêtre suivante :

Affichage de la pile dans le débogueur

Affichage de la pile dans le débogueur

Cette fenêtre représente la pile des appels de méthode en cours d’exécution. Les méthodes appelées se lisent de base en haut. Chaque élément (ligne) de la pile représente un appel de méthode.

Le format affiché par défaut des éléments qui représentent les méthodes appelées est le suivant :
<nom de l’exécutable>!<Nom de la classe complète>.<Méthode>(<Paramètres>) Ligne <N° de la ligne> + <Offset depuis le début de la méthode>

A gauche du nom de la méthode, vous voyez apparaitre une flèche jaune dessus un rond rouge. Vous le comprendrez très rapidement que cette flèche jaune vous indique à quel niveau se situe l’instruction en cours, cela n’est pas très important dans la mesure où l’instruction en cours sera toujours celle se situant en haut de la pile des appels.

Maintenant faites un double clic sur l’élément inférieur à l’instruction en cours. Vous constatez qu’une flèche verte apparait et que l’appel à la méthode « Méthode3 » dans « Méthode2 » est surligné en vert.

Changement de contexte de l'appel d'une méthode

Changement de contexte de l'appel d'une méthode

Affichage du changement de contexte de l'appel d'une méthode dans la pile des appels

Affichage du changement de contexte de l'appel d'une méthode dans la pile des appels

Cela vous indique que vous venez de charger le contexte de l’appel de la méthode « Méthode2 ».
Le contexte d’une méthode représente toutes les variables locales et leurs valeurs qui leurs sont attachées. Il est très important de comprendre ce que représente un contexte d’exécution d’une méthode.
Pour mieux comprendre, avancez d’un pas l’exécution du programme. Consultez la valeur de la variable « entier » dans la méthode « Méthode3 » vous devez avoir « 1665 ». Ce qui est tout à fait normal…
Cependant consultez la valeur de variable « entier » dans la méthode « Méthode2 », vous obtenez « 1665 » !!! Alors que l’on devrait obtenir « 1664 » car cette variable n’est pas modifié dans « Méthode2 ».

En fait, le contexte de méthode qui est actuellement chargé est celui de « Méthode2 ». Lorsque vous chargez un contexte de méthode, Visual Studio associe de manière « brutale » le nom des variables locales avec leurs valeurs. C’est à dire dans notre exemple, Visual Studio associe la variable locale « entier » = « 1665 ».

Partout dans votre code, si vous consultez des variables locales intitulées « entier », Visual Studio affichera la valeur dans le contexte de méthode courante (ici « 1665 »).
Cela ne veut pas dire que votre programme est bogué ! Mais que cela vient d’un « problème d’affichage » au niveau du débogueur de Visual Studio. Attention ! Je n’ai pas dis que c’était un bogue de Visual Studio ! Cela a été fait volontairement par Microsoft.

Imaginez que l’on affiche correctement la valeur des variables locales en fonction de leur emplacement dans le code, c’est à dire que dans notre exemple précédent.
On aurait au même instant le débogueur qui afficherait « 1665 » dans la méthode « Méthode3 » et « 1664 » pour la méthode « Méthode2 ». Cependant ce genre de procédé risquerait de poser des problèmes de confusion si vous êtes entrain de déboguer une méthode qui est appelée au moins 2 fois (méthode récursive par exemple). Comment Visual Studio peut savoir qu’il doit afficher la valeur du deuxième ou premier appel de la méthode ? C’est pour cela que l’on utilise les contextes de méthodes…

Pour remédier à notre problème précédent et pouvoir consultez la valeur « réelle » de la variable locale « entier » de la « Méthode2 », double cliquez dans la pile sur « Méthode2 ». Dans votre code l’appel à la méthode « Méthode3 » doit apparaitre en vert. Consultez alors la valeur de la variable locale « entier ».

Affichage d'une valeur dans le contexte de la méthode actuellement sélectionné

Affichage d'une valeur dans le contexte de la méthode actuellement sélectionné

Pour mettre plus avant ce problème, essayons de programmer une méthode récursive qui calcul la factorielle d’un nombre.

Je rappel pour les personnes qui étaient occupé à draguer leur voisine durant leur cours de Maths de Terminal, ce qu’est une factoriel :
La factorielle d’un nombre (notée n!) est le produit des nombres entiers positifs strictement inférieurs ou égaux à n.
Voici un exemple pour ceux qui séchaient tous les cours de Maths :

8! = 8 x 7 x 6 x 5 x 4 x 3 x 2 x 1

Voici donc le programme permettant de calculer la factorielle d’un nombre de façon récursive :

using System;

namespace  Tourreau.Gilles.VisualStudio.Debogage
{
  class  ProgrammeFactorielle
  {
    static void Main(string[]  args)
    {
      int résultat;

      résultat = Factorielle(8);

      Console.WriteLine(résultat);
      Console.ReadKey();
    }

    static int Factorielle(int  valeur)
    {
      if (valeur == 1)
        return 1;

      return valeur * Factorielle(valeur - 1);
    }
  }
}
Namespace Tourreau.Gilles.VisualStudio.Debogage
  Module  ProgrammeFactorielle

    Sub Main(ByVal args() As  String)
      Dim resultat As Integer

      resultat =  Factorielle(8)

      Console.WriteLine(resultat)
      Console.ReadKey()
    End Sub

    Function Factorielle(ByVal  valeur As Integer) As Integer

      If valeur = 1  Then
        Return 1
      End If

      Return  valeur * Factorielle(valeur - 1)

    End Function

  End  Module
End Namespace

Placez maintenant un point d’arrêt au niveau du « if (valeur == 1) » de la méthode Factorielle. Lancez le débogage de l’application, faites continuer le programme trois fois via la tite flèche verte « Continuer (F5) ». Observez la pile des appels :

Contenu de la pile des appels suite à plusieurs appels de la méthode Factorielle()

Contenu de la pile des appels suite à plusieurs appels de la méthode Factorielle()

Vous constatez que la méthode est appelée plusieurs fois. Consultez maintenant la valeur de la variable « valeur » :

Affichage de la valeur de la variable "valeur"

Affichage de la valeur de la variable "valeur"

L’inconvénient dans les méthodes récursives, c’est que le même code est appelé plusieurs fois. On ne peut donc pas afficher toutes les variables locales de cette méthode à tous les appels… On est obligé dans ce cas de charger les différents contextes d’appels de méthode pour consulter les variables locales à chaque appel.
Dans le cas de la factorielle, double cliquez sur un autre élément de la pile pour charger un autre contexte et consultez de nouveau la valeur de la variable « valeur » :

Affichage de la valeur de la variable "valeur" dans un contexte d'une méthode différent

Affichage de la valeur de la variable "valeur" dans un contexte d'une méthode différent

Prêtez donc bien attention lorsqu’une méthode est appelé plusieurs fois, à être sur le bon contexte d’exécution pour consultez vos variables locales ! Cela est surtout très important pour les méthodes récursives ou appelés plusieurs fois dans différents endroits du code.

Utilisation des différents modes d’avancement

Afin « d’accélérer » la vitesse de déroulement de votre programme, il existe dans tous les débogueurs 4 modes d’avancements :

Voici les 3 premiers dans l’ordre :

Les différents modes d'avancement

Les différents modes d'avancement

  • Pas à pas détaillé
  • Pas à pas principal
  • Pas à pas sortant

Le pas-à-pas détaillé :

Avance instruction par instruction et ceux de façon détaillé en entrant dans toutes les appels de méthode.
Par exemple dans le code suivant :

Avant un pas-à-pas détaillé

Avant un pas-à-pas détaillé

Si l’on avance d’un pas, Visual Studio entre dans la méthode « Méthode1 », on se retrouve alors dans cette situation :

Après un pas-à-pas détaillé

Après un pas-à-pas détaillé

Ce mode d’avancement représente l’avancement le « plus réel » dans l’exécution d’une application, car il exécute toutes les instructions de votre application…

Le pas-à-pas principal :

Avance instruction par instruction, mais ne rentre pas en détail dans les appels de méthode (= n’empile aucune méthode dans la pile des appels).
Par exemple :

Avant un pas-à-pas principal

Avant un pas-à-pas principal

Le pas-à-pas principal va exécuter tout le contenu de la méthode « Méthode1 » mais sans s’arrêter à l’intérieur. On sera donc au pas suivant la situation suivante :

Après un pas-à-pas principal

Après un pas-à-pas principal

Ce mode d’avancement permet d’exécuter du code sans rentrer dans « les détails » de la méthode que vous appelez.

Le pas-à-pas sortant :

Exécute toute la méthode en cours et ressort de celle-ci. (= dépiler la méthode en cours dans la pile des appels).
Par exemple on est dans la situation suivante :

Après un pas-à-pas sortant

Après un pas-à-pas sortant

En utilisant le pas-à-pas sortant, Visual Studio exécute toute la méthode « Méthode1 » et ressort de celle-ci :

Avant un pas-à-pas sortant

Avant un pas-à-pas sortant

ATTENTION : Visual Studio signale « Méthode1 » comme la prochaine instruction qui va être exécutée, mais ce n’est pas le cas ! Cela a été fait volontairement par Microsoft, pour que dans de très gros projets, lorsque vous revenez dans l’appel de la méthode, vous sachez qu’elle est l’instruction à l’origine de l’appel.

Les points d’arrêts dans les pas-à-pas.

Dans les 3 cas, s’il existe un point d’arrêt au milieu de l’exécution de ces pas-à-pas, le programme s’arrête obligatoirement sur ce point d’arrêt.

Continuer l’exécution jusqu’au prochain point d’arrêt…

Le 4ème mode d’avancement est : la tite flèche verte !
La flèche verte signifie tout simplement que vous avancez votre programme jusqu’au prochain point d’arrêt (ou à une levée d’exception ;-)) !

Les exceptions

Lorsque vous déclenchez une exception (ou déclenché par le .NET Framework) qui n’a pas été « attrapée » (en franglais « catché ») et si vous êtes en cours de débogage, Visual Studio vous pointera sur l’instruction en cours qui pose problème :

Arrêt du débogage suite au déclenchement d'une exception

Arrêt du débogage suite au déclenchement d'une exception

L’inconvénient dans ce cas, c’est que l’on est bloqué… Mais on peut toujours consulter toutes les variables au moment de la levée de l’exception…

L’idéal, c’est d’avoir un débogueur (et un langage de programmation évolué) qui supporte le changement de code durant le débogage (c’est le cas de Visual Studio 2005). Dans tous les autres cas, il faudra arrêter le débogueur et mettre un point d’arrêt beaucoup plus haut afin de savoir d’où vient l’erreur qui a engendré une levée d’exception…
Certains débogueurs permettent de faire du pas-à-pas en arrière ! (comme ISDBG sur les CICS d’IBM). Mais malheureusement, Visual Studio ne gère (pas encore) cette fonctionnalité.

Exercices

J’ai crée 2 petits exercices (téléchargeables à la fin de l’article) que vous pouvez faire pour vous entrainer à utiliser un débogueur.

Le premier est un exercice très simple de bug classique que l’on rencontre dans tout programme.
Le second est un exercice mettant en jeu une méthode récursive.

Pensez à regarder le fichier LisezMoi.txt présent dans le projet, pour comprendre le fonctionnement de l’application.
Si vous rencontrez énormément de difficultés pour déboguer ces applications, n’hésitez pas à me contacter !

Conclusion

Vous savez maintenant vous servir de n’importe quel débogueur. C’est un outil indispensable pour la mise au point de votre application. Il permet de trouver aisément et rapidement des erreurs de programmation dans votre application, et contrairement à la bière, il ne faut surtout pas hésiter à en abuser !

Au prochain épisode…

Prochainement, je publierai un article sur l’utilisation du débogueur de manière beaucoup plus avancée … Pour ne rien « rater », ajoutez mon site Web à vos flux RSS qui sont disponibles à la page d’accueil.

Remerciements

Je tiens particulièrement à remercier les personnes suivantes pour leurs commentaires qui m’ont permis d’améliorer la qualité ce tutoriel (du moins je l’espère) :

  • ABRASSART Jacques
  • DE HATTEN Thomas
  • DUTHOO Laurent
  • LEMIERE Valentin

Téléchargements