lalahop
Categorie: Développement

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

Coder des applications Win32/DirectX sous Eclipse

Qui n'a jamais eu envie de vomir en voyant tous les programmes que Visual Studio installe, alors que si peu sont utiles ?

Le problème, pour tous ceux qui codent des applications utilisant l'API Win32 (pas les MFC, l'API de base) ou DirectX, est qu'il est difficile de compiler sans utiliser Visual C++. Qui plus est, l'environnement de développement Visual Studio fournit des fonctionnalités appréciables.

Pour remplacer l'éditeur WYSIWYG de Visual Studio, j'ai donc décidé d'utiliser Eclipse (Helios est la dernière version). Eclipse présente de très nombreux avantages, je ne vous les énumérerais pas, mais sachez qu'il vous suffira d'un seul IDE pour tous les langages que vous utiliserez.
Pour remplacer Visual C++ (le compilateur), j'ai choisi d'utiliser MinGW (le portage de GCC sous Windows).

Pour installer MinGW, référez vous au précédent tutoriel ici. Je vous conseille également de vous rendre dans C:\REPERTOIRE MINGW\bin et de créer un nouveau michier make.bat qui contienne

@mingw32-make %*

(cela vous permettra de taper make au lieu de mingw32-make, ce qui facilite le copier coller depuis les tutos linux =p)

Si vous souhaitez coder en utilisant le DirectX SDK, téléchargez le ici. Installez le comme n'importe quel programme. Une fois que c'est fait, allez dans C:\Program Files\Microsoft DirectX SDK (MOIS ANNEE)\Lib\x86\ et copiez tous le contenu du dossier vers C:\REPERTOIRE MINGW\lib\dx (dx pour directx, mettez ce que vous voulez). Copiez ensuite le contenu de C:\Program Files\Microsoft DirectX SDK (MOIS ANNEE)\Include\ vers C:\REPERTOIRE MINGW\include\dx.

Quand tout cela est fait, vous êtes prêts pour passer à Eclipse.

Si vous utilisez déjà Eclipse CDT, passez à la suite sans plus attendre :p

Si vous ne possédez pas encore Eclipse, vous devriez jeter un coup d'oeil ici. Une fois l'archive téléchargée, extrayez la, puis lancez Eclipse (aucune installation).

Si vous utilisez déjà Eclipse pour du Java, du PHP, ou autre, vous devrez simplement vous rendre dans Eclipse > Menu Help > Install New Software. Dans le champ "Work with :", choisissez "CDT - http://download.eclipse.org/tools/cdt/releases/helios". Si cette valeur n'est pas disponible, faites Add : Name => CDT / Location => "http://download.eclipse.org/tools/cdt/releases/helios".
2 packages devraients être listés, CDT Main Features & CDT Optional Features. Choisissez les Main Features, puis cochez la première case (C/C++ development tools). Vous pouvez également cocher la 2eme case, ainsi que les Optional Features si vous le souhaitez, mais cela reste facultatif. Faites ensuite Next, acceptez la license, et patientez pendant le téléchargement/l'installation. Cela peut être long, et la barre de chargement ne correspond pas forcément à l'avancement réel du processus. Redémarrez finalement Eclipse.

Maintenant que Eclipse est ouvert et fonctionnel, rendez vous dans Menu Window > Open Perspective > Other > C/C++. Vous devriez obtenir un espace de travail correct pour coder en C++. Créez un nouveau projet C/C++, puis rendez vous dans Menu Project > Properties > C/C++ General > Library Path > Add : Directory => C:\MinGW\lib\dx. Rendez vous ensuite dans Onglet Libraries > Add : File => :"d3d9.lib" et Add : File => :"d3dx9.lib" Respectez rigoureusement la syntaxe (deux points et double quotes). Il vous faudra également vous rendre dans Menu Project > Properties > C/C++ Build > Settings > Tool Settings > MinGW C++ Linker et insérer après {$FLAGS} -mwindows (cela permettra à votre application de se lancer sans invite de commande en arrière plan). Enfin dans votre code, il vous faudra écrire

#include "dx/d3d9.h"
#include "dx/d3dx9.h"

pour inclure les en-têtes de DirectX. A partir de ce moment là, vous pourrez compiler simplement en cliquant sur le petit marteau marron (l'équivalent de Build). Un détail cependant, Eclipse ne lance pas les exécutables automatiquement, il faudra cliquer sur le bouton play pour lancer votre programme, à moins de vous rendre sur le menu déroulant du bouton play > Run Configurations et de cocher le radio button "Enable auto build". Attention, il semblerait que Eclipse ne parvienne pas à lancer les exécutables graphiques, il faudra donc les lancer à la main depuis le répetoire Debug ou Release de votre projet !

Eclipse dispose de nombreuses fonctionnalités (disassembler à syntaxe AT&T, debugger...), alors n'hésitez pas à farfouiller !

C'est bon, vous pouvez désinstaller Visual Studio ! Arf, il laisse de la merde partout... =p Plus qu'à reformater ! Héhé

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.

Assembleur 8086+

Table des matières

Aujourd'hui, je vous propose un petit exercice : programmer un petit programme inutile en assembleur 8086 et supérieur (certaines des instructions que l'on va utiliser sont apparues après le 8086 comme DIV qui est apparue avec le 8088).

Quand je dis inutile, c'est que je pense qu'écrire du code en assembleur est aujourd'hui dépassé car les compilateurs des langages de programmation de haut niveau (ex : C) sont optimisés donc on ne gagnera pas grand chose, en terme de performances, à programmer en assembleur.

Mais, comme toujours, il y a des exceptions : connaitre et/ou coder en assembleur peut être utile dans certains cas, notamment :

  • Coder en assembleur des programmes "cas d'école" peut permettre au programmeur de comprendre un peu mieux le fonctionnement interne d'un ordinateur étant donné que ce langage est le plus proche de la machine. Si vous voulez plus proche, il vous reste le binaire 🙂 . Pour illustrer mes propos, il faut savoir que l'assembleur est enseigné en première année de DUT Informatique dans certains IUT.
  • Un autre cas pour lequel l'assembleur peut s'avérer utile : écrire un mini-bootloader voir un mini-noyau, voir ... .
  • Enfin, lorsque l'on désassemble un programme, mieux vaut connaître l'assembleur de l'architecture visée (rappelons que l'assembleur dépend de l'architecture 😉 )

Ceci étant dit, voila ce qui vous sera utile pour comprendre la suite : table des interruptions, la liste des instruction assembleur 8086 et modes d'adressages mémoire.

Exercice 1 : énoncé

Voici maintenant les "fonctionnalités" du programme à écrire :

  1. Afficher une chaine de caractères à l'écran.
  2. En utilisant le mode d'adressage immédiat, afficher une lettre (n'importe laquelle) à l'écran.
  3. En utilisant le mode d'adressage direct, afficher le premier caractère d'une chaine de caractère.
  4. En utilisant le mode d'adressage indirect et le registre BX, afficher deux caractères non consécutifs d'une chaine de caractères.
  5. En utilisant le mode d'adressage de base et le registre BX, afficher afficher un caractère de la chaine.
  6. En utilisant le mode d'adressage indexé direct, afficher un caractère de la chaine.
  7. On stocke un nombre d'une taille moyenne (> 100, < 1000 par exemple) "en dur" dans un registre et on l'affiche à l'écran.

Note : cet énoncé n'est pas de moi : je l'ai repris à T.M. / E.R. et je l'ai légèrement adapté.

Note : Bien que je ne sois pas fan des logiciels privateurs, je tiens à rester accessible pour la majorité de mes visiteurs. C'est pourquoi je propose une solution avec TASM et Windows.

Exercice 1 : sous Windows, avec TASM

Attention : seules les versions 32 bits de Windows ont un sous-système 16 bits (NTVDM pour XP, Windows On Windows depuis) et permettent donc d'exécuter des applications 16 bits. Les versions 64 bits en sont donc dépourvu. Comme compilateur, nous utiliserons TASM. Comme éditeur des liens (linker), nous utiliserons celui fournit avec, à savoir : TLINK.

Pourquoi ne pas faire une version 32 bits ? Parce que, à mon avis, sous Windows, ça manque de panache : on utilise plus les interruptions de la même manière, on se croirait presque plus en ASM vu toutes les fonctions externes que l'on charge ... Néanmoins si ça vous intéresse : Iczelion's Win32 Assembly Homepage.

Il n'y a rien de compliqué en soi. Il faut juste :

  • Ne pas oublier qu'à la fin de la déclaration d'une chaine de caractères, il faut un "$".
  • Il faut connaitre les principaux modes d'adressage du 8086 (je vous ai donné un lien plus haut).
  • Il ne faut pas oublier d'initialiser le segment de données et de quitter le programme en douceur.
  • Il faut connaitre les interruptions fournies par le "DOS" dont nous aurons besoin (afficher une chaine de caractère, afficher un caractère, quitter le programme en douceur). Je vous ai donné un lien plus haut.
  • Il ne faut pas oublier que, dans la table ASCII, pour obtenir un chiffre sous forme de caractère affichable, il suffit de rajouter 30 en hexadécimal à la représentation binaire lui-même.

Pour compiler, linker et exécuter votre programme, il suffit d'ouvrir une invite de commande, de naviguer (commande cd) jusqu'au dossier dans lequel sont stockés TASM et TLINK (a moins d'avoir ajouter le dossier à la variable d'environnement PATH) et de taper :

> tasm votrefichier.asm
> tlink votrefichier
> votrefichier

Voici ce que j'ai écris :

DOSSEG
.MODEL SMALL

 
; 1 ko sera attribué au segment de pile
.STACK
 
.DATA
	message db 'Une chaine de caracteres bidon $'
	message1 db 'Question 1 : $'

	message2 db 13,10,'Question 2 : $'
	message3 db 13,10,'Question 3 : $'

	message4 db 13,10,'Question 4 : $'
	message5 db 13,10,'Question 5 : $'

	message6 db 13,10,'Question 6 : $'
	message7 db 13,10,'Question 7 : $'

 
.CODE
	; Initialisation du segment de données
	mov ax, @DATA
	mov ds, ax

 
	; 1) Affichage d'une chaine
	mov ah, 09h
	mov dx, offset message1
	int 21h

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

 
 
	; 2) On affiche le caractère 'H' à l'écran
	mov ah, 09h
	mov dx, offset message2
	int 21h

 
	mov ah, 02h
	mov dl, 'H'

	int 21h
 
	; 3) On affiche la 1ere lettre de message
	mov ah, 09h

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

	mov dl, message
	int 21h
 
	; 4) On affiche la 1ere et 21eme lettre de message
	mov ah, 09h

	mov dx, offset message4
	int 21h
 
	mov bx, offset message

 
	mov ah, 02h
	mov dl, [bx]

	int 21h
 
	; N'oubliez pas que l'on compte à partir de 0
	mov bx, offset message+20

 
	mov ah, 02h
	mov dl, [bx]

	int 21h
 
	; 5) On affiche la 28eme lettre de message 
	mov ah, 09h

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

	mov bx, offset message
	mov dl, [bx + 27]

	int 21h
 
	; 6) On affiche la 15eme lettre de message
	mov ah, 09h

	mov dx, offset message6
	int 21h
 
	mov SI, 14

	mov ah, 02h
	mov bx, offset message
	mov dl, [bx + SI]

	int 21h
 
	; 7) On stocke le nombre 666 en dur et on l'affiche
	mov ah, 09h

	mov dx, offset message7
	int 21h
 
	mov ax, 666

	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
 
	; FIN

	mov ax, 4C00h
	int 21h
END

Exercice 1 : sous un Windows, avec Nasm et le linker Val

Nasm est un compilateur sous licence BSD. Je vous recommande la lecture de sa documentation qui est bien faite et enrichissante, aussi bien sur le logiciel lui-même que sur l'assembleur.

Concernant le linker, je vous recommande Val qui est dans le domaine public et dont les sources sont disponibles. La seule contrainte est que le nom reste le même.

A part une syntaxe différente, cette solution est la même que celle utilisant TASM et TLINK. Ainsi, je vous recommande d'aller lire la solution utilisant TASM pour plus d'informations.

Concernant les différences de syntaxe entre Nasm et TASM :

  • exit les symboles peu clairs comme DOSSEG, .DATA, END, etc.
  • exit le mot-clé OFFSET. Pour demander l'adresse mémoire d'une variable, on indique juste son nom
  • Pour demander le contenu d'une adresse mémoire, on utilise [ ]

Pour compiler, linker et exécuter votre programme, il suffit d'ouvrir une invite de commande, de naviguer (commande cd) jusqu'au dossier dans lequel sont stockés Nasm et Val (a moins d'avoir ajouter le dossier à la variable d'environnement PATH) et de taper :

> nasm -f obj votrefichier.asm
> val votrefichier.obj
> votrefichier

Vous savez l'essentiel pour refaire le même programme.

Voici ce que j'ai écris :

segment .data
	message db 'Une chaine de caracteres bidon $'
	message1 db 'Question 1 : $'

	message2 db 13,10,'Question 2 : $'
	message3 db 13,10,'Question 3 : $'

	message4 db 13,10,'Question 4 : $'
	message5 db 13,10,'Question 5 : $'

	message6 db 13,10,'Question 6 : $'
	message7 db 13,10,'Question 7 : $'

 
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) Affichage d'une chaine
	mov ah, 09h

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

	mov dx, message
	int 21h
 
 
	; 2) On affiche le caractère "H" à l'écran

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

 
	mov ah, 02h
	mov dl, 'H'

	int 21h
 
	; 3) On affiche la 1ere lettre de message
	mov ah, 09h

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

	mov dl, [message]
	int 21h
 
	; 4) On affiche la 1ere et 21eme lettre de message

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

 
	mov bx, message
 
	mov ah, 02h

	mov dl, [bx]
	int 21h
 
	mov bx, message+20

 
	mov ah, 02h
	mov dl, [bx]

	int 21h
 
	; 5) On affiche la 28eme lettre de message 
	mov ah, 09h

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

	mov bx, message
	mov dl, [bx + 27]

	int 21h
 
	; 6) On affiche la 15eme lettre de message
	mov ah, 09h

	mov dx, message6
	int 21h
 
	mov si,14

	mov ah,02h
	mov bx, message
	mov dl, [bx + si]

	int 21h
 
	; 7) On stocke le nombre 666 en dur et on l'affiche
	mov ah, 09h

	mov dx, message7
	int 21h
 
	mov ax, 666

	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
 
	; FIN

	mov ax, 4C00h
	int 21h

Exercice 1 : sous un Windows, avec le compilateur Nasm mais sans linker

Cette solution est très semblable à celle utilisant Nasm et Val, seule la syntaxe change un peu. Ainsi, je vous recommande d'aller lire la solution utilisant Nasm et Val pour plus d'informations. Je pense que cette solution n'est pas très propre. Néanmoins je vous la montre, libre à vous après de faire un choix.

Pour compiler, linker et exécuter votre programme, il suffit d'ouvrir une invite de commande, de naviguer (commande cd) jusqu'au dossier dans lequel est stocké Nasm (a moins d'avoir ajouter le dossier à la variable d'environnement PATH) et de taper :

> nasm -f bin votrefichier.asm -o nom.exe
> nom.exe

Et voici ce que j'ai écris :

org 100h
 
section .data

	message db 'Une chaine de caracteres bidon $'
	[... Pareil que précédemment ... ]
 
section .text

start:
	; 1) Affichage d'une chaine
	mov ah, 09h
	mov dx, message1
	int 21h

 
	[... Pareil que précédemment ... ]
 
	; FIN
	mov ax, 4C00h

	int 21h

Exercice 1 : sous un GNU/Linux, avec Nasm et ld

D'après la doc de Nasm, le format de fichier ELF ne supporte pas les relocations 32 bits -> 16 bits. Néanmoins ld les supporte comme une extension. J'ai essayé pas mal de choses, j'ai cherché sur le net mais je ne suis pas parvenu à linker mon programme 16 bits (utilisation de [BITS 16] dans le code) car ld me renvoi toujours un "relocation truncated to fit: R_386_16 against `.data'".

Néanmoins ce n'est pas une grande perte : cette méthode n'est pas recommandable comme on peut le lire un certain nombre de fois sur internet. Même la documentation de Nasm ne traite pas le fait d'écrire des programmes 16 bits sous GNU/Linux alors qu'elle le fait pour Windows.

Il est néanmoins possible d'écrire du code 32 bits. Ce qui va changer par rapport à Windows, c'est qu'on ne fera plus int 21h mais int 80h pour appeler le noyau et lui demander un service. Les services sont ici les fonctions qu'on a l'habitude d'utiliser en C : exit, write, open, etc. On leur passe les arguments nécessaires, dans l'ordre, dans les registres eax, ebx, ecx, edx ou par la pile (attention à bien empiler : on commence par empiler le dernier argument, ..., puis le premier). Plus de détails : i386 Linux 2.2+ Syscalls et FreeBSD Assembly Language Programming

Pour compiler :

$ nasm -f elf32 votrefichier.asm
$ ld -s votrefichier.o -o nom

ou (sur un OS 64 bits (x86_64)) :

$ nasm -f elf64 votrefichier.asm
$ ld -s votrefichier.o -o nom

ÉDIT du 03/04/2012 à 16h15 :
Sur un OS 64 bits, il vaut mieux compiler et linker de cette manière :

$ nasm -f elf votrefichier.asm
$ ld -m elf_i386 -s votrefichier.o -o nom

En effet, en utilisant la première méthode, la compilation et le linkage s'effectueront sans problèmes mais vous pourriez rencontrer des erreurs de segmentation sur certains programmes et notamment ceux qui font des manipulations sur des variables mémoires (logique ...) ou qui manipulent la pile. Fin de l'édit.

Et voici ce que j'ai écris :

section .data
	message db 'Une chaine de caractères bidon'

	lenMessage equ $-message
 
	message1 db 'Question 1 : '
	lenMessage1 equ $-message1

 
	message3 db 10,'Question 3 : '
	lenMessage3 equ $-message3
 

	message4 db 10,'Question 4 : '
	lenMessage4 equ $-message4
 
	message7 db 10,'Question 7 : '

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

	lenFin: equ $-fin	
 
; On déclare des variables non initialisées
section .bss
	chiffre1: resb 1

	chiffre2: resb 1
	chiffre3: resb 1
 

section .text
	global _start
 
	_start:
	; 1) Affichage d’une chaine
	; Sous les systèmes de type UNIX, tout est fichier.

	; La console est un fichier. On écrit dedans avec write().
	mov eax, 4
	mov ebx, 1

	mov ecx, message1
	mov edx, lenMessage1
	int 80h

 
	mov eax, 4
	mov ebx, 1

	mov ecx, message
	mov edx, lenMessage
	int 80h

 
	 ; 2) On affiche le caractère « H » à l’écran
	 ; La solution revient à faire 3) car il faut declarer le
	; caractère 'H' dans le segment de données et l'afficher
 
	 ; 3) On affiche la 1ere lettre de message
	mov eax, 4

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

 
	mov eax, 4
	mov ebx, 1

	mov ecx, message
	mov edx, 1
	int 80h

 
	; 4) On affiche la 1ere et la 21eme lettre
	mov eax, 4
	mov ebx, 1

	mov ecx, message4
	mov edx, lenMessage4
	int 80h

 
	mov esi, message
 
	mov eax, 4

	mov ebx, 1
	mov ecx, esi
	mov edx, 1

	int 80h
 
	mov esi, message+20
 

	mov eax, 4
	mov ebx, 1
	mov ecx, esi

	mov edx, 1
	int 80h
 
	; 5) On affiche la 28eme lettre de message

	; Pas possible
 
 
	 ; 6) On affiche la 15eme lettre de message
	; pas possible
 
	 ; 7) On stocke le nombre 666 en dur et on l’affiche
	mov eax, 4

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

 
	mov dx, 0
	mov ax, 666

	mov bl, 100
	div bl
 
	add al, 30h

	mov [chiffre1], al
 
	mov bl, 10

	mov al, ah
	mov ah, 0
	div bl

 
	add al, 30h
	mov [chiffre2], al

 
	add ah, 30h
	mov [chiffre3], 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

 
	; FIN
	mov eax, 4
	mov ebx, 1

	mov ecx, fin
	mov edx, lenFin
	int 80h

 
	mov eax, 1
	mov ebx, 0

	int 80h

Comme vous le constater, certains adressages ne sont pas possibles. Cela est dû au fait que la fonction write attend une adresse mémoire. Or, certains adressages retournent le contenu d'une adresse mémoire. On parait toujours trouver une parade mais cela reviendrait à un adressage direct ou à un adressage indirect via un registre (ou alors n'ai-je pas assez réfléchis ?).