<-Précédent Retour à l'accueil Contact : etienne"point"sauvage"at"gmail.com Suivant->
  1. De la directive org
  2. Du mnémonique INT
  3. Des boucles
  4. Des soucis

    Assembleur : suite

  1. De la directive org
  2. 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.

  3. Du mnémonique INT
  4. 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.

    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 :

    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.

  5. Des boucles
  6. 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 :

    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.

    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.

  7. Des soucis
  8. 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 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 refait 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 uncaractè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