Introduction

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

Dans ce didacticiel, vous allez rattrapper les erreurs levées par les instructions.

Le temps de lecture de ce didacticiel est estimé à 15 minutes si les fonctions ont été abordées.

Mise en place

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

#!/usr/bin/env svm
DESCRIPTION
Software interruptions example
END
LOG
DEBUG "Software interruptions" STYLE "default"
PLUGIN "svmcom.so"
PLUGIN "svmstr.so"
PROCESS "application"
	CODE "main" INLINE
		:memory INT/integer, STR/command
	:label wait
		:com.prompt "command" STR -> &command
		:str.join "command_" @&command -> &command
		:call @&command integer
		:goto wait
	:label command_quit
		:shutdown
	:label command_set
		:com.prompt "integer" INT -> &integer
		:return
	:label command_inc
		:shift &integer
		:return
	:label command_dec
		:shift -1 &integer
		:return
	:label command_print
		:com.message "integer: " @&integer
		:return
	END
END

Les interruptions

Erreurs d'instructions

Lancez l'application sans aucune option. Un prompt apparaît, et vous pouvez lancer des commandes :

./interruptions.svm
command: set
integer: 17
command: inc
command: inc
command: print
integer: 19
command: exit
### Simple Virtual Machine 1234 : PROCESS application | 2023-01-01 00:00:00 GMT ######################################################################
Kernel interrupted: @(main, line 5) Interruption FAILURE not handled: Label command_exit is not defined in code main.

Core dump:
Kernel:
State: I, transmit_interruption, interrupted @(main, line 5) Interruption FAILURE not handled: Label command_exit is not defined in code main.

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

Code at <main:1/3>:
     :memory INT/integer , STR/command  # <0> @(main, line 1)
  :label wait
     :com.prompt "command" STR -> &command      # <1> @(main, line 3) WAITING
     :str.join "command_" @&command -> &command # <2> @(main, line 4)
====== HERE ======
     :call @&command integer    # <3> @(main, line 5)
==================
     :goto wait # <4> @(main, line 6)
  :label command_quit
     :shutdown  # <5> @(main, line 8)
  :label command_set
     :com.prompt "integer" INT -> &integer      # <6> @(main, line 10) WAITING
     :return    # <7> @(main, line 11)
  :label command_inc
     :shift &integer    # <8> @(main, line 13)
     :return    # <9> @(main, line 14)
  :label command_dec
     :shift -1 &integer # <10> @(main, line 16)
     :return    # <11> @(main, line 17)
  :label command_print
     :com.message "integer: " @&integer # <12> @(main, line 19)
     :return    # <13> @(main, line 20)
 Labels:
  command_dec -> <10>
  command_inc -> <8>
  command_print -> <12>
  command_quit -> <5>
  command_set -> <6>
  wait -> <1>
 Symbols:


Memory:
  &0: INT 19
  &1: STR "command_exit"
 Aliases:
  command -> &1*1
  integer -> &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: Label command_exit is not defined in code main.

Et là, c'est un échec. A la moindre erreur de l'utilisateur de l'application, celle-ci s'arrête en crachant sur le terminal une erreur certes très utile pour le développeur, mais peu agréable pour l'utilisateur.

Interruption du processeur

Cependant, avant d'agir sur cette condition d'erreur, lisez attentivement le texte généré. Il y apparaît une information importante : il s'agit d'une interruption de type FAILURE.

Comme vous l'avez déjà constaté, l'application est exécutée par un processeur. En cas d'erreur, comme ici, une interruption est émise, et le processeur arrête d'exécuter l'application : on parle d'interruption non rattrapée.

Une condition d'erreur dans une instruction est matérialisée par une interruption du processeur.

Lorsque le processeur est interrompu, il arrête l'exécution du code.

Quelques types d'interruption

Les types d'interruption fréquemment rencontrés sont :

Gestionnaire d'interruption

Rattrapons les interruptions

Ce que l'on aimerait faire dans cette application est de pouvoir exécuter un code spécial en cas d'erreur, pour :

Modifiez le code de l'application pour obtenir :

#!/usr/bin/env svm
DESCRIPTION
Software interruptions example
END
LOG
DEBUG "Software interruptions" STYLE "default"
PLUGIN "svmcom.so"
PLUGIN "svmstr.so"
PROCESS "application"
	CODE "main" INLINE
		:memory INT/integer, STR/command
	:label wait
		:com.prompt "command" STR -> &command
		:str.join "command_" @&command -> &command
		:interruption FAILURE wrong_command
		:debug BREAK
		:call @&command integer
		:interruption FAILURE
		:goto wait
	:label command_quit
		:shutdown
	:label command_set
		:com.prompt "integer" INT -> &integer
		:return
	:label command_inc
		:shift &integer
		:return
	:label command_dec
		:shift -1 &integer
		:return
	:label command_print
		:com.message "integer: " @&integer
		:return
	:label wrong_command
		:com.message "Invalid command."
		:return
	END
END

Lancez maintenant l'application :

./interruptions.svm
command: set 
integer: 17
command: inc
command: inc
command: dec 
command: exit
Invalid command.
command: quit

L'erreur est maintenant issue d'une instruction de l'application, et l'application continue de s'exécuter.

Relancez l'application en mode débugueur, et regardez le processeur lorsqu'il exécute l'instruction :call :

Software interruptions
Processor - K main - P application
State:
Next instruction: <main:1/5>
Current instruction: <main:1/4>
Code
Current memory: &0*0
Allocated memory: &0*2
Defined aliases: command integer
Local interruptions: FAILURE => <main:1/17>
Code
Cascaded local interruptions:
Flags:
Cascaded flags:
Return stack:
Global interruptions:
Waiting interruptions:
=Code main - K main - P application
:memory INT/integer , STR/command
:label wait

:com.prompt "command" STR -> &command
:str.join "command_" @&command -> &command
:interruption FAILURE wrong_command
:debug BREAK
:call @&command integer
:interruption FAILURE
:goto wait
:label command_quit

:shutdown
:label command_set

:com.prompt "integer" INT -> &integer
:return
:label command_inc

:shift &integer
:return
:label command_dec

:shift -1 &integer
:return
:label command_print

:com.message "integer: " @&integer
:return
:label wrong_command

:com.message "Invalid command."
:return
Auto-scroll to
Current
with above
Display

Dans la fenêtre du processeur, un lien existe entre l'interruption FAILURE et l'adresse dans le code indiquée par l'étiquette "wrong_command" : il s'agit d'un gestionnaire d'interruption.

Quand ce gestionnaire est défini, au lieu d'arrêter toute l'exécution lorsqu'une interruption FAILURE arrive, le processeur va réaliser un appel de fonction à l'adresse indiquée.

Exécutez pas à pas l'application, et observez ce qu'il se passe lorsqu'une commande inconnue est entrée dans le terminal : l'exécution normale du code est bien interrompue, et un appel implicite à "wrong_command" est effectué.

Types de gestionnaires d'interruption

La gestion des interruptions est plutôt sensible dans une application. Pour couvrir un maximum de besoins, il existe trois niveaux de gestionnaires d'interruptions :

  1. Gestionnaire local : un gestionnaire d'interruption local est actif uniquement dans la fonction où est défini le gestionnaire d'interruption,
  2. Gestionnaire cascadé : un gestionnaire d'interruption cascadé est actif dans la fonction où est défini le gestionnaire d'interruption et toutes les fonctions appellées depuis cette fonction,
  3. Gestionnaire global : un gestionnaire d'interruption global est actif tout le temps.

Lorsque plusieurs gestionnaires sont définis pour une interruption donnée, le premier dans la liste ci-dessus est utilisé.

Pour illustrer cet ordre de précédence, remplacez le code par :

#!/usr/bin/env svm
DESCRIPTION
Software interruptions example
END
LOG
DEBUG "Software interruptions" STYLE "default"
PLUGIN "svmcom.so"
PLUGIN "svmstr.so"
PLUGIN "svmint.so"
PROCESS "application"
	CODE "main" INLINE
		:memory INT/integer, STR/command
		:interruption GLOBAL NUMERIC wrong_operation
	:label wait
		:com.prompt "command" STR -> &command
		:str.join "command_" @&command -> &command
		:interruption FAILURE wrong_command
		:debug BREAK
		:call @&command integer
		:interruption FAILURE
		:goto wait
	:label command_quit
		:shutdown
	:label command_set
		:interruption NUMERIC use_zero
		:com.prompt "integer" INT -> &integer
		:return
	:label use_zero
		:com.message "Use zero."
		0 -> &integer
		:return 2
	:label command_inc
		:shift &integer
		:return
	:label command_dec
		:shift -1 &integer
		:return
	:label command_div
		:int.div 100 @&integer -> &integer
		:return
	:label command_print
		:com.message "integer: " @&integer
		:return
	:label wrong_command
		:com.message "Invalid command."
		:return
	:label wrong_operation
		:com.message "Invalid operation."
		:return
	END
END

Puis lancez cette application (potentiellement pas à pas dans le débugueur) :

./interruptions.svm
command: set
integer: 17
command: set
integer: one    
Use zero.
command: div
Invalid operation.
command: quit

Dans cet exemple, vous pouvez constater plusieurs choses :

Un gestionnaire d'interruption est une fonction qui sera appellée lorsqu'une interruption est levée.

Pour chaque interruption, il peut y avoir jusqu'à trois gestionnaires d'interruption dans cet ordre de priorité :

  1. local,
  2. cascadé,
  3. global.

Conclusion

Vous venez de voir comment gérer les erreurs émises par les instructions.

Une gestion des erreurs complète donne de la robustesse à une application. Il convient de bien anticiper où peuvent apparaître des erreurs et y apporter le traitement approprié.