Mécanismes génériques d’inspection du programme

Télécharger le fichier pdf d’un mémoire de fin d’études

Le problème du débogage des langages de haut niveau

Le vrai problème des débogueurs symboliques actuels est que ce sont des outils conçus pour déboguer des langages des années 70. Or, le débogage des langages compilés se com-plique dès lors qu’on considère les langages de haut niveau.
Ces dernières années ont connu une tendance vers l’unification des plates-formes d’exé-cutions des langages de haut niveau : les implanteurs de langages ont cherché des solutions de compilation qui leur permettraient de se passer de leur environnement d’exécution ad-hoc adaptés à leur langage, au profit d’une seule plateforme d’exécution généralisée. Cela a permis d’augmenter l’inter-opérabilité des langages et de leurs bibliothèques d’exécution. Parmi ces plates-formes généralistes, citons la Java Virtual Machine [LY97] et le .NET Common Language Infrastructure [GG01], deux machines virtuelles de haut niveau.
Le problème est que la compilation des langages de haut niveau vers une plateforme d’exécution généraliste comme la JVM est souvent complexe car les caractéristiques des langages ne correspondent pas aux fonctionnalités de la plateforme. Par exemple, les pro-priétés des structures de données des langages peuvent ne pas être compatibles avec celles supportées nativement par la plateforme d’exécution. De même, les langages peuvent dispo-ser de constructions qui n’ont pas d’équivalent dans la plateforme d’exécution, comme par exemple les fermetures. Ces différences entraînent la production de structures de données synthétiques et d’appels de fonctions intermédiaires dans le code compilé. Cela a plusieurs conséquences du point de vue du débogage :
– l’inspection des fonctions peut faire apparaître des variables locales intermédiaires produites par le compilateur et qui ne sont pas présente dans le code source. De même, les structures de données utilisateurs peuvent contenir des champs synthé-tiques. Des types synthétiques produits pour implanter certaines fonctionnalités du langage peuvent aussi apparaître .
– pour implanter certaines fonctionnalités, le compilateur peut avoir besoin de construire des fonctions synthétiques et de rajouter des appels de fonctions dans les fonctions utilisateurs originales. Cela pollue l’inspection de la pile d’exécution et perturbe gran-dement l’exécution pas-à-pas.
En résumé, la compilation délicate vers des plates-formes d’exécution généralistes n’est pas prise en compte par les débogueurs actuels. Ce manque de support spécifique interdit dans la pratique l’emploi d’un outil générique pour déboguer les langages de haut niveau et les programmes multi-langages.

Vers un meilleur débogage symbolique

Comme vu précédemment, les principales raisons du désintérêt des utilisateurs pour les débogueurs actuels peuvent être résumées comme suit :
– les débogueurs de code interprété sont intrinsèquement incapables de déboguer des programmes composés de plusieurs langages ou contenant du code compilé .
– les débogueurs de code compilé imposent de lourdes contraintes sur la compilation des programmes. De plus, ils ne sont pas équipés pour déboguer efficacement les langages de haut niveau .
– quel que soit le modèle, les débogueurs offrent un contrôle sur l’exécution et des fonctionnalités limités et ne sont malheureusement pas adaptables ou extensibles.
En conséquence, les débogueurs sont peu utilisés non pas parce que le modèle de débo-gage symbolique est mauvais — pour preuve, le débogage par prints ne fait que reproduire ce schéma — mais parce qu’ils sont trop contraignants.
La question que nous nous posons dans ces travaux est la suivante : comment améliorer les débogueurs symboliques pour les rendre plus attrayants ? Il est possible de répondre à cette question en suivant trois idées :
il est souhaitable d’avoir un outil unique basé sur le modèle des débogueurs de code compilé, car le modèle interprété ne permet pas de déboguer suffisamment de pro-grammes « de tous les jours » .
il faut réduire au maximum les contraintes d’utilisation (la compilation spéciale ou les limitations fonctionnelles) qu’implique l’architecture sous-jacente ;
il faut que l’outil puisse déboguer efficacement tous les aspects des langages de haut niveau (y compris leur interprète), ainsi que les programmes composés de plusieurs langages de haut niveau.

L’opportunité de la machine virtuelle Java

Avec l’arrivée du JDK 1.3 de la machine virtuelle Java (ou JVM), on dispose pour la première fois dans l’histoire du débogage symbolique de deux interfaces de programmation standard pour instrumenter une plateforme d’exécution : Java Debug Interface (JDI) et JVM Tool Interface2 (JVMTI).
La JVM suit le modèle des débogueurs de code compilé : le débogueur et le débogué s’exécutent chacun dans leur propre JVM. JDI est une interface Java utilisée par le débo-gueur depuis sa JVM et qui permet d’instrumenter la JVM du débogué (par exemple poser un point d’arrêt, inspecter la pile d’exécution. . . ). JVMTI est une interface C uniquement accessible depuis la JVM du débogué, qui permet de réagir à des événements de plus bas niveau, comme l’allocation dans le tas.
La plateforme JVM est très attrayante en ce qui concerne le débogage, car elle permet d’éviter toutes les contraintes dûes à l’architecture sous-jacente que l’on rencontre dans les débogueurs traditionnels :
– Les interfaces standards assurent la portabilité des débogueurs sur toutes les archi-tectures supportées par la JVM et la cohérence des instrumentations et donc des fonctionnalités de débogage.
2JVMTI est en fait apparue dans le JDK 1.5. C’est une évolution de JVMDI et de JVMPI, précédemment apparues dans le JDK 1.3.
– L’implantation de l’instrumentation est déjà fournie, ce qui libère les implanteurs de débogueur d’une tâche fastidieuse.
– Il n’y a pas de compilation « spéciale débogage » du point de vue de l’utilisateur, puisque la compilation est effectuée à la volée au moment de l’exécution.
– Depuis le JDK 1.4, le compilateur à la volée (ou JIT) de la JVM reste activé pendant le débogage. Cela permet d’éviter des chutes de performances par rapport aux exécutions « normales ».
– Le JIT peut recompiler différemment [THL02] une fonction si les contraintes de dé-bogage l’exigent. Par exemple, cela permet de conserver des optimisations comme l’inlining tout en gardant la possibilité de poser des points d’arrêt.
– En plus des fonctionnalités d’instrumentation fournies par JDI, JVMTI permet d’im-planter des fonctionnalités avancées comme le débogage mémoire, ou d’ajouter des fonctionnalités à la volée selon le langage qu’on débogue (par exemple charger dyna-miquement l’interprète du langage).
En résumé, la JVM libère les utilisateurs des contraintes matérielles (points 1 et 2 évoqués précédemment) et offre les outils nécessaires pour fournir de fonctionnalités de débogage à l’utilisateur.

Synthèse de la contribution de ces travaux

Dans ce document, il est proposé un ensemble d’améliorations à apporter au modèle de débogage basé sur l’instrumentation dynamique des programmes compilés afin de rendre les débogueurs plus efficaces et plus agréables à utiliser. Ces travaux se focalisent sur le problème de l’instrumentation d’un seul processus, pouvant contenir une ou plusieurs piles d’exécution (multi-threading). Ils ne prennent pas en compte le débogage systémique, qui consiste à instrumentation plusieurs processus simultanément (par exemple pour les pro-grammes utilisant la migration). Ils ne prennent pas non plus en compte l’instrumentation du système d’exploitation. Le débogage de performances à l’exécution, ou profilage, n’est pas non plus abordé dans ces travaux.
Nos travaux ont conduit à l’implantation d’un débogueur appelé Bugloo [Cia03], dis-tiné aux programmes compilés pour la JVM et composés d’un ou plusieurs langages de programmation. Voici ses principales caractéristiques :
– l’utilisation de la plateforme JVM permet d’éviter les contraintes de compilation spéciale des programmes. Cela simplifie grandement l’utilisation du débogueur .
– il se contrôle à la ligne de commande à l’aide du langage fonctionnel Scheme [KCE98], ce qui permet de le scripter. Pour le rendre plus agréable à l’usage, il s’intègre à l’environnement de programmation GNU Emacs ou XEmacs .
– contrairement aux débogueurs actuels, son architecture est modulaire et extensible : on peut facilement ajouter un support de débogage pour un nouveau langage, ou des fonctionnalités supplémentaires communes à plusieurs langages (par exemple le débogage de threads) .
– c’est un débogueur entièrement programmable : une interface de programmation en Scheme expose ses fonctionnalités et ses capacités d’instrumentation. Cette inter-face est aussi accessible à la ligne de commande. Enfin, certaines fonctionnalités (par exemple les points d’arrêt) sont elles-même programmables, ce qui permet d’augmen-ter leurs expressivité.
Bugloo se singularise par sa capacité à déboguer des langages de haut niveau dont la compilation engendre des structures et des fonctions intermédiaires nuisibles à leur débo-gage. Pour cela, des techniques de représentations virtuelles ont été développées dans le but de masquer la structure physique réelle des différentes abstractions du langage après leur compilation. Tout d’abord, le débogueur propose des mécanismes génériques d’affichage et d’inspection de l’état du débogué :
– un dispositif d’affichage de noms d’identifiants (fonction, variable, type. . . ) encodés lors de la compilation pour pouvoir être représentables dans la JVM .
– un mécanisme pour spécifier une ou plusieurs façons de formater les valeurs des abs-tractions fournies par un langage (fonctions, objets structurés. . . ).
– un mécanisme générique permettant d’accéder aux différents niveaux de portées lexi-cales définies dans un langage et qui n’ont pas de contre-partie dans la JVM, par exemple les variables capturées ou globales .
– un moyen d’inspecter des structures de données natives d’un langage qui doivent être émulées pour être représentables dans la JVM (listes, types énumérés. . . ).
Le débogueur propose aussi un ensemble d’outils permettant de construire une repré-sentation virtuelle de l’état du débogué dans le but de contrôler son exécution de manière transparente, quels que soient les langages utilisés dans le programme ou les particularités de leurs compilations :
– il peut construire une vue virtuelle de la pile d’exécution afin de masquer les appels de fonctions intermédiaires qui sont produits pour implanter les abstractions d’un langage. Une telle vue permet aussi de visualiser du code natif JVM et du code interprété dans une unique pile, de manière totalement transparente .
– il peut faire du filtrage de saut durant l’exécution pas-à-pas afin de ne pas s’arrêter dans une fonction synthétique produite par le compilateur .
– il fournit un moyen de poser des points d’arrêt dans des fonctions logiques — c’est-à-dire des fonctions définies dans le code source — même si elles n’ont pas été direc-tement compilées en fonctions JVM.
Enfin, le débogueur offre des fonctionnalités de débogage mémoire, elles aussi indépen-dantes des langages utilisés et de la complexité de leur compilation :
– il peut produire des statistiques sur la quantité de mémoire utilisée dans le tas et la proportion pour chaque type du programme, aussi bien pour des types JVM que pour des types ad-hoc .
– il fournit un inspecteur de références qui permet de retrouver l’origine de fuites mé-moire .
– il propose un profileur d’allocation mémoire adapté aux langages de haut niveau et à leur compilation complexe. Il construit des statistiques dans lesquelles les fonctions synthétiques produites par le compilateur sont remplacées par les fonctions utilisateur originales.

Le contexte de développement

Afin de valider les méthodes de prise en charge des langages de haut niveau développées dans ces travaux, un module de débogage complet a été réalisé pour permettre le débogage des programmes écrits dans le langage Bigloo [SW95], un dialecte du langage fonctionnel Scheme. Traditionnellement les débogueurs de Scheme opèrent sur la forme interprétée des pro-grammes, sans doute parce que ce langage se prête bien à la transformation de code. Dans ces débogueurs, la pile d’exécution est représentée par la liste des expressions en attente d’évaluation et la granularité de l’exécution pas-à-pas est l’expression.
Il peut paraître étonnant de choisir le modèle de débogueur de programmes compilés : en effet, à la différence de C, Scheme est un langage fonctionnel ; à ce titre, il ne différencie pas les expressions des instructions. Par conséquent, le flot de contrôle des programmes Scheme est souvent plus « perturbé » que celui des programmes C. Cependant, contrairement aux langages paresseux comme Haskell qui rendent l’utilisation des débogueurs symboliques très difficile (cf. les travaux de Ennals [EJ03]), les programmes Scheme sont aisément débogables, car leur évaluation suit une progression séquentielle.
Le compilateur Bigloo dispose de plusieurs générateurs de code. Ainsi, il peut produire du code C, du code-octet JVM ou du code-octet .NET. Bigloo fournissait déjà un débogueur nommé BDB [SB00] pour les programmes compilés en C, mais il a été abandonné. Il était trop contraignant à utiliser car il nécessitait une compilation spéciale pour le débogage. De plus, il utilisait le débogueur GDB [SP91] pour instrumenter les programmes et cette connexion était trop difficile à maintenir. Enfin, il ne permettait pas de déboguer certains aspects du langage comme l’interprète. Une des raisons pour concevoir un générateur de code-octet JVM était de pouvoir bénéficier d’un débogueur comme Bugloo qui ne souffre pas de ces limitations.
Le langage Bigloo est significatif car il présente beaucoup de fonctionnalités de haut niveau : fonctions d’ordre supérieur, capture de variables, capture du flot d’exécution avec des continuations, macros, interprète de code, structures de données ad-hoc, etc. . . Cha-cune de ces fonctionnalités requiert une compilation assez complexe. De plus, le langage dispose d’un système de threads particulier, dans lequel des fils d’exécutions partagent l’es-pace mémoire du programme et s’exécutent de manière synchrone [SBS04]. En résumé, les caractéristiques de ce langage permettent d’illustrer un grand nombre de mécanismes mis en œuvre dans un débogueur qui a vocation à être générique et extensible.

Assertions et contrats pour le débogage

Les assertions sont des expressions logiques qui permettent d’exprimer des propriétés sur un programme. Le débogage statique de ces assertions est très vieux. Par exemple, Syntox [Bou93b, Bou93a] est un débogueur qui utilise des techniques d’interprétations abs-traites [CC77] d’un sous-ensemble du langage Pascal pour déterminer statiquement certains comportements d’un programme à l’exécution. Dans Syntox, les assertions sont essentielle-ment utilisées pour décrire des invariants de programmes de petite taille.
La programmation par contrat [Mey92a] a été popularisé avec le langage Eiffel [Mey92b]. Elle consiste à annoter les fonctions de son programme avec des assertions exécutées avant (pré-condition) et après (post-condition) la fonction utilisateur. Par exemple, on peut uti-liser les contrats pour exprimer le fait que la valeur d’une variable de type entier doit être comprise entre 0 et 100. En ce sens, les contrats peuvent être vus comme un complément de typage.
JML [LBR99] est un langage de spécification comportemental pour Java. Pour construire des assertions, il fournit un langage proche de Java étendu par des quantificateurs universels pour l’expressivité de la logique. Les travaux de Trentelman et Huisman [TH02] étendent le langage d’assertion de JML pour fournir des opérateurs de la logique temporelle.

Le débogage durant l’exécution

Le débogage dynamique permet d’instrumenter l’exécution d’un programme d’un point de vue « bas niveau ». Pour pouvoir inspecter l’état d’un programme, il faut souvent outre-passer la sémantique du langage de programmation utilisé dans ce programme. Par exemple, un débogueur efficace doit pouvoir inspecter les champs privés des objets structurés du lan-gage, même si la sémantique l’interdit. La suite de cette section présente un panorama des techniques de débogage à l’exécution.

Débogage pour environnements interprétés

Les débogueurs conçus pour les environnements interprétés sont en général des inter-prète particulier pouvant être contrôlé afin d’instrumenter l’exécution du programme. Dans ce modèle, l’utilisateur peut toujours accéder aux variables de son programme à travers l’interprète, ce qui permet un débogage très interactif.
Le langage Smalltalk [Sho79, GR83] fournit dans les années 70 ce qui est sans doute l’ancêtre des débogueurs symboliques modernes. Il continue encore de nos jours à influencer les débogueurs, comme par exemple le débogueur générique d’Eclipse1 .
Le débogueur Smalltalk est un interprète spécialisé de code-octet Smalltalk qui inter-rompt le programme lorsque ce dernier provoque une erreur. Il peut aussi être invoqué par un appel de fonction placé dans le code source pour suspendre le programme à un point précis. Lorsque le programme est suspendu, le débogueur donne différent types d’informa-tions :
– il affiche le contenu de la pile d’exécution, c’est-à-dire la liste des message Smalltalk en attente d’exécution .
– il permet d’inspecter la liste des variables locale pour chaque fonction présente dans la pile .
– il permet de continuer d’exécuter le programme instruction par instruction .
– il permet de changer le corps d’une fonction « à chaud » et de poursuivre l’exécution du programme débogué .
En Smalltalk, le débogueur est un simple programme, ce qui le rend aisément program-mable : l’utilisateur peut construire des scripts servant à étendre son comportement ou ses fonctionnalités. De même, il est possible de « déboguer le débogueur ».
Self [US87] est un langage à prototype descendant de Smalltalk. Dans la machine vir-tuelle Self, le code octet interprété est dynamiquement traduit en code natif optimisé. Le débogueur peut fournir des services similaire à celui de Smalltalk grâce à sa capacité à rem-placer les remplacer les fonctions optimisées présentes dans la pile par leur contre-partie interprété (en code-octet Self). VisualWorks [How95] est un environnement de programmation Smalltalk-80. En plus des fonctionnalités de débogage précédemment citées, son débogueur dispose de point d’ar-rêt conditionnels permettant de suspendre le programme en fonction de la valeur d’une expression Smalltalk. Il permet aussi de déboguer des parties du programme source en les sélectionnant avec la souris dans l’éditeur intégré. Enfin, il peut inspecter les champs des structures de données de manières graphiques et afficher les pointeurs reliant ces struc-tures dans la mémoire. « Squeak [IKM+97] est un environnement Smalltalk graphique dont la machine virtuelle est elle-même écrite en Smalltalk. Le débogueur intégré peut instru-menter ou inspecter n’importe quelle partie du programme, mais aussi n’importe quelle partie de l’environnement graphique intégré. D’autre débogueur sont disponibles, à l’image d’Unstuck [HDD06] : cet outil trace l’exécution du programme pour pouvoir examiner les différentes valeurs qu’ont pris les variables avant la survenue d’un bogue. »

L’inspecteur structurel

La connexion entre Bugloo et Emacs permet de visualiser simplement l’état des diffé-rentes variables locales ou globales du programme débogué. Lorsque l’utilisateur souhaite visualiser la valeurs des champs d’un objet structuré, il peut utiliser l’inspecteur graphique de Bugloo. Cet outil utilise Biglook [GS03a], une bibliothèque de construction d’interfaces graphiques pour Scheme. Cela permet de s’affranchir des limites imposées par l’interface textuelle de l’éditeur Emacs.
Un inspecteur construit pour l’utilisateur une représentation graphique — appelée une vue — de l’état d’un objet présent dans le programme débogué. La vue utilisée par l’ins-pecteur est choisie en fonction du type de l’objet. Les implanteurs de langages ou de biblio-thèques peuvent utiliser l’API de Bugloo pour programmer des extensions fournissant de nouvelles vues.
La figure 3.3 est une copie d’écran présentant un inspecteur structurel. À la base de la fenêtre graphique, la barre de statut indique le type de l’objet en cours d’inspection. Au milieu de la fenêtre réside la vue choisie pour représenter l’objet en cours d’inspection. Dans une vue, les champs pointant sur d’autres objets peuvent être eux-mêmes inspectés dans la fenêtre d’inspection courante ou dans une nouvelle. La barre d’outil au sommet de l’inspecteur fournit des fonctionnalités communes à toutes les vues :
– quand l’utilisateur inspecte un nouvel objet dans la fenêtre courante, la vue représen-tant l’ancien objet est conservée dans un historique des inspections. Il est possible de se déplacer dans cet historique, à la manière des navigateurs Web. Quand une nou-velle vue est créée, elle est insérée à la position courante dans l’historique et seules les vues précédentes sont conservées.
– un objet particulier peut être représenté par différentes vues. Par exemple, si l’objet inspecté provient d’un langage de haut niveau, sa structure peut contenir des champs laissant apparaître des détails de compilation (comme des champs synthétiques). Une vue spéciale peut lui être associée afin de masquer ces détails. Le dernier bouton de la barre d’outils permet de choisir la vue à utiliser pour l’inspection. Quel que soit le type de l’objet inspecté, le débogueur fournit une vue par défaut (figure 3.3) présentant une introspection basique de l’objet.

Contrôle par le langage de commande

Le débogueur est contrôlable au moyen d’un langage de commande. Contrairement à de nombreux débogueurs [TD86, SP91, Fie99, BKO+02] contrôlable au moyen d’un langage ad-hoc souvent limité, le langage de commande fourni par Bugloo est le langage Scheme. Cela permet à l’utilisateur de « personnaliser » le débogueur avec toute la puissance d’un vrai langage. Il peut, par exemple, créer des macros-commandes en utilisant des fermetures ou des variables globales. Bugloo étant programmé en Scheme, toute l’API d’instrumentation du débogué est facilement accessible à partir de la ligne de commande. Par exemple, l’utilisateur a la possibilité de charger des classes JVM à la volée dans le programme débogué et d’appeler des fonctions distantes. De plus, les objets que retournent ces fonctions peuvent être manipulés depuis la ligne de commande comme s’il s’agissait d’objet « locaux ». Le fait d’utiliser une API unique pour programmer la ligne de commande et pour ajouter des fonctionnalités de débogage complexes comme le support des FairThreads (cf. chapitre 8) est important : cela permet une grande liberté de personnalisation tout en offrant un modèle de programmation homogène.
Le mécanisme de la ligne de commande diffère de la boucle de lecture-évaluation-affichage classique de Scheme. En effet, Bugloo maintient un historique de toutes les commandes lues depuis le clavier durant une session de débogage. Cet historique peut être sauvegardé sur disque et rechargé ultérieurement. L’intérêt de cet enregistrement est double :
– il permet de sauver de manière « rudimentaire » l’état du débogué et la configuration courante du débogueur. L’utilisateur peut ainsi arrêter une session de débogage pour la reprendre ultérieurement. Il peut aussi « rejouer » une session sans avoir à retaper toutes les commandes. Bien sûr, cette fonctionnalité ne fonctionne pas correctement sur des programmes non-déterministes, mais elle reste pratique dans de nombreux cas.
– le chargement d’historique est utilisé comme mécanisme de configuration. Au démar-rage de Bugloo, un historique « système » est évalué pour initialiser le débogueur. Cet historique peut être modifié par les implanteurs de langages afin d’initialiser les fonctionnalités de débogage propres à leur langage. Cela leur permet par exemple de créer des points d’arrêt par défaut à chaque nouvelle session, de rajouter des vues à
l’inspecteur graphique ou bien de définir des filtres fournissant une vue virtuelle de la pile d’exécution (cf. chapitre 4).
La ligne de commande Bugloo évalue continuellement des expressions Scheme. L’exé-cution d’une commande Bugloo s’apparente à un appel de fonction classique, mais dans lequel les arguments ne sont pas évalués1 . Par exemple, la commande suivante pose un point d’arrêt dans la fonction fun de la classe foo :
Les arguments sont passés à la commande bp sous leur forme symbolique. L’utilisateur peut forcer la ligne de commande à évaluer des arguments en utilisant la forme unquote de Scheme : (bugloo) (bp add foo ,(+ 10 20)).
La commande précédente pose ainsi un point d’arrêt dans la classe foo à la ligne 30.

Instrumentation du flot de contrôle

Comme tous les débogueurs, Bugloo permet de poser des points d’arrêt dans différents points logiques de l’exécution du programme débogué afin de suspendre son exécution :
– sur passage à un endroit donné dans le code source .
– sur lecture ou écriture d’un champs d’un objet structuré .
– sur le déclenchement d’une exception .
– sur l’entrée ou le retour d’une fonction.
Contrairement aux autres débogueurs, il n’existe pas dans Bugloo de type particulier pour représenter des variantes des points d’arrêts précédents, comme par exemple les points d’arrêt temporaires ou conditionnels. Pour le débogueur, ces variantes sont considérées comme des attributs.
Dans Bugloo, les attributs sont des fonctions prenant un point d’arrêt en paramètre et retournant un booléen. Ils peuvent être associés à n’importe quel type de point d’arrêt précédemment évoqué. Dans le débogueur, ils sont représentés par des mot-clés Scheme, suivis par leurs arguments éventuels : le choix a été fait de ne pas évaluer les arguments des commandes Bugloo afin de faciliter l’utilisation de la ligne de commande.
Le point d’arrêt précédent est posé dans la classe foo au début de la fonction bar. Lorsqu’un point d’arrêt est atteint, tous ses attributs sont exécutés en séquence. Si l’un des attributs renvoie la valeur faux, l’action de suspension du point d’arrêt est invalidée et l’exécution du programme reprend. Différents attributs sont disponible par défaut dans le débogueur :
ttl la durée de vie d’un point d’arrêt. À chaque passage sur le point d’arrêt, sa durée est dé-crémentée. Lorsqu’elle atteint zéro, l’attribut commande au débogueur de supprimer le point d’arrêt et renvoie la valeur faux pour reprendre l’exécution.
footprint cet attribut permet d’afficher un message à chaque fois qu’un point d’arrêt est atteint avant de reprendre aussitôt l’exécution. C’est un moyen de tracer l’exécution sans avoir à insérer de print et recompiler le programme.
thread cet attribut active le point d’arrêt seulement si le thread qui a déclenché la suspen-sion correspond à l’argument associé à l’attribut.
trace cet attribut enregistre le nom de la fonction se trouvant en sommet de pile avant de relancer l’exécution. Il peut être utilisé avec des points d’arrêt sur entrée de fonction pour obtenir la liste des fonctions appelées dans une partie particulière du programme. Ce type de trace est lent mais néanmoins utile dans certains cas.
emacs cet attribut est utilisé lorsque le débogueur est démarré depuis Emacs. Lorsqu’un point d’arrêt est atteint, l’attribut envoie un code spécial sur la sortie standard pour forcer l’éditeur à actualiser ses fenêtres de code source. Comme cet attribut a une vocation uniquement informative, il renvoie toujours vrai.
L’attribut custom est un attribut générique qui permet d’exécuter une fonction per-sonnelle, dans le but de modéliser des comportements supplémentaires. Par exemple, il est possible de rendre effectif un point d’arrêt après un certain nombre de passages de la manière suivante : (bugloo) (bp add foo bar :custom ,(let ((n 10)) (lambda () (or (<= 0 n) (begin (set! n (- n 1)) #f) )))).
Les implanteurs de langages peuvent créer leur propres attributs additionnels à l’aide de l’API de programmation de Bugloo. Ce type d’approche est beaucoup plus efficace que celle employée dans les débogueurs traditionnels car elle est extensible et elle est applicable
n’importe quel type de point d’arrêt. De plus, elle multiplie les types de points d’arrêt que peut construire l’utilisateur, plutôt que de le limiter à un nombre prédéfini de combinaisons, comme cela est le cas dans les débogueurs traditionnels.
Hormis le mécanisme de sauvegarde d’historique, le débogueur ne prévoit pas de moyen direct de sauver la liste des points d’arrêt présents dans le débogueur pour les réutiliser dans une session ultérieure. Toutefois, l’utilisateur peut définir sa propre fonction. Cette fonction peut utiliser l’API de programmation de Bugloo pour retrouver la liste des points d’arrêt et en sauver une représentation sur disque. Dans ce cas, c’est à lui de s’assurer de la validité des valeurs sauvées. En particulier, la fonction associée à l’attribut custom ne peut pas être sauvegardée car, lors de l’appel à la commande bp, la forme symbolique de cette fonction n’est pas conservée et ne peux donc plus être recréée.

Affichage virtuel pour les langages de haut-niveau

La compilation des langages de haut niveau vers des plates-formes généralistes produit en général diverse structures de données intermédiaires nécessite souvent un encodage des identificateurs présents dans le code source. Pour déboguer de tels programmes, il faut savoir interpréter le contenu des informations de débogage et prendre en considération les spécificités des langages de programmation durant l’inspection. La suite de cette section décrit les différents mécanismes mis au point dans Bugloo pour y parvenir, quel que soit le ou les langages de programmation utilisés dans le programme à déboguer.

Le système d’informations de lignes en strates

La compilation des langages de haut niveau ne produit pas forcément directement du langage machine pour la plateforme d’exécution. Dans de nombreux cas, la compilation produit un code équivalent au code original mais dans un langage de plus bas niveau, proche de la plateforme d’exécution. Cela permet de déléguer la phase de production de code natif à un compilateur déjà existant. Dans le cas de la JVM, le langage intermédiaire est souvent Java.
L’inconvénient de la compilation intermédiaire est que les informations de lignes pré-sentes dans l’exécutable final décrivent le code source intermédiaire au lieu du code original. Si certains langages intermédiaires permettent de contourner cette limitation (par exemple, pour le langage C, à l’aide de la directive #line du pré-processeur), cela reste impossible en utilisant le langage Java.

Le rapport de stage ou le pfe est un document d’analyse, de synthèse et d’évaluation de votre apprentissage, c’est pour cela chatpfe.com propose le téléchargement des modèles complet de projet de fin d’étude, rapport de stage, mémoire, pfe, thèse, pour connaître la méthodologie à avoir et savoir comment construire les parties d’un projet de fin d’étude.

Table des matières

1 Introduction 
1.1 Présentation du débogage
1.2 Les débogueurs symboliques et leurs limitations
1.2.1 Les débogueurs de programmes interprétés
1.2.2 Les débogueurs de programmes compilés
1.2.3 Les limitations des modèles de débogueurs actuels
1.2.4 Le problème du débogage des langages de haut niveau
1.3 Vers un meilleur débogage symbolique
1.3.1 L’opportunité de la machine virtuelle Java
1.3.2 Synthèse de la contribution de ces travaux
1.3.3 Le contexte de développement
1.4 Organisation de ce document
2 État de l’art 
2.1 Le débogage par analyses statiques
2.1.1 Outils et analyses classiques
2.1.2 Assertions et contrats pour le débogage
2.2 Le débogage durant l’exécution
2.2.1 Débogage pour environnements interprétés
2.2.2 Débogage pour environnements compilés
2.2.3 Évolution des débogueurs symboliques
2.2.4 Débogage par analyse de traces
2.2.5 À la frontière du débogage : le profilage
2.2.6 Autres approches de débogage à l’exécution
2.3 Approche retenue dans ces travaux
I Un débogueur générique 
3 Le débogueur Bugloo 
3.1 Présentation du débogueur
3.1.1 L’environnement de débogage Bugloo
3.1.2 L’inspecteur structurel
3.2 Contrôle par le langage de commande
3.3 Instrumentation du flot de contrôle
3.4 Affichage virtuel pour les langages de haut-niveau
3.4.1 Le système d’informations de lignes en strates
3.4.2 Mécanismes génériques d’inspection du programme
3.5 Implantation
3.5.1 APIs de débogage de la Machine Virtuelle Java
3.5.2 L’architecture du débogueur Bugloo
3.5.3 Traitement des événements provenant du débogué
3.5.4 Extensibilité grâce aux appels de fonctions distants
3.6 Performances du débogueur et pénalités à l’exécution
3.7 Travaux reliés
3.8 Conclusion
4 Filtrage de la pile d’exécution 
4.1 Le problème des blocs d’activation synthétiques
4.1.1 Nature des blocs d’activations synthétiques
4.1.2 Solution pour expurger la pile d’exécution
4.2 Détection de bloc d’activation
4.2.1 Définitions d’un bloc d’activation
4.2.2 Les filtres de bloc
4.2.3 Support des références dans les filtres
4.3 Détection de motifs de blocs d’activation dans la pile
4.3.1 Le langage de motif Omega
4.3.2 Exemples de syntaxe concrète
4.3.3 Les motifs de pile comme condition d’arrêt
4.4 Construction d’une vue virtuelle de la pile d’exécution
4.4.1 Principes de la construction de la vue virtuelle
4.4.2 L’algorithme de construction de la vue virtuelle
4.4.3 Exemple de construction de vue virtuelle
4.4.4 Syntaxe concrète
4.5 Contrôle de l’exécution pas-à-pas
4.5.1 Mécanismes de contrôle de l’exécution
4.5.2 Syntaxe concrète et exemple d’utilisation
4.6 Implantation dans Bugloo
4.6.1 Implantation de l’inspecteur de pile d’exécution
4.6.2 Performances de la construction de la vue virtuelle
4.6.3 Support du filtrage de l’exécution pas-à-pas
4.7 Travaux reliés
4.7.1 environnement de débogage pour langages de haut niveau
4.7.2 Origine des techniques d’abstraction de pile
4.8 Conclusion
5 Débogage de l’allocation mémoire 
5.1 Plateformes d’exécution utilisant des GCs
5.1.1 Rappel sur le fonctionnement d’un GC
5.1.2 Débogage mémoire pour les architectures à GC
5.1.3 Organisation de ce chapitre
5.2 L’inspecteur d’allocations
5.2.1 Principe de fonctionnement
5.2.2 Inspecter les objets de types non-natifs
5.2.3 Inspecter l’allocation d’une partie du programme
5.2.4 Site d’allocation d’un objet
5.3 Inspecter les références entre les objets du tas
5.3.1 L’inspecteur graphique de tas
5.3.2 Inspection des références des objets du tas
5.4 Exemple de débogage mémoire
5.5 Le profileur mémoire
5.5.1 Principe de fonctionnement du profileur BMem
5.5.2 Profilage des langages de haut niveau
5.5.3 Attribution des allocations des fonctions synthétiques
5.5.4 L’algorithme de construction de la trace virtuelle
5.5.5 Exemples de filtrages de pile
5.5.6 Retarder l’attribution des allocations
5.6 Exemple d’utilisation : profilage du GZip Bigloo
5.7 Implantation des fonctionnalités de débogage mémoire
5.7.1 Principes d’instrumentation
5.7.2 Inspection du tas et site d’allocation
5.7.3 Le profileur mémoire
5.8 Travaux reliés
5.9 Conclusion
II Spécialiser le débogueur pour les langages de haut niveau 
6 Filtrage de la pile pour le langage Bigloo 
6.1 Extensions des fonctionnalités du débogueur
6.1.1 Utilisation de l’interprète embarqué
6.1.2 Points d’arrêt supplémentaire pour Scheme
6.1.3 Modules de décodage et d’affichage
6.1.4 Exécution pas-à-pas par caractère
6.2 Vue virtuelle pour les programmes Bigloo
6.2.1 Exemple de filtre trivial : le démarrage du programme
6.2.2 Masquer une fonctionnalité du système : les appels d’ordre supérieur
6.2.3 Masquer une fonctionnalité de bibliothèque : les fonctions génériques
6.2.4 Règles complexes : masquer l’implantation des exceptions
6.3 Filtrage de l’exécution pas-à-pas
6.3.1 Filtrage simple
6.3.2 Filtrage par sauts virtuels
6.4 Filtrage avancé : masquer l’interprète Bigloo
6.4.1 Principe de fonctionnement de l’interprète Bigloo
6.4.2 Remplacer les blocs d’activation de l’interprète dans la vue virtuelle
6.4.3 Règle complexe : Masquer les appels de fonctions interprétées
6.4.4 Masquer l’évaluation des macros dans l’interprète
6.5 Rapport d’expérience sur l’implantation
6.5.1 Les blocs d’activation virtuels de Bigloo
6.5.2 Masquer l’évaluateur
6.5.3 Présence des informations de débogage
6.5.4 Adaptations apportées à l’interprète Bigloo
6.6 Conclusion
7 Expérimentations sur d’autres langages 
7.1 Extension de débogage pour l’environnement Rhino
7.1.1 Prise en charge de la pile d’exécution
7.1.2 L’inspection des variables locales
7.1.3 Conclusion de la prise en charge du langage
7.2 Extension de débogage pour l’environnement Jython
7.2.1 Gestion de la pile d’exécution Jython
7.2.2 Gestion des variables de Jython
7.3 Exemple de débogage d’un programme multi-langages
7.3.1 Blocs d’activation virtuels pour Rhino
7.3.2 Blocs d’activation virtuels pour Jython
7.4 Expérience acquise avec les règles de remplacement
7.5 Conclusion
8 Étendre Bugloo : le débogage des Fair Threads 
8.1 La programmation concurrente et le débogage
8.1.1 Ordonnancement préemptif
8.1.2 Ordonnancement coopératif
8.2 Le modèle de programmation des Fair Threads
8.3 Le modèle de programmation des Fair Threads
8.3.1 Principe d’ordonnancement des Fair Threads
8.3.2 Utilisation des Fair Threads
8.3.3 L’API des Fair Threads
8.3.4 Bogues rencontrés dans le modèle des Fair Threads
8.4 Débogage des Fair Threads
8.4.1 La boîte à outils de débogage des Fair Threads
8.5 Inspecteurs spécialisés pour les Fair Threads
8.5.1 Vues pour l’ordonnanceur
8.5.2 Vue pour un thread
8.5.3 Vue pour un signal
8.6 Tracer les événements de l’ordonnanceur
8.7 Utilisation des outils de débogage
8.7.1 L’architecture de gestion d’événement dans Bugloo
8.7.2 Débogage du débogueur
8.8 Expérience pratique
8.8.1 Implantation dans le débogueur
8.8.2 Bénéfices et limitations des outils développés
8.9 Conclusion
9 Conclusion 
A Règles de remplacement pour Bigloo 

Télécharger le rapport complet

Télécharger aussi :

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *