Exercice assembleur x86 inversion de chaîne

Décembre 2016




Introduction


Ce petit exercice d'assembleur vise les architectures x86 (Processeurs Intel et Amd 32 bits) et utilise la syntaxe de Nasm, un assembleur libre, gratuit et utilisable sur différentes plateformes telles que Windows ou Linux.
De même, les fonctions externes utilisées sont issues de la bibliothèque C standard.

Ainsi vous n'aurez pas de problèmes liés à votre machine pour faire cet exercice: il n'est pas dépendant du système d'exploitation utilisé. Il est uniquement dépendant de l'architecture x86.

NOTE: Pour utiliser nasm afin de tester cet exercice, vous trouverez un tutoriel d'utilisation/installation de nasm pour Windows et Linux en cliquant ici.

Notions abordées dans cet exercice

  • Les fonctions avec paramètre d'entrée
  • Les sauts (jmp, jz/je etc...)
  • La pile (et surtout la pile à vrai dire :-)
  • Les chaînes de caractères

Énoncé


Le but est d'écrire une fonction en assembleur capable d'inverser une chaîne de caractères.
Cette fonction devra prendre en paramètre d'entrée un pointeur vers une chaîne de caractères et inverser cette chaîne sur le même pointeur.

Voici ce que donnerait cette fonction en C:
void inverse_chaine(char *str); //Le prototype de cette fonction
//Exemple d'utilisation:
char string[] = "Je suis une chaîne a inverser";
inverse_chaine(string);
printf(string); //Affichera "resrevni a eniahc enu sius eJ"


Il vous faudra insérer ce code là-dedans:
extern printf

section .data
	chaine db 'Inverse moi! Je te dirais quel programmeur tu es!', 0x0

section .text
	global main, inverse_chaine

inverse_chaine:
		;Mettez votre code ici


main:
		mov eax, chaine ;Adresse de chaîne dans eax
		push eax
                ;Appel de inverse_chaine avec l'adresse de la chaîne à inverser
		call inverse_chaine 
                ;Les deux lignes suivantes sont optionnelles car l'adresse 
                de la chaîne (maintenant inversée) est toujours dans la pile. 
                mov eax, chaine ;Adresse de chaîne dans eax
		push eax
                ;Affichage de la chaîne avec printf
		call printf
		add esp, 4 ;On sort de la fonction main
		mov eax, 0

Essayez de résoudre cet exercice par vous-même dès maintenant sans regarder les sections suivantes. Plus on réfléchit par soi-même, plus on évolue. Si vous avez du mal, regardez la section qui suit (Indices) puis réessayez.
Bonne chance! C'est en forgeant que l'on devient forgeron ;-)

Rappel

  • Un caractère ascii standard est codé sur un octet. Une chaîne de caractères est donc une suite de caractères de 1 octet et non pas 4 comme les entiers.
  • Une chaîne se termine par le caractère 0 (la valeur 0 et non pas le caractère '0').
  • Vous ne pouvez empiler que des éléments de 2 ou 4 octets dans la pile (à moins qu'il s'agisse de constante d'un octet).

Indices


Vous n'avez pas une idée? Allez, un petit indice pour vous faire démarrer : vous savez probablement que la pile est une mémoire dont l'accès est de type Last In First Out. Ce qui signifie que si vous poussez dans la pile trois lettres les unes après les autres comme a puis b puis c, vous les récupèrerez dans l'ordre inverse: c puis b puis a.

Corrigé


J'espère que vous avez réussi à faire quelque chose, même si vous pensez ne pas avoir utilisé la bonne manière, ce n'est pas grave, c'est comme ça qu'on apprend.

Voici une solution:
inverse_chaine:
                ;Prologue de la fonction
		push ebp
		mov ebp, esp
	
                ;On charge le pointeur passé en paramètre dans eax
		mov eax, [ebp+8]
                ;On push le caractère de fin de chaîne dans la pile
		push word 0

	chaine_dans_pile:
                ;On va empiler chaque caractère de la chaîne

                ;Récupération caractère courant
		mov bl, byte [eax]
                ;Est-ce la fin de chaîne? (bl = 0 ?)
		test bl, bl
                ;Si oui on passe à l'étape suivante
		jz fin_chaine_dans_pile
                ;Sinon on empile le caractère suivant
		push bx
                ;On incrémente le pointeur de 1 pour traiter le caractère suivant
		inc eax
                ;On passe au caractère suivant
		jmp chaine_dans_pile

	fin_chaine_dans_pile:
                ;On recharge le pointeur de la chaîne pour dépiler un à un chaque caractère
		mov eax, [ebp + 8]

	inversion:
                ;On dépile le caractère courant
		pop bx
                ;On le charge dans le pointeur de chaîne
		mov byte [eax], bl
                ;On incrémente l'adresse
		inc eax
                ;C'était la fin de la chaîne? (le 0 qu'on a empilé au début?)
		test bl, bl
                ;Non alors on continue
		jnz inversion
                ;C'est la fin de la chaîne, on marque la conclusion de la fonction
		leave
		ret

Explication


Comme suggéré dans la section "indices", l'idéal est d'utiliser la pile. On empile chaque caractère de la chaîne. Lorsqu'ils seront dépilés, on les récupèrera dans l'ordre inverse.
Voici donc ce qui se passe: on empile d'abord 0 qui sera donc dépilé plus tard, en dernier, pour marquer la fin de chaîne.
Ensuite, on empile un à un chaque caractère de la chaîne en passant par bl (les 8 caractères de poids faibles de ebx) qui est suffisant pour un caractère. Lorsqu'on empile, on prend bx (16 bits de poids faible de ebx) qui contient lui-même bl car on est obligé d'empiler au moins 2 octets.
Durant ces empilements successifs, on fera bien attention de ne pas empiler le caractère de fin.

A l'étape suivante, on recharge le pointeur à l'adresse du début de la chaîne. Puis, on dépile successivement chaque caractère dans la chaîne en écrasant les anciennes valeurs. Lorsqu'on a dépilé le 0 de fin de chaîne, on l'insère également dans la chaîne puis on arrête. Et le tour est joué.

A voir également :

Ce document intitulé «  Exercice assembleur x86 inversion de chaîne  » issu de CommentCaMarche (www.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.