• Aucun résultat trouvé

Expérience acquise avec les règles de remplacement

la construction de la vue virtuelle, les règles de remplacement détectant ces deux types de motifs ne sont pas en conflit puisqu’elles terminent différemment.

En appliquant les différentes règles de remplacement présentées dans cette section, la

construction de la vue virtuelle retourne une représentation correcte de la pile logique2. Si

l’on ajoute à cela une exécution pas-à-pas adaptée aux bibliothèques d’exécution Rhino et Jython, cela permet au débogueur de retrouver toute son efficacité.

#0 (<anonymous:1003:multiscm.scm:4>) in file multiscm.scm:6

#1 gee(obj, obj) in file multipy.py (no line info...)

#2 foo() in file multijs.js:5

#3 <toplevel-init>() in file multijs.js:8

7.4 Expérience acquise avec les règles de remplacement

Les différentes expériences menées dans ces travaux ont montré que la manière d’extraire des informations de débogage en explorant la pile est très différente selon l’implantation du langage et son schéma de compilation. Par exemple, dans le cas de Bigloo, le compilateur transforme le code Scheme en des fonctions JVM de manière directe. Pour que la construc- tion des blocs d’activation virtuels fonctionne même lorsqu’une classe n’est pas compilée en mode débogage, le compilateur doit inclure suffisamment d’« indices » dans le nom des fonc- tions produites. Cette approche rend l’ensemble de règles de remplacement très dépendant de propriétés syntaxiques, ce qui peut être gênant en cas de modification du compilateur. A contrario, dans la plateforme Jython, le compilateur produit des informations de débogage ad-hoc conservées dans des champs de classes JVM. Ces informations sont systématique- ment produites et accessibles à l’aide de l’interprète du langage. Par conséquent, les règles de remplacement à concevoir sont moins complexes.

L’utilisation de langages complets comme Bigloo, Rhino et Jython a mis en évidence le fait que certaines règles de remplacement sont délicates à mettre en œuvre. Par exemple, pour masquer les motifs de code utilisant l’API de reflexivité Java, il faut coder « en dur » le nom de certains implanteurs de l’API (comme Sun ou IBM). De même, pour masquer les appels de fonctions Rhino, il ne faut pas utiliser la valeur des arguments pour ne pas dé- pendre des informations de débogage. Dans la pratique, du fait que la conception des règles de remplacement incombe aux personnes en charge du développement de ces compilateurs, la difficulté globale de mise en œuvre reste tout à fait raisonnable.

L’exemple de débogage multi-langage a montré que la construction de pile virtuelle reste effective en présence de plusieurs langages et donc de plusieurs ensembles de règles de remplacement. Par exemple, même si les plates-formes Jython et Rhino produisent du code similaire pour les appels de fonctions étrangères, le contexte de la détection de motif est suffisant pour éviter toute ambiguïté dans le choix du remplacement. Dans la pratique, ce risque d’ambiguïté est directement dépendant de la précision des règles de remplacement utilisées durant la session de débogage.

2

Il n’y a pas d’information de ligne dans le bloc 1 car à ce jour, le compilateur Jython ne la produit pas pour les fonctions compilées.

CHAPITRE 7. EXPÉRIMENTATIONS SUR D’AUTRES LANGAGES

7.5 Conclusion

Ce chapitre a présenté des exemples concrets d’utilisation des règles de remplacement sur deux langages de haut niveau : Rhino, un compilateur de programmes ECMAScript qui produit du code-octet JVM et Jython, une plateforme d’exécution de programmes Python pour la JVM. Le chapitre a aussi décrit en détail un exemple de débogage de programme multi-langages mêlant Bigloo, Rhino et Jython.

Les supports de débogage pour les plates-formes Rhino et Jython présentés dans ce chapitre ne sont pas encore complet. Il serait nécessaire de permettre la pose de points d’arrêt pour Jython, de prendre en charge les fonctions interprétés créés par l’évaluateur ou de fournir une exécution pas-à-pas correcte quel que soit le contexte d’exécution. Toute- fois, les expériences menées dans ce chapitre ont validées les mécanismes de représentations virtuelles développés dans Bugloo. Tout d’abord, elles ont montrées que ces mécanismes étaient applicables à différents types de langage de haut niveau et qu’ils étaient efficaces quelle que soit le schéma de compilation employé. De plus, elles ont montré que les mé- canismes développés pouvaient être utilisés sans problème pour déboguer des programmes multi-langages, ce qui était une exigence majeure énoncée au début de ce manuscrit.

Chapitre 8

Étendre Bugloo : le débogage des

Fair Threads

I

l existe deux grandes politiques d’ordonnancement de programmes concur-

rents : l’ordonnancement préemptif et le coopératif. Le premier est connu pour être difficile à déboguer car il est en général non déterministe, ce qui engendre des bogues complexes et très difficile à détecter de manière systématique. Le second est un modèle plus simple à débogueur.

Le langage Bigloo fournit une bibliothèque de programmation concurrente basée sur un ordonnancement déterministe et sur la programmation réactive synchrone. Ce chapitre présente une série d’outils développés pour permettre le débogage de cette bibliothèque. Il montre aussi comment utiliser l’API de Bugloo pour implanter ce genre d’extensions de débogage.

8.1 La programmation concurrente et le débogage

Les plates-formes d’exécution modernes permettent d’exécuter des programmes « multi- tâches » : au sein d’une application unique, il peut ainsi exister plusieurs flots d’exécution virtuels indépendant appelés threads. Ces derniers sont couramment utilisés dans les pro- grammes de nos jours.

La programmation concurrente est une tâche difficile : tout d’abord, parce qu’entremêler des flots d’exécution est une tâche intrinsèquement complexe ; ensuite, parce que les types de bogues causés par les programmes multi-tâches sont en général difficiles à prendre en charge avec des débogueurs traditionnels.

Il existe différents modèles de programmation concurrente. Les différentes approches peuvent être regroupées en deux grandes catégories : celles utilisant un ordonnancement préemptif et celles utilisant un ordonnancement coopératif. Chacune de ces catégories offre des avantages et des inconvénients du point de vue du débogage.

8.1.1 Ordonnancement préemptif

L’ordonnancement préemptif est apparu dans les systèmes d’exploitations à la fin des années 70 avec le modèle des PThreads [NBF96]. Cette approche s’est ensuite généralisée dans les langages de programmation au milieu des années 90. Dans les PThreads, la bi- bliothèque de thread (pouvant être le système d’exploitation sous-jacent) peut suspendre

CHAPITRE 8. ÉTENDRE BUGLOO : LE DÉBOGAGE DES FAIR THREADS

l’exécution d’un thread à tout moment pour en ordonnancer un autre. En général, ce genre de modèle tire parti des architectures multi-processeurs (SMP ou SMT). Malheureusement, les implantations de ce modèle induisent un ordonnancement non-déterministe, ce qui com- plique la conception des programmes et rend leur débogage « douloureux » :

– les bogues liés à la concurrence se produisent de manière aléatoire du fait du non- déterminisme de l’ordonnanceur. Le simple fait d’ajouter des prints dans le pro- gramme ou de le déboguer suffit souvent à faire disparaître un bogue. On appelle cela

un Heisenbug [Bou04], en référence au fameux principe d’incertitude de Heisenberg1 ;

– pour accéder à une ressource partagée, les threads acquièrent des verrous pour garantir une exclusion mutuelle entre eux. L’oubli d’une prise de verrou peut entraîner des corruptions mémoires, auquel cas le débogueur n’est plus d’aucune utilité ;

– l’ordonnancement des threads peut mener à un état de blocage collectif appelé verrou mortel. Cela peut se produire lorsque différents threads sont en concurrence pour l’acquisition d’un même verrou. La cause du bogue est difficile à comprendre sans connaître l’ordonnancement qui a mené au blocage ;

– les communications et les synchronisations entre threads s’effectuent par émission de notifications. Elles peuvent échouer si un thread n’est pas encore à l’écoute au moment de l’émission. Là encore, le débogueur n’est pas utile car il ne peut pas inspecter l’ordonnancement fautif ;

– certaines bibliothèques permettent de fixer les priorités d’ordonnancement des threads. Ces propriétés peuvent entraîner des problèmes d’inversion de priorités [SRL90] que les débogueurs n’aident pas à résoudre.

Certains modèles de programmation ne souffrent pas de tous les problèmes précédents. Par exemple, CML [Rep91] et Erlang [Arm97] restreignent la communication entre les threads à des envois de messages à travers des canaux. Ainsi, les corruptions mémoires sont évitées et les messages envoyés sont toujours reçus. En revanche, dans ces modèles, il n’y a plus réellement de mémoire partagée et la survenue de verrous mortels est toujours possible. L’idée d’employer des traces pour comprendre la cause d’un verrou mortel ou d’une cor- ruption mémoire n’est pas envisageable dans la pratique du fait de la nature trop aléatoire de la survenue de ce type de bogue. Les utilisateurs sont donc contraints d’utiliser des outils

basés sur des analyses statiques [Car97, FF00b,WTE05] pour détecter des sources poten-

tielles d’erreurs dans leur programme. Lorsque le programme le permet, ils peuvent utiliser des model checkers [HP98], des outils se servant de logiques temporelles et d’exploration d’espace d’état pour exhiber des séquences d’exécution menant à des verrous mortels. 8.1.2 Ordonnancement coopératif

L’ordonnancement coopératif est un modèle plus ancien, dans lequel c’est aux threads eux-même de coopérer, c’est-à-dire de rendre la main à l’ordonnanceur pour qu’un autre thread puisse s’exécuter. C’est un modèle déterministe dans lequel un seul thread s’exécute à la fois (ce qui empêche de profiter des multi-processeurs). Ce type d’ordonnancement élimine les deux sources de bogues les plus problématiques du modèle préemptif : les corruptions mémoires causés par des problèmes d’exclusions mutuelles et l’impossibilité de rejouer des exécutions à cause du non-déterminisme. De plus, il conserve la propriété de mémoire partagée.

1

Ce principe décrit l’impossibilité de mesurer à la fois la position d’une particule et sa vitesse de façon exacte.

8.2. LE MODÈLE DE PROGRAMMATION DES FAIR THREADS