Assembleur Intel avec NASM


précédentsommairesuivant

II. Assembleur : suite

49 commentaires Donner une note à l'article (5)

Relu par ClaudeLELOUP.

II.1. De la directive org

La première ligne de notre programme n'est pas, comme nous l'avons vu, une instruction du processeur. C'est un message destiné à NASM. On appelle ça une directive de compilation. Cette directive va demander à NASM d'ajouter un nombre (celui écrit juste après la directive) à toutes les adresses qu'on utilise dans le code. On peut donc, si on veut, le faire à la main et supprimer cette directive. Ca nous donne donc :

 
Sélectionnez
mov dx, hello + 256

Et on supprime la ligne de la directive. En compilant, on obtient le même résultat.

Néanmoins, cette directive est utile. Si on veut compiler notre programme dans un autre format que COM, ce décalage d'adresses n'aura plus lieu d'être. Plutôt que d'enlever dans toutes les adresses ce décalage, on modifiera uniquement la directive : beaucoup plus facile. Nous garderons donc cette directive, surtout qu'elle servira à introduire des décalages autres que celui du format COM.

II.2. Du mnémonique INT

Le mnémonique INT appelle un sous-programme connu du processeur, appelé une interruption. Ces interruptions sont stockées en mémoire, quelque part. Le où ne m'intéresse pas pour l'instant. Il y en a de différentes sortes, dont deux qui m'intéressent tout de suite.

  • Les interruptions fournies par le DOS.
  • Les interruptions fournies par le BIOS.

L'interruption 0x21, celle que nous avons utilisée, est une interruption DOS. Donc, ce code ne fonctionne pas sur un autre système d'exploitation. Par contre, il existe des interruptions BIOS. Le BIOS est le système d'exploitation fourni avec tous les PC sans exception. BIOS signifie, si je ne m'abuse, Basic Input Output System, système basique d'entrée et de sortie. C'est lui qui démarre l'ordinateur, parce qu'il est directement stocké sur la carte mère, et que quand on démarre l'ordinateur, le malheureux ne sait même pas utiliser son disque dur, sur lequel est stocké le vrai système d'exploitation. Donc, que vous ayez un ordinateur sous Windows, Linux, BeOS ou autre, le BIOS est là au démarrage. Et il est suffisamment standard pour en tirer parti, ce que nous allons faire pas plus tard que maintenant.

Alors, l'interruption du gestionnaire d'affichage vidéo porte le numéro 0x10. Il existe une fonction qui permet d'afficher un seul caractère à l'écran. Hé oui, un seul. Il faut lui remplir plein de registres :

  • le registre AH porte le numéro de la fonction, 0x0A ;
  • le registre AL contient le numéro du caractère à afficher ;
  • le registre BH contient la page d'affichage, mettons 0 pour l'instant ;
  • le registre CX contient le nombre de fois que l'on va afficher le caractère.

Pour l'essayer, tapons dans un fichier :

 
Sélectionnez

				mov ah, 0x0A
				mov al, 'B'
				xor bx, bx
				mov cx, 1
				int 0x10
				ret
				

Compilons, exécutons. Un B s'affiche. Mais ce n'est pas fabuleux. On a un B, mais pas de chaîne entière. La source de joie est que l'interruption fonctionne, et que nous nous sommes libérés de l'étreinte du système d'exploitation.

Néanmoins, nous avons là une nouvelle instruction : XOR, mnémonique de la fonction "ou exclusif". On l'utilise à la place de mov bx, 0 parce qu'elle permet une mise à zéro légèrement plus rapide.

II.3. Des boucles

Ce qui serait bien, à présent, ce serait qu'on mette dans le registre AL le premier caractère de notre chaîne "Bonjour papi.", qu'on affiche, puis qu'on mette le deuxième caractère, puis qu'on affiche, puis qu'on mette le troisième et ainsi de suite. Ce qu'il ne faut pas oublier quand on programme, c'est : quand est-ce qu'on s'arrête ?

On ne va pas faire comme le DOS, qui s'arrête quand il rencontre un "$", parce que ça implique qu'on aura quelques difficultés à afficher un $. On va utiliser ce qui se fait dans d'autres langages plus évolués : on va marquer la fin de la chaîne par un caractère non affichable, le caractère justement appelé NULL, de valeur numérique 0.

Voyons le code :

 
Sélectionnez
org 0x0100 ; Adresse de début .COM
 
				;Ecriture de la chaîne hello dans la console
				mov si, hello; met l'adresse de la chaîne à afficher dans le registre SI
				xor bh, bh; RAZ de bh, qui stocke la page d'affichage
				mov ah, 0x03
				int 0x10; appel de l'interruption BIOS qui donne la position du curseur, stockée dans dx
				mov cx, 1; nombre de fois  l'on va afficher un caractère
				affiche_suivant:
				mov al, [si];on met le caractère à afficher dans al
				or al, al;on compare al à zéro pour s'arrêter
				jz fin_affiche_suivant
				mov ah, 0x02;on positionne le curseur
				int 0x10
				mov ah, 0x0A;on affiche le caractère courant cx fois
				int 0x10
				inc si; on passe au caractère suivant
				inc dl; on passe à la colonne suivante pour la position du curseur
				jmp affiche_suivant
				fin_affiche_suivant:
				ret
				hello: db 'Bonjour papi.', 0
				

Plus dur, non ? Plusieurs choses ont fait leur apparition :

  • des labels en plein milieu du code ;
  • des crochets ;
  • des instructions ;
  • de nouveaux registres.

Je n'ai pas trouvé plus concis comme code. Si on peut, je suis preneur.

Les labels suivis par ":" sont des marqueurs d'adresse. NASM va les remplacer par leur véritable adresse, mais pour écrire dans le code, c'est bien plus clair comme ça.

Les crochets indiquent qu'il ne faut pas prendre la valeur du label, mais la valeur du contenu de la case mémoire dont l'adresse est la valeur du label. Il faut considérer en fait les labels comme des adresses. Quand on met des crochets, on indique qu'il faut prendre ce qu'il y a à cette adresse, et non l'adresse elle-même.

  • L'instruction OR : c'est un "ou" logique. La seule valeur pour laquelle le "ou" logique vaut 0 avec deux fois le même opérande, c'est quand cet opérande est zéro. C'est la façon la plus rapide pour tester si une valeur est égale à zéro : si le résultat de ce "ou" vaut zéro, un bit spécial va être mis à 1 quelque part dans la machine, et pourra être utilisé par l'instruction suivante.
  • L'instruction JZ : Jump if Zero. Si le bit évoqué au-dessus est à 1, le programme va continuer à l'adresse indiquée en paramètre. Sinon, on continue normalement.
  • L'instruction INC : INCrémente. Ajoute 1 au registre indiqué en paramètre.
  • L'instruction JMP : JuMP. Continue le programme à l'adresse indiquée en paramètre. Cette instruction est la base des boucles infinies tant redoutées des programmeurs.

Un processeur dispose d'une tripotée de registres, qui sont les seules cases mémoire qui n'ont pas d'adresse et sur lesquelles le processeur peut faire des opérations. Nous utilisons les registres généraux, qui contiennent des nombres et uniquement des nombres, et, ici, un registre d'adressage qui contient des adresses. Les registres généraux sont A, B, C et D pour l'instant. Ils se décomposent en registres haut et bas, en leur ajoutant H pour haut et L pour low (bas in eunegliche). Le regroupement des parties haute et basse se suffixe par X, pour euh... x (eXtended). On a donc AX composé de AH et AL, BX composé de BH et BL, etc. Le registre SI est un registre d'adressage, utile pour faire des calculs sur les adresses. On ne peut pas utiliser directement l'adresse, puisque "hello" ne contient que l'adresse du premier caractère de la chaîne. Il faut l'adresse de tous les caractères de la chaîne, et donc ajouter 1 à l'adresse courante à chaque fois qu'on affiche un nouveau caractère.

II.4. Des soucis

Vous avez essayé de mettre un retour chariot ? C'est pas beau, non ? Pourquoi ? Parce que le BIOS est bête, et ne fait que ce qu'on lui demande : il affiche un caractère à l'écran. Si ce caractère n'est pas affichable, il fait ce qu'il peut ! Le caractère 13, notamment, est un caractère de mise en forme (dans l'esprit des gens, mais rien n'interdit de le considérer comme affichable). Donc, on peut s'améliorer en déplaçant le curseur d'une ligne vers le bas et en RAZant (de l'informaticien RAZer : effectuer une RAZ, Remise A Zéro) le numéro de colonne quand on rencontre ce caractère.

Ca donne (à insérer après le ret, histoire que ça ne s'exécute pas n'importe comment) :

 
Sélectionnez

				nouvelle_ligne:
				inc dh; on passe à la ligne suivante
				xor dl, dl; colonne 0
				jmp positionne_curseur
				Et juste après le JZ, on insère :
				cmp al, 13
				je nouvelle_ligne
				positionne_curseur:
				

Et ce sera tout.

Je vous refais le code en entier, pour la forme :

 
Sélectionnez

				org 0x0100 ; Adresse de début .COM
 
				;Ecriture de la chaîne hello dans la console
				mov si, hello; met l'adresse de la chaîne à afficher dans le registre SI
				xor bh, bh; RAZ de bh, qui stocke la page d'affichage
				mov ah, 0x03
				int 0x10; appel de l'interruption BIOS qui donne la position du curseur, stockée dans dx
				mov cx, 1; nombre de fois  l'on va afficher un caractère
				affiche_suivant:
				mov al, [si];on met le caractère à afficher dans al
				or al, al;on compare al à zéro pour s'arrêter
				jz fin_affiche_suivant
				cmp al, 13
				je nouvelle_ligne
				positionne_curseur:
				mov ah, 0x02;on positionne le curseur
				int 0x10
				mov ah, 0x0A;on affiche le caractère courant cx fois
				int 0x10
				inc si; on passe au caractère suivant
				inc dl; on passe à la colonne suivante pour la position du curseur
				jmp affiche_suivant
				fin_affiche_suivant:
				ret
				nouvelle_ligne:
				inc dh; on passe à la ligne suivante
				xor dl, dl; colonne 0
				jmp positionne_curseur
				hello: db 'Bonjour papi.', 13, 0
				

précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2011 Etienne Sauvage. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.