Introduction

Ce didacticiel est destiné aux utilisateurs sachant écrire un peu de code de la Simple Virtual Machine.

Dans ce didacticiel, vous allez exécuter des noyaux en séquence.

Le temps de lecture de ce didacticiel est estimé à 20 minutes.

Mise en place

Pour commencer, créez le canevas de l'application dans le fichier exécutable sequenceur.svm en utilisant ce code :

#!/usr/bin/env svm
DESCRIPTION
Sequencer exploration
END
LOG
DEBUG "Sequencer"
PLUGIN "svmcom.so"
PROCESS "application"
	CODE "main" INLINE
		:memory com.device/stdin, STR/source, LIB/library, STR/data
		:com.open com.terminal STDIN -> &stdin
		:com.read @&stdin com.all -> &source
		:library "library" @&source -> &library
		:call $(@&library/) data
		:goto no_value :unless &data INITIALISED
		:com.message @&data
		:shutdown
	:label no_value
		:com.message "No value"
	END
END

Plusieurs noyaux

Code modulaire

Commencez par lancer l'application :

cat << EOM | ./sequenceur.svm 
> :com.message "a"
> "b" -> &P
> :return
> EOM
a
b

cat << EOM | ./sequenceur.svm 
> :com.message "a"
> :return
> EOM
a
No value

Dans ces deux exécutions, le code entré sur le terminal "joue le jeu" et l'exécution globale de l'application se passe comme prévue.

Cependant, il est ici aisé de corrompre l'application :

cat << EOM | ./sequenceur.svm 
> :com.message "a"
> :shutdown 0
> :return
> EOM
a

cat << EOM | ./sequenceur.svm 
> :com.message "a"
> EOM
a

cat << EOM | ./sequenceur.svm 
> [ ] -> source
> [ ] -> library
> :return
> EOM
No value

Ici, nous pouvons faire deux remarques :

  1. Le fait de pouvoir compiler du code à la demande et l'exécuter permet d'écrire des applications dont une partie du comportement peut être fournie par l'utilisateur. Cela est terriblement pratique dans de grosses applications demandant une forte personnalisation, et il serait dommage de s'en passer à cause d'un problème de sécurité.
  2. Le fait même que cela soit possible nativement dans le langage de la machine virtuelle peut sembler étonnant, vu la gravité des problèmes rencontrés.

Résolution des problèmes rencontrés

La situation n'est pourtant pas désespérée : la machine virtuelle peut contenir plusieurs noyaux !

Et cela change tout, car le code compilé peut être exécuté sur un processeur différent et avec une mémoire différente.

Pour créér un nouveau noyau, l'extension "run" contient une instruction dédiée à la création de noyau en mode protégé, conçue pour résoudre ces problèmes de sécurité.

Ajoutez au code l'extension "run" et modifiez le code :

#!/usr/bin/env svm
DESCRIPTION
Sequencer exploration
END
LOG
DEBUG "Sequencer"
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
PROCESS "application"
	CODE "main" INLINE
		:memory com.device/stdin, STR/source, LIB/library, STR/data
		:com.open com.terminal STDIN -> &stdin
		:com.read @&stdin com.all -> &source
		:library "library" @&source -> &library
		:run.protected_call $(@&library/) data
		:goto no_value :unless &data INITIALISED
		:com.message @&data
		:shutdown
	:label no_value
		:com.message "No value"
	END
END

Et relancez l'application :

cat << EOM | ./sequenceur.svm 
> :com.message "a"
> "b" -> &P
> :return
> EOM
### Simple Virtual Machine 1234 : PROCESS application | 2023-01-01 00:00:00 GMT ######################################################################
Kernel interrupted: @(main, line 5) Interruption FAILURE not handled: Unable to attach a protected kernel to the current process

Core dump:
Kernel:
State: I, transmit_interruption, interrupted @(main, line 5) Interruption FAILURE not handled: Unable to attach a protected kernel to the current process

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

Code at <main:1/4>:
     :memory com.device/stdin , STR/source , LIB/library , STR/data     # <0> @(main, line 1)
     :com.open com.terminal STDIN -> &stdin     # <1> @(main, line 2) WAITING SYSTEM
     :com.read @&stdin com.all -> &source       # <2> @(main, line 3) WAITING
     :library "library" @&source -> &library    # <3> @(main, line 4)
====== HERE ======
     :run.protected_call $(@&library/) data     # <4> @(main, line 5)
==================
     :goto no_value :unless &data INITIALISED   # <5> @(main, line 6)
     :com.message @&data        # <6> @(main, line 7)
     :shutdown  # <7> @(main, line 8)
  :label no_value
     :com.message "No value"    # <8> @(main, line 10)
 Labels:
  no_value -> <8>
 Symbols:


Memory:
  &0: com.device Terminal STDIN
  &1: STR ":com.message \"a\"\n\"b\" -> &P\n:return\n"
  &2: LIB <library>
  &3: STR (sync)
 Aliases:
  data -> &3*1
  library -> &2*1
  source -> &1*1
  stdin -> &0*1
 Free space:



### Simple Virtual Machine 1234 : SYSTEM | 2023-01-01 00:00:00 GMT ###################################################################################
Process application interrupted: @(main, line 5) Interruption FAILURE not handled: Unable to attach a protected kernel to the current process

Et là, c'est le drame. Une erreur aussi incompréhensible qu'inattendue !

Comme deux noyaux ne peuvent (pour le moment) pas être exécutés simultanément, il faut impérativement les intégrer dans une séquence d'exécution, où chaque noyau est exécuté à son tour.

Séquenceur

La séquence d'exécution des noyaux est entièrement gérée par un nouveau concept : le séquenceur.

Un séquenceur intervient principalement lorsque le noyau en cours d'exécution est suspendu ou terminé. C'est à ce moment précis qu'il décide quel noyau prendra le relais pour poursuivre l'exécution de l'application, en fonction de sa politique de séquencement.

Et pour qu'un séquenceur puisse prendre en charge un noyau, il faut attacher ce noyau au séquenceur. Or, dans notre cas, le séquenceur par défaut de la machine virtuelle n'accepte qu'un seul noyau et refuse celui créé par l'instruction :run.protected_call.

Changement de séquenceur

Modifiez le code pour changer le séquenceur en celui fourni avec l'extension "run" (comme précisé dans l'aide de l'instruction :run.protected_call) :

#!/usr/bin/env svm
DESCRIPTION
Sequencer exploration
END
LOG
DEBUG "Sequencer"
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
PROCESS "application"
	CODE "main" INLINE
		:memory com.device/stdin, STR/source, LIB/library, STR/data
		:com.open com.terminal STDIN -> &stdin
		:com.read @&stdin com.all -> &source
		:library "library" @&source -> &library
		:run.protected_call $(@&library/) data
		:goto no_value :unless &data INITIALISED
		:com.message @&data
		:shutdown
	:label no_value
		:com.message "No value"
	END
	SEQUENCER run.stack
END

Relancez l'application exactement de la même manière :

cat << EOM | ./sequenceur.svm 
> :com.message "a"
> "b" -> &P
> :return
> EOM
a
b

Cela fonctionne à nouveau dans le cas nominal. Voyons maintenant dans les cas incorrects vu précédemment.

Instruction système

Relancez l'application avec l'instruction :shutdown 0 :

cat << EOM | ./sequenceur.svm 
> :com.message "a"
> :shutdown 0
> :return
> EOM
### Simple Virtual Machine 1234 : PROCESS application | 2023-01-01 00:00:00 GMT ######################################################################
Kernel interrupted: @(library, line 2, from @(main, line 4)) Interruption SECURITY not handled: System instruction :shutdown 0 execution attempt in protected mode.

Core dump:
Kernel:
State: I, transmit_interruption, last_return_is_shutdown, protected_mode, interrupted @(library, line 2, from @(main, line 4)) Interruption SECURITY not handled: System instruction :shutdown 0 execution attempt in protected mode.

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

Code at <library/1>:
     :com.message "a"   # <0> @(library, line 1, from @(main, line 4))
====== HERE ======
     :shutdown 0        # <1> @(library, line 2, from @(main, line 4)) SYSTEM
==================
     :return    # <2> @(library, line 3, from @(main, line 4))
 Labels:
 Symbols:


Memory:
  &0: STR (sync)
 Aliases:
 Free space:



### Simple Virtual Machine 1234 : PROCESS application | 2023-01-01 00:00:00 GMT ######################################################################
Kernel interrupted: @(library, line 2, from @(main, line 4)) Interruption SECURITY not handled: System instruction :shutdown 0 execution attempt in protected mode.

Core dump:
Kernel:
State: I, transmit_interruption, interrupted @(library, line 2, from @(main, line 4)) Interruption SECURITY not handled: System instruction :shutdown 0 execution attempt in protected mode.

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

Code at <main:1/4>:
     :memory com.device/stdin , STR/source , LIB/library , STR/data     # <0> @(main, line 1)
     :com.open com.terminal STDIN -> &stdin     # <1> @(main, line 2) WAITING SYSTEM
     :com.read @&stdin com.all -> &source       # <2> @(main, line 3) WAITING
     :library "library" @&source -> &library    # <3> @(main, line 4)
====== HERE ======
     :run.protected_call $(@&library/) data     # <4> @(main, line 5)
==================
     :goto no_value :unless &data INITIALISED   # <5> @(main, line 6)
     :com.message @&data        # <6> @(main, line 7)
     :shutdown  # <7> @(main, line 8)
  :label no_value
     :com.message "No value"    # <8> @(main, line 10)
 Labels:
  no_value -> <8>
 Symbols:


Memory:
  &0: com.device Terminal STDIN
  &1: STR ":com.message \"a\"\n:shutdown 0\n:return\n"
  &2: LIB <library>
  &3: STR (sync)
 Aliases:
  data -> &3*1
  library -> &2*1
  source -> &1*1
  stdin -> &0*1
 Free space:



### Simple Virtual Machine 1234 : SYSTEM | 2023-01-01 00:00:00 GMT ###################################################################################
Process application interrupted: @(library, line 2, from @(main, line 4)) Interruption SECURITY not handled: System instruction :shutdown 0 execution attempt in protected mode.

Cette erreur est intéressante à plusieurs titres :

Ajoutez un tel gestionnaire d'interruption :

#!/usr/bin/env svm
DESCRIPTION
Sequencer exploration
END
LOG
DEBUG "Sequencer"
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
PROCESS "application"
	CODE "main" INLINE
		:memory com.device/stdin, STR/source, LIB/library, STR/data
		:com.open com.terminal STDIN -> &stdin
		:com.read @&stdin com.all -> &source
		:library "library" @&source -> &library
		:interruption SECURITY error
		:run.protected_call $(@&library/) data
		:goto no_value :unless &data INITIALISED
		:com.message @&data
		:shutdown
	:label no_value
		:com.message "No value"
		:shutdown
	:label error
		:com.message "Error found"
		:shutdown
	END
	SEQUENCER run.stack
END

Puis relancez l'application à l'identique :

cat << EOM | ./sequenceur.svm 
> :com.message "a"
> :shutdown 0
> :return
> EOM
a
### Simple Virtual Machine 1234 : PROCESS application | 2023-01-01 00:00:00 GMT ######################################################################
Kernel interrupted: @(library, line 2, from @(main, line 4)) Interruption SECURITY not handled: System instruction :shutdown 0 execution attempt in protected mode.

Core dump:
Kernel:
State: I, transmit_interruption, last_return_is_shutdown, protected_mode, interrupted @(library, line 2, from @(main, line 4)) Interruption SECURITY not handled: System instruction :shutdown 0 execution attempt in protected mode.

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

Code at <library/1>:
     :com.message "a"   # <0> @(library, line 1, from @(main, line 4))
====== HERE ======
     :shutdown 0        # <1> @(library, line 2, from @(main, line 4)) SYSTEM
==================
     :return    # <2> @(library, line 3, from @(main, line 4))
 Labels:
 Symbols:


Memory:
  &0: STR (sync)
 Aliases:
 Free space:



Error found

Cette fois, l'exécution de l'application continue comme l'atteste la dernière ligne. Le premier noyau est toutefois toujours présent dans le résultat, car celui-ci se termine sur une interruption, ce qui est toujours soit une erreur importante dans l'application soit un événement notable comme ici.

Arrêt processeur implicite

Cette fois, lancez l'application avec le code sans instruction :return :

cat << EOM | ./sequenceur.svm 
> :com.message "a"
> EOM
a
No value

La présence du "No value" indique que malgré l'absence de retour de fonction, tout se passe bien. En effet, l'instruction d'arrêt du processeur implicitement jouée à la fin du code compilé arrête le noyau dans lequel le code compilé est exécuté. Celui qui exécute le code de l'application reprend l'exécution grâce au séquenceur run.stack.

Accès mémoire illicite

Relancez une dernière fois l'application avec le code tentant d'accéder aux alias "source" et "library" :

cat << EOM | ./sequenceur.svm 
> [ ] -> source
> [ ] -> library
> :return
> EOM
### Simple Virtual Machine 1234 : PROCESS application | 2023-01-01 00:00:00 GMT ######################################################################
Kernel interrupted: @(library, line 1, from @(main, line 4)) Interruption MEMORY not handled: Alias source is not linked to a pointer.

Core dump:
Kernel:
State: I, transmit_interruption, last_return_is_shutdown, protected_mode, interrupted @(library, line 1, from @(main, line 4)) Interruption MEMORY not handled: Alias source is not linked to a pointer.

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

Code at <library/0>:
====== HERE ======
     [  ] -> source     # <0> @(library, line 1, from @(main, line 4))
==================
     [  ] -> library    # <1> @(library, line 2, from @(main, line 4))
     :return    # <2> @(library, line 3, from @(main, line 4))
 Labels:
 Symbols:


Memory:
  &0: STR (sync)
 Aliases:
 Free space:



### Simple Virtual Machine 1234 : PROCESS application | 2023-01-01 00:00:00 GMT ######################################################################
Kernel interrupted: @(library, line 1, from @(main, line 4)) Interruption MEMORY not handled: Alias source is not linked to a pointer.

Core dump:
Kernel:
State: I, transmit_interruption, interrupted @(library, line 1, from @(main, line 4)) Interruption MEMORY not handled: Alias source is not linked to a pointer.

Processor:
 State:
   Next instruction: <main:1/6> (Current one: <main:1/5>)
   Current memory  : &0*0
   Allocated memory: &0*4
   Aliases         : data library source stdin
   Flags           :
   Cascaded flags  :
   Local interruption handlers:
    SECURITY => <main:1/11>
   Cascaded local interruption handlers:
 Saved states:
 Global interruption handlers:

Code at <main:1/5>:
     :memory com.device/stdin , STR/source , LIB/library , STR/data     # <0> @(main, line 1)
     :com.open com.terminal STDIN -> &stdin     # <1> @(main, line 2) WAITING SYSTEM
     :com.read @&stdin com.all -> &source       # <2> @(main, line 3) WAITING
     :library "library" @&source -> &library    # <3> @(main, line 4)
     :interruption SECURITY error       # <4> @(main, line 5)
====== HERE ======
     :run.protected_call $(@&library/) data     # <5> @(main, line 6)
==================
     :goto no_value :unless &data INITIALISED   # <6> @(main, line 7)
     :com.message @&data        # <7> @(main, line 8)
     :shutdown  # <8> @(main, line 9)
  :label no_value
     :com.message "No value"    # <9> @(main, line 11)
     :shutdown  # <10> @(main, line 12)
  :label error
     :com.message "Error found" # <11> @(main, line 14)
     :shutdown  # <12> @(main, line 15)
 Labels:
  error -> <11>
  no_value -> <9>
 Symbols:


Memory:
  &0: com.device Terminal STDIN
  &1: STR "[ ] -> source\n[ ] -> library\n:return\n"
  &2: LIB <library>
  &3: STR (sync)
 Aliases:
  data -> &3*1
  library -> &2*1
  source -> &1*1
  stdin -> &0*1
 Free space:



### Simple Virtual Machine 1234 : SYSTEM | 2023-01-01 00:00:00 GMT ###################################################################################
Process application interrupted: @(library, line 1, from @(main, line 4)) Interruption MEMORY not handled: Alias source is not linked to a pointer.

Ici, vous pouvez constater que les alias de l'application ne sont pas définis, et seule une adresse est disponible : celle voulue par l'application, qui sert de valeur d'échange entre les deux noyaux.

Ce mécanisme d'échange de valeur, dénoté par la mention "(sync)" sur les adresses mémoires n'est pas lié à la création d'un noyau mais au comportement de l'instruction :run.protected_call : cette instruction, pour simuler un appel de fontion, partage la zone mémoire des paramètres de la fonction entre les deux noyaux, ce qui active la synchronisation de l'adresse.

Débugueur

Il reste un point pratique à aborder en rapport avec le débugueur. Modifiez le code pour ajouter un point d'arrêt :

#!/usr/bin/env svm
DESCRIPTION
Sequencer exploration
END
LOG
DEBUG "Sequencer"
PLUGIN "svmcom.so"
PLUGIN "svmrun.so"
PROCESS "application"
	CODE "main" INLINE
		:debug BREAK
		:memory com.device/stdin, STR/source, LIB/library, STR/data
		:com.open com.terminal STDIN -> &stdin
		:com.read @&stdin com.all -> &source
		:library "library" @&source -> &library
		:interruption SECURITY error
		:run.protected_call $(@&library/) data
		:goto no_value :unless &data INITIALISED
		:com.message @&data
		:shutdown
	:label no_value
		:com.message "No value"
		:shutdown
	:label error
		:com.message "Error found"
		:shutdown
	END
	SEQUENCER run.stack
END

Et lancez l'application en mode débugueur pour l'exécuter pas à pas :

cat << EOM | ./sequenceur.svm -d 8080
> :com.message "a"
> "b" -> &P
> :return
> EOM

L'exécution pas à pas semble éviter le code compilé, qui s'exécute avec un autre processeur : le mode d'exécution dans le débugueur est lié au processeur courant.

Pour pouvoir aussi exécuter en mode pas à pas le code compilé, ajoutez aussi un point d'arrêt sur le code compilé :

cat << EOM | ./sequenceur.svm -d 8080
> :debug BREAK
> :com.message "a"
> "b" -> &P
> :return
> EOM

Dans le débugueur, on constate bien la présence des deux noyaux :

Sequencer
Processor - K main - P application
State:
Next instruction: <main:1/7>
Current instruction: <main:1/6>
Code
Current memory: &0*0
Allocated memory: &0*4
Defined aliases: data library source stdin
Local interruptions: SECURITY => <main:1/12>
Code
Cascaded local interruptions:
Flags:
Cascaded flags:
Return stack:
Global interruptions:
Waiting interruptions:
Code main - K main - P application
:debug BREAK
:memory com.device/stdin , STR/source , LIB/library , STR/data
:com.open com.terminal STDIN -> &stdin
:com.read @&stdin com.all -> &source
:library "library" @&source -> &library
:interruption SECURITY error
:run.protected_call $(@&library/) data
:goto no_value :unless &data INITIALISED
:com.message @&data
:shutdown
:label no_value

:com.message "No value"
:shutdown
:label error

:com.message "Error found"
:shutdown
Auto-scroll to
Current
with above
Display
Kernel list
PROCESS application - Kernel main (S)
PROCESS application - Kernel library (D)
Kernel library - P application
State: D, transmit_interruption, last_return_is_shutdown, protected_mode
Processor
Memory
Processor - K library - P application
State:
Next instruction: <library/1>
Current instruction: <library/0>
Code
Current memory: &0*1
Allocated memory: &0*1
Defined aliases:
Local interruptions:
Cascaded local interruptions:
Flags:
Cascaded flags:
Return stack:
Global interruptions:
Waiting interruptions:
Code library - K library - P application
:debug BREAK
:com.message "a"
"b" -> &P
:return
Auto-scroll to
Current
with above
Display

Dans la liste des noyaux, notez l'état des deux noyaux : celui de l'application est suspendu (en pause), et l'autre est en débogage.

Dans la fenêtre du noyau "library", les options du noyau indiquent clairement le mode protégé qui a empêché l'exécution de l'instruction système :shutdown 0 et l'option qui permet à une instruction :return d'arrêter le processeur sans interruption.

Conclusion

Vous venez de voir que la machine virtuelle peut contenir plusieurs noyaux exécutés en séquence.

La machine fournit un séquenceur minimaliste qui n'accepte qu'un noyau, souvent suffisant pour la majorité des applications.

Les extensions peuvent cependant proposer d'autres séquenceurs ayant des caractéristiques différentes, notamment la capacité à accepter plusieurs noyaux avec une politique de séquencement adaptée.

Même si l'emploi d'un séquenceur d'extension est plutôt rare, il ne faut jamais négliger cette possibilité qui donne des résultats impressionnants lorsqu'elle est bien utilisée.

Enfin, les instructions qui créent des noyaux précisent souvent un séquenceur qui est adapté à leur fonctionnement.