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é
- Afficher l'alphabet à l'écran en utilisant l'instruction loop et sans déclarer l'alphabet dans le segment des données
- 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
- 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
- Copier, caractère par caractère, une chaine de caractères, déclarée dans le segment des données, dans une autre chaine.
- 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é.
Exercice 2 : sous Windows, avec TASM et TLINK
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.