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é.