lalahop

Assembleur 8086+ : exercice 2

Table des matières

Tout ayant été dit dans le premier "exercice", je ne détaillerai rien concernant la compilation/l'édition des liens cette fois-ci. Reportez-vous au premier billet pour plus d'informations.

Exercice 2 : énoncé

  1. Afficher l'alphabet à l'écran en utilisant l'instruction loop et sans déclarer l'alphabet dans le segment des données
  2. Afficher une chaine de caractères déclarée dans le segment des données, caractère par caractère, en utilisant l'instruction loop
  3. Afficher une chaine de caractère, déclarée dans le segment des données, caractère par caractère, à l'envers, en utilisant l'instruction loop
  4. Copier, caractère par caractère, une chaine de caractères, déclarée dans le segment des données, dans une autre chaine.
  5. Concaténer, dans une troisième chaine de caractère, deux chaines de caractères saisies par l'utilisateur. Chaque chaine saisie fera au plus 15 caractères. Dans la troisième chaine, les 2 chaines saisies seront séparées par un espace.

Note : Comme la dernière fois, cet énoncé n’est pas de moi : je l’ai repris à T.M. / E.R. et je l’ai légèrement adapté.

Il faut :

  • Penser à utiliser l'instruction movsb pour copier les chaines et penser à initialiser l'extra segment, en même temps que le segment des données, en faisant pointer l'extra segment sur le segment des données
  • Penser que les lettres minuscules vont de 61 à 7A (en notation hexadécimale, soit de 97 à 122 en décimale) dans la table ASCII
  • Trouver l'interruption qui permet de réaliser une saisie au clavier. mov ah, 0Ch, mov al, 0Ah mov dx, chaine int 21h
  • Connaitre la structure d'une chaine de caractère retourner par l'interruption précédemment évoquée : d'abord la taille maximum de la chaine, y compris le retour charriot (c'est à nous d'entrer cette valeur), ensuite le nombre de caractères réellement présent dans la chaine (cette valeur sera automatiquement renseignée par la fonction mais elle n'inclue pas le retour charriot finale), la chaine, le caractère retour charriot. Cela signifie que pour saisir une chaine de 15 caractères, il faut déclarer un espace mémoire de 18 octets dont le premier sera initialisé à la valeur 16.
  • Pour déclarer un espace mémoire initialisé à une certaine valeur, on utilise, avec TASM, l'instruction dup. 18 dup(16) déclare un espace mémoire de 18 octets, tous initialisés avec la valeur 16.
  • Ne pas oublier de rajouter un $ à la fin des chaines saisies si vous voulez les afficher.

Voici ce que j'ai écris :

DOSSEG
.MODEL SMALL
 
; 1 ko sera attribué au segment de pile
.STACK
 
.DATA
    message db "Afficher l'alphabet avec une boucle loop : $"

 
    message1 db 13,10,10,'Afficher une chaine caractere par caractere, avec une boucle loop (on suppose la taille de la chaine connue) : $'
    message2 db 13,10,10,"Meme chose mais a l'envers : $"

    chaine db 'Bonjour GuiGui$'
 
    message3 db 13,10,10,'Copier une chaine declaree, caractere par caractere dans une autre chaine et afficher la chaine de destination : $'

    chaine2 db 15 dup(0)
 
    message4 db 13,10,10,"Concatener 2 chaines saisies par l'utilisateur dans une 3eme chaine et afficher cette chaine : $"

    demandesaisie1 db 10,' -> Saisie de la premiere chaine (15 caracteres max) : $'
    demandesaisie2 db 10,' -> Saisie de la seconde chaine (15 caracteres max) : $'

    message5 db 10,10,' -> Affichage de la premiere chaine : $'
    message6 db 10,' -> Affichage de la seconde chaine : $'

    message7 db 10,' -> Affichage de la troisieme chaine (avec un espace entre chaine 1 et 2 pour le fun) : $'
    chainesaisie1 db 18 dup(16)

    chainesaisie2 db 18 dup(16)
    chainefinale db 33 dup(32)

 
 
.CODE
    mov ax, @DATA
    mov ds, ax

    mov es, ax
 
    mov ah, 09h

    mov dx, offset message
    int 21h
 
    mov cx, 26

    mov dl, 61h
    mov ah, 02h
    bcl:     
        int 21h

        inc dl
    loop bcl
 
 
    ; 2) Afficher une chaine caractère par caractère
    mov ah, 09h

    mov dx, offset message1
    int 21h
 
    mov si, 0

    mov cx, 14
    mov ah, 02H
    bcl1 :    mov dl, [chaine + si]

        int 21h
        inc si
    loop bcl1
 
 
    ; 3) Afficher une chaine caractère par caractère, à l'envers

    mov ah, 09h
    mov dx, offset message2
    int 21h

 
    mov si, 13
    mov cx, 14

    mov ah, 02H
    bcl2 :     mov dl, [chaine + si]

        int 21h
        dec si
    loop bcl2
 
 
    ; 4) Copier une chaine, déclarée, dans une autre

    mov ah, 09h
    mov dx, offset message3
    int 21h

 
    mov si, offset chaine
    mov di, offset chaine2
    mov cx, 15

    copie : 
        movsb
    loop copie
 
    mov ah, 09h

    mov dx, offset chaine2
    int 21h
 
 
    ; 5) Concatener 2 chaines saisies par l'utilisateur dans une 3eme chaine
    ; Début des saisies

    mov ah, 09h
    mov dx, offset message4
    int 21h

 
    mov ah, 09h
    mov dx, offset demandesaisie1
    int 21h

 
    mov ax, 0C0Ah
    mov dx, offset chainesaisie1
    int 21h

 
    mov ah, 09h
    mov dx, offset demandesaisie2
    int 21h

 
    mov ax, 0C0Ah
    mov dx, offset chainesaisie2
    int 21h

 
    ; On stocke la taille de la première chaine saisie dans cx (pour boucler dessus)
    mov cx, 0
    mov cl, [chainesaisie1 + 1]

 
    ; On copie la premiere chaine saisie dans la 3eme chaine
    mov si, offset chainesaisie1 + 2
    mov di, offset chainefinale + 2

    transfertA :     movsb
            inc [chainefinale + 1]
    loop transfertA

 
    add [chainefinale + 1], 1     ; ajout d'un espace
 
    ; On stocke la taille de la 3eme chaine

    mov bx, 0
    mov bl, [chainefinale + 1]

 
    ; On stocke la taille de la deuxieme chaine saisie, toujours pour boucler dessus
    mov cx, 0000h
    mov cl, [chainesaisie2 + 1]

 
    ; On copie la deuxieme chaine saisie dans la 3eme chaine
    mov si, offset chainesaisie2 + 2
    mov di, offset chainefinale + 2

    add di, bx
    transfertB :     movsb
            inc [chainefinale + 1]

    loop transfertB
 
    ; On ajoute un $ à la fin des trois chaines            
    mov bx, 0
    mov bl, [chainesaisie1 + 1]

    mov [chainesaisie1 + 2 + bx], '$'
 
    mov bx, 0

    mov bl, [chainesaisie2 + 1]
    mov [chainesaisie2 + 2 + bx], '$'

 
    mov bx, 0
    mov bl, [chainefinale + 1]

    mov [chainefinale + 2 + bx], '$'
 
    ; Pour finir, on affiche les trois chaines

    mov ah, 09h
    mov dx, offset message5
    int 21h

 
    mov ah, 09h
    mov dx, offset chainesaisie1 + 2

    int 21h
 
    mov ah, 09h
    mov dx, offset message6
    int 21h

 
    mov ah, 09h
    mov dx, offset chainesaisie2 + 2

    int 21h
 
    mov ah, 09h
    mov dx, offset message7
    int 21h

 
    mov ah, 09h
    mov dx, offset chainefinale + 2

    int 21h
 
    ; FIN
    mov ax, 4C00h

    int 21h
END

Exercice 2 : sous Windows, avec Nasm et le linker Val

A part quelques spécificités propres à Nasm, le code reste identique donc reportez-vous ci-dessus pour quelques explications.

Les spécificités de nasm :

  • Pour déclarer un espace mémoire initialisé à une certaine valeur, on utilise, avec Nasm, l’instruction times. times 18 db 16 déclare un espace mémoire de 18 octets, tous initialisés avec la valeur 16.
  • On peut utiliser le segment bss pour déclarer un espace mémoire non initialisé. Cela est aussi possible sous TASM bien que je n'ai pas cherché à le faire. On utilise les instruction resb, resw, pour réserver un octet, un mot, etc.
  • Pour copier quelquechose dans la mémoire, avec le mode d'adressage direct, il faut indiquer la taille de ce que l'on veut copier : un octet, un mot, etc ... Exemple : mov byte [chaine], '$' ; copie du caractère $ au début de chaine. On n'a pas besoin de faire ça quand on veut stocker le contenu d'un registre dans une variable

Voici ce que j'ai écris :

segment .data
    message db "Afficher l'alphabet avec une boucle loop : $"

 
    message1 db 13,10,10,'Afficher une chaine caractere par caractere, avec une boucle loop (on suppose la taille de la chaine connue) : $'
    message2 db 13,10,10,"Meme chose mais a l'envers : $"

    chaine db 'Bonjour GuiGui$'
 
    message3 db 13,10,10,'Copier une chaine declaree, caractere par caractere dans une autre chaine et afficher la chaine de destination : $'

 
    message4 db 13,10,10,"Concatener 2 chaines saisies par l'utilisateur dans une 3eme chaine et afficher cette chaine : $"
    demandesaisie1 db 10,' -> Saisie de la premiere chaine (15 caracteres max) : $'

    demandesaisie2 db 10,' -> Saisie de la seconde chaine (15 caracteres max) : $'
    message5 db 10,10,' -> Affichage de la premiere chaine : $'

    message6 db 10,' -> Affichage de la seconde chaine : $'
    message7 db 10,' -> Affichage de la troisieme chaine (avec un espace entre chaine 1 et 2 pour le fun) : $'

    chainesaisie1 times 18 db 16
    chainesaisie2 times 18 db 16

    chainefinale times 33 db 32
 
segment .bss
    chaine2 resb 15

 
segment stack stack
    resb 64
    stackstop:
 

segment .code
..start:
    mov ax, data
    mov ds, ax

    mov es, ax ; Initialisation de l'extra segment neccesaire à movsb
 
    ; 1) Affichage de l'alphabet
    mov ah, 09h

    mov dx, message
    int 21h
 
    mov cx, 26

    mov dl, 61h
    bcl:     mov ah, 02h

        int 21h
        inc dl
    loop bcl
 
 
    ; 2) Afficher une chaine caractère par caractère

    mov ah, 09h
    mov dx, message1
    int 21h

 
    mov si, 0
    mov cx, 14

    mov ah, 02H
    bcl1 :    mov DL, [chaine + si]

        int 21h
        inc si
    loop bcl1
 
 
    ; 3) Afficher une chaine caractère par caractère, à l'envers

    mov ah, 09h
    mov dx, message2
    int 21h

 
    mov si, 13
    mov cx, 14

    mov ah, 02H
    bcl2 :     mov dl, [chaine + si]

        int 21h
        dec si
    loop bcl2
 
 
    ; 4) Copier une chaine, déclarée, dans une autre

    mov ah, 09h
    mov dx, message3
    int 21h

 
    mov si, chaine
    mov di, chaine2
    mov cx, 15

    copie : 
        movsb
    loop copie
 
    mov ah, 09h

    mov dx, chaine2
    int 21h
 
 
    ; 5) Concatener 2 chaines saisies par l'utilisateur dans une 3eme chaine
    ; Début des saisies

    mov ah, 09h
    mov dx, message4
    int 21h

 
    mov ah, 09h
    mov dx, demandesaisie1
    int 21h

 
    mov ax, 0C0Ah
    mov dx, chainesaisie1
    int 21h

 
    mov ah, 09h
    mov dx, demandesaisie2
    int 21h

 
    mov ax, 0C0Ah
    mov dx, chainesaisie2
    int 21h

 
    ; On stocke la taille de la premiere chaine saisie dans cx (pour boucler desus)
    mov cx, 0
    mov cl, [chainesaisie1 + 1]

 
    ; On copie la premiere chaine saisie dans la 3eme chaine
    mov si, chainesaisie1 + 2
    mov di, chainefinale + 2

    transfertA :     movsb
            inc byte [chainefinale + 1]
    loop transfertA

 
    add byte [chainefinale + 1], 1     ; ajout d'un espace
 

    ; On stocke la taille de la 3eme chaine
    mov bx, 0
    mov bl, [chainefinale + 1]

 
    ; On stocke la taille de la deuxieme chaine saisie, toujours pour boucler desus
    mov cx, 0000h
    mov cl, [chainesaisie2 + 1]

 
    ; On copie la deuxieme chaine saisie dans la 3eme chaine
    mov si, chainesaisie2 + 2
    mov di, chainefinale + 2

    add di, bx
    transfertB :     movsb
            inc byte[chainefinale + 1]

    loop transfertB
 
    ; On ajoute un $ à la fin des trois chaines            
    mov bx, 0
    mov bl, [chainesaisie1 + 1]

    mov byte [chainesaisie1 + 2 + bx], '$'

 
    mov bx, 0
    mov bl, [chainesaisie2 + 1]

    mov byte [chainesaisie2 + 2 + bx], '$'

 
    mov bx, 0
    mov bl, [chainefinale + 1]

    mov byte [chainefinale + 2 + bx], '$'

 
    ; Pour finir, on affiche les trois chaines
    mov ah, 09h
    mov dx, message5
    int 21h

 
    mov ah, 09h
    mov dx, chainesaisie1 + 2

    int 21h
 
    mov ah, 09h
    mov dx, message6
    int 21h

 
    mov ah, 09h
    mov dx, chainesaisie2 + 2

    int 21h
 
    mov ah, 09h
    mov dx, message7
    int 21h

 
    mov ah, 09h
    mov dx, chainefinale + 2

    int 21h
 
    ; FIN
    mov ax, 4C00h

    int 21h

Exercice 2 : sous GNU/Linux, avec Nasm et ld

Ici, ce sont les spécificités de GNU/Linux qu'il va falloir remarquer. Il faut :

  • L'instruction movsb ne fonctionne pas en 32 bits. Nous allons devoir faire la copie caractère par caractère nous même. C'est pas compliqué
  • Pour la saisie au clavier : eax : 3, ebx 0 (stdin), ecx chaine, edx, nombre de caractères à lire (y compris le retour à la ligne (LF, 0Ah). Elle retourne dans eax le nombre de caractères lus (y compris le retour à la ligne).
  • Étant donné que la fonction write attend une adresse mémoire, il faudra probablement utiliser un buffer (une variable mémoire servent de buffer en tout cas) pour afficher une lettre.
  • La structure de la chaine retournée par la fonction read est ici toute simple : juste les caractères saisis par l'utilisateur plus le retour à la ligne en fin de chaîne.
  • Comme les fonctions systèmes occupent tous les registres, et notamment le registre cx qui sert aux boucles, il faudra utiliser un mécanisme de sauvegarde des registres importants (une variable en mémoire), notamment ceux qui font office de compteur de boucle. On peut aussi stocker des données sur la pile.

Voici ce que j'ai écris :

section .data
    message db "Afficher l'alphabet avec une boucle loop : "

    lenMessage equ $-message
 
    message1 db 10,10,'Afficher une chaîne caractère par caractère, avec une boucle loop (on suppose la taille de la chaine connue) : '

    lenMessage1 equ $-message1
 
    message2 db 10,10,"Même chose mais a l'envers : "

    lenMessage2 equ $-message2
    chaine db 'Bonjour GuiGui'
    lenChaine equ $-chaine

 
    message3 db 10,10,'Copier une chaîne declarée, caractère par caractère dans une autre chaîne et afficher la chaîne de destination : '
    lenMessage3 equ $-message3    

 
    message4 db 10,10,"Concatener 2 chaîne saisies par l'utilisateur dans une 3éme chaîne et afficher cette chaîne : "
    lenMessage4 equ $-message4
    demandesaisie1 db 10,' -> Saisie de la première chaîne (15 caractères max) : '

    lenDemandesaisie1 equ $-demandesaisie1
    demandesaisie2 db 10,' -> Saisie de la seconde chaîne (15 caractères max) : '
    lenDemandesaisie2 equ $-demandesaisie2
    message5 db 10,' -> Affichage de la première chaîne : '

    lenMessage5 equ $-message5
    message6 db 10,' -> Affichage de la seconde chaîne : '
    lenMessage6 equ $-message6
    message7 db 10,' -> Affichage de la troisième chaîne (avec un espace entre les chaînes pour le fun) : '

    lenMessage7 equ $-message7
 
    fin db 10,'FIN',10

    lenFin equ $-fin 
 
section .bss
    chaine2 resb 15

    buffer resb 1
    save resb 1
    lenSaisie1 resb 4

    lenSaisie2 resb 4
    chainesaisie1 resb 15
    chainesaisie2 resb 15

    chainefinale resb 31
 
section .text
global _start
_start:    
    ; 1) Affichage de l'alphabet

    mov eax, 4
    mov ebx, 1
    mov ecx, message
    mov edx, lenMessage
    int 80h

 
    mov ecx, 0
    mov cx, 26

    mov byte [buffer], 97
    bcl:     
        mov [save], cx ;Sauvegarde du compteur

 
        mov eax, 4
        mov ebx, 1

        mov ecx, buffer
        mov edx, 1
        int 80h

        inc byte [buffer]
 
        mov ecx, 0    ; Restauration du compteur

        mov cx, [save]
    loop bcl
 
 
    ; 2) Afficher une chaine caractère par caractère

    mov eax, 4
    mov ebx, 1
    mov ecx, message1
    mov edx, lenMessage1
    int 80h

 
    mov esi, 0
    mov ecx, 0

    mov cx, 14
    bcl1 :    
        mov [save], cx

 
        mov dl, [chaine + esi]
        mov [buffer], dl

        inc esi
 
        mov eax, 4
        mov ebx, 1

        mov ecx, buffer
        mov edx, 1
        int 80h

 
        mov ecx, 0
        mov cx, [save]

    loop bcl1
 
 
    ; 3) Afficher une chaine caractère par caractère, à l'envers
    mov eax, 4

    mov ebx, 1
    mov ecx, message2
    mov edx, lenMessage2
    int 80h

 
    mov esi, 13
    mov ecx, 0

    mov cx, 14
    bcl2 :     
        mov [save], cx

 
        mov dl, [chaine + esi]
        mov [buffer], dl

        dec esi
 
        mov eax, 4
        mov ebx, 1

        mov ecx, buffer
        mov edx, 1
        int 80h

 
        mov ecx, 0
        mov cx, [save]

    loop bcl2
 
 
    ; 4) Copier une chaine, déclarée, dans une autre
    mov eax, 4

    mov ebx, 1
    mov ecx, message3
    mov edx, lenMessage3
    int 80h

 
    mov esi, 0
    mov ecx, 0

    mov cx, 16
    copie : 
        mov dl, [chaine + esi]

        mov [chaine2 + esi], dl
        inc esi
    loop copie

 
    mov eax, 4
    mov ebx, 1

    mov ecx, chaine2
    mov edx, lenChaine
    int 80h

 
 
    ; 5) Concatener 2 chaines saisies par l'utilisateur dans une 3eme chaine
    ; Début des saisies
    mov eax, 4
    mov ebx, 1

    mov ecx, message4
    mov edx, lenMessage4
    int 80h

 
    mov eax, 4
    mov ebx, 1

    mov ecx, demandesaisie1
    mov edx, lenDemandesaisie1
    int 80h

 
    mov eax, 3
    mov ebx, 0

    mov ecx, chainesaisie1
    mov edx, 16
    int 80h

 
    mov [lenSaisie1], eax ; On stocke la taille réelle de la chaine
 
    mov eax, 4

    mov ebx, 1
    mov ecx, demandesaisie2
    mov edx, lenDemandesaisie2
    int 80h

 
    mov eax, 3
    mov ebx, 0

    mov ecx, chainesaisie2
    mov edx, 16
    int 80h

 
    mov [lenSaisie2], eax ; Idem
 
    ; On copie la premiere chaine saisie dans la 3eme chaine
    mov esi, 0

    mov ecx, 0
    mov cx, [lenSaisie1]
    transfertA : 
        mov dl, [chainesaisie1 + esi]

        mov [chainefinale + esi], dl
        inc esi
    loop transfertA

 
    ; Ajout d'un espace entre les deux chaines
    mov ebx, [lenSaisie1]
    sub ebx, 1

    mov byte [chainefinale + ebx], 32
 
    ; On copie la seconde chaine saisie dans la 3eme chaine

    mov esi, 0
    mov edi, ebx
    add edi, 1

    mov ecx, 0
    mov cx, [lenSaisie2]
    transfertB : 
        mov dl, [chainesaisie2 + esi]

        mov [chainefinale + edi], dl
        inc esi
        inc edi

    loop transfertB
 
    ; Et pour finir, on affiche toutes les chaines
    mov eax, 4
    mov ebx, 1

    mov ecx, message5
    mov edx, lenMessage5
    int 80h

 
    mov eax, 4
    mov ebx, 1

    mov ecx, chainesaisie1
    mov edx, [lenSaisie1]
    int 80h

 
    mov eax, 4
    mov ebx, 1

    mov ecx, message6
    mov edx, lenMessage6
    int 80h

 
    mov eax, 4
    mov ebx, 1

    mov ecx, chainesaisie2
    mov edx, [lenSaisie2]
    int 80h    

 
    mov eax, 4
    mov ebx, 1

    mov ecx, message7
    mov edx, lenMessage7
    int 80h

 
    mov eax, 4
    mov ebx, 1

    mov ecx, chainefinale
    mov edx, [lenSaisie1]
    add edx, [lenSaisie2]

    add edx, 1
    int 80h        
 
    ; FIN

    mov eax, 4
    mov ebx, 1
    mov ecx, fin
    mov edx, lenFin
    int 80h

 
    mov eax, 1
    mov ebx, 0

    int 80h

Il me reste un dernier point à vous indiquer. Pourquoi, pour donner la taille des chaines saisies à la fonction write, ais-je écris la variable entre crochet alors que d'habitude je ne le fais pas. C'est très simple (et en plus la réponse est à moitié dans la question). D'habitude, je stocke la longueur de chaque chaine dans une constante (equ). Mais ne connaisant pas la longueur des chaines saisies par l'utilisateur, je stocke cela dans une variable. Or, les constantes n'existent plus dans le programme finale : elles sont remplacées, par leur valeur, lors de la compilation. Par exemple : "mov edx, lenChaine" équivaut à "mov edx, 14". On comprend tout de suite que si on avait écrit les crochets, on aurait demandé le contenu de l'adresse mémoire 14 et il y a de grandes chances de recevoir une erreur de segmentation. Les variables sont remplacées par leur adresse mémoire. Donc les crochets sont ici obligatoires.