lalahop
Categorie: Assembleur 8086+

Assembleur 8086+ : exercice 6

Table des matières

Cet exercice sera le dernier de la saga assembleur 8086+. Nous allons donc finir par un jeu rudimentaire 😎 .

Comme d'habitude, je ne détaillerai rien concernant la compilation/l'édition des liens encore cette fois-ci. Reportez-vous au premier billet pour plus d'informations.

Exercice 6 : énoncé

Nous allons faire un mini jeu de motus en assembleur. Le but est de deviner le mot qui sera défini dans la zone data en un minimum d'essais. Si le mot n'est pas découvert en 7 essais, la partie est perdue. La première lettre du mot sera montrée au joueur dès le début d'une partie. Lors de chaque essai, il faudra afficher le numéro de l'essai, un rappel des lettres trouvés lors des essais précédents ainsi qu'une invitation à saisir une nouvelle proposition. Des messages "perdu"/"gagné" devront également être prévus.

Note : Comme d'habitude, cet énoncé n’est pas de moi : je l’ai repris à T.M. / E.R. et je l’ai modifié.

Exercice 6 : aides

Pas d'aide pour cet exercice car il n'y a aucune nouvelle notion ni aucune difficulté.

TITLE MOTUS
 
DOSSEG
.MODEL SMALL
.STACK 100H

 
 
.DATA
	mot db "bonjour$"	; le mot à trouver
	longueur db 7		; longueur du mot à trouver

	mot2 db "b------$"	; les lettres connues
 
	trouve db 1		; nb lettres trouvées
	essai db 1		; combien d'essai nécessaires ?

	saisie db 8,9 dup(?)	; stockera la saisie utilisateur
 
	; messages écran
	msgEssai db 13,10,"Essai $"
	demandeSaisie db ", saisissez un mot : ",13,10,10,"$"

	msgGagne db "Bravo, vous avez trouve le mot !",13,10,"$"
	msgPerdu db "Perdu, vous n'avez pas trouve en 7 essais !",13,10,"$"

	ligne db 13,10,"$"
 
.CODE
	mov ax, @DATA

	mov ds, ax
 
	mov cx, 7
	bcl:

		push cx
 
		;AFFICHAGE NUM ESSAI
		; "Essai numero"
		mov ah, 09h
		mov dx, offset msgEssai
		int 21h
 
		; affichage numero de l'essai
		mov ah, 02h
		mov dl, [essai]
		add dl, 30h
		int 21h

		; affichage "saisissez un mot"
		mov ah, 09h
		mov dx, offset demandeSaisie
		int 21h

		;SAISIE DE LA PROPOSITION
		; affiche lettres déjà connues
		mov ah, 09h
		mov dx, offset mot2
		int 21h

		; retour ligne
		mov ah, 09h
		mov dx, offset ligne
		int 21h

		; saisie
		mov ah, 0Ch
		mov al, 0ah
		mov dx, offset saisie
		int 21h
 
		; retour ligne
		mov ah, 09h
		mov dx, offset ligne
		int 21h
 
		;EVALUATION DE LA SAISIE
		mov [trouve], 0		; à chaque essai, on considére qu'aucune lettre n'a été trouvée
		mov bx, 0
		mov dl, longueur
 
		; On va comparer lettre par lettre la saisie au mot à trouver

		bcl2:
			cmp bx, dx
			ja finbcl2
 
			mov al, [mot+bx]


			cmp [saisie+bx+2], al		; Attention il y a une différence de 2 entre la saisie et le mot
			JNE remonte			; revoir le format d'une saisie : les 2 octets initiaux servent à
							; stocker taille maximale de la chaîne et taille réelle
			mov [mot2+bx], al		; Si l'on a la même lettre, on l'ajoute aux lettres connues

			inc [trouve]			; et on incrémente le compteur de lettres trouvées
 
			remonte:
				inc bx

				jmp bcl2
		finbcl2:
 
 
		;EVALUATION GAGNE/PERDU
		mov al, longueur			
		cmp trouve, al				; Si toutes les lettres ont été trouvé, c'est gagné

		je gagne
 
		inc essai				; Sinon on incrémente le nombre d'essais, 
		pop cx					; on restaure cx et on repart pour un essai
		loop bcl
 
		; Si l'on arrive là, alors la boucle s'est finie donc les essais sont écoulés

		; donc c'est perdu : affichage du message
		mov ah, 09h
		mov dx, offset msgPerdu
		int 21h

		jmp fin
 
		gagne:	
			; affiche message "gagné"
			mov ah, 09h
			mov dx, offset msgGagne
			int 21h

 
	fin:
		mov ax, 4c00h
		int 21h
END

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

segment .data
	mot db "bonjour$"	; le mot à trouver
	longueur db 7		; longueur du mot à trouver

 
	mot2 db "b------$"	; les lettres connues
 
	trouve db 1		; nb lettres trouvées
	essai db 1		; combien d'essai necessaires ?

	saisie times 10 db 8
 
	; messages écran
	msgEssai db 13,10,"Essai $"

	demandeSaisie db ", saisissez un mot : ",13,10,10,"$"
	msgGagne db "Bravo, vous avez trouve le mot !",13,10,"$"

	msgPerdu db "Perdu, vous n'avez pas trouve en 7 essais !",13,10,"$"
	ligne db 13,10,"$"

 
 
segment stack stack
	resb 64
	stackstop:
 
 

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

 
	mov cx, 7
	bcl:
		push cx

 
		;AFFICHAGE NUM ESSAI
		; "Essai numero"
		mov ah, 09h
		mov dx, msgEssai
		int 21h

 
		; affichage numero de l'essai
		mov ah, 02h
		mov dl, [essai]

		add dl, 30h
		int 21h
 
		; affichage "saisissez un mot"
		mov ah, 09h

		mov dx, demandeSaisie
		int 21h
 
 
		;SAISIE DE LA PROPOSITION
		; affiche lettres déjà connues

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

 
		; retour ligne
		mov ah, 09h
		mov dx, ligne
		int 21h

 
		; saisie
		mov ah, 0Ch
		mov al, 0ah

		mov dx, saisie
		int 21h
 
		; retour ligne
		mov ah, 09h

		mov dx, ligne
		int 21h
 
 
		;EVALUATION DE LA SAISIE
		mov byte [trouve], 0		; à chaque essai, on considére qu'aucune lettre n'a été trouvée

		mov bx, 0
		mov dl, [longueur]

 
		; On va comparer lettre par lettre la saisie au mot à trouver
		bcl2:
			cmp bx, dx
			ja finbcl2
 
			mov al, [mot+bx]

 
			cmp [saisie+bx+2], al		; Attention il y a une différence de 2 entre la saisie et le mot
			JNE remonte			; revoir le format d'une saisie : les 2 octets initiaux servent à

							; stocker taille maximale de la chaîne et taille réelle
 
			mov [mot2+bx], al		; Si l'on a la même lettre, on l'ajoute aux lettres connues

			inc byte [trouve]		; et on incrémente le compteur de lettres trouvées
 
			remonte:
				inc bx

				jmp bcl2
		finbcl2:
 
 
		;EVALUATION GAGNE/PERDU
		mov al, [longueur]			
		cmp [trouve], al				; Si toutes les lettres ont été trouvé, c'est gagné

		je gagne
 
		inc byte [essai]				; Sinon on incrémente le nombre d'essais, 
		pop cx						; on restaure cx et on repart pour un essai

		loop bcl
 
		; Si l'on arrive là, alors la boucle s'est finie donc les essais sont écoulés
		; donc c'est perdu : affichage du message
		mov ah, 09h

		mov dx, msgPerdu
		int 21h
		jmp fin
 
		gagne:	
			; affiche message "gagné"

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

 
	fin:
		mov ax, 4c00h
		int 21h

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

section .data
	mot db "bonjour"	; le mot à trouver
	lenmot: equ $-mot	; longueur du mot à trouver

 
	mot2 db "b------"	; les lettres connues
	lenmot2: equ $-mot2
 
	trouve db 1		; nb lettres trouvées

	essai db 1		; combien d'essai necessaires ?	
 
	msgEssai db 13,10,"Essai "

	lenMsgEssai: equ $-msgEssai	
 
	demandeSaisie db ", saisissez un mot : ",13,10,10

	lenDemandeSaisie: equ $-demandeSaisie	
 
	msgGagne db "Bravo, vous avez trouvé le mot !",10
	lenMsgGagne: equ $-msgGagne	
 
	msgPerdu db "Perdu, vous n'avez pas trouvé le mot en 7 essais !",10

	lenMsgPerdu: equ $-msgPerdu
 
	ligne db 13,10
	lenligne: equ $-ligne
 
 

section .bss
	saisie resb 8		; Stocker la saisie. Prévoir une case de plus pour le retour-chariot
 
section .text

	global _start
 
_start:	
	bcl:					; loop ainsi que les sauts conditionnels ne sont pas utilisables
		cmp byte [essai], 7		; car l'adresse destination est trop éloignée. On procède donc

		jle suite			; à une indirection : un saut conditionnel pointe sur un 
						; saut inconditionnel (qui ne fait pas partie des sauts courts).
		jmp finbcl
 
	suite:	
		; AFFICHAGE NUM ESSAI

		; "Essai numero"
		mov eax, 4
		mov ebx, 1

		mov ecx, msgEssai
		mov edx, lenMsgEssai
		int 80h	

 
		; affichage numero de l'essai
		mov eax, 4
		mov ebx, 1

		mov ecx, essai
		add byte [ecx], 30h		; Rappel : 30h c'est la différence entre un chiffre

		mov edx, 1			; et son équivalent caractère imprimable dans la table ASCII
		int 80h	
		sub byte [ecx], 30h

 
		; affichage "saisissez un mot"
		mov eax, 4
		mov ebx, 1

		mov ecx, demandeSaisie
		mov edx, lenDemandeSaisie
		int 80h

 
		;SAISIE DE LA PROPOSITION
		; affiche lettres déjà connues
		mov eax, 4
		mov ebx, 1

		mov ecx, mot2
		mov edx, lenmot2
		int 80h

 
		; retour ligne
		mov eax, 4
		mov ebx, 1

		mov ecx, ligne
		mov edx, lenligne
		int 80h

 
		; saisie
		mov eax, 3
		mov ebx, 0

		mov ecx, saisie
		mov edx, lenmot
		inc edx

		int 80h
 
		; retour ligne
		mov eax, 4
		mov ebx, 1

		mov ecx, ligne
		mov edx, lenligne
		int 80h

 
 
		;EVALUATION DE LA SAISIE
		mov byte [trouve], 0			; à chaque essai, on considére qu'aucune lettre n'a été trouvée

		mov ebx, 0
		mov dl, lenmot
		sub dx, 1

 
		; On va comparer lettre par lettre la saisie au mot à trouver
		bcl2:
			cmp bx, dx
			ja finbcl2
 
			mov al, [mot+ebx]

 
			cmp [saisie+ebx], al
			JNE remonte
 
			mov [mot2+ebx], al		; Si l'on a la même lettre, on l'ajoute aux lettres connues

			inc byte [trouve]		; et on incrémente le compteur de lettres trouvées
 
			remonte:
				inc ebx

				jmp bcl2
		finbcl2:
 
 
		;EVALUATION GAGNE/PERDU
		mov al, lenmot
		cmp [trouve], al			; Si toutes les lettres ont été trouvé, c'est gagné

		je gagne
 
		inc byte [essai]			; Sinon on incrémente le nombre d'essais et on repart pour un essai
		jmp bcl
 
	finbcl:

		; Si l'on arrive là, alors la boucle s'est finie donc les essais sont écoulés
		; donc c'est perdu : affichage du message
		mov eax, 4
		mov ebx, 1

		mov ecx, msgPerdu
		mov edx, lenMsgPerdu
		int 80h

		jmp fin
 
		gagne:	
			; affiche message "gagné"
			mov eax, 4
			mov ebx, 1

			mov ecx, msgGagne
			mov edx, lenMsgGagne
			int 80h

 
	fin:
		mov eax, 1
		mov ebx, 0

		int 80h

Assembleur 8086+ : exercice 5

Table des matières

Cela fait longtemps que j'avais promis la suite des billets relatifs à l'assembleur 8086+ 😀 .

Comme d'habitude, je ne détaillerai rien concernant la compilation/l'édition des liens encore cette fois-ci. Reportez-vous au premier billet pour plus d'informations.

Exercice 5 : énoncé

  • Écrire deux procédures/fonctions. L'une permettant d'afficher un entier (word ou dword soit 16 ou 32 bits) en hexadécimal. L'autre permettant d'afficher un entier (word ou dword soit 16 ou 32 bits) en binaire. Tester ces deux procédures sur un exemple.
  • Un micro-ordinateur reçoit une suite de 10 mots de 16 bits représentants les résultats de 10 mesures successives de 2 grandeurs physiques G1 et G2. Dans chaque mot, G1 est codé sur les 5 octets de poids fort et G2 est codé sur les 11 bits de poids faible restants. Exemple : 1011100110011110. G1 = 10111 et G2 = 00110011110.
    • Dans un premier temps, notre ordinateur ne fait que collecter les mots reçus en les stockant en pile. Écrire le traitement qui réalise cette opération en supposant que les mots sont définis par la déclaration suivante (section data) : Mesures DW 0E765h, 6AB3h, 8FA1h, 905Ch, 0A9FDh, 5DA9h, 4807h, 8FA2h, 0A10Bh, 0B95h
    • Dans un deuxième temps, les mots en pile sont traités afin d'extraire de chacun la valeur de G1 et celle de G2 et de les ranger respectivement dans des tableaux Tab_G1 et Tab_G2 préalablement déclarés. Écrire le traitement qui réalise cette opération.
    • Écrire le traitement qui permet de calculer la moyenne entière des 10 mesures G1. Même chose pour G2.
    • Faire afficher la partie entière de la moyenne des G1 en hexadécimal et la partie entière de la moyenne des G2 en binaire.
  • Avec les mesures données, la moyenne n'est pas entière. Calculer la moyenne en virgule flottante et l'afficher.

Note : Comme d'habitude, cet énoncé n’est pas de moi : je l’ai repris à T.M. / E.R. et je l’ai modifié.

Exercice 5 : aides

  • On a déjà vu comment afficher un nombre en binaire dans l'exercice 3. On a aussi déjà vu comment afficher un nombre en hexadécimal dans l'exercice 4. Donc les algorithmes sont prêts, il n'y a plus qu'à faire les procédures.
  • La syntaxe pour créer une fonction/procédure est assez simple : un label d'un côté, l'instruction "ret" de l'autre. L'important est de comprendre les notions attenantes : passage de paramètres, prologue/épilogue (responsabilité appelant/appelé), ... Je vous ai sélectionné quelques ressources : Les procédures en assembleur, 3.2. Les fonctions en assembleur et Function prologue.
  • Vous l'aurez compris/remarqué, il est impossible de parler de fonction sans parler de pile. Là encore, la base est simple mais les implications sont importantes. Quelques notions à comprendre : usages (sauvegarder des valeurs, sauvegarder des registres pour protéger contre effacement, appel et retour de fonctions) ; la pile croît depuis les adresses hautes de la mémoire vers les adresses basses (en terme s'adresse, le sommet est plus bas que le paramètre précédent) ; on ajoute/supprime des données par bloc de 2/4 octets (selon architecture, 16 ou 32 bits). Je vous ai sélectionné quelques ressources : Pile en assembleur, Understanding the Stack et Stack.
  • Pour faire des calculs en virgule flottante, il faut utiliser la FPU. Dans un temps reculé, il s'agissait d'une unité à part, sur un circuit différent. Depuis le 80486 (au minimum), c'est intégré dans le CPU lui-même. C'est d'ailleurs pour cela que l'on parle aussi de co-processeur arithmétique. Il y a des instructions dédiées pour exécuter des calculs en virgule flottante, une pile dédiée et des registres dédiés. Pour une introduction rapide : ASM Community • The Assembly Language Resource - The Floating Point Unit (FPU). Pour une ressource plus complète : SIMPLY FPU . Je vais vous donner quelques indications supplémentaires : une fois que vous aurez calculé la somme de G1 et la somme de G2, vous les placerez, chacune, dans une variable mémoire de type (sous nasm) dq/dt/do (le choix influe sur la précision mais dans notre cas, même un dq conviendra). Ensuite, vous chargerez la valeur de cette variable au sommet de la pile du coprocesseur grâce à l'instruction dédiée. Puis vous réaliserez la division avec la bonne instruction.
  • Le résultat de la division en virgule flottante sera au format IEE754. Plutôt que de parser nous-même cette valeur pour obtenir un affichage cohérent, ce qui est techniquement compliqué, nous allons utiliser la fonction printf du C. Elle sait afficher un nombre réel. En plus, cela nous donnera l'occasion de "linker du code C et de l'assembleur" et de passer des paramètres à une fonction externe via la pile. Pour Windows, je ne sais pas comment il faut faire mais sous Debian, il faut le package libc6-dev (libc6-dev-i386 pour les amd64). En début de programme, il faut rajouter la directive "extern printf". Lors du linkage avec ld, il faudra rajouter les paramètres "-I/lib/ld-linux.so.2 -lc".

Cette version Windows est un peu vieille, je ne l'ai pas retouché depuis car il n'est pas dans mes préoccupations actuelles d'aller faire un voyage avec Windows et TASM. Donc cette version, contrairement à la version GNU/Linux ne compte ni les calculs en virgule flottante, ni la gestion du fait que la somme des G1, peut dépasser 2^8 selon les valeurs choisies dans Mesures et donc ne pas loger dans un octet/registre 8 bits.

dosseg
.model small
.stack 100h		; 256 octets suffisent plus qu'amplement pour une pile qui 

			; va stocker 10 mots mémoire au maximum de son utilisation
 
 
.data
	; exercice 1
		;données
		nombre dw 12666

		tab_hexa db "0123456789abcdef"
 
		; messages écran
		exercice1 db "Exercice 1 :",13,10,"$"

		message_binaire db "Affichage du nombre declare dans le segment de donnees (ici : 12666) en binaire : ","$"
		message_hexa db 13,10,"Affichage du nombre declare dans le segment de donnees (ici : 12666) en hexadecimal : ","$"

 
	; exercice 2
		;données
		mesures dw 0e765h, 6ab3h, 8fa1h, 905ch, 0a9fdh, 5da9h, 4807h, 8fa2h, 0a10bh, 0b95h

		tab_g1	db 10 dup(0)
		tab_g2	dw 10 dup(0)

 
		; messages écran
		exercice2 db 13,10,10,"Exercice 2 :","$"

		msg_hexa db 13,10,"Affichage de la moyenne des mesures g1 en hexadecimal : ","$"
		msg_binaire db 13,10,"Affichage de la moyenne des mesures g2 en binaire : ","$"

 
 
.code
	; initialisation du segment de données
	mov ax, @data
	mov ds, ax

 
	; exercice 1
	; affichage du message écran de début de l'exercice 1
	mov ah, 09h
	mov dx, offset exercice1
	int 21h

 
	; affichage du message annonçant l'affichage du nombre déclaré dans le segment de données, en binaire
	mov ah, 09h
	mov dx, offset message_binaire
	int 21h

 
	mov bx, nombre			; passage d'un paramètre, par un registre, à la procédure
	call affichage_binaire 		; appel de la procédure
 
	; affichage du message annonçant l'affichage du nombre déclaré dans le segment de données en hexadécimal

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

 
	push nombre			; passage d'un paramètre, par la pile, à la procédure
	call affichage_hexa		; appel de la procédure
 
 
	; exercice 2
	; affichage du message écran de début de l'exercice 2

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

 
	; 1) Empiler les mesures sur la pile
	; On fait une boucle pour empiler chacune des mesures.
	; Comme la pile du 8086 est de type lifo (= last in, first out), 
	; nous empilerons la 10eme mesure en premier et la 1er en dernier.
	; ainsi, dans le 2), la première mesure dépilée sera bien la première du tableau mesure.
 
	; Attention : "mesures" est un tableau de word (16 bits), pas un tableau de byte (8 bits)

	; donc on ne se déplace pas de 1 en 1 entre les cases (entre leurs adresses pour être précis)
	; mais de 2 en 2.
	mov cx, 20
 
	bcl_empile_mesures :	
				cmp cx, 2

				jb fin_bcl_empile_mesures
 
				mov si, cx				; nous aurions pu ecrire une boucle en utilisant le registre si (ou di ou bx), 
				sub si, 2				; et en decrementant celui-ci avant le "push". mais, par convention, on ne 

				push [mesures + si]			; touche pas le compteur d'une boucle au milieu du deroulement de celle-ci. d'où 2 registres.
 
				sub cx, 2

				jmp bcl_empile_mesures
	fin_bcl_empile_mesures :
 
	; 2) Extraire et classer les mesures g1 et g2 dans deux tableaux independants
	; Attention : tab_g1 est un tableau de byte, tab_g2 est un tableau de word ... on ne se déplace pas de la même façon ...
	; prévoir 2 "compteurs".
	mov si, 0

	mov cl, 11
	mov di, 0
 
	bcl_extraction :

				cmp si, 10
				je fin_bcl_extraction
 
				pop ax				; on désempile une mesure

				mov dx, ax 			; on sauvegarde cette valeur afin de pouvoir réaliser l'extraction
								; de g1 et de g2 dans la même boucle.
 
				; extraction de la mesure g1 dans le registre al par la méthode du décalage logique à droite 
				; de 11 rangs binaires puis stockage dans le tableau tab_g1 dédié

				shr ax, cl
				mov [tab_g1 + si], al

 
				; extraction de la mesure g2 dans le registre dx par la méthode du masque logique
				; puis stockage dans le tableau tab_g2 dédié
				and dx, 07ffh
				mov [tab_g2 + di], dx

 
				inc si
				add di, 2
				jmp bcl_extraction
	fin_bcl_extraction :

 
	; 3) calculer la moyenne des mesures g1 et g2
	; attention : tab_g1 est un tableau de byte, tab_g2 est un tableau de word ... on ne se déplace pas de la même façon ...
	; prévoir 2 "compteurs".
	mov si, 0

	mov al, 0
	mov dx, 0
	mov di, 0

 
	; on fait une boucle qui additionne toutes les valeurs des mesures g1 dans al 
	; et toutes les valeurs des mesures g2 dans dx
	bcl_addition :		cmp si, 10

				je fin_bcl_addition
 
				add al, [tab_g1 + si]
 
				add dx, [tab_g2 + di]

 
				inc si
				add di, 2
				jmp bcl_addition
	fin_bcl_addition :

 
	mov cl, al 	; sauvegarde, dans cl, la somme des mesures g1
 
	; calcul de la moyenne des valeurs des mesures g2 (division) et stockage du résultat sur la pile
	mov ax, dx

	mov dx, 0
	mov bx, 10
	div bx

 
	push ax
 
	; calcul de la moyenne des valeurs des mesures g1 (division) et stockage du résultat sur la pile
	mov al, cl	; restauration de la somme des mesures g1

	mov ah, 0 
	mov bl, 10
	div bl

 
	mov ah, 0
	push ax
 
	; 4) affichage de la moyenne des mesures g1 en hexadécimal et affichage de la moyenne des mesures g2 en binaire
	; affichage du message écran annonçant l'affichage de la moyenne des mesures g1 en hexadécimal

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

 
	call affichage_hexa 	; la moyenne des mesures g1 est déjà la dernière donnée empilée. 
				; la procédure peut donc être appelé.
 
	; affichage du message écran annonçant l'affichage de la moyenne des mesures g2 en binaire	
	mov ah, 09h

	mov dx, offset msg_binaire
	int 21h
 
	pop bx				; nous mettons la moyennes des mesures g2 dans le registre bx 

	call affichage_binaire		; et nous appelons la procédure.
 
 
	; retour au dos
	mov ax, 4c00h

	int 21h
 
 
	; affichage_binaire :
	; Objectif : 			Afficher à l'écran, sous forme binaire, un nombre passé en paramètre.
	; Passage des paramètre : 	Par un registre. Le nombre à afficher doit se trouver dans BX.
	; Pré-condition : 		BX doit contenir un nombre. L'interprétation du résultat binaire revient

	;				à l'utilisateur. Ex : dans le cas d'un nombre réel ... A noter que même
	;				si BX ne contient pas un nombre, la procédure s'exécutera avec succès
	;				et affichera le contenu du registre BX mais l'interprétation perdra son sens.
	affichage_binaire proc near 	; signalement (non obligatoire) du début d'une procédure dans le même segment de code que le programme appelant
	        ; ESP désigne le sommet actuel de la pile alors qu'EBP pointe sur le bas "relatif" de la pile.
		; Pour l'instant, EBP pointe sur le "bas" de l'espace dédié ("stack-frame") au programma appelant.

		; On va donc le faire pointer sur ESP (le sommet) pour dire que l'espace de travail de notre
		; programme appelé commence ici. Cela nous fera d'ailleurs, en même temps, une sauvegarde d'ESP.	
		; Bien sûr, il faut penser à sauvegarder le registre EBP actuel pour pouvoir le faire à nouveau
		; pointer sur l'espace dédié au programme appelant à la fin de notre procédure.
		push ebp	; Sauvegarde du contexte précédent

		mov ebp, esp	; Sauvegarde du pointeur de pile
 
		mov cx, 16					; on initialise une boucle de 16 itérations nous permettant ainsi de traiter

								; des nombres codés sur 16 bits ou moins.
		bcl0 :	
			debut_si : 		
					shl bx, 1

					jc aff1			; on effectue un déplacement logique vers la gauche. le bit sortant active le
								; carry flag. il suffit de tester celui-ci pour en déduire l'état du bit sortant
					mov ah, 02h		; (0 ou 1) et d'afficher en conséquence le bon caractère ('0' ou '1').

					mov dl, '0'
					int 21h
					jmp fin_si
 
			aff1 : 		
					mov ah, 02h

					mov dl, '1'
					int 21h
			fin_si : 
		loop bcl0
 
		; Restauration du contexte : ESP doit pointer sur l'adresse de retour !

	        mov esp, ebp
	        pop ebp
 
		ret						; permet de revenir au programme principal, à l'instruction suivant l'instruction "call" 

								; qui appellera cette procédure.
 
	affichage_binaire endp					; signalement (non obligatoire) de la fin d'une procédure 
 
 
	; affichage_hexa :
	; Objectif :			Afficher à l'écran, sous forme hexadécimal, un nombre passé en paramètre.
	; Passage des paramètre :	Par la pile. Le nombre a afficher doit être le dernier élément
	;				empilé avant l'appel de cette procédure.

	; Pré-condition : 		Le dernier élément empilé doit être un nombre. Même remarques que pour
	;				affichage_binaire.
	affichage_hexa proc near
                ; ESP désigne le sommet actuel de la pile alors qu'EBP pointe sur le bas "relatif" de la pile.
	        ; Pour l'instant, EBP pointe sur le "bas" de l'espace dédié ("stack-frame") au programma appelant.
	        ; On va donc le faire pointer sur ESP (le sommet) pour dire que l'espace de travail de notre
	        ; programme appelé commence ici. Cela nous fera d'ailleurs, en même temps, une sauvegarde d'ESP.	

	        ; Bien sûr, il faut penser à sauvegarder le registre EBP actuel pour pouvoir le faire à nouveau
	        ; pointer sur l'espace dédié au programme appelant à la fin de notre procédure.
	        push ebp	; Sauvegarde du contexte précédent
	        mov ebp, esp	; Sauvegarde du pointeur de pile

 
                mov ah, 02h
 
		bcl1 :		cmp cl, 0				; à chaque itération, on effectue un décalage logique vers la droite de

				jl fin_bcl1				; moins en moins important (12, 8, 4, 0) et on applique un masque. cela nous permet
									; de garder qu'un seul groupe de 4 rangs binaires à la fois. on affiche ce groupe à l'écran.
 
				mov bx, ss:[bp + 4]

									; on récupère, dans la pile, le paramètre passé à cette procédure (= le nombre à afficher) 
				shr bx, cl				; ss est facultatif et désigne l'adresse mémoire du segment de pile.
				and bx, 000fh				; dans ce segment on veut accéder au contenu de l'offset

									; bp + 4 (4 car 4 octets = 2 emplacements sur la pile),
				mov dl, [tab_hexa + bx]			; car c'est dans cet emplacement que se trouve notre paramètre (bp + 2 contient 

				int 21h					; la sauvegarde de bp faite au début de cette procédure.
									; il faut remonter (+4) à l'emplacement précèdent car la pile croît vers
				sub cl, 4				; les adresses basses de la mémoire donc le sommet se trouve plus bas que le paramètre. 

				jmp bcl1			 
		fin_bcl1 :						
 
 
		; Restauration du contexte : ESP doit pointer sur l'adresse de retour !
	        mov esp, ebp

	        pop ebp
 
 
		ret	2			; par défaut, ret récupère l'adresse de retour au programme et la stocke dans 
						; le registre ip (car prochaine instruction) puis incrémente le registre sp de 
						; 2 octets afin de "libérer" l'emplacement occupé par cette adresse de retour.

	affichage_hexa endp			; ici, nous lui demandons de "libérer" 2 octets de plus afin de libérer 
						; l'emplacement contenant le paramètre passé à cette procédure (= le nombre à afficher).
end

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

Cette version Windows est un peu vieille, je ne l'ai pas retouché depuis car il n'est pas dans mes préoccupations actuelles d'aller faire un voyage sous Windows. Donc cette version, contrairement à la version GNU/Linux ne compte ni les calculs en virgule flottante, ni la gestion du fait que la somme des G1, peut dépasser 2^8 selon les valeurs choisies dans Mesures et donc ne pas loger dans un octet/registre 8 bits.

segment .data

	; exercice 1
		;données
		nombre dw 12666
		tab_hexa db "0123456789abcdef"

 
		; messages écran
		exercice1 db "Exercice 1 :",13,10,"$"
		message_binaire db "Affichage du nombre declare dans le segment de donnees (ici : 12666) en binaire : ","$"

		message_hexa db 13,10,"Affichage du nombre declare dans le segment de donnees (ici : 12666) en hexadecimal : ","$"
 
	; exercice 2
		;données

		mesures dw 0e765h, 6ab3h, 8fa1h, 905ch, 0a9fdh, 5da9h, 4807h, 8fa2h, 0a10bh, 0b95h

		tab_g1 times 10 db '0'
		tab_g2 times 10 dw '0'

 
		; messages écran
		exercice2 db 13,10,10,"Exercice 2 :","$"

		msg_hexa db 13,10,"Affichage de la moyenne des mesures g1 en hexadecimal : ","$"
		msg_binaire db 13,10,"Affichage de la moyenne des mesures g2 en binaire : ","$"

 
 
segment stack stack
	resb 64
	stackstop:
 
 

segment .code
..start:
	; initialisation du segment de données
	mov ax, data

	mov ds, ax
 
	; exercice 1
	; affichage du message écran de début de l'exercice 1
	mov ah, 09h

	mov dx, exercice1
	int 21h
 
	; affichage du message annonçant l'affichage du nombre déclaré dans le segment de données, en binaire
	mov ah, 09h

	mov dx, message_binaire
	int 21h
 
	mov bx, [nombre]		; passage d'un paramètre, par un registre, à la procédure

	call affichage_binaire 		; appel de la procédure
 
	; affichage du message annonçant l'affichage du nombre déclaré dans le segment de données en hexadécimal
	mov ah, 09h

	mov dx, message_hexa
	int 21h
 
	push word [nombre]			; passage d'un paramètre, par la pile, à la procédure

	call affichage_hexa		; appel de la procédure
 
 
	; exercice 2
	; affichage du message écran de début de l'exercice 2
	mov ah, 09h

	mov dx, exercice2
	int 21h
 
	; 1) Empiler les mesures sur la pile
	; On fait une boucle pour empiler chacune des mesures.

	; Comme la pile du 8086 est de type lifo (= last in, first out), 
	; nous empilerons la 10eme mesure en premier et la 1er en dernier.
	; ainsi, dans le 2), la première mesure dépilée sera bien la première du tableau mesure.
 
	; Attention : "mesures" est un tableau de word (16 bits), pas un tableau de byte (8 bits)
	; donc on ne se déplace pas de 1 en 1 entre les cases (entre leurs adresses pour être précis)
	; mais de 2 en 2.

	mov cx, 20
 
	bcl_empile_mesures :	
				cmp cx, 2

				jb fin_bcl_empile_mesures
 
				mov si, cx				; nous aurions pu ecrire une boucle en utilisant le registre si (ou di ou bx), 
				sub si, 2				; et en decrementant celui-ci avant le "push". mais, par convention, on ne 

				push word [mesures + si]			; touche pas le compteur d'une boucle au milieu du deroulement de celle-ci. d'où 2 registres.
 
				sub cx, 2

				jmp bcl_empile_mesures
	fin_bcl_empile_mesures :
 
	; 2) Extraire et classer les mesures g1 et g2 dans deux tableaux independants
	; Attention : tab_g1 est un tableau de byte, tab_g2 est un tableau de word ... on ne se déplace pas de la même façon ...
	; prévoir 2 "compteurs".
	mov si, 0

	mov cl, 11
	mov di, 0
 
	bcl_extraction :

				cmp si, 10
				je fin_bcl_extraction
 
				pop ax				; on désempile une mesure

				mov dx, ax 			; on sauvegarde cette valeur afin de pouvoir réaliser l'extraction
								; de g1 et de g2 dans la même boucle.
 
				; extraction de la mesure g1 dans le registre al par la méthode du décalage logique à droite 
				; de 11 rangs binaires puis stockage dans le tableau tab_g1 dédié

				shr ax, cl
				mov [tab_g1 + si], al

 
				; extraction de la mesure g2 dans le registre dx par la méthode du masque logique
				; puis stockage dans le tableau tab_g2 dédié
				and dx, 07ffh
				mov [tab_g2 + di], dx

 
				inc si
				add di, 2
				jmp bcl_extraction
	fin_bcl_extraction :

 
	; 3) calculer la moyenne des mesures g1 et g2
	; attention : tab_g1 est un tableau de byte, tab_g2 est un tableau de word ... on ne se déplace pas de la même façon ...
	; prévoir 2 "compteurs".
	mov si, 0

	mov al, 0
	mov dx, 0
	mov di, 0

 
	; on fait une boucle qui additionne toutes les valeurs des mesures g1 dans al 
	; et toutes les valeurs des mesures g2 dans dx
	bcl_addition :		cmp si, 10

				je fin_bcl_addition
 
				add al, [tab_g1 + si]
 
				add dx, [tab_g2 + di]

 
				inc si
				add di, 2
				jmp bcl_addition
	fin_bcl_addition :

 
	mov cl, al 	; sauvegarde, dans cl, la somme des mesures g1
 
	; calcul de la moyenne des valeurs des mesures g2 (division) et stockage du résultat sur la pile
	mov ax, dx

	mov dx, 0
	mov bx, 10
	div bx

 
	push ax
 
	; calcul de la moyenne des valeurs des mesures g1 (division) et stockage du résultat sur la pile
	mov al, cl	; restauration de la somme des mesures g1

	mov ah, 0 
	mov bl, 10
	div bl

 
	mov ah, 0
	push ax
 
	; 4) affichage de la moyenne des mesures g1 en hexadécimal et affichage de la moyenne des mesures g2 en binaire
	; affichage du message écran annonçant l'affichage de la moyenne des mesures g1 en hexadécimal

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

 
	call affichage_hexa 	; la moyenne des mesures g1 est déjà la dernière donnée empilée. 
				; la procédure peut donc être appelé.
 
	; affichage du message écran annonçant l'affichage de la moyenne des mesures g2 en binaire	
	mov ah, 09h

	mov dx, msg_binaire
	int 21h
 
	pop bx				; nous mettons la moyennes des mesures g2 dans le registre bx 

	call affichage_binaire		; et nous appelons la procédure.
 
 
	; retour au dos
	mov ax, 4c00h

	int 21h
 
 
; affichage_binaire :
; Objectif : 			Afficher à l'écran, sous forme binaire, un nombre passé en paramètre.
; Passage des paramètre : 	Par un registre. Le nombre à afficher doit se trouver dans BX.
; Pré-condition : 		BX doit contenir un nombre. L'interprétation du résultat binaire revient
;				à l'utilisateur. Ex : dans le cas d'un nombre réel ... A noter que même
;				si BX ne contient pas un nombre, la procédure s'exécutera avec succès

;				et affichera le contenu du registre BX mais l'interprétation perdra son sens.
affichage_binaire:
        ; ESP désigne le sommet actuel de la pile alors qu'EBP pointe sur le bas "relatif" de la pile.
	; Pour l'instant, EBP pointe sur le "bas" de l'espace dédié ("stack-frame") au programma appelant.
	; On va donc le faire pointer sur ESP (le sommet) pour dire que l'espace de travail de notre
	; programme appelé commence ici. Cela nous fera d'ailleurs, en même temps, une sauvegarde d'ESP.	

	; Bien sûr, il faut penser à sauvegarder le registre EBP actuel pour pouvoir le faire à nouveau
	; pointer sur l'espace dédié au programme appelant à la fin de notre procédure.
	push ebp	; Sauvegarde du contexte précédent
	mov ebp, esp	; Sauvegarde du pointeur de pile

 
	mov cx, 16					; on initialise une boucle de 16 itérations nous permettant ainsi de traiter
							; des nombres codés sur 16 bits ou moins.
	bcl0 :	
		debut_si : 		
				shl bx, 1

				jc aff1			; on effectue un déplacement logique vers la gauche. le bit sortant active le
							; carry flag. il suffit de tester celui-ci pour en déduire l'état du bit sortant
				mov ah, 02h		; (0 ou 1) et d'afficher en conséquence le bon caractère ('0' ou '1').

				mov dl, '0'
				int 21h
				jmp fin_si
 
		aff1 : 		
				mov ah, 02h

				mov dl, '1'
				int 21h
		fin_si : 
	loop bcl0
 
        ; Restauration du contexte : ESP doit pointer sur l'adresse de retour !

	mov esp, ebp
	pop ebp
 
	ret						; permet de revenir au programme principal, à l'instruction suivant l'instruction "call" 

							; qui appellera cette procédure.
 
 
; affichage_hexa :
; Objectif :			Afficher à l'écran, sous forme hexadécimal, un nombre passé en paramètre.
; Passage des paramètre :	Par la pile. Le nombre a afficher doit être le dernier élément
;				empilé avant l'appel de cette procédure.
; Pré-condition : 		Le dernier élément empilé doit être un nombre. Même remarques que pour
;				affichage_binaire.
affichage_hexa:

	; ESP désigne le sommet actuel de la pile alors qu'EBP pointe sur le bas "relatif" de la pile.
	; Pour l'instant, EBP pointe sur le "bas" de l'espace dédié ("stack-frame") au programma appelant.
	; On va donc le faire pointer sur ESP (le sommet) pour dire que l'espace de travail de notre
	; programme appelé commence ici. Cela nous fera d'ailleurs, en même temps, une sauvegarde d'ESP.	
	; Bien sûr, il faut penser à sauvegarder le registre EBP actuel pour pouvoir le faire à nouveau
	; pointer sur l'espace dédié au programme appelant à la fin de notre procédure.

	push ebp	; Sauvegarde du contexte précédent
	mov ebp, esp	; Sauvegarde du pointeur de pile						

 
	mov cl, 12
	mov ah, 02h
 
	bcl1 :		cmp cl, 0				; a chaque itération, on effectue un décalage logique vers la droite de

			jl fin_bcl1				; moins en moins important (12, 8, 4, 0) et on applique un masque. cela nous permet
								; de garder qu'un seul groupe de 4 rangs binaires à la fois. on affiche ce groupe à l'écran.
 
			mov bx, word [bp + 4]			; normalement, on utilise sp pour se déplacer mais tasm est un peu dur de la feuille ...

								; on récupère, dans la pile, le paramètre passé à cette procédure (= le nombre à afficher) 
			shr bx, cl				; ss est facultatif et désigne l'adresse mémoire du segment de pile.
			and bx, 000fh				; dans ce segment on veut accéder au contenu de l'offset

								; bp + 4 (4 car 4 octets = 2 emplacements sur la pile),
			mov dl, [tab_hexa + bx]			; car c'est dans cet emplacement que se trouve notre paramètre (bp + 2 contient 

			int 21h					; la sauvegarde de bp faite au début de cette procédure.
								; il faut remonter (+2) à l'emplacement précèdent car la pile croît vers
			sub cl, 4				; les adresses basses de la mémoire donc le sommet se trouve plus bas que le paramètre. 

			jmp bcl1			 
	fin_bcl1 :						
 
	; Restauration du contexte : ESP doit pointer sur l'adresse de retour !
	mov esp, ebp

	pop ebp
 
					; par défaut, ret récupère l'adresse de retour au programme et la stock dans 
	ret 2				; le registre ip (car prochaine instruction) puis incrémente le registre sp de 
					; 2 octets afin de "libérer" l'emplacement occupé par cette adresse de retour.

					; ici, nous lui demandons de "libérer" 2 octets de plus afin de libérer 
					; l'emplacement contenant le paramètre passé à cette procédure (= le nombre à afficher).

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

section .data
	; exercice 1

		; données
		nombre dd 4269666
		tab_hexa db "0123456789ABCDEF"
 
		; messages écran

		exercice1 db "Exercice 1 :",13,10
		lenexo1: equ $-exercice1
 
		message_binaire db "Affichage du nombre déclaré dans le segment de données (ici : 4269666) en binaire : "

		lenmsgbinaire: equ $-message_binaire	
 
		message_hexa db 13,10,"Affichage du nombre déclaré dans le segment de données (ici : 4269666) en hexadecimal : "

		lenmsghexa: equ $-message_hexa	
 
	; exercice 2
		;données
		;mesures dw 0ffffh, 0ffffh, 0ffffh, 0ffffh, 0ffffh, 0ffffh, 0ffffh, 0ffffh, 0ffffh, 0ffffh		

		mesures dw 0e765h,6ab3h, 8fa1h, 905ch, 0a9fdh, 5da9h, 4807h, 8fa2h, 0a10bh, 0b95h

 
		; messages écran
		exercice2 db 13,10,10,"Exercice 2 :"
		lenexo2: equ $-exercice2	
 
		msg_hexa db 13,10,"Affichage de la moyenne des mesures G1 en hexadecimal : "

		lenmsg2hexa: equ $-msg_hexa	
 
		msg_binaire db 13,10,"Affichage de la moyenne des mesures G2 en binaire : "

		lenmsg2binaire: equ $-msg_binaire	
 
	; exercice 3
		exercice3 db 13,10,10,"Exercice 3 :"

		lenexo3: equ $-exercice3
 
		msg_g1_flottant db 10,"Affichage de la moyenne G1 en virgule flottante : ",0

		msg_g2_flottant db "Affichage de la moyenne G2 en virgule flottante : ",0
 
		diviseur dq 10
		format db "%s%f", 10, 0

 
	; fin
		fin: db 10,'fin',10
		lenfin: equ $-fin
 
 

section .bss
	tab_g1	resw 10
	tab_g2	resw 10
 
	dividende_g1: resq 1

	dividende_g2: resq 1
 
	compteur: resb 1
 
 
section .text

	global _start
	extern printf
 
_start:	
	; EXERCICE 1
	; Affichage du message écran de début de l'exercice 1
	mov eax, 4

	mov ebx, 1
	mov ecx, exercice1
	mov edx, lenexo1
	int 80h	

 
	; Affichage du message annonçant l'affichage du nombre déclaré dans le segment de données, en binaire
	mov eax, 4
	mov ebx, 1

	mov ecx, message_binaire
	mov edx, lenmsgbinaire
	int 80h	

 
	mov eax, [nombre]		; Passage d'un paramètre, par un registre, à la procédure
	call affichage_binaire 		; Appel de la procédure

 
	; Affichage du message annonçant l'affichage du nombre déclaré dans le segment de données en hexadécimal
	mov eax, 4
	mov ebx, 1

	mov ecx, message_hexa
	mov edx, lenmsghexa
	int 80h	

 
	push dword [nombre]		; Passage d'un paramètre, par la pile, à la procédure
	call affichage_hexa		; Appel de la procédure

 
 
	; exercice 2
	; affichage du message écran de début de l'exercice 2
	mov eax, 4
	mov ebx, 1

	mov ecx, exercice2
	mov edx, lenexo2
	int 80h	

 
	; 1) empiler les mesures sur la pile
	; on fait une boucle pour empiler chacune des mesures.
	; comme la pile du 8086 est de type lifo (= last in, first out), 
	; nous empilerons la 10eme mesure en premier et la 1er en dernier.
	; ainsi, dans le 2), la première mesure désempilée sera bien la première du tableau mesure.
 
        ; Attention : "mesures" est un tableau de word (16 bits), pas un tableau de byte (8 bits)

	; donc on ne se déplace pas de 1 en 1 entre les cases (entre leurs adresses pour être précis)
	; mais de 2 en 2.
	mov ecx, 20
 
	bcl_empile_mesures :	
				cmp ecx, 2

				jb fin_bcl_empile_mesures
 
				mov eax, ecx
				sub eax, 2							
				push word [mesures+eax]

 
				sub ecx, 2
				jmp bcl_empile_mesures
	fin_bcl_empile_mesures :
 
 
	; 2) extraire et classer les mesures g1 et g2 dans deux tableaux independants

	mov ecx, 0
 
	bcl_extraction :	cmp ecx, 20

				je fin_bcl_extraction
 
				pop ax				; on désempile une mesure
				mov dx, ax 			; on dupplique cette valeur afin de pouvoir réaliser l'extraction

								; de g1 et de g2 dans la même boucle.
 
				; extraction de la mesure g1 dans le registre ax par la méthode du décalage logique à droite 
				; de 11 rangs binaires puis stockage dans le tableau tab_g1 dédié
				shr ax, 11

				mov [tab_g1+ecx], ax
 
				; extraction de la mesure g2 dans le registre dx par la méthode du masque logique
				; puis stockage dans le tableau tab_g2 dédié

				and dx, 07ffh
				mov [tab_g2+ecx], dx

 
				add ecx, 2
				jmp bcl_extraction
	fin_bcl_extraction :
 
	; 3) calculer la moyenne des mesures g1 et g2

	mov eax, 0
	mov ecx, 0
	mov edx, 0

 
	; on fait une boucle qui additionne toutes les valeurs des mesures g1 dans ax 
	; et toutes les valeurs des mesures g2 dans dx
	bcl_addition :				
				cmp ecx, 20

				je fin_bcl_addition
 
				add ax, [tab_g1 + ecx]
 
				add dx, [tab_g2 + ecx]

 
				add ecx, 2
				jmp bcl_addition
	fin_bcl_addition :
 
	; Sauvegarde pour plus tard (calcul flottant entre autres)

	mov [dividende_g1], ax
	mov [dividende_g2], dx

 
	; calcul de la moyenne des valeurs des mesures g2 (division) et stockage du résultat sur la pile	
	mov ax, dx
	mov dx, 0

	mov bx, word [diviseur]
	div bx	

 
	push eax
 
 
	; calcul de la moyenne des valeurs des mesures g1 (division) et stockage du résultat sur la pile	
	mov ax, word [dividende_g1]		; restauration de la somme des mesures g1

	mov dx, 0 
	mov bx, word [diviseur]

	div bx
 
	push eax
 
 
	; 4) affichage de la moyenne des mesures g1 en hexadécimal et affichage de la moyenne des mesures g2 en binaire
	; affichage du message écran annonçant l'affichage de la moyenne des mesures g1 en hexadécimal
	mov eax, 4

	mov ebx, 1
	mov ecx, msg_hexa
	mov edx, lenmsg2hexa
	int 80h	

 
	call affichage_hexa 	; la moyenne des mesures g1 est déjà la dernière donnée empilée. 
				; la procédure peut donc être appelé.
 
 
	; affichage du message écran annonçant l'affichage de la moyenne des mesures g2 en binaire	
	mov eax, 4

	mov ebx, 1
	mov ecx, msg_binaire
	mov edx, lenmsg2binaire
	int 80h	

 
	pop eax				; nous mettons la moyennes des mesures g2 dans le registre eax 
	call affichage_binaire		; et nous appelons la procédure.
 
 
	; EXERCICE 3
	; Affichage du message écran annonant l'exercice 3

	mov eax, 4
	mov ebx, 1
	mov ecx, exercice3
	mov edx, lenexo3
	int 80h

 
	; Calcul flottant de la moyenne des G1
	fld qword [dividende_g1]	; on positionne le dividende au sommet de la pile du coproc arithmètique
    	fdiv qword [diviseur]		; on effectue la division

 
	; Affichage résultat G1
	fstp qword [esp]		; on exporte le résultat de la pile du coproc vers le sommet de la pile "classique"
	push msg_g1_flottant		; message présentant le résultat

	push format			; format printf classique : un string puis un flottant
	call printf			; appel de printf
 
	; Calcul flottant de la moyenne des G2
	fld qword [dividende_g2]

    	fdiv qword [diviseur]
 
	; Affichage résultat G2
	fstp qword [esp]

	push msg_g2_flottant
	push format
	call printf
 
 
	; fin
	mov eax, 4

	mov ebx, 1
	mov ecx, fin
	mov edx, lenfin
	int 80h

 
	mov eax, 1
	mov ebx, 0
	int 80h

 
 
; affichage_binaire :
; Objectif : 			Afficher à l'écran, sous forme binaire, un nombre passé en paramètre.
; Passage des paramètre : 	Par un registre. Le nombre à afficher doit se trouver dans EAX.
; Pré-condition : 		BX doit contenir un nombre. L'interprétation du résultat binaire revient
;				à l'utilisateur. Ex : dans le cas d'un nombre réel ... A noter que même
;				si EAX ne contient pas un nombre, la procédure s'exécutera avec succès
;				et affichera le contenu du registre EAX mais l'interprétation perdra son sens.
affichage_binaire:

	push ebp 	; Sauvegarde du contexte précédent
	mov ebp, esp	; Sauvegarde du pointeur de pile

 
	push eax	; Pour se simplifier la vie, on stocke le paramètre sur la pile
 
	mov byte [compteur], 32	; on initialise une boucle de 32 itérations nous permettant ainsi de traiter

				; des nombres codés sur 16 bits ou moins.
 
 
	bcl0 :	
				cmp byte [compteur], 0

				jle fin_bcl10
 
		debut_si :	pop eax
				shl eax, 1

				push eax		; On garde la modification, pour ne pas toujours travailler sur le même bit
				jc aff1			; on effectue un déplacement logique vers la gauche. le bit sortant active le
							; carry flag. il suffit de tester celui-ci pour en déduire l'état du bit sortant
							; (0 ou 1) et d'afficher en conséquence le bon caractère ('0' ou '1').

				mov eax, 4
				mov ebx, 1			
				mov ecx, tab_hexa
				mov edx, 1

				int 80h
				jmp fin_si
 
		aff1 :		
				mov eax, 4

 				mov ebx, 1
				mov ecx, tab_hexa
				add ecx, 1

				mov edx, 1
				int 80h				
 
		fin_si : 	
				dec byte [compteur]			
				jmp bcl0
	fin_bcl10 :

				pop eax		; On supprime le paramètre empilé au début de cette procèdure
 
 
	; Restauration du contexte : ESP doit pointer sur l'adresse de retour !
	mov esp, ebp	
	pop ebp

 
	; On revient au programma appelant
	ret		; Permet de revenir au programme principal, à l'instruction suivant l'instruction "call" 
			; qui appellera cette procédure.
 
 
; affichage_hexa :
; Objectif :			Afficher à l'écran, sous forme hexadécimal, un nombre passé en paramètre.
; Passage des paramètre :	Par la pile. Le nombre a afficher doit être le dernier élément

;				empilé avant l'appel de cette procédure.
; Pré-condition : 		Le dernier élément empilé doit être un nombre. Même remarques que pour
;				affichage_binaire.
affichage_hexa:
	; ESP désigne le sommet actuel de la pile alors qu'EBP pointe sur le bas "relatif" de la pile.
	; Pour l'instant, EBP pointe sur le "bas" de l'espace dédié ("stack-frame") au programma appelant.
	; On va donc le faire pointer sur ESP (le sommet) pour dire que l'espace de travail de notre

	; programme appelé commence ici. Cela nous fera d'ailleurs, en même temps, une sauvegarde d'ESP.	
	; Bien sûr, il faut penser à sauvegarder le registre EBP actuel pour pouvoir le faire à nouveau
	; pointer sur l'espace dédié au programme appelant à la fin de notre procédure.
	push ebp	; Sauvegarde du contexte précédent
	mov ebp, esp	; Sauvegarde du pointeur de pile	

 
	; Déclage de 28 bits au maximum	
	mov byte [compteur], 28
 
	bcl1 :		cmp byte [compteur], 0		; A chaque itération, on effectue un décalage logique vers la droite de

			jl fin_bcl1			; moins en moins important (24, 20, 16, 12, 8, 4, 0) et on applique un masque. cela nous permet
							; de garder qu'un seul groupe de 4 rangs binaires à la fois. On affiche ce groupe à l'écran.
 
			mov ebx, [ebp+8]		; On récupère, dans la pile, le paramètre passé à cette procédure (= le nombre à afficher)

			mov ecx, [compteur]		; EBP 		->	Sauvegarde EBP réalisée au début de cette procédure
			shr ebx, cl			; EBP + 4 	->	Adresse de retour au programme appelant

			and ebx, 000fh			; EBP + 8	->	Notre paramètre
							; On incrémente par pas de 4 car on est sur du 32 bits. Chaque "case" de la pile fait donc 32 bits, soit 4 bytes.
			mov eax, 4

			mov ecx, tab_hexa
			add ecx, ebx
			mov ebx, 1			
			mov edx, 1

			int 80h	
 
			sub byte [compteur], 4					
			jmp bcl1			 
	fin_bcl1 :						

 
	; Restauration du contexte : ESP doit pointer sur l'adresse de retour !
	mov esp, ebp
	pop ebp
 
	ret 4		; Par défaut, RET récupère l'adresse de retour au programme et la stocke dans 

			; le registre IP (car prochaine instruction) puis incrémente le registre ESP de 
			; 2/4 octets (selon architecture) afin de "libérer" l'emplacement occupé par cette adresse de retour.
			; Ici, nous lui demandons de "libérer" 4 octets de plus afin de libérer 
			; l'emplacement contenant le paramètre passé à cette procédure (= le nombre à afficher).
			; Cela permet d'éviter que le paramètre reste sur la pile et fasse confusion à la prochaine execution.

Assembleur 8086+ : exercice 4

Table des matières

Comme d'habitude, je ne détaillerai rien concernant la compilation/l'édition des liens encore cette fois-ci. Reportez-vous au premier billet pour plus d'informations.

Explications préalables

Avant de commencer ce nouvel exercice, je souhaite revenir sur deux ou trois trucs qui m'ont été demandés.

Pourquoi mov ax, 4C00h pour fermer le programme ?

Il y a deux questions ici.

  1. D'une part, on ne peut pas utiliser une autre instruction pour terminer le programme. L'interruption 20h se destine aux programmes COM et la fonction 00h de l'interruption 21h a un usage incertain sur les programmes EXE. Comme d'habitude, je ne l'invente pas
  2. D'autre part, pourquoi mettre le nombre 4C00 en hexadécimal dans le registre AX ? L'interruption 21h s'attend à avoir le "nom" de la fonction à appeler (4Ch) dans AH et la fonction 4Ch attend le code retour à retourner dans AH. On retourne 0 (donc 00h) quand on veut indiquer que l'exécution du programme s'est bien déroulée (équivalent à un exit(0) en C). Mov ax, 4C00h revient à faire mov ah, 4Ch mov al, 00h si vous préférez. Il en va de même pour la saisie d'une chaîne de caractères : mov ax, 0C0Ah permet d'appeler la fonction qui a la charge de vider le buffer d'entrée puis d'appeler la fonction de saisie voulue (ici on choisi la 0Ah).

Pourquoi j'utilise des accents dans mes chaînes de caractères sous GNU/Linux mais pas sous Windows ?

Car cela serait plus compliqué pour vous.

Sous GNU/Linux, les éditeurs ont souvent le jeu de caractères (charset dans la langue de Shakespeare) "UTF-8" d'actif par défaut (en tout cas, je n'en ai pas croisé un qui ne le soit pas). Le shell est aussi dans ce jeu de caractères. Il n'y a donc pas de problèmes pour l'affichage des caractères accentués.

Sous Windows, il est possible de faire afficher des caractères accentués dans l'invite de commande, contrairement à l'idée reçue très répandue mais il faut choisir le bon charset dans votre éditeur : le charset "OEM 850". Comme tous les éditeurs ne le propose pas (Notepad++ does), je propose du code sans accents afin de ne pas embrouiller ceux qui ne veulent pas s'embêter avec ça. Tester et approuver sous XP.

Tes codes sont pas optimisés, tu utilises n'importe quel registre pour n'importe quoi !

J'ai jamais prétendu le contraire 😛 .

Concernant l'optimisation, je ne suis pas convaincu par tous les conseils qu'on trouve sur le net et dans le doute, j'évite de les utiliser. Exemple : pour vider un registre, il est de coutume de faire un xor registre, registre (remplacez registre par le registre à vider). Je fais un mov registre,0. Si on regarde la liste des instructions que je vous ai donné dans le premier billet, on voit que sur le 8086 le mov registre, immédiat coûte plus de cycle d'horloge que le xor registre, registre (4 contre 3). Mais sur les générations suivantes, on tombe à égalité à 2 cycles. Donc je me dis qu'aujourd'hui ça doit être pareil voir mieux. Après, je dois aussi être complet et vous informer que certaines instructions prennent moins de code machine que d'autres et que donc le poids des exécutables générés peuvent variés. Exemple : un xor eax, eax a un code machine plus petit qu'un mov eax, 0. Cela peut avoir son importance dans certains types de codes (ex.: exploit).

Concernant l'utilisation des registres, je ne peux être que d'accord. Mais étant donné que je souhaite montrer progressivement les possibilités, j'évite de parler de la pile dès le premier programme 😉 .

Ceci étant dit, on va pouvoir commencer un nouvel exercice.

Exercice 4 : énoncé

  1. Transformer une lettre minuscule saisie par l'utilisateur en une lettre majuscule. Si le caractère saisi est bien une lettre minuscule, on la transforme et on l'affiche, sinon on affiche un message d'erreur.
  2. Transformer une chaîne de caractères minuscules saisie par l'utilisateur en une chaîne de caractères majuscules. On affichera la chaîne transformée. Si une lettre n'est pas une minuscule, on l'affichera sans tenter de la transformer. Pareil si la chaîne entière ne contient aucune minuscule.
  3. Faire saisir une chaîne à l'utilisateur et n'afficher que les voyelles de cette chaîne. S'il n'y a aucune voyelle dans la chaîne, on affichera un message.
  4. Déclarer un nombre inférieur à 65535 dans le segment des données puis faire afficher ce nombre en hexadécimal.

Note : Comme d'habitude, cet énoncé n’est pas de moi : je l’ai repris à T.M. / E.R. et je l’ai modifié.

Exercice 4 : aides

Il faut :

  • Regarder la table ASCII et remarquer qu'une minuscule est comprise entre 61h et 7A (soit entre 97 et 122 en décimal). Les majuscules vont de 41h à 5Ah, soit 20h en moins que les minuscules leur correspondant. A partir de là, la question 1 est résolue : il ne vous reste plus qu'a utiliser l'instruction de comparaison et les sauts pour réagir en fonction du caractère saisi 😉 .
  • Pour la question 2, on réutilise la question 1 à l'intérieur d'une boucle qui parcourt la chaine saisie. Si le caractère est une minuscule, on le remplace dans la chaîne par son équivalent majuscule. Sinon non. C'est aussi simple que cela.
  • Pour la question 3, il suffit de parcourir la chaîne. Si le caractère est compris dans l'ensemble {a, e, i, o, u, y, A, E, I, O, U, Y}, on l'affiche, sinon non. Je suis sûr que certains d'entre vous sont déjà en train de compter le nombre de tests à faire. Ce n'est pas la bonne méthode, je pense. Il suffit, pour chaque caractère de la chaîne saisie, de boucler sur un tableau qui contient les voyelles et de tester si le caractère saisi correspond à la voyelle en cours.
  • Pour savoir si le caractère saisi par l'utilisateur n'est pas une minuscule, ou pour savoir si il n'y a pas de voyelle dans la chaîne et afficher un message en conséquence, il suffit de se faire une sorte de booléen en mémoire ou dans un registre. Si j'ai affiché une voyelle, je met le booléen à vrai, par exemple.
  • Pour afficher un nombre <= 65535 (donc 16 bits) en hexadécimal, il faudrait pouvoir isoler 4 groupes de 4 bits et afficher la correspondance hexadécimale de chaque groupe. On utilisera un masque (and 000Fh) afin de récupérer uniquement les 4 bits les plus à droite dans un registre donné et de mettre à 0 les autres bits (car AND 0,1 = 0 😉 ). Ensuite, il devient simple d’afficher le caractère hexadécimal qui correspond à chacun des groupes isolés à l’aide d'une boucle et d’un tableau associatif qui, à un index numérique (0, 1, …, 15), associe le caractère hexadécimal équivalent a ce nombre ("0", "1", …., "F".). Dis comme ça, vous comprenez qu'on va toujours récupérer les mêmes 4 bits et donc afficher un nombre faux. Il faut donc décaler les bits du registre de 4 en 4 (12, 8, 4, 0) vers la droite avant de récupérer les 4 bits les plus faibles avec le masque et d'afficher le caractère hexadécimal qui correspond au groupe.

Vous avez toutes les cartes en main.

 DOSSEG
.MODEL SMALL
 
.STACK
 

.DATA
	; 1)
	message db "Transformation d'une lettre miniscule saisie au clavier en lettre majuscule : $"
	message2 db 13,10,"-> Saisie d'une lettre minuscule : $"

	message3 db 13,10,'-> Caractere saisi : $'
	message4 db 13,10,'-> Caractere transforme : $'

	messageException db "Le caractere saisi n'est pas une lettre minuscule $"
 
	; 2)
	message5 db 13,10,10,"Transformation d'une chaine minuscule saisie au clavier en une chaine majuscule : $"

	message6 db 13,10,"-> Saisie d'une chaine minuscule (15 caracteres max) : $"
	message7 db 13,10,'-> Chaine saisie : $'

	message8 db 13,10,'-> Chaine transforme : $'
 
	; 3)
	message9 db 13,10,10,"Afficher que les voyelles d'une chaine : $"

	message10 db 13,10,"-> Saisie d'une chaine (15 caracteres max) : $"
	message11 db 13,10,'-> Chaine saisie : $'

	message12 db 13,10,'-> Chaine transforme : $'
	messageException2 db "Il n'y a pas de voyelle dans cette chaine. $"

	voyelles db 'aeiouyAEIOUY'
 
	; 4)
	message13 db 13,10,10,'Afficher un nombre <= 65535 initialise dans la zone DATA (ici : 1984) en hexadecimal : $'

	nombre dw 1984
	TAB_Hexa db "0123456789ABCDEF"
 
	; Saisies
	caractereSaisi db 4 dup(2)

	chaineSaisie db 18 dup(16)
 
.CODE
	mov ax, @DATA

	mov ds, ax
 
	; 1) Transformation d'une lettre minuscule saisie en majuscule
	mov ah, 09h

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

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

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

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

	mov dl, [caractereSaisi+2]
	int 21h
 
	mov ah, 09h

	mov dx, offset message4
	int 21h
 
	mov dl, [caractereSaisi+2]

	cmp dl, 61h
	jb invalide
 
	cmp dl, 7Ah

	ja invalide
 
	sub dl, 20h
	mov ah, 02h

	int 21h
	jmp finQ1
 
	invalide: 
	mov ah, 09h

	mov dx, offset messageException
	int 21h
 
	finQ1 :
 

	; 2) Transformation d'une chaine saisie en majuscule
	mov ah, 09h
	mov dx, offset message5
	int 21h

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

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

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

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

	mov [chaineSaisie + 2 + bx], '$'
 
	mov ah, 09h

	mov dx, offset chaineSaisie+2
	int 21h	
 
	mov ah, 09h

	mov dx, offset message8
	int 21h	
 
	sub bx, 1	
	bcl:

		cmp bx, 0
		jl finbcl
 
		cmp [chaineSaisie+2+bx], 61h

		jb finP
 
		cmp [chaineSaisie+2+bx], 7Ah

		ja finP
 
		sub [chaineSaisie+2+bx], 20h

 
		finP:
		sub bx, 1
		jmp bcl
	finbcl :

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

	int 21h	
 
	; 3) Afficher que les voyelles d'une chaine
	mov ah, 09h

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

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

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

	mov dx, offset message11
	int 21h	
 
	mov bx, 0

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

	sub bx, 1
 
	mov ah, 09h

	mov dx, offset chaineSaisie+2
	int 21h	
 
	mov ah, 09h

	mov dx, offset message12
	int 21h	
 
	mov cx, 0

	mov dh, bl
	mov bx, 0
	mov ah, 02h

	bcl2:
		cmp bl, dh
		ja finbcl2
 

			mov si, 11
			bcl3:
				cmp si, 0

				jl finbcl3
 
				mov cl, [voyelles+si]
				cmp [chaineSaisie + 2 + bx], cl

				jne finP3
 
				mov ch, 1
				mov dl, cl

				int 21h
 
				finP3:
				sub si, 1

				jmp bcl3
 
			finbcl3:
 
		add bl, 1

		jmp bcl2
	finbcl2 :
 
	cmp ch, 1
	je fin3

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

 
	fin3 :
 
	; 4) Afficher un nombre en hexadecimal
	mov ah, 09h

	mov dx, offset message13
	int 21h
 
	mov ah, 02h

	mov cl, 12
	bcl4 :		
		cmp cl, 0

		jl fin_bcl4
 
		mov bx, [nombre]
		shr bx, cl

		and bx, 000Fh
 
		mov dl, [TAB_Hexa + bx] 
		int 21h

 
		sub cl, 4
		jmp bcl4			 
	fin_bcl4 :
 
	; FIN

	mov ax, 4C00h
	int 21h
END

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

segment .data
; 1)
	message db "Transformation d'une lettre miniscule saisie au clavier en lettre majuscule : $"
	message2 db 13,10,"-> Saisie d'une lettre minuscule : $"

	message3 db 13,10,'-> Caractere saisi : $'
	message4 db 13,10,'-> Caractere transforme : $'

	messageException db "Le caractere saisi n'est pas une lettre minuscule $"
 
	; 2)
	message5 db 13,10,10,"Transformation d'une chaine minuscule saisie au clavier en une chaine majuscule : $"

	message6 db 13,10,"-> Saisie d'une chaine minuscule (15 caracteres max) : $"
	message7 db 13,10,'-> Chaine saisie : $'

	message8 db 13,10,'-> Chaine transforme : $'
 
	; 3)
	message9 db 13,10,10,"Afficher que les voyelles d'une chaine : $"

	message10 db 13,10,"-> Saisie d'une chaine (15 caracteres max) : $"
	message11 db 13,10,'-> Chaine saisie : $'

	message12 db 13,10,'-> Chaine transforme : $'
	messageException2 db "Il n'y a pas de voyelle dans cette chaine. $"

	voyelles db 'aeiouyAEIOUY'
 
	; 4)
	message13 db 13,10,10,'Afficher un nombre <= 65535 initialise dans la zone DATA (ici : 1984) en hexadecimal : $'

	nombre dw 1984
	TAB_Hexa db "0123456789ABCDEF"
 
	; Saisies
	caractereSaisi times 4 db 2

	chaineSaisie times 18 db 16
 
segment stack stack

	resb 64
	stackstop:
 
segment .code
..start:

	mov ax, data
	mov ds, ax
 

	mov ax, stack
	mov ss, ax
	mov sp, stackstop

 
	; 1) Transformation d'une lettre minuscule saisie en majuscule
	mov ah, 09h
	mov dx, message
	int 21h

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

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

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

 
	mov ah, 02h
	mov dl, [caractereSaisi+2]

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

 
	mov dl, [caractereSaisi+2]
	cmp dl, 61h

	jb invalide
 
	cmp dl, 7Ah
	ja invalide

 
	sub dl, 20h
	mov ah, 02h

	int 21h
	jmp finQ1
 
	invalide: 
	mov ah, 09h

	mov dx, messageException
	int 21h
 
	finQ1 :
 

	; 2) Transformation d'une chaine saisie en majuscule
	mov ah, 09h
	mov dx, message5
	int 21h

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

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

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

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

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

 
	mov ah, 09h
	mov dx, chaineSaisie+2

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

 
	sub bx, 1	
	bcl:
		cmp bx, 0

		jl finbcl
 
		cmp byte [chaineSaisie+2+bx], 61h

		jb finP
 
		cmp byte [chaineSaisie+2+bx], 7Ah

		ja finP
 
		sub byte [chaineSaisie+2+bx], 20h

 
		finP:
		sub bx, 1
		jmp bcl
	finbcl :

 
	mov ah, 09h
	mov dx, chaineSaisie+2

	int 21h	
 
	; 3) Afficher que les voyelles d'une chaine
	mov ah, 09h

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

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

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

	mov dx, message11
	int 21h	
 
	mov bx, 0

	mov bl, [chaineSaisie + 1]
	mov byte [chaineSaisie + 2 + bx], '$'

	sub bx, 1
 
	mov ah, 09h

	mov dx, chaineSaisie+2
	int 21h	
 
	mov ah, 09h

	mov dx, message12
	int 21h	
 
	mov cx, 0

	mov dh, bl
	mov bx, 0
	mov ah, 02h

	bcl2:
		cmp bl, dh
		ja finbcl2
 

			mov si, 11
			bcl3:
				cmp si, 0

				jl finbcl3
 
				mov cl, [voyelles+si]
				cmp [chaineSaisie + 2 + bx], cl

				jne finP3
 
				mov ch, 1
				mov dl, cl

				int 21h
 
				finP3:
				sub si, 1

				jmp bcl3
 
			finbcl3:
 
		add bl, 1

		jmp bcl2
	finbcl2 :
 
	cmp ch, 1
	je fin3

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

 
	fin3 :
 
	; 4) Afficher un nombre en hexadecimal
	mov ah, 09h

	mov dx, message13
	int 21h
 
	mov ah, 02h

	mov cl, 12
	bcl4 :		
		cmp cl, 0

		jl fin_bcl4
 
		mov bx, [nombre]
		shr bx, cl

		and bx, 000Fh
 
		mov dl, [TAB_Hexa + bx] 
		int 21h

 
		sub cl, 4
		jmp bcl4			 
	fin_bcl4 :
 
	; FIN

	mov ax, 4C00h
	int 21h

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

section .data
	; 1)
	message db "Transformation d'une lettre miniscule saisie au clavier en lettre majuscule : "
	lenMessage equ $-message

 
	message2 db 10,"-> Saisie d'une lettre minuscule : "
	lenMessage2 equ $-message2

 
	message3 db 10,'-> Caractère saisi : '
	lenMessage3 equ $-message3

 
	message4 db 10,'-> Caractère transformé : '
	lenMessage4 equ $-message4

 
	messageException db "Le caractère saisi n'est pas une lettre minuscule "
	lenMessageException equ $-messageException
 
	; 2)

	message5 db 10,10,"Transformation d'une chaine minuscule saisie au clavier en une chaine majuscule : "
	lenMessage5 equ $-message5

 
	message6 db 10,"-> Saisie d'une chaine minuscule (15 caractères max) : "
	lenMessage6 equ $-message6

 
	message7 db 10,'-> Chaine saisie : '
	lenMessage7 equ $-message7

 
	message8 db 10,'-> Chaine transformée : '
	lenMessage8 equ $-message8

 
	; 3)
	message9 db 10,10,"Afficher que les voyelles d'une chaine : "
	lenMessage9 equ $-message9

 
	message10 db 10,"-> Saisie d'une chaine (15 caractères max) : "
	lenMessage10 equ $-message10

 
	message11 db 10,"-> Chaine saisie : "
	lenMessage11 equ $-message11

 
	message12 db 10,"-> Chaine transformée : "
	lenMessage12 equ $-message12

 
	messageException2 db "Il n'y a pas de voyelle dans cette chaine. "
	lenMessageException2 equ $-messageException2
 
	voyelles db 'aeiouyAEIOUY'

 
	; 4)
	message13 db 10,10,'Afficher un nombre <= 65535 initialisé dans la zone DATA (ici : 1984) en hexadécimal : '
	lenMessage13 equ $-message13

 
	nombre dw 1984
	hexa db '0123456789ABCDEF'
 
	; FIN

	fin db 10,'FIN',10
	lenFin equ $-fin 

 
 
section .bss
	caractereSaisi resb 2
	chaineSaisie resb 16

	lenSaisie resd 1
	buffer resb 1
	noVoyelle resb 1

	buffertwo resb 32
 
 
section .text
global _start
 
_start:

	; 1) Transformation d'une lettre minuscule saisie en majuscule
	mov eax, 4
	mov ebx, 1

	mov ecx, message
	mov edx, lenMessage
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, message2
	mov edx, lenMessage2
	int 80h

 
	mov eax, 3
	mov ebx, 0

	mov ecx, caractereSaisi
	mov edx, 2
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, message3
	mov edx, lenMessage3
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, caractereSaisi
	mov edx, 1
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, message4
	mov edx, lenMessage4
	int 80h

 
	mov dl, [caractereSaisi]
	cmp dl, 61h

	jb invalide
 
	cmp dl, 7Ah
	ja invalide

 
	sub dl, 20h
	mov [buffer], dl

 
	mov eax, 4
	mov ebx, 1

	mov ecx, buffer
	mov edx, 1
	int 80h	
	jmp finQ1

 
	invalide: 
	mov eax, 4
	mov ebx, 1

	mov ecx, messageException
	mov edx, lenMessageException
	int 80h

 
	finQ1 :
 
	; 2) Transformation d'une chaine saisie en majuscule
	mov eax, 4

	mov ebx, 1
	mov ecx, message5
	mov edx, lenMessage5
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, message6
	mov edx, lenMessage6
	int 80h

 
	mov eax, 3
	mov ebx, 0

	mov ecx, chaineSaisie
	mov edx, 16
	int 80h

 
	mov [lenSaisie], eax
 
	mov eax, 4

	mov ebx, 1
	mov ecx, message7
	mov edx, lenMessage7
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, chaineSaisie
	mov edx, [lenSaisie]
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, message8
	mov edx, lenMessage8
	int 80h

 
	; On enleve 2 car :
	; -1 car la taille de la chaine ne correspond pas à ses indices :
	; 	une chaîne d'une taille 7 va de 0 à 6
	; -1 car on ne veut pas traiter le retour à la ligne
	mov ebx, [lenSaisie]

	sub ebx, 2
	bcl:
		cmp ebx, 0

		jl finbcl
 
		cmp byte [chaineSaisie+ebx], 61h

		jb finP
 
		cmp byte [chaineSaisie+ebx], 7Ah

		ja finP
 
		sub byte [chaineSaisie+ebx], 20h

 
		finP:
		sub ebx, 1
		jmp bcl
	finbcl :

 
	mov eax, 4
	mov ebx, 1

	mov ecx, chaineSaisie
	mov edx, [lenSaisie]
	int 80h

 
 
	; 3) Afficher que les voyelles d'une chaine
	mov eax, 4
	mov ebx, 1

	mov ecx, message9
	mov edx, lenMessage9
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, message10
	mov edx, lenMessage10
	int 80h

 
	mov eax, 3
	mov ebx, 0

	mov ecx, chaineSaisie
	mov edx, 16
	int 80h

 
	mov [lenSaisie], eax
 
	mov eax, 4

	mov ebx, 1
	mov ecx, message11
	mov edx, lenMessage11
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, chaineSaisie
	mov edx, [lenSaisie]
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, message12
	mov edx, lenMessage12
	int 80h

 
	mov byte [noVoyelle], 1
	mov esi, 0

	mov edi, 0
	mov ebx, 1
	mov edx, 1

 
	sub dword [lenSaisie], 2
	bcl2:
		cmp edi, [lenSaisie]

		ja finbcl2
 
			mov esi, 11
			bcl3:

				cmp esi, 0
				jl finbcl3
 
				mov cl, [voyelles+esi]

				cmp [chaineSaisie+edi], cl
				jne finP3
 
				mov eax, 4

				mov byte [buffer], cl
				mov ecx, buffer
				int 80h

 
				mov byte [noVoyelle], 0
 
				finP3:
				sub esi, 1

				jmp bcl3
			finbcl3:
 
		add edi, 1
		jmp bcl2
	finbcl2 :

 
	cmp byte [noVoyelle], 0
	je fin3
 
	mov eax, 4

	mov ebx, 1
	mov ecx, messageException2
	mov edx, lenMessageException2
	int 80h

 
	fin3 :	
 
	; 4) Afficher un nombre en hexadecimal
	mov eax, 4

	mov ebx, 1
	mov ecx, message13
	mov edx, lenMessage13
	int 80h

 
	mov cl, 12
	bcl4:		
		cmp cl, 0

		jl fin_bcl4
 
		mov byte [buffer], cl
		mov dx, 0

		mov dx, [nombre]
		shr dx, cl
		and dx, 000Fh

 
		mov eax, 4
		mov ebx, 1

		mov ecx, hexa
		add ecx, edx
		mov edx, 1

		int 80h
 
		mov cl, [buffer]
		sub cl, 4

		jmp bcl4			 
	fin_bcl4:
 
	; FIN
	mov eax, 4

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

 
	mov eax, 1
	mov ebx, 0

	int 80h

Assembleur 8086+ : exercice 3

Table des matières

Comme d'habitude, je ne détaillerai rien concernant la compilation/l'édition des liens encore cette fois-ci. Reportez-vous au premier billet pour plus d'informations.

Exercice 3 : énoncé

  1. Déclarez un tableau d'octets dans le segment de données. Celui-ci contiendra 10 nombres entiers entre 0 et 255 inclus, que vous choisirez. Déclarez ensuite un mot, que vous appellerez, par exemple, somme. Sans utiliser l'instruction loop, et en utilisant les sauts, faites la somme des entiers du tableau puis affichez cette somme avec les chiffres décimaux.
  2. Afficher cette somme en binaire

Note : Comme d'habitude, cet énoncé n’est pas de moi : je l’ai repris à T.M. / E.R. et je l’ai modifié.

Aides

Il faut :

  • Se renseigner sur les différents sauts conditionnels disponibles et réfléchir sur la manière de construire une boucle en les utilisant.
  • Se souvenir que l'on a déjà écrit un bout de code permettant d'afficher un nombre < 1000. Il suffit de l'adapter pour traiter les nombres < 10000. Pour cela, il suffit d'ajouter une "itération" à la division successive.
  • Pour afficher le contenu d'un registre ou d'une adresse mémoire en binaire, il suffit de faire une boucle (dont le nombre d'itération dépend la taille du registre ou de la taille de l'espace mémoire. Exemple : pour un registre 16 bits => 16 itérations), à l'intérieur de laquelle on effectue un décalage logique vers la gauche (instruction SHL) puis de tester le Carry Flag (drapeau de retenue), si le drapeau est levé, alors on affiche le caractère 1, sinon on affiche le caractère 0. Pour tester cela, il existe le saut JC.
  • Pour les contraintes liées aux primitives système de GNU/Linux (sauvegarder les registres avant, la fonction write veut une adresse mémoire, etc.), vous savez déjà tout si vous avez suivi les billets précédent.
 DOSSEG

.MODEL SMALL
 
.STACK
 
.DATA
    message db 'Somme des entiers contenus dans le tableau : $'

    message2 db 13,10,'Somme, en binaire, des entiers contenus dans le tableau : $'
 
    tableau db 255, 234, 185, 164, 18, 15, 16, 32, 64, 91

    somme dw 0
 
.CODE
    mov ax, @DATA

    mov ds, ax
 
    mov ax, 0

    mov bx, 0
    bcl: 
        cmp bx, 9

        ja fin
 
        mov al, [tableau+bx]
        add [somme], ax

 
    inc bx
    jmp bcl
 
    fin:
        mov ah, 09h

        mov dx, offset message
        int 21h
 
        mov dx, 0

        mov ax, [somme]
        mov bx, 1000
        div bx

 
        mov cx, ax
        mov bx, dx

        mov ah, 02h
        mov dl, cl
        add dl, 30h

        int 21h
 
        mov ax, bx
        mov bl, 100

        div bl
 
        mov cx, ax
        mov ah, 02h

        mov dl, cl
        add dl, 30h
        int 21h

 
        mov ax, 0000h
        mov bl, 10

        mov al, ch
        div bl
 
        mov cx, ax

        mov ah, 02h
        mov dl, cl
        add dl, 30h

        int 21h
 
        mov ah, 02h
        mov dl, ch

        add dl, 30h
        int 21h
 
        mov ah, 09h

        mov dx, offset message2
        int 21h
 
        mov bx, [somme]

        mov cx, 16
        bcl0 :    
            debut_si :     shl bx, 1

                        jc aff1                
 
                        mov ah, 02h            
                        mov dl, '0'

                        int 21h
                        jmp fin_si
 
            aff1 :         mov ah, 02h

                        mov dl, '1'
                        int 21h
 
            fin_si : 
        loop bcl0

 
        ; FIN
        mov ax, 4C00h
        int 21h

END

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

segment .data
    message db 'Somme des entiers contenus dans le tableau : $'
    message2 db 13,10,'Somme, en binaire, des entiers contenus dans le tableau : $'

 
    tableau db 255, 234, 185, 164, 18, 15, 16, 32, 64, 91

    somme dw 0
 
segment stack stack
    resb 64

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

    mov ds, ax
 
    mov ax, stack

    mov ss, ax
    mov sp, stackstop
 
    mov ax, 0

    mov bx, 0
    bcl: 
        cmp bx, 9

        ja finbcl
 
        mov al, [tableau+bx]
        add [somme], ax

 
    inc bx
    jmp bcl
 
    finbcl:
        mov ah, 09h

        mov dx, message
        int 21h
 
        mov dx, 0

        mov ax, [somme]
        mov bx, 1000
        div bx

 
        mov cx, ax
        mov bx, dx

        mov ah, 02h
        mov dl, cl
        add dl, 30h

        int 21h
 
        mov ax, bx
        mov bl, 100

        div bl
 
        mov cx, ax
        mov ah, 02h

        mov dl, cl
        add dl, 30h
        int 21h

 
        mov ax, 0000h
        mov bl, 10

        mov al, ch
        div bl
 
        mov cx, ax

        mov ah, 02h
        mov dl, cl
        add dl, 30h

        int 21h
 
        mov ah, 02h
        mov dl, ch

        add dl, 30h
        int 21h
 
        mov ah, 09h

        mov dx, message2
        int 21h
 
        mov bx, [somme]

        mov cx, 16
        bcl0 :    
            debut_si :     shl bx, 1

                        jc aff1                
 
                        mov ah, 02h            
                        mov dl, '0'

                        int 21h
                        jmp fin_si
 
            aff1 :         mov ah, 02h

                        mov dl, '1'
                        int 21h
 
            fin_si : 
        loop bcl0

 
        ; FIN
        mov ax, 4C00h
        int 21h

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

section .data
    message db 'Somme des entiers contenus dans le tableau : '
    lenMessage equ $-message

 
    message2 db 13,10,'Somme, en binaire, des entiers contenus dans le tableau : '
    lenMessage2 equ $-message2

 
    fin db 10,'FIN',10
    lenFin equ $-fin 

 
    tableau db 255, 234, 185, 164, 18, 15, 16, 32, 64, 91

 
    zero db '0'
    lenZero equ $-zero
    un db '1'

    lenUn equ $-un
 
section .bss
    chiffre1 resb 1

    chiffre2 resb 1
    chiffre3 resb 1
    chiffre4 resb 1

    save: resb 1
    somme: resw 1
 

section .text
global _start
_start:
    mov word [somme], 0

    mov ax, 0
    mov ebx, 0
    bcl: 
        cmp ebx, 9

        ja finbcl
 
        mov al, [tableau+ebx]
        add [somme], ax

 
        inc ebx
    jmp bcl
 
    finbcl:
        mov eax, 4

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

 
        mov dx, 0
        mov ax, [somme]

        mov bx, 1000
        div bx
 
        add ax, 30h

        mov [chiffre1], ax
 
        mov ax, dx

        mov bl, 100
        div bl
 
        add al, 30h

        mov [chiffre2], al
 
        mov bl, 10

        mov al, ah
        mov ah, 0
        div bl

 
        add al, 30h
        mov [chiffre3], al

 
        add ah, 30h
        mov [chiffre4], ah

 
        mov eax, 4
        mov ebx, 1

        mov ecx, chiffre1
        mov edx, 1
        int 80h

 
        mov eax, 4
        mov ebx, 1

        mov ecx, chiffre2
        mov edx, 1
        int 80h

 
        mov eax, 4
        mov ebx, 1

        mov ecx, chiffre3
        mov edx, 1
        int 80h

 
        mov eax, 4
        mov ebx, 1

        mov ecx, chiffre4
        mov edx, 1
        int 80h

 
        mov eax, 4
        mov ebx, 1

        mov ecx, message2
        mov edx, lenMessage2
        int 80h

 
        mov ebx, 0
        mov si, [somme]

        mov cx, 16
        bcl0 :    
            debut_si :     mov [save], cx

 
                    shl si, 1
                    jc aff1                
 
                    mov eax, 4

                    mov ebx, 1
                    mov ecx, zero
                    mov edx, lenZero
                    int 80h

                    jmp fin_si
 
            aff1 :         mov eax, 4
                    mov ebx, 1

                    mov ecx, un
                    mov edx, lenUn
                    int 80h

 
            fin_si :     mov ecx, 0
                    mov cx, [save]

        loop bcl0
 
        ; FIN
        mov eax, 4
        mov ebx, 1

        mov ecx, fin
        mov edx, lenFin
        int 80h

 
        mov eax, 1
        mov ebx, 0

        int 80h

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.