Introduction

Ce didacticiel est destiné aux utilisateurs maîtrisant la programmation en C ou C++, ainsi que l'architecture de la Simple Virtual Machine.

Dans ce didacticiel, vous allez interagir avec la machine virtuelle depuis une extension.

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

Mise en place

Pour commencer, créez le canevas de l'extension dans le fichier interactions.svm_plugin en utilisant ce code :

PLUGIN interact

DEFINE

INSTRUCTION interact.jump INT
%{
%}

INSTRUCTION interact.write INT VALUE ?
%{
%}

INSTRUCTION interact.read INT -> VALUE ?
%{
%}

Interface programmatique

Première instruction

Modifiez le code de l'extension pour ajouter le code de la première instruction :

PLUGIN interact

DEFINE

INSTRUCTION interact.jump INT
%{
	auto address = ARGV_VALUE(0,integer);
	if(address<0)
	{
		ERROR_INTERNAL(PROCESSOR,"Invalid address");
	}
	::svm_processor_jump_local(svm,CURRENT(kernel),address);
%}

INSTRUCTION interact.write INT VALUE ?
%{
%}

INSTRUCTION interact.read INT -> VALUE ?
%{
%}

Générez et compilez l'extension. Ensuite, créez cette application de test dans un fichier exécutable interactions.svm :

#!/usr/bin/env svm
LOG
PLUGIN "svmcom.so"
LOCAL PLUGIN "svmplugininteract/libsvminteract.so"
PROCESS "first"
	CODE "main" INLINE
		:debug BREAK
		:memory INT/i, INT/s
		0 -> &i
		0 -> &s
		:shift @&i &s
		:goto end :unless @&s IN &0*10000
		:shift &i
		:interact.jump 4
	:label end
		:com.message @&i " => " @&s
	END
END

Enfin, lancez l'application (en mode débugueur si vous le souhaitez) :

./interactions.svm
141 => 10011

En fait, cette extension introduit un nouveau type de saut : un saut local sans utiliser d'étiquette, où il suffit de préciser directement l'adresse locale de l'instruction cible en argument de l'instruction de saut !

Pour bien comprendre comment cela est possible, il faut étudier le code de l'instruction :

Cette fonction svm_processor_jump_local fait en réalité partie d'un grand nombre de fonctions C et de types C, qui sont l'interface programmatique de la machine virtuelle : cela permet aux extensions d'interagir avec la machine virtuelle afin d'en modifier l'état.

Pour trouver le fichier d'interface de la machine, vous pouvez vous aider de l'extension générée :

cat svmplugininteract/src/plugin.h | tail -n 4

#include "/usr/local/include/svm/svm_compatibility.h"
#include "/usr/local/include/svm/svm.h"

La dernière ligne indique le fichier d'interface programmatique. Vous pouvez ouvrir ce fichier pour regarder le prototype de la fonction C svm_processor_jump_local.

Si vous observez le code généré de l'extension, vous noterez que les macros définies par le générateur d'extension utilisent aussi des fonctions de l'interface programmatique.

Autres instructions

Modifiez le code de l'extension pour ajouter le code des deux autres instructions :

PLUGIN interact

DEFINE

INSTRUCTION interact.jump INT
%{
	auto address = ARGV_VALUE(0,integer);
	if(address<0)
	{
		ERROR_INTERNAL(PROCESSOR,"Invalid address");
	}
	::svm_processor_jump_local(svm,CURRENT(kernel),address);
%}

INSTRUCTION interact.write INT VALUE ?
%{
	auto address = ARGV_VALUE(0,integer);
	SVM_Value value = NEW_NULL_VALUE(automatic);
	if(argc>1)
	{
		value = ::svm_parameter_value_get(svm,argv[1]);
	}
	if(::svm_memory_address_is_writable(svm,CURRENT(kernel),address,value))
	{
		::svm_memory_write_address(svm,CURRENT(kernel),address,value);
	}
%}

INSTRUCTION interact.read INT -> VALUE ?
%{
	auto address = ARGV_VALUE(0,integer);
	if(::svm_memory_address_is_initialised(svm,CURRENT(kernel),address))
	{
		return ::svm_memory_read_address(svm,CURRENT(kernel),address);
	}
	return NEW_NULL_VALUE(automatic);
%}

Modifiez le code de l'application de test pour obtenir :

#!/usr/bin/env svm
LOG
PLUGIN "svmrun.so"
LOCAL PLUGIN "svmplugininteract/libsvminteract.so"
PROCESS "others"
	CODE "main" INLINE
		:debug BREAK
		:memory INT/i, INT/j, STR/s, INT*2/r
		:interact.write 1 17
		:interact.write 2 17
		:interact.read 1 -> (r/0)
		:interact.read 2 -> (r/1)
		:run.coredump STDOUT
	END
END

Puis lancez l'application :

./interactions.svm
Kernel:
State: R, transmit_interruption

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

Code at <main:1/5>:
     :memory INT/i , INT/j , STR/s , INT*2/r    # <0> @(main, line 1)
     :interact.write 1 17       # <1> @(main, line 2)
     :interact.write 2 17       # <2> @(main, line 3)
     :interact.read 1 -> (r/0)  # <3> @(main, line 4)
     :interact.read 2 -> (r/1)  # <4> @(main, line 5)
====== HERE ======
     :run.coredump STDOUT       # <5> @(main, line 6) SYSTEM
==================
 Labels:
 Symbols:


Memory:
  &0: INT
  &1: INT 17
  &2: STR
  &3: INT 17
  &4: INT
 Aliases:
  i -> &0*1
  j -> &1*1
  r -> &3*2
  s -> &2*1
 Free space:

Les deux instructions ici définissent des opérations mémoire paresseuses, qui ne réalisent l'action de lecture ou d'écriture en mémoire que lorsque cela est possible, mais sans lever d'interruption !

Ici, il convient de remarquer que :

Doxygen

L'interface programmatique, sous sa forme originelle de fichier d'en-tête C, est relativement peu pratique pour retrouver des fonctions en dehors des recherches de texte brut.

Pour pallier à cette limitation, ce fichier d'en-tête C est écrit de manière à pouvoir produire une documentation en HTML via l'utilitaire "Doxygen". (Reportez vous à la documentation de Doxygen pour la génération de cette documentation.)

Une fois généré par Doxygen, le site permet de naviguer dans l'interface de manière thématique grâce aux modules.

Chaque fonction possède une aide rapide qui permet de comprendre ses paramètres, son éventuelle valeur de retour, son fonctionnement et les interruptions qu'elle peut lever. Parfois, lorsque la fonction est complexe à utiliser, un exemple peut être fourni.

Variables C/C++ magiques

Notion de variable magique

Lors de l'écriture des instructions, certaines variables C/C++ apparaissent dans le code sans pour autant avoir été définies : ces variables sont définies par le générateur d'extension et peuvent être considérées comme des variables magiques.

Trouver les variables magiques

Les variables magiques peuvent varier d'un bloc de code à l'autre dans le fichier source de l'extension.

Les seules méthodes efficaces pour trouver les variables magiques d'un bloc de code sont :

Ouvrez le code C++ de l'extension et lisez les prototypes des fonctions liées aux instructions : les variables magiques sont "svm", "argc" et "argv".

Vous pouvez noter que la variable magique "svm" existe dans toutes les fonctions exceptée celle générée pour la configuration de l'extension. Pour éviter une instabilité de la machine virtuelle en cours de fonctionnement, il est impératif de respecter l'emploi de la variable magique "svm" en tant que premier paramètre des fonctions de l'interface programmatique.

Conclusion

Vous venez de voir le mécanisme utilisé par une extension pour modifier la machine virtuelle.

En prime, l'accès à l'interface programmatique est simplifiée par l'utilisation de Doxygen permettant une documentation d'accès plus confortable.

Enfin, les variables magiques créées par le génération d'extension ont été exposées. Leur utilisation sera largement pratiquée dans les didacticiels suivants.