II. Assembleur : suite▲
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 :
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 :
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 :
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 où 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) :
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 :
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 où 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