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 apprendre à manipuler le débugueur depuis les extensions.

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

Mise en place

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

PLUGIN debug

DEFINE

Investigation d'extension

La première interaction entre les extensions et le débugueur permet d'investiguer des problèmes au sein du code des extensions, ou de rendre visibles dans le débugueur des étapes intermédiaires du traitement des extensions.

Micro-points d'arrêt

La première manière consiste en un point d'arrêt placé dans le code de l'extension.

Modifiez le code de l'extension :

PLUGIN debug

DEFINE

INSTRUCTION debug.seq INT -> PTR
%{
	auto s = ARGV_VALUE(0,integer);
	if(s<0)
	{
		ERROR_INTERNAL(FAILURE,"Negative size");
	}
	::svm_debug_break__raw(svm,CURRENT(kernel),"Before allocation");
	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
	::svm_memory_zone_append_internal__raw(svm,z,INTEGER,s);
	SVM_Value_Pointer p = ::svm_memory_allocate(svm,CURRENT(kernel),z);
	::svm_debug_break__raw(svm,CURRENT(kernel),"Before write");
	SVM_Address pa = ::svm_value_pointer_get_address(svm,p);
	SVM_Size ps = ::svm_value_pointer_get_size(svm,p);
	for(SVM_Size i=0 ; i<ps ; ++i)
	{
		SVM_Value_Integer v = ::svm_value_integer_new(svm,i);
		::svm_value_state_set_movable(svm,v);
		::svm_memory_write_address(svm,CURRENT(kernel),(pa+i),v);
		::svm_debug_break__raw(svm,CURRENT(kernel),"Write");
	}
	::svm_debug_break__raw(svm,CURRENT(kernel),"Before return");
	return p;
%}

Générez et compilez l'extension, puis écrivez une application qui appelle cette instruction. Puis, lancez l'application dans le débugueur : chaque passage du code de l'instruction dans la fonction d'interface programmatique svm_debug_break arrête l'exécution de l'instruction jusqu'à la reprise de l'exécution depuis le débugueur.

Notifications

Parfois, provoquer un arrêt à chaque étape de l'exécution est plutôt lourd. Pour pallier à cela, l'interface programmatique permet aussi d'envoyer des notifications. Ces notifications envoient un texte comme un micro point d'arrêt, mais n'arrête pas l'exécution du code.

Modifiez le code de l'extension :

PLUGIN debug

DEFINE

INSTRUCTION debug.seq INT -> PTR
%{
	::svm_debug_break__raw(svm,CURRENT(kernel),"Start");
	auto s = ARGV_VALUE(0,integer);
	if(s<0)
	{
		ERROR_INTERNAL(FAILURE,"Negative size");
	}
	::svm_debug_notify__raw(svm,CURRENT(kernel),"Before allocation");
	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
	::svm_memory_zone_append_internal__raw(svm,z,INTEGER,s);
	SVM_Value_Pointer p = ::svm_memory_allocate(svm,CURRENT(kernel),z);
	::svm_debug_notify__raw(svm,CURRENT(kernel),"Before write");
	SVM_Address pa = ::svm_value_pointer_get_address(svm,p);
	SVM_Size ps = ::svm_value_pointer_get_size(svm,p);
	for(SVM_Size i=0 ; i<ps ; ++i)
	{
		SVM_Value_Integer v = ::svm_value_integer_new(svm,i);
		::svm_value_state_set_movable(svm,v);
		::svm_memory_write_address(svm,CURRENT(kernel),(pa+i),v);
		::svm_debug_notify__raw(svm,CURRENT(kernel),"Write");
	}
	::svm_debug_notify__raw(svm,CURRENT(kernel),"Before return");
	return p;
%}

Regénérez et compilez l'extension. Puis, lancez la même application dans le débugueur : cette fois, l'instruction s'arrête une seule fois au début. Ensuite, les notifications apparaissent dans le débugueur sans que la machine n'arrête l'exécution de l'instruction.

Points d'arrêts

Les extensions peuvent également manipuler les points d'arrêt portant sur le code de la machine virtuelle.

Code

La première possibilité est un ajout de point d'arrêt à un endroit du code machine.

Modifiez le code de l'extension :

PLUGIN debug

DEFINE

INSTRUCTION debug.add SYM
%{
	SVM_Value_Symbol s = ::svm_parameter_value_get(svm,argv[0]);
	::svm_debug_breakpoint_add_break(svm,CURRENT(kernel),s);
%}

INSTRUCTION debug.remove SYM
%{
	SVM_Value_Symbol s = ::svm_parameter_value_get(svm,argv[0]);
	::svm_debug_breakpoint_remove_break(svm,CURRENT(kernel),s);
%}

Regénérez et compilez l'extension, puis utilisez ce code pour créer une petite application de test :

#!/usr/bin/env svm
LOG
LOCAL PLUGIN "svmplugindebug/libsvmdebug.so"
PLUGIN "svmrun.so"
PROCESS "test"
	CODE "main" INLINE
		:debug BREAK
		:run.trace 1
		:debug.add $"s"
		:run.trace 2
	:symbol s
		:run.trace 3
		:debug.remove $"s"
		:run.trace 4
	END
END

Lancez cette application dans le débugueur, et exécutez la pas à pas en regardant le code. Lorsque l'instruction :debug.add est exécutée, un point d'arrêt apparaît au niveau du symbole comme si un clic de souris l'avait placé. Il disparaît ensuite lorsque l'instruction :debug.remove est invoquée.

Mémoire

La seconde possibilité concerne les points d'arrêt mémoire.

Modifiez le code de l'extension :

PLUGIN debug

DEFINE

INSTRUCTION debug.add_read INT
%{
	SVM_Address a = ARGV_VALUE(0,integer);
	::svm_debug_breakpoint_add_memoryread(svm,CURRENT(kernel),a);
%}

INSTRUCTION debug.remove_read INT
%{
	SVM_Address a = ARGV_VALUE(0,integer);
	::svm_debug_breakpoint_remove_memoryread(svm,CURRENT(kernel),a);
%}

INSTRUCTION debug.add_write INT
%{
	SVM_Address a = ARGV_VALUE(0,integer);
	::svm_debug_breakpoint_add_memorywrite(svm,CURRENT(kernel),a);
%}

INSTRUCTION debug.remove_write INT
%{
	SVM_Address a = ARGV_VALUE(0,integer);
	::svm_debug_breakpoint_remove_memorywrite(svm,CURRENT(kernel),a);
%}

INSTRUCTION debug.add_access INT
%{
	SVM_Address a = ARGV_VALUE(0,integer);
	::svm_debug_breakpoint_add_memoryaccess(svm,CURRENT(kernel),a);
%}

INSTRUCTION debug.remove_access INT
%{
	SVM_Address a = ARGV_VALUE(0,integer);
	::svm_debug_breakpoint_remove_memoryaccess(svm,CURRENT(kernel),a);
%}

INSTRUCTION debug.add_free INT
%{
	SVM_Address a = ARGV_VALUE(0,integer);
	::svm_debug_breakpoint_add_memoryfree(svm,CURRENT(kernel),a);
%}

INSTRUCTION debug.remove_free INT
%{
	SVM_Address a = ARGV_VALUE(0,integer);
	::svm_debug_breakpoint_remove_memoryfree(svm,CURRENT(kernel),a);
%}

Regénérez et compilez l'extension. Créez une petite application de test qui emploie ces instructions pour placer des points d'arrêt mémoire, et lancez la en mode débugueur.

Interruptions

La dernière possibilité concerne les interruptions.

Modifiez le code de l'extension :

PLUGIN debug

DEFINE

INSTRUCTION debug.add IRQ
%{
	SVM_Value_Interruption i = ::svm_parameter_value_get(svm,argv[0]);
	::svm_debug_breakpoint_add_interruption(svm,CURRENT(kernel),i);
%}

INSTRUCTION debug.remove IRQ
%{
	SVM_Value_Interruption i = ::svm_parameter_value_get(svm,argv[0]);
	::svm_debug_breakpoint_remove_interruption(svm,CURRENT(kernel),i);
%}

Regénérez et compilez l'extension. Créez une petite application de test qui emploie ces instructions pour placer des points d'arrêt interruption et qui utilise l'instruction :run.interrupt pour lancer des interruptions. Lancez cette application en mode débugueur.

Formulaires

Un dernier type d'arrêt est disponible, et celui-ci est un peu particulier : au lieu de fournir des informations à celui qui investigue dans le débugueur, il permet d'injecter des données dans l'application depuis le débugueur. Ces point d'arrêt sont appelés formulaires, et sont affichés dans la boite de dialogue des points d'arrêt.

Création

Modifiez le code de l'extension :

PLUGIN debug

DEFINE

INSTRUCTION debug.form -> PTR ?
%{
	SVM_Debug_Form f = ::svm_debug_form_new(svm,"Form example");
	::svm_debug_form_append_checkbox__raw(svm,f,"boolean",FALSE);
	SVM_Value *t = ::svm_value_array_new(svm,3);
	t[0] = ::svm_value_string_new__raw(svm,"a");
	t[1] = ::svm_value_string_new__raw(svm,"b");
	t[2] = ::svm_value_integer_new(svm,42);
	::svm_debug_form_append_selection(svm,f,"restricted",3,t);
	::svm_debug_form_append_integer__raw(svm,f,"integer",3,1,10);
	::svm_debug_form_append_string__raw(svm,f,"string","a string",3,8);
	::svm_debug_form_append_text(svm,f,"text",20,10);
%}

Pour le moment, ce n'est pas la peine de générer l'extension, cette instruction est encore incomplète.

Cependant, elle contient déjà la création d'un formulaire, ainsi que la définition des champs de ce formulaire :

Soumission

Une fois créé, le formulaire peut être soumis à l'utilisateur.

Modifiez le code de l'extension :

PLUGIN debug

DEFINE

INSTRUCTION debug.form -> PTR ?
%{
	SVM_Debug_Form f = ::svm_debug_form_new(svm,"Form example");
	::svm_debug_form_append_checkbox__raw(svm,f,"boolean",FALSE);
	SVM_Value *t = ::svm_value_array_new(svm,3);
	t[0] = ::svm_value_string_new__raw(svm,"a");
	t[1] = ::svm_value_string_new__raw(svm,"b");
	t[2] = ::svm_value_integer_new(svm,42);
	::svm_debug_form_append_selection(svm,f,"restricted",3,t);
	::svm_debug_form_append_integer__raw(svm,f,"integer",3,1,10);
	::svm_debug_form_append_string__raw(svm,f,"string","a string",3,8);
	::svm_debug_form_append_text(svm,f,"text",20,10);
	SVM_Value *r = ::svm_debug_form_request(svm,f);
	if(not r)
	{
		return NEW_NULL_VALUE(pointer);
	}
	SVM_Size s = 0;
	for(SVM_Value *it=r ; *it ; ++it)
	{
		::svm_value_state_set_movable(svm,*it);
		++s;
	}
	SVM_Memory_Zone z = ::svm_memory_zone_new(svm);
	::svm_memory_zone_append_internal__raw(svm,z,AUTOMATIC,s);
	SVM_Value_Pointer p = ::svm_memory_allocate(svm,CURRENT(kernel),z);
	::svm_memory_write_pointer(svm,CURRENT(kernel),p,r);
	return p;
%}

Regénérez et compilez l'extension. Créez une petite application de test qui emploie cette instruction et lancez la en mode débugueur. Dans la fenêtre mémoire du noyau contenant le formulaire, observez les valeurs renvoyées par le formulaire.

Utilisations possibles

Les formulaires peuvent être utilisés pour configurer les extensions en mode débugueur, notamment pour influencer leur niveau de verbosité dans le log système ou celui du débugueur.

Ils peuvent aussi être utilisés pour introduire des valeurs inattendues dans l'exécution du code de l'application pour observer son comportement.

Une autre utilisation possible est tout simplement l'injection de code depuis le débugueur :

PLUGIN debug

DEFINE

INSTRUCTION debug.code
%{
	SVM_Debug_Form f = ::svm_debug_form_new(svm,"Code injection");
	::svm_debug_form_append_string__raw(svm,f,"Name","debug",3,8);
	::svm_debug_form_append_text(svm,f,"Code",30,10);
	::svm_debug_form_append_checkbox__raw(svm,f,"Debug mode",TRUE);
	SVM_Value *r = ::svm_debug_form_request(svm,f);
	if(not r)
	{
		RETURN;
	}
	SVM_String sc = ::svm_value_string_get(svm,r[1]);
	std::string rsc = RAW_STRING(sc)+"\n:return\n";
	SVM_Variable c = ::svm_code_compile(svm,r[0],::svm_value_string_new__raw(svm,rsc.c_str()));
	if(::svm_variable_type_is_code(svm,c))
	{
		SVM_Value_Symbol s = ::svm_value_symbol_new(svm,c,0);
		if(::svm_value_boolean_get(svm,r[2]))
		{
			::svm_debug_breakpoint_add_break(svm,CURRENT(kernel),s);
		}
		::svm_processor_call_global(svm,CURRENT(kernel),s,::svm_processor_get_currentpointer(svm,CURRENT(kernel)));
	}
	else
	{
		::svm_debug_notify(svm,CURRENT(kernel),c);
	}
%}

L'utilisation de cette instruction dans une application permet de faire exécuter du code en plus du code de l'application, dans une fonction dédiée.

Conclusion

Vous venez de voir comment manipuler le débugueur depuis une extension.

Les fonctions présentées ici permettent simplement d'augmenter la capacité du débugueur à investiguer à l'intérieur des extensions.

Elles permettent aussi d'automatiser des tâches d'investigation comme la gestion des points d'arrêt.

Enfin, les formulaires ouvrent des possiblités d'interférence avec l'application en mode débugueur, en lui apportant des données au travers de l'interface du débugueur.