# Introduction

Ce didacticiel est destiné aux nouveaux utilisateurs de la Simple Virtual Machine.

Dans ce didacticiel, vous allez piloter le débugueur de la machine depuis le code.

Le temps de lecture de ce didacticiel est estimé à 20 minutes si les didacticiels numéros 2 et 3 ont été lus auparavant.

# Mise en place

Pour ce didacticiel, nous utiliserons l'application ci-dessous mise dans le fichier exécutable debugueur.svm :

    #!/usr/bin/env svm
    DESCRIPTION
    Debugger control
    END
    LOG
    DEBUG "Debugger" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmint.so"
    ARGUMENT INT nb
    PROCESS "debugger"
    	CODE "main" INLINE
    		:shift &nb
    		:memory INT/i, INT*@&nb/t
    		1 -> &i
    		1 -> &t
    	:label compute
    		:int.mul @((t/@&i)-1) @&i -> (t/@&i)
    		:shift &i
    		:goto compute :when @&i IN t
    		1 -> &i
    	:label display
    		:com.message @(t/@&i)
    		:shift &i
    		:goto display :when @&i IN t
    	END
    	MEMORY nb
    END

# Points d'arrêt simples

## Point d'arrêt anonyme

Comme pour le didacticiel 3, ajoutez à cette application un point d'arrêt au début du code de l'application :

    #!/usr/bin/env svm
    DESCRIPTION
    Debugger control
    END
    LOG
    DEBUG "Debugger" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmint.so"
    ARGUMENT INT nb
    PROCESS "debugger"
    	CODE "main" INLINE
    		:debug BREAK
    		:shift &nb
    		:memory INT/i, INT*@&nb/t
    		1 -> &i
    		1 -> &t
    	:label compute
    		:int.mul @((t/@&i)-1) @&i -> (t/@&i)
    		:shift &i
    		:goto compute :when @&i IN t
    		1 -> &i
    	:label display
    		:com.message @(t/@&i)
    		:shift &i
    		:goto display :when @&i IN t
    	END
    	MEMORY nb
    END

Maintenant, lancez l'application avec le débugueur :

    ./debugueur.svm -d 8080 10

Dans la fenêtre des points d'arrêts, on trouve :

Debugger
Breakpoint list

    Breakpoint:
    At <main:1/0>

Nous avons déjà vu ce mécanisme à l'œuvre dans le didacticiel numéro 3 sans aucune explication.

En réalité, lorsque la machine est lancée en mode débugueur, elle ne s'arrête pas automatiquement avant la première instruction car cela n'est pas toujours souhaitable.

Il faut donc indiquer à la machine où arrêter l'exécution du code. Pour cela, il existe une instruction dédiée : `:debug`

La forme la plus simple de point d'arrêt est obtenue en ajoutant le paramètre `BREAK` comme ci-dessus. Cela indique au processeur de s'arrêter (uniquement lorsque le débugueur est actif) sur cette instruction, et d'afficher ce point d'arrêt dans l'interface utilisateur.

## Point d'arrêt nommé

Grâce à ce mécanisme, il est possible de placer un point d'arrêt n'importe où dans le code. En particulier, sur la portion de code dont l'exécution doit être surveillée de près.

Enlevez le point d'arrêt précédent, et placez un autre point d'arrêt comme indiqué ci-dessous :

    #!/usr/bin/env svm
    DESCRIPTION
    Debugger control
    END
    LOG
    DEBUG "Debugger" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmint.so"
    ARGUMENT INT nb
    PROCESS "debugger"
    	CODE "main" INLINE
    		:shift &nb
    		:memory INT/i, INT*@&nb/t
    		1 -> &i
    		1 -> &t
    	:label compute
    		:int.mul @((t/@&i)-1) @&i -> (t/@&i)
    		:shift &i
    		:goto compute :when @&i IN t
    		:debug BREAK "display"
    		1 -> &i
    	:label display
    		:com.message @(t/@&i)
    		:shift &i
    		:goto display :when @&i IN t
    	END
    	MEMORY nb
    END

Puis, relancez l'application avec le débugueur :

    ./debugueur.svm -d 8080 10

Dans la fenêtre des points d'arrêts, on trouve maintenant :

Debugger
Breakpoint list

    Breakpoint:
    At <main:1/7>
    display

Cette fois, le point d'arrêt affiché contient le texte "display", ce qui est fort utile pour déterminer quel point d'arrêt a été atteint lorsque plusieurs ont été définis.

Ouvrez maintenant le processeur, le code et la mémoire :

Debugger
Memory - K main - P debugger

Address | Type | Value
--- | --- | ---
&0 | INT | 11
&1 | INT | 11
&2 | INT | 1
&3 | INT | 1
&4 | INT | 2
&5 | INT | 6
&6 | INT | 24
&7 | INT | 120
&8 | INT | 720
&9 | INT | 5040
&10 | INT | 40320
&11 | INT | 362880
&12 | INT | 3628800

Alias | Pointer
--- | ---
i | &1*1
nb | &0*1
t | &2*11

Address:
Display
Aliases
P
Focus
Back
Clear
Processor - K main - P debugger
State:
Next instruction: <main:1/8>
Current instruction: <main:1/7>
Code
Current memory: &0*0
Allocated memory: &1*12
Defined aliases: i t
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
Return stack:
Global interruptions:
Waiting interruptions:
Code main - K main - P debugger

    :shift &nb
    :memory INT/i , INT*@&nb/t
    1 -> &i
    1 -> &t
    :label compute
    :int.mul @((t/@&i)-1) @&i -> (t/@&i)
    :shift &i
    :goto compute :when @&i IN t
    :debug BREAK "display"
    1 -> &i
    :label display
    :com.message @(t/@&i)
    :shift &i
    :goto display :when @&i IN t

Auto-scroll to
Current
with
above
Display

L'exécution s'est bien arrêtée au milieu du code, avec en mémoire les valeurs calculées lors de la première partie du code. La seconde partie du code peut immédiatement être exécutée pas à pas !

L'exécution du code peut être arrêtée en insérant l'instruction `:debug BREAK` au point voulu dans le code.

Les points d'arrêt peuvent être :

- anonymes,
- nommés en ajoutant le nom entre deux double-quotes.

# Une première investigation

Lancez l'application sans le mode débugueur avec zéro en argument :

    ./debugueur.svm 0

Et là, le résultat n'est pas celui attendu :

    ### Simple Virtual Machine 1234 : PROCESS debugger | 2023-01-01 00:00:00 GMT ########################################################################
    Kernel interrupted: @(main, line 6) Interruption MEMORY not handled: Index 1 is outside pointer &2*1.

    Core dump:
    Kernel:
    State: I, transmit_interruption, interrupted @(main, line 6) Interruption MEMORY not handled: Index 1 is outside pointer &2*1.

    Processor:
     State:
       Next instruction: <main:1/5> (Current one: <main:1/4>)
       Current memory  : &0*0
       Allocated memory: &1*2
       Aliases         : i t
       Flags           :
       Cascaded flags  :
       Local interruption handlers:
       Cascaded local interruption handlers:
     Saved states:
     Global interruption handlers:

    Code at <main:1/4>:
         :shift &nb # <0> @(main, line 1)
         :memory INT/i , INT*@&nb/t # <1> @(main, line 2)
         1 -> &i    # <2> @(main, line 3)
         1 -> &t    # <3> @(main, line 4)
      :label compute
    ====== HERE ======
         :int.mul @((t/@&i)-1) @&i -> (t/@&i)       # <4> @(main, line 6)
    ==================
         :shift &i  # <5> @(main, line 7)
         :goto compute :when @&i IN t       # <6> @(main, line 8)
         :debug BREAK "display"     # <7> @(main, line 9)
         1 -> &i    # <8> @(main, line 10)
      :label display
         :com.message @(t/@&i)      # <9> @(main, line 12)
         :shift &i  # <10> @(main, line 13)
         :goto display :when @&i IN t       # <11> @(main, line 14)
     Labels:
      compute -> <4>
      display -> <9>
     Symbols:

    Memory:
      &0: INT 1
      &1: INT 1
      &2: INT 1
     Aliases:
      i -> &1*1
      nb -> &0*1
      t -> &2*1
     Free space:

    ### Simple Virtual Machine 1234 : SYSTEM | 2023-01-01 00:00:00 GMT ##################################################################################
    Process debugger interrupted: @(main, line 6) Interruption MEMORY not handled: Index 1 is outside pointer &2*1.

Avec ce résultat, on a déjà :

1. la cause exacte de l'erreur,
2. la ligne exacte où l'erreur est apparue,
3. le contexte dans lequel l'erreur s'est produite.

Cependant, sur la ligne, trois valeurs peuvent avoir provoqué l'erreur. Pour déterminer celle qui ne s'évalue pas correctement, enlevez le point d'arrêt précédent, et ajoutez :

    #!/usr/bin/env svm
    DESCRIPTION
    Debugger control
    END
    LOG
    DEBUG "Debugger" STYLE "default"
    PLUGIN "svmcom.so"
    PLUGIN "svmint.so"
    ARGUMENT INT nb
    PROCESS "debugger"
    	CODE "main" INLINE
    		:shift &nb
    		:memory INT/i, INT*@&nb/t
    		1 -> &i
    		1 -> &t
    	:label compute
    		:debug EXPLAIN @((t/@&i)-1)
    		:debug EXPLAIN @&i
    		:debug EXPLAIN (t/@&i)
    		:int.mul @((t/@&i)-1) @&i -> (t/@&i)
    		:shift &i
    		:goto compute :when @&i IN t
    		1 -> &i
    	:label display
    		:com.message @(t/@&i)
    		:shift &i
    		:goto display :when @&i IN t
    	END
    	MEMORY nb
    END

Lancez maintenant l'application avec le débugueur :

    ./debugueur.svm -d 8080 0

Dans l'interface du débugueur, après avoir passé les trois points d'arrêt, vous devez obtenir :

Debugger
Breakpoint list

    Explain:
    At <main:1/4>
    Value @((t/@&i)-1)
    @(( t => &2*1 /@&i)-1)
    @((&2*1/@& i => &1*1 )-1)
    @((&2*1/@ &&1*1 => &1 )-1)
    @((&2*1/ @&1 => 1 )-1)
    @( (&2*1/1) => ... -1)
    Runtime error (MEMORY): Index 1 is outside pointer &2*1.
    Explain:
    At <main:1/5>
    Value @&i
    @& i => &1*1
    @ &&1*1 => &1
    @&1 => 1
    1
    Explain:
    At <main:1/6>
    Address (t/@&i)
    ( t => &2*1 /@&i)
    (&2*1/@& i => &1*1 )
    (&2*1/@ &&1*1 => &1 )
    (&2*1/ @&1 => 1 )
    (&2*1/1) => ...
    Runtime error (MEMORY): Index 1 is outside pointer &2*1.

L'expression `(t/@&i)` semble être la cause de l'erreur. Dans ce didacticiel, nous ne chercherons pas à corriger cette erreur.

A la place, nous vous invitons à relancer l'application débugueur activé avec une autre valeur que zéro, et de suivre l'exécution au travers des valeurs expliquées.

Les points d'arrêt `:debug EXPLAIN` permettent de détailler le calcul de valeurs (ou d'adresses). Le détail du calcul est présenté par la succession de substitutions faites :

- dans l'ordre de l'évaluation des différents opérateurs,
- dans le contexte où se trouve l'instruction `:debug EXPLAIN`.

# Conclusion

Vous venez de voir les interactions basiques avec le débugueur depuis le code de l'application.

Ces interactions permettent de placer des points d'arrêt qui focalisent l'attention sur une portion particulière du code de l'application ou sur des valeurs spéciales calculées.

En outre, ces points d'arrêt perdurent d'une exécution à l'autre (contrairement à ceux placés via l'interface du débugueur) facilitant la rejouabilité d'une investigation sans pour autant gêner l'exécution hors débugueur.