IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Assembleur Intel avec NASM sur machine virtuelle sans système d'exploitation

Date de publication : 20 mars 2012.


III. Des fonctions pour KarlOS
III.1. Pour rigoler
III.2. Table
III.3. CPU
III.4. Date
III.5. Du code


III. Des fonctions pour KarlOS

On a fait une ligne de commande dans laquelle on a mis deux fonctions. On va voir maintenant ce qu'elles font, voire, on va en rajouter.


III.1. Pour rigoler

Mais avant cela : rigolage, comme promis. Alors, je me suis dit, comme ça, dans le train : "Mais z'au fait, mon garçon, c'est quoi, la taille d'une chaîne de caractère ?" Réponse : c'est la position de la première occurence du caractère 0. Comme, ma foi, on fait un SCASB, appeler tailleChaineB, c'est appeler indexChaineB avec 0 dans AL. Et ça, personne n'en parle. Et en quoi c'est rigolo ? C'est rigolo parce que (faut toujours tout expliquer) avec un seul code, on a deux fonctions ! Oui, monsieur, deux fonctions pour le prix d'une ! Même le regretté Ritchie ne nous ferait pas un coup pareil. Regardez plutôt :
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; tailleChaineB													;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ECX renvoie la longueur de la chaîne EDI							;;
;; AL est mis à 0													;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
tailleChaineB:
	xor al, al
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; indexChaineB														;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ECX renvoie l'index du caractère AL dans la chaîne EDI			;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
indexChaineB:
	push edi
	or ecx, 0xFFFFFFFF
	repne scasb
	neg ecx
	dec ecx
	pop edi
	ret
Et, ça, aucun compilo au monde ne l'aurait fait... Pas que ce soit forcément bien, notez.


III.2. Table

La toute première fonction codée pour la ligne de commande de KarlOS. Cela ne fait qu'afficher notre table de caractères.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; tabCaracteres													;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Affiche les 122 premiers caractères de KarlOS					;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
tabCaracteres:
	push ecx
	push eax
	mov ecx, 122
	xor eax, eax
.afficheAlphabet:
	call afficheCaractere
	inc al
	loop .afficheAlphabet
	call retourChariot
	pop eax
	pop ecx
	ret

III.3. CPU

La toute deuxième (quoi, y'a bien la toute première) fonction codée pour la ligne de commande de KarlOS. Cela ne fait qu'afficher une malheureuse information sur le microprocesseur. Bon, on utilise l'instruction CPUID, qui est une instruction versatile, voire lunatique. Utilisez-la dans un langage évolué, celle-là, qu'on rigole un peu. Bon, je vous la livre en l'état.

Il faut d'abord tester si cette fonction est supportée par le processeur. Ce n'est pas évident. Ca se passe dans le registre EFLAGS. Le bit n°21 de ce registre (notez qu'on est nécessairement au moins en 32 bits) peut être modifié ssi CPUID existe. C'est la base du test. Mais, car il faut bien un "mais" sinon on s'ennuie, mais donc, aucune instruction ne permet de modifier ce registre. On aurait voulu nous mettre des bâtons dans les roues qu'on ne s'y serait pas pris autrement. La ruse (car tous les programmeurs de système sont des fourbes félons fielleux et fallots fomentant ferme de fabuleux mais faux complots, bienvenue chez nous, nouvel initié), la ruse consiste à sauver ce registre sur la pile. PUSHFD est notre premier homme. Comme c'est sur la pile, hé, vieux, 'suffit de le récupérer dans un vrai registre ! POP EAX, et nous avons EFLAGS dans un registre d'honnête homme. Il suffirait que POPFD ait le bon goût d'exister, et nous serions les plus heureux développeurs modifieurs d'EFLAG. Savourons notre bonheur, elle existe. Donc, on récupère dans un registre "normal" EFLAGS, on fait un XOR sur le bit 21, on le met dans le registre maudit, on le relit juste derrière, si la valeur est égale à celle avant le XOR, CPUID n'est pas implanté dans le processeur, merci d'avoir joué, repassez nous voir à la prochaine release.
	pushfd ; Sauve le registre EFLAGS
	pop eax ; Le récupère dans EAX
	mov ebx, eax ; Le copie dans EBX pour la comparaison postérieure
	xor eax, 00200000h ; on change l'état du bit 21
	push eax ; On le met sur la pile
	popfd ; Pour le remettre dans le registre EFLAGS
	pushfd ; On re-sauve le registre EFLAGS (c'est l'idée du test)
	pop eax ; Le récupère dans EAX
	cmp eax, ebx ; Et on le compare avec sa sauvegarde
	jz .NO_CPUID ; Si rien n'a changé, l'instruction CPUID n'est pas supportée.
Admettons que l'instruction existe. Que fait-elle ? Et bien, très chers frères, il faut savoir que cette instruction ô combien utlie à l'homme de bien, fut conçue tout près de nos verts bocages, au milieu des vergers de pommes et de poires, sous les douces effluves de Camembert, dans notre Normandie. Conséquemment, ce qu'elle fait, ça dépend. En gros, il en existe autant de versions qu'il y a de fondeurs de processeurs, de familles de processeurs et de modèles de processeurs. Néanmoins, elle fonctionne un peu comme une interruption. Suivant ce qu'on a dans le registre EAX, on obtient différents résultats. Avec 0 (alias "la bulle"), on obtient, partout, toujours et en tout lieu et nonobstant les conditions sus-citées, le nombre de fonctions dont dispose CPUID, mais aussi une chaîne de caractères identifiant le fondeur. On n'arrête pas le progrès des sciences forensiques. Vous noterez, au passage, le tropisme de la valeur zéro.
	xor eax, eax
	cpuid
	mov [nbFonctions], eax
	mov edi, chaine	;Affichage de la marque
	mov eax, ebx
	stosd
	mov eax, edx
	stosd
	mov eax, ecx
	stosd
	xor al, al
	stosb
	mov esi, chaine
	call conversionASCII
	call afficheChaine
	call retourChariot
Dans EAX, on récupère le nombre de fonctions disponibles dans CPUID. Donc, premier coup, on a la marque et jusqu'où on peut aller (cf juste au-dessus) :
.marqueAMD:
	inc al; AL = 2
.marqueIntel:
	inc al; AL = 1
	jmp .marqueInconnue

	xor eax, eax
	cmp [Intel.ebx], ebx
	je .marqueIntel
	cmp [AMD.ebx], ebx
	je .marqueAMD
.marqueInconnue:
	mov byte [Marque], al
	call chargeCPUIDFamille ; On charge les familles de processeurs du constructeur
La fonction chargeCPUIDFamille prépare un tableau dans lequel on trouvera les noms des familles de processeur du constructeur considéré.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; EAX contient la marque du CPU, et est détruit ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
chargeCPUIDFamille:
	push edi
	push ecx

	mov edi, listeFamilles
	mov ecx, 16
	cmp al, 1 ;Test Intel
	je .setEAXIntel2
	cmp al, 2 ;Test AMD
	je .setEAXAMD2
.suite:
	call chargeListeSansTest
	pop ecx
	pop edi
	ret

.setEAXIntel2:
	mov eax, Intel.Familles
	jmp .suite

.setEAXAMD2:
	mov eax, AMD.Familles
	jmp .suite
En gros, on se contente d'initialiser les paramètres de ChargeListeSansTest, que je vous présente.
;; ECX contient le nombre de chaînes à indexer
;; EAX contient l'adresse de la liste des chaînes
;; EDI contient l'adresse de la liste d'index à mettre à jour
chargeListeSansTest:
	push eax
	push ebx
	mov ebx, eax
.chaineSuivante:
	stosd
	push ecx
	xchg edi, ebx
	xor al, al
	or ecx, 0xFFFFFFFF
	repne scasb
	mov eax, edi
	xchg edi, ebx
	pop ecx
	loop .chaineSuivante
	pop ebx
	pop eax
	ret
Cette fonction indexe les adresses de ECX chaînes de caractères consécutives en mémoire dans un tableau. Ca n'utilise que des registres, et encore, pas tous, et je n'ai pas plus élégant. L'idée est de stocker l'adresse du caractère suivant chaque caractère nul, plus le premier. Pour ce faire, on va échanger le contenu de EDI avec EBX souvent. EBX parce qu'il m'en fallait un, pas envie de le mettre sur la pile. Tout d'abord, on met dans EBX l'adresse des chaînes à indexer. On met dans EDI EAX, qui contient la même chose que EBX. On échange EDI et EBX, EDI contient maintenant les chaînes à indexer. SCASB va comparer l'octet à l'adresse EDI à la valeur dans AL (donc mettre le drapeau d'égalité au besoin), puis incrémenter EDI. En mettant AL à zéro et en répétant SCASB tant que le drapeau égalité n'est pas mis, EDI sera placé sur le premier caractère de la chaîne suivante, soit pile celui qu'on veut. Non, vraiment, des fois on dirait que les instructions ont été faites pour la programmation. On échange EBX et EDI, on n'oublie pas de mettre EAX à jour, et on est repartis !

Ceci étant fait, allons, courage camarade, essayons la fonction n°1. Chez Intel et AMD, elle renvoie des champs cabalistiques donnant l'identité du processeur dans EAX. Ces gens bien documentés nous donnent la structure de ce que nous cherchons. Occupons-nous d'abord de la famille. Car la familia, c'est important, disait mon parrain.

Pour l'obtenir, il faut appeler la fonction n°1 de CPUID.
	mov eax, 1
	cmp eax, [nbFonctions]
	jae .fin ; La fonction n'existe pas
	cpuid
On fera ensuite des déplacements de bits couplés à des ET logiques afin d'isoler les nombres qui nous intéressent. Dans le code de démonstration, vous noterez que je n'ai pas tout fait, loin s'en faut. CPUID a suivi l'histoire des compatibles PC depuis leurs 32 bits et en est la digne héritière : c'est fouillis au possible, et c'est la rencontre entre la technique et la réclame (le marketing pour les franglais). On notera qu'AMD, plus jeune qu'Intel en tant que concepteur de micro-processeurs compatibles PC, est sensiblement plus propre dans ses dénominations.


III.4. Date

Je vous propose maintenant de voir un peu ce que les stockages de masse ont à nous dire. Ce qui n'a rien à voir avec le titre du chapitre. Et en fait, si. Camarade, accroche-toi au pinceau, j'ôte l'échelle. Nous allons tenter de communiquer avec le lecteur de disquettes. Exercice de longue haleine pour nous, mais commençons petitement. Petitement signifie : jouons un peu avec quelque chose de facile à voir qui dispose d'un point commun avec le sujet qui nous intéresse. Le point commun en question, c'est les ports d'E/S. On communique logiquement avec nos camarades disquettes par les ports d'entrée-sortie. Quelque chose dans le même genre est plus facile à appréhender du premier coup, il s'agit du CMOS. CMOS comme Complementary Metal Oxide Semiconductor, soit en français : transistors complémentaires à effet de champ à grille isolée. Ce qui est incompréhensible. On l'appelle comme ça pour des raisons qui m'échappent mais qui sont d'une pertinence confondante, pour peu qu'on soit anglo-saxon et timbré.

Je pourrai évoquer la physique des semi-conducteurs et des batteries, les casse-têtes d'ingénieurs pour fabriquer des mémoires rémanentes, l'histoire moderne des prospecteurs miniers à la recherche des filons purs, nouveaux chercheurs d'or du Klondike (oui, j'aime beaucoup Picsou) pour vous expliquer l'origine de ce terme et ce qu'il désigne. Mais je m'abstiendrai

Parce qu'entre nous, et n'en dites rien, profitez d'être dans la confidence des dieux, il s'agit d'une montre.

Oui, il y a trois bricoles en plus, mais franchement.

Entre nous.

C'est une montre.

Et on lit cette montre par les ports d'E/S.

Cette montre écoute le port 70h et parle sur le 71h. En gros, on demande une case mémoire sur le 70h et on récupère sa valeur sur le 71h.

Alors, ces cases mémoires, de quel choix mirifique disposons-nous ? J'ai trouvé ceci, qui a le bon goût de correspondre assez bien à ma réalité. Si, vous et moi, ne vivons pas dans le même multivers et que votre réalité ne correspond pas à la mienne, je peux faire un effort et faire le correctif.

Numéro Fonction
0 RTC seconds. Contient les secondes de l'heure courante.
2 RTC minutes. Contient les minutes de l'heure courante.
4 RTC hours. Contient les heures de l'heure courante.
6 RTC day of week. Contient le jour de la semaine courant
7 RTC date day. Contient le numéro du jour courant.
8 RTC date month. Contient le mois courant.
9 RTC date year. Contient l'année courante (siècles exclus).
50 RTC date century. Contient le siècle courant.
Je n'ai écrit ici que les valeurs qui nous préoccupent au premier chef. Je puis donc affirmer qu'au moins une machine sur cette terre utilise ces valeurs. Elles sont codées en "BCD" (Binaire Codé Décimal) ou "DBC" (Décimal Codé Binaire) ou qu'importe le sigle pourvu qu'on ait la valeur. Le BCD est l'art de perdre de la place en mémoire : Wikipédia, qui est notre ami, explique assez bien de quoi il retourne. Nous, tout ce qu'on a besoin de savoir, c'est que les octets du tableau ci-dessus doivent être passés à cette fonction de conversion :
DCBVersBinaire:
	push bx
	push ax			;On sauve le Décimal Codé Binaire
	and al, 0xF0	;On récupère les dizaines
	push ax			;On sauve les dizaines
	shr al, 1		;Qu'on divise par 2 (ie on multiplie le _chiffre_ des dizaines par 8)
	pop bx
	shr bl, 3		;Qu'on divise par 8 (ie on multiplie le _chiffre_ des dizaines par 2)
	add al, bl		;On a donc le nombre des dizaines (8+2)*chiffre des dizaines
	pop bx
	and bl, 0xF		;On récupère les unités
	add al, bl		;Et voici la somme des 2, ie notre nombre
	pop bx
	ret
Allez, c'est parti, on tranforme notre bête de calcul, notre 64 bits Quad Core Tri Channels Moto Krote en montre à quartz. On commence par charger les chaînes de caractères intéressantes, comme, au pif, les noms des jours et des mois.
	mov eax, chMois
	mov edi, indexChMois
	mov ecx, 12
	call chargeListe
	mov eax, chJours
	mov edi, indexChJours
	mov ecx, 7
	call chargeListe
Et ensuite, on demande gentiment au CMOS, qui s'appelle aussi RTC (Real Time Clock, ou Regarde Ton Cadran - horloge temps réel), la date. On le fait dans une boucle parce que je n'aime pas le code dupliqué :
	mov edi, Annee
	xor ah, ah
	mov ecx, 4
.boucleDate:
	mov al, cl	; Registres de date du RTC du CMOS
	add al, 5
	out 0x70, al
	in al, 0x71
	call DCBVersBinaire
	stosb
	loop .boucleDate
Soyez chics, n'oubliez pas de prévoir de la place dans Annee, comme ceci :
Annee: db 0
Mois: db 0
JourDuMois: db 0
JourDeLaSemaine: db 0
Siecle : db 0
Heure : db 0
Minute : db 0
Seconde : db 0
Ca fait 8 octets. Croyez-moi, vous voulez nommer chaque octet. Bon, on récupère le siècle :
	mov al, 50	; Registre de siècle du RTC du CMOS (peut-être)
	out 0x70, al
	in al, 0x71
	call DCBVersBinaire
	stosb
Et enfin l'heure par une boucle de 2 en 2 :
	mov ecx, 3
.boucleHeure:
	mov al, cl	; Registres d'heure du RTC du CMOS
	dec al
	shl al, 1
	out 0x70, al
	in al, 0x71
	call DCBVersBinaire
	stosb
	loop .boucleHeure
Reste à faire l'affichage. Et passer un coup de balai, c'est à dire transformer en fonction le code redondant.


III.5. Du code

L'ensemble du code de ce chapitre se trouve ici : Tuto15

 

Valid XHTML 1.0 TransitionalValid CSS!