Categorie: Développement

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.

Toujours bon à savoir

À défaut d'être un billet technique, ce billet se propose de regrouper quelques-unes de mes découvertes plus ou moins récentes en informatique.

Table des matières

Différence entre un firewall stateless (pare-feu sans états) et un firewall stateful (pare-feu avec états)

Une bonne définition et une présentation détaillée de l'apport de sécurité amené par le stateful sont exposées ici (même si cette problématique n'est pas le thème majeur du billet) : Palo Alto Networks : Faut-il repenser notre vision du Firewall ? chez Guiguiabloc. Je rajouterai juste que le mode stateful permet une hausse des performances puisque les paquets appartenant à une connexion existante ne sont pas analysés "à fond" contrairement aux premiers paquets de chaque connexion.

Sécuriser un échange chiffré sur le long terme

Pourquoi vouloir sécuriser une connexion déjà sécurisée et temporaire, comme par exemple une session SSL/TLS, sur le long terme ? Imaginons simplement qu'une personne ou une organisation malveillante (pensez plus loin que les soit disant méchants pirates : autorités, prestataire réseau, ...) ait, un jour, l'occasion de faire un man-in-the-middle passif entre vous et un quelconque serveur avec lequel vous effectuez une connexion sécurisée : elle ne verra qu'une connexion chiffrée. Bien. Supposons maintenant qu'elle conserve votre échange avec le serveur et qu'un beau jour, elle soit en mesure de récupérer la clé privée du serveur (mauvais recyclage, saisie du serveur, ...) : elle sera en mesure de déchiffrer l'échange conservé. Afin d'éviter ceci, une méthode existe : le perfect forward secrecy (on peut traduire ça en "confidentialité persistante" ou en "confidentialité de transmission parfaite" mais je pense qu'il vaut mieux garder ça en anglais pour le coup). Cette technique s'applique aux protocoles d'échange de clé qui utilisent la cryptographie asymétrique (= à clé publique) et, de ce fait, aux protocoles utilisant ces derniers : SSL/TLS, SSH, IPSec, ... Notez que l’utilisation du perfect forward secrecy (PFS) est une option en SSL/TLS ou dans IPSec. Par contre, la version 2 de SSH, utilise, par défaut, diffie-hellman pour l'échange des clés et utilise donc le PFS.

Si cette méthode fait parler d'elle ces derniers temps, c'est tout simplement à cause de l'annonce de Google qui nous informe l'avoir mise en place sur ses services qui proposent un accès sécurisé (GMail, Google search, ...).

Je voulais donc profiter de l’occasion pour vous proposer d'en apprendre plus au sujet du PFS via ces deux sites :

ÉDIT du 23/12/2011 à 17h50 :

J'ai modifié le paragraphe sur le PFS car il contenait un combo de boulettes. D'une part, le blog de Vincent Bernat est aussi accessible en langue française pour les irréductibles et d'autre part, le style d'écriture laissait entendre que le PFS n'était possible qu'avec SSL/TLS alors que je me servais uniquement de l'annonce de Google, qui pour le coup concerne uniquement SSL/TLS, pour évoquer le PFS.
Fin de l'édit

GNOME 3 - gestion de l'énergie et ordinateur portable - ne pas hiberner quand on ferme l'écran

Avec GNOME 2, il suffisait d'aller dans les paramètres systèmes. Plus aujourd'hui ... Qu'à cela ne tienne : utilisons gsettings :

$ gsettings set org.gnome.settings-daemon.plugins.power lid-close-ac-action blank

Dans la même veine, GNOME 3 ne laisse plus le choix de ne rien faire lorsque le niveau de la batterie atteint un niveau critique. Qu'à cela ne tienne (bis) :

$ gsettings set org.gnome.settings-daemon.plugins.power critical-battery-action nothing

Et pour avoir une idée des autres paramètres disponibles pour la gestion de l'énergie, il suffit d'utiliser cette commande :

$ gsettings list-keys org.gnome.settings-daemon.plugins.power

Présentation de Netkit

À tous ceux qui veulent faire des expériences réseaux "sanstoutcasser", je recommande Netkit. J'avais déjà entendu parler de ce projet dans GNU/Linux Magazine France mais je ne l'avais pas testé. C'est aujourd'hui chose faite et je le recommande. Il ne me reste plus qu'à essayer quelques uns des intéressants labs proposés sur le wiki du projet.

Et pour faire, facilement, une capture réseau sur un lien entre deux machines d'un lab, c'est par ici : New package of uml_dump for netkit 2.7 chez Kartoch's Blog. Ce patch fonctionne encore tel quel avec la version 2.8 de Netkit.

ÉDIT du 28/02/2012 à 23h45 : Et pour ceux qui se demande quelle est la syntaxe de vdump, voici (copier/coller du Kartoch's Blog) : vdump <domaine de collision> | wireshark -i - -k . Pour trouver le domaine de collision entre deux machines d'un lab Netkit, il suffit de regarder le lab.conf. Par exemple, quel paramètre dois-je passer à la commande vdump pour écouter le trafic réseau entre pc1 et pc2 en fonction de l'extrait de lab.conf suivant ?

pc1[0]="A"
root[0]="A"
root[1]="B"
bidule[0]="B"

pc2[0]="A"

Réponse : vdump A | wireshark -i - -k
Fin de l'édit

ÉDIT du 23/12/2011 à 17h50 : Et pour ceux qui veulent une configuration plus poussée et/ou mieux comprendre comment ça se passe sous le capot, je vous propose de lire ceci : Labo virtuel avec User Mode Linux chez Vincent Bernat.
Fin de l'édit

Fichier avec des zones vides (sparse file)

À ceux qui se demande comment font les logiciels de virtualisation pour créer des disques dur de taille dynamique (transition depuis Netkit : done), ne vous posez plus la question : il s'agit de sparse files. Je vous dirige vers Wikipedia pour une définition : sparse file sur Wikipédia. Si vous souhaitez obtenir plus de renseignements, c'est par là : Sparse files – what, why, and how chez UNIX Administratosphere. Ce type de fichier est également utilisé par les utilitaires de sauvegarde. Par exemple : Clonezilla sauvegarde l'intégralité de votre disque dur, même les zones vides, sans pour autant créer une image disque de même taille (je ne parle pas d'une quelconque compression).

Pour ma part, j'ai fait le rapprochement entre le nom (sparse file) et les usages en lisant le "Kernel corner" du numéro de novembre 2011 de GNU/Linux Magazine France qui parle de deux nouvelles opérations ajoutées à l'appel système lseek dans la version 3.1 du noyau Linux : SEEK_HOLE et SEEK_DATA qui permettent, respectivement de se placer au début d'une zone vide ou d'une zone de données.

Pointeur de fonction : maFonction(void) != maFonction() ?

N'étant pas un as du développement logiciel (chacun son truc on va dire), je me posais une question toute simple : pourquoi ce code compile-t-il sans erreur ni avertissement ?

void maFonction(void (*f)(double)) { 
     ; 

}
 
void FUSR1() {
     ;
}
 
int main(void) {

     maFonction(FUSR1);
     return 0;
}

Pour ceux que cela intéresse, la réponse se trouve ici : Pointeur de fonction : maFonction(void) != maFonction() ? sur la catégorie "C" du forum du SdZ.

Masque de réseau identique ne signifie pas réseau identique

Récemment, j'ai dépanné une personne se plaignant que deux machines, soit disant sur le même réseau, ne puisse pas se pinger. Le problème venait en fait du fait que cette personne croyait que les deux machines étaient sur le même réseau alors qu'elles ne l'étaient pas. Elle se fiait au masque de réseau, pourtant identique.

Les machines avaient des IPs ressemblant à : 192.168.0.1/30 et 192.168.0.5/30. Un /30 suppose que le dernier octet soit composé de 6 bits pour l'adresse réseau et 2 bits pour l'adresse machine. Cela donne donc un masque égal à 255.255.255.252. Calculons le pas : 256 - 252 = 4. Toutes les 4 adresses, nous changeons donc de sous-réseau :

192.168.0.0/30 est l'adresse du premier réseau
192.168.0.1/30 est la première machine du premier réseau
192.168.0.2/30 est la deuxième machine du premier réseau
192.168.0.3/30 est l'adresse de broadcast du premier réseau

192.168.0.4/30 est l'adresse du deuxième réseau
192.168.0.5/30 est la première adresse du deuxième réseau

192.168.0.6/30 est la deuxième adresse du deuxième réseau
192.168.0.7/30 est l'adresse de broadcast du deuxième réseau

...

On voit clairement que les machines 192.168.0.1/30 et 192.168.0.5/30 ne sont pas sur le même sous-réseau et qu'une passerelle sera donc nécessaire pour les faire communiquer.

Ce qui m'a mis la puce à l'oreille pendant que je faisais les vérifications d'usage (cablage), c'est l'interrogation de la personne : "les masques sont identiques mais les adresses de broadcast sont différentes, je ne comprend pas comment c'est possible. Si elles ont le même masque, les machines sont sur le même réseau et elles devraient donc avoir la même adresse de broadcast".

Comment indiquer un port supérieur ou égal à 1024 dans iptables ?

Une autre question que l'on m'a posé récemment. Il suffit simplement d'utiliser la syntaxe "1024:". Exemple :

iptables -t filter -A OUTPUT -o eth0 -s 172.16.0.0/24 -d 192.168.0.5/32 -p tcp -m tcp --sport 1024: --dport 80 -m state --state NEW -j ACCEPT

Pourquoi Windows 7 ne voit pas le disque dur et ses partitions lors d'une réinstallation ?

Encore un dépannage étonnant :) . L'ordinateur est équipé de Windows 7 Ultimate et d'Ubuntu tous deux installés par le propriétaire de la machine. Celui-ci souhaite réinstaller Windows 7 Ultimate en reformatant. Mais le programme d'installation ne détecte pas le disque dur et ses partitions.

Le mode AHCI est activé dans le BIOS mais cela ne devrait pas avoir d'importance puisque Windows 7 contient les drivers nécessaires. Je pars néanmoins à la recherche des drivers Intel AHCI et les mets sur une clé USB (voir le début de Adding Intel Matrix Drivers to Your XP Image for AHCI SATA Support chez Symantec Connect Community pour extraire les fichiers .inf de l'exécutable fourni par Intel). Windows les détecte bien mais affiche un message d'erreur en tentant de les charger. Encore plus fort : désactiver l'AHCI dans le BIOS ne résoud pas le problème !

Je pense pour un problème de disque dur et je démarre sur un live-cd de Gparted. Le disque dur se compose d'une partition de type inconnu de 200 Mo, de la partition dédiée à Windows, de la partition dédiée à Ubuntu et d'une partition dédiée au stockage des données. Mais à quoi sert donc cette partition de type inconnu ? L'utilisateur n'a pas le souvenir d'avoir créé cette partition ...

Doutant que la suppression de cette partition soit la solution au problème, je tente de cherche d'autres pistes. L'utilisateur m'incite à détruire la partition et je me laisse convaincre. Et le pire, c'est que cela fonctionne ! La suppression de cette partition a permis au programme d'installation de Windows 7 de détecter le disque dur et ses partitions contre toute logique (en même temps avec Windows ... je dis ça, je dis rien :) ).

Je n'ai par contre aucune piste concernant l'origine de cette partition atypique ...

Et comme mot de la fin : écoutez les personnes que vous dépannez, elles n'ont pas toujours tords ;) .

Quelques changements sur le blog (bis)

Table des matières

Ces temps-ci, j'ai effectué quelques modifications sur le blog.

Mise à jour de billets

J'ai mis à jour quelques billets, afin, comme toujours, d'apporter un complément d'information ou une correction. Ce coup-ci, c'est les billets suivants ont été mis à jour :

Amélioration de la structure des billets

Qu'est ce que j'entends par "structure" ? Le titre du blog ("GuiGui's Show" dans mon cas) est mis entre des balises qui symbolisent un titre de premier niveau (h1) et le titre d'un billet est mis entre des balises qui symbolisent un titre de deuxième niveau. Il apparaît donc logique, pour les sous-parties d'un billet, de continuer avec un titre de troisième niveau, etc.

Cela permet de mieux faire ressortir la structure du document auprès des moteurs de recherche afin que ceux-ci le "comprennent" mieux et l'indexent mieux. Mais pas seulement : une meilleure structure aide les logiciels dédiés aux personnes ayant une déficience visuelle.

Or, la qualité de ce blog en la matière était inégale. Certains corps de billet commençaient avec un titre de premier niveau, d'autres avec un titre de deuxième niveau, etc. Bref, c'était la jungle.

J'ai donc harmonisé tout cela en accord avec ce que je viens de vous dire.

J'ai également modifié les feuilles de style afin de faire ressortir la structure des billets et ainsi améliorer la lisibilité des billets.

Validation W3C

J'ai profité de l'optimisation ci-dessus afin d'améliorer la validation W3C des pages de ce blog. En effet, certaines balises sont mal fermées par WordPress et d'autres erreurs sont purement dues à des actions humaines ;) . Mais cela est corrigé.

Les seules erreurs qui restent sont provoquées :

  • Par le JS que l’hébergeur incruste en bas de chaque page, en dehors de la balise html ...
  • L'attribut aria-required, qui indique si un champ de formulaire est obligatoire. Il fait partie du brouillon WAI-ARIA qui, comme je viens de le dire, est un brouillon et donc pas reconnu par le validateur.

J'ai fait cette optimisation dans le même but que la précédente : afin d'améliorer l’accessibilité et l'affichage de ce blog sur l'ensemble des navigateurs. Après, ce n'est pas encore la perfection (notamment au niveau du CSS) mais c'est moins urgent ... et puis, je ne suis pas le pire du web ;) .

Table des matières

Contrairement aux autres changements cités dans ce billet, la mise en place d'une table des matières au début de chaque billet, était une fonctionnalité prévue depuis quelque temps sur ma TODO list.

Désormais, une table des matières sera donc insérée au début de chaque billet pour lequel cela se justifie (longueur, complexité entre autres). Comme vous pouvez le constater, les anciens billets sont déjà passés à la moulinette.

Choix de l'extension

Pour réaliser une table des matière de manière automatique, j'utilise l'extension Table of Contents Generator. Une extension libre (GNU GPL), simple et légère (un fichier, 120 lignes de PHP, point). C'est la seule extension parmi celles que j'ai essayées (Hackadelic SEO Table Of Contents, Table of Contents Creator) qui me convient.

Hacker l'extension

Personnaliser le style de l'extension

Cette extension utilise la feuille de style de votre thème. Il est donc très facile de la personnaliser : il suffit de créer/modifier un style div#main div.post div.entry ul dans la feuille de style de votre thème et de lui affecter les propriétés voulues.

On peut même faire un style rien que pour les tables des matières : il suffit de créer un style dédié, div.post div.entry ul.toc par exemple. Ensuite, dans le code de l'extension, il suffit de modifier deux lignes de la fonction get_toc() : la 51e et la 67e. Exemple avec la ligne 67 :

$html .= "<ol><li>$link";

devient

$html .= "<ol class=\"toc\"><li>$link";

Même modification pour la ligne 51.

Utiliser une liste non ordonnée à la place d'une liste ordonnée :

Il suffit de modifier le code de l'extension et notamment celui de la fonction get_toc() et de remplacer les écritures de balise "ol" par des balises "ul". Cela se joue aux lignes : 51, 59, 67, 73. Exemple avec la ligne 51:

$html .= "<ol><li>$link";

devient

$html .= "<ul><li>$link";
Un bug bien ennuyeux

Parfois, il arrive que la table des matières présente un bug d'affichage. En effet, WordPress ajoute une balise "</p>" dés qu'il voit un retour à la ligne ("\n" ou "<br />"). Le problème, c'est que cette balise est parfois insérée n'importe où et provoque un bug d'affichage de la table des matières. Voir un exemple.

La solution à ce problème est dans l'énoncé : il suffit d'enlever les retours à la ligne : <br /> ou \n. Là encore, il suffit de modifier, dans le code de l'extension, le code de la fonction get_toc. Le caractère \n est présent aux lignes 36, 55, 64, 67 mais il n'est pas utile de modifier la ligne 36. Voici un exemple de la modification de la ligne 55 :

$html .= "</li>\n<li>$link";

devient

$html .= "</li><li>$link";

Pour les autres lignes, la modification est identique. Mais ça, vous l'aviez déjà compris.

De nouveaux billets sont en cours de rédaction, stay tuned ! (Tiens, je n'ai pas déjà dit ça ? ;) )

Utiliser l’API de Piwik pour générer des rapports

Table des matières

L'API de Piwik permet, entre autres, de générer des rapports automatiques et/ou personnalisables.

Rappels de base sur l'API

L'API peut être interrogée via HTTP ou être utilisée directement dans vos scripts PHP. Voir ici pour des exemples : Piwik Analytics API – Calling Techniques. Ci-dessous, nous intégrerons l'API à un script PHP mais il est tout à fait envisageable de scripter l'appel à l'API via HTTP grâce à un script shell et à curl/wget. Quand on a compris une méthode, l'autre coule de source, seul le formalisme change.

Si je n'ai qu'un seul reproche à formuler à l'API, c'est son manque de documentation. Les prototypes des fonctions disponibles sont bien annoncés mais on ne nous explique pas toujours ce que la fonction attend en paramètre. Il faut alors aller lire le code source pour comprendre. De plus, l’hétérogénéité (ou le manque d'harmonisation) du code n'aide pas (ex. : pour certaines fonctions, on doit passer un tableau, pour d'autres une liste dont les termes sont séparés par une virgule ...).

Comment créer un rapport depuis l'API ?

Pourquoi créer un rapport via l'API alors que l'interface web le permet ? Parfois, la création d'un rapport via l'interface web échoue. Je n'ai pas encore cherché la cause du problème.

Voici le code :

<?php

	define('PIWIK_INCLUDE_PATH', realpath('./piwik')); //Répertoire racine de Piwik
	define('PIWIK_USER_PATH', realpath('./piwik')); //Idem
	define('PIWIK_ENABLE_DISPATCH', false);
	define('PIWIK_ENABLE_ERROR_HANDLER', false);
	define('PIWIK_ENABLE_SESSION_START', false);
	require_once PIWIK_INCLUDE_PATH . "/index.php";
	require_once PIWIK_INCLUDE_PATH . "/core/API/Request.php";
 
        //On créer le contrôleur
	Piwik_FrontController::getInstance()->init();
 
        //On prépare la requête que l'on souhaite effectuer
	$request = new Piwik_API_Request('
			method=PDFReports.addReport
			&idSite=1
			&description=test API
			&period=never
			&reportFormat=html
                        &reports=VisitsSummary_get,VisitTime_getVisitInformationPerLocalTime,Actions_getPageTitles,Actions_getOutlinks,Referers_getRefererType,Referers_getKeywords,Referers_getWebsites,Referers_getSearchEngines,UserCountry_getCountry,VisitorInterest_getNumberOfVisitsPerVisitDuration,VisitorInterest_getNumberOfVisitsPerPage,VisitFrequency_get,Provider_getProvider,UserSettings_getConfiguration
			&emailMe=0
			&token_auth=votretoken
	');

 
        //On exécute la requête
        $result = $request->process();
 
        //On affiche le résultat de la requête
	echo $result,'<br />';
?>

La méthode Piwik_API_Request permet de préparer une requête pour l'API. Les paramètres de cette méthode sont variables selon ce que l'on veut obtenir. Ici (seuls les paramètres peu ou pas documentés sont expliquées) :

  • method permet de spécifier ce que l'on veut obtenir. Ici, nous utilisons la méthode addReports du module PDFReports. Pour avoir une idée des opérations disponibles, il suffit d'aller dans la doc de Piwik (j'ai déjà donné un lien plus haut)
  • description permet de spécifier la description du rapport (voir interface web)
  • reportFormat permet de choisir un format de rapport (pdf ou html)
  • reports permet de choisir les informations (configuration matérielle du visiteur, heure de visite, ...) que l'on veut intégrer au rapport. La liste est disponible en appelant la méthode getReportMetadata du module API. Voir un exemple en ligne. Il suffit de récupérer l'uniqueId associé au rapport que vous voulez et de l'indiquer dans ce paramètre. Ici, on récupère, dans l'ordre : le récapitulatif des visites, les visites en fonction de l'heure locale, le titre des pages vues, les liens sortants, les types de referer (entrée directe, site web, ...), les mots clés dans les moteurs de recherche, les sites web, les moteurs de recherches d'où provient l'entrée, le pays du visiteur, la durée d'une visite, le nombre de pages par visite, la fréquence des visites, le FAI, la configuration complète du visiteur.
  • emailMe permet de demander la réception du rapport par courrier. Ici, on refuse (0).

Si la création du rapport fonctionne, l'API vous retourne l'id du rapport, sinon, l'API vous retourne un message d'erreur explicite.

Comment générer un rapport automatiquement depuis un script ?

Si vous avez compris l'exemple précédent, rien de bien compliqué ici : il suffit de lire la doc et les fichiers sources.

<?php
	define('PIWIK_INCLUDE_PATH', realpath('./piwik')); //Répertoire racine de Piwik
	define('PIWIK_USER_PATH', realpath('./piwik')); //Idem
	define('PIWIK_ENABLE_DISPATCH', false);
	define('PIWIK_ENABLE_ERROR_HANDLER', false);
	define('PIWIK_ENABLE_SESSION_START', false);
	require_once PIWIK_INCLUDE_PATH . "/index.php";
	require_once PIWIK_INCLUDE_PATH . "/core/API/Request.php";

        //On créer le contrôleur
	Piwik_FrontController::getInstance()->init();
        //On prépare la requête que l'on souhaite effectuer
	$request = new Piwik_API_Request('
			method=PDFReports.generateReport
			&idSite=1
			&idReport=2
			&date=2011-07-05
			&language=fr
			&period=month
			&reportFormat=html
			&outputType=2
			&token_auth=votretoken
        ');

 
        //On exécute la requête
        $result = $request->process();
?>

Explications :

  • idReport doit correspondre à l'id d'un rapport existant. Cet id est retourné par la méthode addReport ou est disponible dans la base de données et plus précisément dans la table prefixe_pdf. En remplaçant "prefixe" par le préfixe de vos tables Piwik.
  • outputType correspond à la manière dont le rapport sera sauvegardé. Si la valeur 1 est passée, alors le fichier sera disponible en téléchargement. Si la valeur 2 est passée, alors le fichier sera stocké sur le serveur, dans le repertoire piwik/tmp/assets/ . La valeur 1 est la valeur par défaut. Ces deux valeurs sont définies dans les constantes OUTPUT_DOWNLOAD et OUTPUT_SAVE_ON_DISK de la classe Piwik_PDFReports_API (fichier Piwik/plugins/PDFReports/API.php.

Là où cela devient intéressant, c'est dans le cadre d'un processus de sauvegarde secondaire. En effet, si vous n'avez jamais exporté vos données sur une longue période, il est fastidieux de le faire à la main.

L'API possède une option YYYY-MM-DD,YYYY-MM-DD pour le paramètre date et une option "month" pour le paramètre period. Cela permet donc de récupérer, comme le dit la doc, des informations pour chaque mois de la période donnée. Ex. : index.php?module=API&method=VisitsSummary.get&idSite=1&period=month&date=2010-07-01,2011-03-01&format=html permet de récupérer le récapitulatif des visiteurs pour chacun des mois compris entre juillet 2010 et mars 2011, au format html. Mais, cela ne fonctionne pas avec toutes les méthodes de l'API. C'est notamment le cas avec le module live ou le module PDFReports.

Il va donc falloir scripter afin de récupérer un rapport par mois, tous les mois d'une période donnée. Une simple boucle et l'utilisation de la classe DateTime suffiront. Cela donne :

<?php
	define('PIWIK_INCLUDE_PATH', realpath('./piwik')); //Répertoire racine de Piwik
	define('PIWIK_USER_PATH', realpath('./piwik')); //Idem
	define('PIWIK_ENABLE_DISPATCH', false);
	define('PIWIK_ENABLE_ERROR_HANDLER', false);
	define('PIWIK_ENABLE_SESSION_START', false);
	require_once PIWIK_INCLUDE_PATH . "/index.php";
	require_once PIWIK_INCLUDE_PATH . "/core/API/Request.php";
 
        //On créer le contrôleur
	Piwik_FrontController::getInstance()->init();
 
        //On prépare la période sur laquelle on va travailler
	$dateDebut = '2010-08-01';
	$dateFin = '2011-08-01';	
	$dateInc = new DateTime($dateDebut);

 
        //Tant que la date qui va être incrémenté n'est pas égale à la date de fin ...
	while ($dateInc->format('Y-m-d') != $dateFin)
	{	
                // ... on prépare la requête ...

	        $request = new Piwik_API_Request('
				method=PDFReports.generateReport
				&idSite=1
				&idReport=1
				&date='.$dateInc->format('Y-m-d').'
				&language=fr
				&period=month
				&reportFormat=html
				&outputType=2
				&token_auth=votretoken
		');

 
		// ... et on l’exécute ...
		$result = $request->process();
 
                // ... et enfin on incrémente la date sur laquelle on travaille		
		$dateInc->modify('+1 month');
	}
?>

ÉDIT 31/07/2011 à 1h12 :
Le script ci-dessus peut-être optimisé. Nous profitons de l'opérateur d'égalité (pas d'identité !) qui permet de faire la différence entre deux objets d'une même classe en fonction de la valeur de leur attributs. Ainsi, nous ne faisons plus appel à la méthode "format()" de la classe DateTime. Ce qui donne :

        // code identique
        $dateDebut = new DateTime('2010-08-01');
        $dateFin = new DateTime('2011-08-01');	
 
        //Tant que la date qui va être incrémenté n'est pas égale à la date de fin ...
	while ($dateDebut != $dateFin)
	{
		// code identique
	}

Côté performance, cela est insignifiant : environ 0.0012 secondes d'écart entre les deux scripts, en faveur du deuxième script. Mesure effectuée avec la fonction xdebug_time_index() de XDebug, en ne faisant rien d'autre qu'afficher un message avec echo dans la boucle while et en prenant une période de 490 mois .
Fin de l'édit

Comment débloquer la limite du nombre de lignes de chaque tableau ?

Comme je l'ai déjà dit, dans un rapport, le nombre de lignes par tableau est limité (ex. : seulement les 30 mots-clés les plus utilisés sont affichés).

L'API précise qu'il existe un paramètre optionnel, filter_truncate, qui permet de changer cette limite. Néanmoins, ce paramètre est contourné par le module PDFReports. C'est dans le fichier /piwik/plugins/PDFReports/API.php que cela se passe :

Lignes 313-314 :

$filterTruncateGET = Piwik_Common::getRequestVar('filter_truncate', false);
$_GET['filter_truncate'] = 30;

Lignes 333-337 :

// Restore values
if($filterTruncateGET !== false)
{
        $_GET['filter_truncate'] = $filterTruncateGET;
}

Bien que cette option a sans doute été désactivée pour des raisons de performances, il y a un moyen de la réactiver :

Il suffit de remplacer les lignes 313/314 par :

$_GET['filter_truncate'] = $filterTruncateGET = Piwik_Common::getRequestVar('filter_truncate', false);

Et de commenter les lignes 333-337.

Ensuite, vous pouvez utiliser filter_truncate. Avec ce code, les tableaux du rapport généré feront jusqu'à 100 lignes. Ainsi, dans le rapport sur les mots-clés utilisés, vous aurez le top 100 au lieu du top 30 :

$request = new Piwik_API_Request('
		method=PDFReports.generateReport
		&idSite=1
		&idReport=1
		&date='.$dateInc->format('Y-m-d').'
		&language=fr
		&period=month
		&reportFormat=html
		&outputType=2
                &filter_truncate=100
		&token_auth=votretoken
');

Comment utiliser l'API pour générer des rapports plus personnels

De nombreuses méthodes existent dans l'API Piwik. Cela vous permet de vous fabriquer un rapport sur mesure (avec uniquement les informations que vous voulez) ou de surveiller une seule information (le nombre de visiteurs uniques du mois, par exemple). De plus, l'API donne accès à des informations que les rapports n’intègrent pas (ex. : le module live n'apparait pas dans les rapports), ce qui permet un export des données.

C'est ici que je vous laisse car vous trouverez des exemples dans la documentation de Piwik.

PS : Les codes donnés ci-dessus n'ont pas vocation à être utilisés en production sans amélioration. Par exemple : il n'y a pas de contrôle des erreurs.

PS2 : Ce billet n'évoque pas la Piwik Tracker API. Il peut tout de même être intéressant de jeter un oeil à la documentation, pour, par exemple, prendre un minimum en compte les utilisateurs ayant désactivé javascript.