• Aucun résultat trouvé

Construction d’une vue virtuelle de la pile d’exécution

d’activation contenus dans la pile d’exécution. Cette section introduit la notion de vue virtuelle de pile, qui consiste à construire une représentation de la pile d’exécution dans laquelle les informations non désirables résultant de la compilation des langages de haut niveau sont cachées. La méthode développée permet de visualiser différentes représentations de code disjointes (comme du code natif et du code interprété) dans une seule et unique pile virtuelle. Cette section présente tout d’abord les principes de la vue virtuelle. Puis, elle décrit l’algorithme permettant sa construction et un exemple d’utilisation.

4.4.1 Principes de la construction de la vue virtuelle

La construction d’une vue virtuelle de pile s’effectue en extrayant de l’information sé- mantique de la pile d’exécution brute : on utilise un motif de pile pour localiser un groupe de blocs d’activation correspondant à un motif de code connu produit par un compilateur. Ce groupe est remplacé dans la vue virtuelle par un ou plusieurs blocs représentant uni- quement des fonctions « logiques », c’est-à-dire des fonctions définies par l’utilisateur dans le code source. En itérant ce processus sur l’ensemble de la pile brute, on obtient une vue virtuelle de la pile originale où tous les détails non désirables issus de la compilation ont été masqués et où l’information perdue lors de la compilation a pu être reconstruite. Selon sa nature, un motif de bloc d’activation peut être :

2

Le double anti-slash dans le second filtre provient de la notation C pour écrire un anti-slash dans une chaîne de caractères.

4.4. CONSTRUCTION D’UNE VUE VIRTUELLE DE LA PILE D’EXÉCUTION – caché dans la vue virtuelle, au cas où les blocs d’activation représentent uniquement

des appels de fonctions intermédiaires produits par le compilateur ;

– remplacé par un ou plusieurs blocs d’activation détectés par ce motif. Par exemple, quand un appel de fonction utilisateur est précédé d’un appel de fonction engendré par le compilateur, seul l’appel utilisateur est conservé dans la vue virtuelle ;

– remplacé par un ou plusieurs « blocs virtuels » qui n’existent pas dans la pile originale. Par exemple, si le groupe de blocs d’activation détecté représente l’interprète du langage, il doit être remplacé par un bloc virtuel qui représente la véritable fonction utilisateur en train d’être évaluée par l’interprète.

La construction de la vue virtuelle de pile est une technique générique. Ainsi, pour pouvoir masquer les détails de compilation d’un langage particulier, les implanteurs du langage ont seulement besoin de fournir un ensemble de règles de substitution, une règle étant composée d’un motif de pile chargé de détecter un motif de code synthétique et d’une action à effectuer pour remplacer les blocs d’activation détectés.

4.4.2 L’algorithme de construction de la vue virtuelle

virtualT : [Frame] × [Rule] × Subst−→[Frame]

Subst: [Frame]−→[Frame]−→[Frame]

virtualT(∅, R, S) = ∅

match_stack(f1. . .fn, ω, ∅) = hfalse, F, F′i

virtualT(f1. . .fn, hπ, ω, λρ, λσi.R, S) = virtualT(f1. . .fn, R, S)

virtualT(f1. . .fn, ∅, S) = pending(S, f1).virtualT(f2. . .fn, T, ∅)

match_stack(hf1. . .fn, ω, ∅i) = htrue, f1. . .fi, fi+1. . .fni λρ(f1. . .fi) = g1. . .gj

virtualT(f1. . .fn, hπ, ω, λρ, λσi.R, S) = pending(S, g1. . .gj).virtualT(fi+1. . .fn, R, ∅)

match_stack(hf1. . .fn, ω, ∅i) = htrue, f1. . .fi, fi+1. . .fni λρ(f1. . .fi) = ∅

virtualT(f1. . .fn, hπ, ω, λρ, λσi.R, S) = virtualT(fi+1. . .fn, R, λσ(f1. . .fi).S)

avec pending(S, f1. . .fn) = ( f1. . .fn si S = ∅; pending(S′, σ(f 1. . .fn)) si S ≡ σ.S′ et σ : [Frame]−→[Frame]

Fig. 4.4: l’algorithme de construction de la vue virtuelle

La figure4.4présente l’algorithme complet de construction de vue virtuelle. La fonction

virtualT est la transformation qui retourne une vue virtuelle de la pile pour un ensemble

donné de règles de remplacement T . L’algorithme est découpé en cinq cas qui représentent les différentes itérations possibles durant la construction. La partie au dessus de la barre horizontale représente les conditions devant être réalisées pour qu’une transition soit choi- sie pour l’itération suivante. La partie au dessous de la barre représente le résultat de la prochaine itération. Une règle de remplacement est un quadruplet de la forme :

CHAPITRE 4. FILTRAGE DE LA PILE D’EXÉCUTION

L’élément π est un entier qui représente la priorité de la règle de remplacement r dans l’ensemble T , au cas où plusieurs règles seraient applicables à un endroit donné de la pile brute. Dans ce cas, la règle choisie pour le remplacement serait celle qui a l’entier le plus petit. La valeur π est arbitraire : elle est fixée par l’implanteur du langage en fonction de la complexité des règles qu’il a définies.

L’élément ω est un motif de pile, il définit la portion de pile que la règle r peut remplacer.

L’élément λρest une fonction que l’on applique à l’ensemble des blocs d’activation détectés

par ω pour obtenir l’ensemble des blocs d’activation qui resteront dans la vue virtuelle. La construction de la vue virtuelle est un processus itératif qui commence au sommet de la pile et qui se déplace vers les blocs d’activation plus profonds. À chaque itération, les blocs accessibles depuis la position courante sont comparés à chaque motif ω définis dans T pour savoir s’il est possible d’appliquer un remplacement. Si c’est le cas, les blocs détectés par cette règle sont remplacés et la construction de la vue continue avec les blocs restants dans la pile. Durant la transformation, l’algorithme ne fait pas d’itération sur les blocs d’activation qui résultent d’un remplacement : la construction se poursuit à partir des blocs d’activation restant dans la pile brute. Si aucune règle n’est applicable pour une position donnée dans la pile brute, le bloc d’activation courant est considéré propre, il est conservé tel quel dans la vue virtuelle et la construction continue un bloc plus bas dans la pile.

L’élément λσ est appelé une substitution retardée. C’est un argument optionnel employé

lorsque la séquence de bloc détectée par la règle ne correspond pas à un bloc d’activation entier dans la vue virtuelle, mais seulement à un ou plusieurs attributs de blocs (typiquement l’information de ligne ou les variables locales). Dans ce cas, ces attributs sont « mémorisés » jusqu’à ce qu’un nouveau bloc d’activation soit ajouté à la vue virtuelle, puis ces attributs

sont reportés dans ce bloc. L’algorithme de la figure 4.4indique que la λσ est une fonction

prenant en paramètre la séquence de blocs d’activation détectée par ω et retournant une fermeture de type σ : [Frame]−→[Frame] qui a mémorisé les attributs devant être conservés. Chaque fois qu’un remplacement r est appliqué, sa substitution retardée est ajoutée à la liste des substitutions en attente dénotée S. Dès qu’un nouveau bloc est ajouté dans la vue virtuelle, la fonction pending applique toutes les substitutions en attente au bloc d’activation.

4.4.3 Exemple de construction de vue virtuelle

L’exemple de la figure 4.5illustre l’utilisation des différents types de remplacement. À

gauche de la figure se trouve la pile brute d’un programme qui a commencé son exécution dans une fonction main, qui a ensuite fait un appel d’ordre supérieur à la fonction foo et qui a atteint un point d’arrêt dans cette fonction à la ligne 7. Supposons qu’à cet endroit du programme, l’exécution se trouve dans un bloc de traitement d’exception. Dans cette pile, les fonctions n’ayant pas d’information de ligne représentent des fonctions synthétiques produites par le compilateur pour implanter le traitement des exceptions et les appels d’ordre supérieur. La compilation du bloc de traitement d’exception a produit la fonction synthétique __try_foo1.

Supposons que dans le but de masquer ces deux motifs de code, l’implanteur du langage

a déjà conçu une règle de remplacement r0 qui détecte les blocs d’activation 3 et 4 pour

masquer l’implantation des appels d’ordre supérieur. Il doit maintenant concevoir une règle

r1 telle que les trois premiers blocs d’activation par un bloc virtuel représentant la fonction

foo à la ligne 7. La règle ne doit pas rechercher le troisième bloc d’activation dans la

4.4. CONSTRUCTION D’UNE VUE VIRTUELLE DE LA PILE D’EXÉCUTION main __highcall foo __try __try_foo1 ligne 1 ligne 2 ligne 7 main __highcall foo ligne 1 ligne 2 main ligne 1 foo ligne 2 foo ligne 2 main ligne 1 r0 r1 σ: ligne 5 appliqueσ cadre propre appliqueλσ p il e d ’e x éc u ti o n b r u te v u e v ir tu e ll e

étape (i) étape (ii)

Fig. 4.5: exemple de substitution retardée

pile car cela empêcherait la règle r0 de s’appliquer3. Il est donc nécessaire de procéder en

plusieurs étapes. Tout d’abord, l’implanteur conçoit r1pour que seulement les deux premiers

blocs d’activation soient détectés et masqués et pour créer une substitution retardée σ qui mémorise l’information « ligne 7 ». Cela ne crée pas de bloc dans la vue virtuelle et termine

l’étape (i) dans le schéma. Ensuite la règle r0 remplace les blocs d’activation 3 et 4 par

une fonction foo localisée à la ligne 2, ce qui nous donne la vue virtuelle temporaire au milieu du schéma. À ce moment, la substitution σ peut être appliquée au bloc de la vue virtuelle pour corriger son information de ligne. Cela termine l’étape (ii). Enfin le dernier bloc d’activation dans la pile brute n’a pas besoin d’être transformé et apparaît donc tel quel dans la vue virtuelle, ce qui termine sa construction.

4.4.4 Syntaxe concrète

Dans la syntaxe concrète, une règle de remplacement est représentée par une liste de

trois à quatre éléments, selon que l’on se serve de λσ ou pas. À titre d’exemple, voici

comment on peut écrire une règle de priorité 10 masquant les occurrences de la fonction

foodans la pile si elles sont précédées de la fonction bar ou de la fonction gee :

(10

[("foo" any any any any) (| ("bar" any any any any)

("gee" any any any any))] mask)

La fonction mask est définie par (λ (frames) ’()). Cette règle ne contient pas de substitution retardée. Le chapitre suivant présentera des exemples concrets d’utilisation des règles de remplacement, ainsi que des utilisations des substitutions retardées.

3

car un bloc d’activation dans la pile brute ne peut être détecté qu’un seule fois durant la construction de la vue.

CHAPITRE 4. FILTRAGE DE LA PILE D’EXÉCUTION

4.5 Contrôle de l’exécution pas-à-pas

Le débogueur Bugloo permet d’effectuer trois types de sauts durant l’exécution pas- à-pas :

– le saut entrant avance d’un pas dans l’exécution. Si la prochaine instruction dans le code source est un appel de fonction, le saut emmène à l’intérieur de cette fonction. Sinon, il fait avancer d’une ligne dans le code source ;

– le saut de ligne avance jusqu’à la prochaine ligne dans le code source de manière inconditionnelle. Tous les appels se trouvant avant la prochaine ligne sont exécutés durant le saut ;

– le saut sortant permet de continuer l’exécution jusqu’à sortir de la fonction courante. Comme expliqué au début de ce chapitre, la compilation des langage de haut niveau introduit souvent dans le code utilisateur des appels intermédiaires à des fonctions de biblio- thèque ou des fonctions synthétiques qui nuisent au débogage durant l’exécution pas-à-pas. Par exemple, certaines implantations de langages manipulent les nombres entiers à travers des « boîtes » allouées en tas [JL91]. L’allocation de ces boîtes entraîne des appels à leurs constructeurs, ce qui perturbe l’exécution pas-à-pas du code utilisateur.

Pour éviter les arrêt superflus, les débogueurs traditionnels permettent de définir un ensemble de fichiers ou de bibliothèques dans lesquels l’exécution pas-à-pas ne doit pas

s’arrêter4. Or, pour tenir compte du fait que la compilation des langages de haut niveau

ajoute des fonctions synthétiques au niveau du code utilisateur, il est nécessaire d’adapter le mécanisme de filtrage de saut traditionnel :

– il faut permettre de filtrer les sauts « par fonction » et non plus par fichier ou par bibliothèque, afin d’isoler les fonctions synthétiques intermédiaires qui sont produites par la compilation d’un module de code utilisateur ;

– pour s’assurer qu’une fonction représente bien du code intermédiaire, il faut pouvoir consulter la pile afin de s’assurer que les fonctions en sommet de pile représentent bien un motif de code synthétique connu. Pour cela, on peut réutiliser le langage Omega

présenté dans la section 4.3.1 ;

– il faut mettre au point un mécanisme de contrôle spécifique pour éviter les sauts superflus. Par exemple, lorsqu’un saut provoque la sortie d’une fonction utilisateur et le retour dans une fonction synthétique, il faut pouvoir continuer l’exécution jusqu’à revenir dans une fonction utilisateur ;

– il faut pouvoir adapter le comportement de l’exécution pas-à-pas en fonction du contexte d’exécution, afin de fournir une exécution pas-à-pas correcte même en pré- sence d’évaluation de fonctions interprétées.

La suite de la section décrit les mécanismes mis en œuvre dans le débogueur pour contrôler l’exécution pas-à-pas en fonction des motifs de code présents en sommet de pile. 4.5.1 Mécanismes de contrôle de l’exécution

Contrôle de l’exécution pas-à-pas

Dans le but de masquer les sauts indésirables causés par la compilation complexe des langages, les implanteurs de langage doivent définir un ensemble de filtres, nommés sauts virtuels, qui spécifient les configurations de pile dans lesquelles l’exécution ne doit pas

4

Dans JVMTI, la granularité des sites est la classe ou le package JVM.

4.5. CONTRÔLE DE L’EXÉCUTION PAS-À-PAS