Assembleur Intel avec NASM


précédentsommairesuivant

VI. Assembleur : et si on en avait encore moins ?

49 commentaires Donner une note à l'article (5)

Relu par ClaudeLELOUP.

On a écrit à l'écran, dessiné, lu l'entrée clavier. Nous l'avons fait sans utiliser les facilités fournies par le système d'exploitation. Mais nous utilisons toujours le système d'exploitation, parce que nous lui demandons l'exécution de notre programme. Il nous modifie notre fichier, c'est le fameux org 256 que nous devons écrire pour que nos adresses restent valides dans notre programme. Et bien, ça aussi, je n'en veux plus.

VI.1. Des machines virtuelles

Pour que le système d'exploitation n'existe plus, un bon moyen est le "format c:". C'est très bien, mais très gênant, puisque finalement, ça supprime tout, et donc aussi notre éditeur de texte pour écrire notre programme. Et sans éditeur, nous ne pourrons pas écrire quoi que ce soit, et donc il sera impossible de refaire fonctionner l'ordinateur. C'est une mauvaise idée. Mais il se trouve que beaucoup de gens ont besoin d'un ordinateur avec rien dessus. Et à l'ère de l'internet, si beaucoup de gens ont besoin de quelque chose, alors quelqu'un l'a fait. Ca s'appelle une machine virtuelle, c'est un logiciel qui simule un ordinateur sur votre ordinateur. Oui, le concept est étrange, mais c'est bien pratique. Il en existe plusieurs, prenez celui que vous voulez tant que l'on peut y configurer un PC sans système d'exploitation. Personnellement, j'ai tenté ces deux-là :

VirtualBox est plus sympa à utiliser que Bochs, mais Bochs a aussi ses avantages, c'est même pour ça que les deux existent.

Ce qu'il nous faut, nous, c'est un PC avec un lecteur de disquettes avec une disquette dedans. La disquette sera de préférence un fichier ".img" mais c'est au choix. L'idée est d'avoir un ordinateur, même faux, sur lequel on va pouvoir jouer autant qu'on veut sans pour autant tout casser.

VI.2. Du chargeur de tige de botte

Au démarrage, un ordinateur a un gros problème : il doit charger en mémoire un programme qui lui permettra de lancer d'autres programmes, qui éventuellement, lanceront d'autres programmes, etc. Mais le premier programme, comment se charge-t-il en mémoire ?

C'est un peu le problème de la poule et de l'oeuf...

L'ordinateur va utiliser la technique du baron de Münchausen : alors que le baron allait se noyer, il appelle à l'aide. Comme personne ne répond, le baron se trouve contraint, pour se sauver, de se tirer lui-même de l'eau par ses tiges de botte. En anglais, ça se dit "bootstrap". En français, on est moins poétique et on appelle le "bootstrap loader" : le chargeur de secteur d'amorçage.

Le BIOS d'un PC va, au démarrage, chercher, sur certains périphériques, un bloc de 512 octets qui se termine par la séquence 0x55AA. 0x55AA est un nombre magique : ça n'a aucune raison particulière d'être ce nombre-là plutôt qu'un autre, simplement, il en faut un, alors, on en sort un du chapeau. Et sortir du chapeau, c'est magique.

Ce bloc de 512 octets, il va le mettre à l'adresse 0x07C0:0000, et lui donner la main. C'est comme ça. C'est 0x07C0:0000 et puis c'est marre.

Ce bloc de 512 octets, nous l'allons écrire.

VI.3. D'un secteur d'amorçage pour nous

Bien sûr, on peut utiliser des secteurs d'amorçage déjà faits. Mais mon but est de partir du plus bas. Il me faut donc le mien.

Alors, hardi, compagnons, portons haut la flamberge et écrivons :

 
Sélectionnez

				org 0x0000 ; On n'a pas de décalage d'adresse
 
				    jmp 0x07C0:debut     ; On est chargé dans le segment 0x07C0
				debut:
				    ; Met les segments utiles au segment de code courant
				    mov ax, cs
				    mov ds, ax
				    call detect_cpu
 
				initialise_disque: ; Initialise le lecteur de disque
				    xor ax, ax
				

Ici, une parenthèse : on est censé donner à l'interruption 0x13 l'identifiant du disque sur lequel on est chargé via le registre DL. Or, il se trouve que le BIOS, décidément conçu par des gens qui ne veulent pas s'embêter, nous donne le lecteur sur lequel il nous a trouvé dans le registre DL. Comme notre programme sera sur le même disque, on n'a rien à changer. Fin de la parenthèse.

 
Sélectionnez

				    int 0x13
				    jc initialise_disque; En cas d'erreur on recommence (sinon, de toute façon, on ne peut rien faire)
 
				lire:
				

De nouveau une parenthèse : on va charger un autre programme que le secteur d'amorçage en mémoire. Où allons-nous le mettre ? Pour l'instant, où on veut, on est l'chef, après tout. 0x1000:0000 n'a pas l'air trop mal en première approximation. Et on va charger 5 secteurs, parce que notre programme fera moins de 2,5 ko. Ah oui, c'est comme ça.

 
Sélectionnez

					mov ax, 0x1000 ; ES:BX = 1000:0000
					xor bx, bx
					mov es, ax
					mov ah, 2 ; Fonction 0x02 : chargement mémoire
					mov al, 6 ; On s'arrête au secteur  6
					xor ch, ch ; Premier cylindre (n° 0)
					mov cl, 2 ; Premier secteur (porte le  2, le  1, on est dedans, et le  0 n'existe pas)
					; Ca fait donc 5 secteurs
					xor dh, dh ; Tête de lecture  0
					; Toujours pas d'identifiant de disque, c'est toujours le même.
					int 0x13 ; Lit !
					jc lire ; En cas d'erreur, on recommence
					mov si, sautNoyau ; Un petit message pour rassurer les troupes.
					call affiche_chaine
					jmp 0x1000:0000 ; Et on donne la main au programme que nous venons de charger
				

Alors, pour la fine bouche : on va faire de l'affichage et du test. D'abord, on va tester le processeur, car depuis qu'on a commencé, j'ai appris des trucs : pour savoir si on a à faire à un 8086, 80286 ou 80386, on teste certains bits du registre de drapeaux.

 
Sélectionnez

				detect_cpu:
					mov si, processormsg ; Dit à l' utilisateur ce qu'on est en train de faire
					call affiche_chaine
 
					mov si, proc8086 ; De base, on considère qu'il s'agit d'un 8086
					pushf ; sauvegarde les valeurs originales des drapeaux
 
					; teste si un 8088/8086 est présent (les bits 12-15 sont à 1)
					xor ah, ah ; Met les bits 12-15 à 0
					call test_drapeaux
					cmp ah, 0xF0
					je finDetectCpu ; 8088/8086 détecté
 
					mov si, proc286 ; On considère qu'il s'agit d'un 286
					; teste si un 286 est présent (les bits 12-15 sont effacés)
					mov ah, 0xF0 ; Met les bits 12-15 à 1
					call test_drapeaux
					jz finDetectCpu ; 286 détecté
 
					mov si, proc386 ; aucun 8088/8086 ou 286, donc c'est un 386 ou plus
				finDetectCpu:
					popf ; restaure les valeurs originales des flags
					call affiche_chaine
					ret
 
				test_drapeaux:
					push ax ; copie AX dans la pile
					popf ; Récupère AX en tant que registre de drapeaux. Les bits 12-15 sont initialisés pour le test
					pushf ; Remet le registre de drapeaux sur la pile
					pop ax ; Les drapeaux sont mis dans AX pour analyse
					and ah, 0xF0 ; Ne garde que les bits 12-15
					ret
				

Et maintenant, la culte routine d'affichage en mode texte

 
Sélectionnez

				affiche_chaine:
					push ax
					push bx
					push cx
					push dx
					xor bh, bh; RAZ de bh, qui stocke la page d'affichage
					mov ah, 0x03
					int 0x10; appel de l'interruption BIOS qui donne la position du curseur, stockée dans dx
					mov cx, 1; nombre de fois  l'on va afficher un caractère
				affiche_suivant:
					lodsb
					or al, al;on compare al à zéro pour s'arrêter
					jz fin_affiche_suivant
					cmp al, 13
					je nouvelle_ligne
					mov ah, 0x0A;on affiche le caractère courant cx fois
					int 0x10
					inc dl; on passe à la colonne suivante pour la position du curseur
					cmp dl, 80
					jne positionne_curseur
				nouvelle_ligne:
					inc dh; on passe à la ligne suivante
					xor dl, dl; colonne 0
				positionne_curseur:
					mov ah, 0x02;on positionne le curseur
					int 0x10
					jmp affiche_suivant
				fin_affiche_suivant:
					pop dx
					pop cx
					pop bx
					pop ax
					ret
				;fin de affiche_chaine
 
				proc8086: db '8086', 13, 0
				proc286: db '286', 13, 0
				proc386: db '386', 13, 0
				processormsg: db 'Test du processeur : ', 0
				sautNoyau: db 'Saut au noyau', 13, 0
				

Petit souci : notre secteur d'amorçage doit se terminer par 0x55AA, qui est un mot et qui doit donc être à la fin des 512 octets. Pour le mettre à la fin, nous allons simplement remplir les octets libres jusque 510 avec des 0. Cela fait donc 510 octets, moins l'adresse du dernier octet de code, moins l'adresse du premier octet de la section (qui est égale à 0, mais là n'est pas le problème). NASM fournit "$" comme étant l'adresse du début de la ligne de code courante, et "$$" comme l'adresse de la première instruction de la section. NASM fournit "times", qui répète ce qui le suit un certain nombre de fois. On utilise times 510 - taille de notre code fois pour écrire 0, et le tour est joué.

 
Sélectionnez

				times 510-($-$$) db 0
				dw 0xAA55 ; Le nombre magique écrit à l'envers parce que M. Intel est grosboutiste, ce qui signifie qu'il inverse les octets d'un mot.
				

Nous avons maintenant notre secteur d'amorçage, à compiler avec : nasm -o amorce.com amorce.asm

Et on ne le lance pas bêtement. On attend d'avoir fait la suite.

VI.4. Passer par noyau planète

La suite est un autre programme : nouveau fichier, page blanche... Noooon... pas page blanche, on va être un peu malin, on va partir du chapitre précédent. Simplement, on va, au départ, initialiser les segments utilisés par notre programme. Comme tout est dans un seul segment, on va les initialiser avec la valeur de CS, le segment de code, qui est bon, puisqu'il a été initialisé par le secteur d'amorçage. Et pour ne pas s'emberlificoter, on va utiliser un autre segment pour la pile. Au pif, encore une fois.

Ensuite, techniquement, nous développons ce qu'il est convenu d'appeler un noyau de système d'exploitation. Alors, bon, c'est pas vraiment un noyau, mais on va faire comme si. Et faire comme si, ça veut dire que notre programme ne rend jamais la main. D'ailleurs, à qui la rendrait-il ? Au secteur d'amorçage, qui a fini son travail il y a bien longtemps ? Non. Il ne rendra pas la main. Ainsi, au lieu du "ret" final, ce sera "jmp $", qui fait une boucle infinie. D'autre part, il n'y aura pas d'en-tête du système d'exploitation, vu que le système d'exploitation, c'est lui. Comme le secteur d'amorçage, nous aurons "org 0".

VI.5. Des 45 tours

Comment va-t-on utiliser notre programme ?

Conceptuellement, nous allons en faire une disquette de démarrage, oui, une disquette dite "de boot" ! On compile notre programme normalement. Ensuite, on copie bout à bout notre secteur d'amorçage et notre programme dans un seul fichier. Sous Windows, DOS et Cie, la commande est :

 
Sélectionnez
copy amorce.com/B+programme.com/B disk.img /Y

"/B" signifie qu'il s'agit d'un fichier binaire, ce qui évitera au DOS de le modifier. "/Y" évite d'avoir la confirmation de réécriture qui ne manque pas d'arriver dès la deuxième fois.

Sous système Unix, je ne connais pas la commande.

Le fichier copié est la disquette de démarrage.

Dans le code assembleur, on termine le fichier comme pour le secteur d'amorçage, avec un "times 4096 - ($ - $$) db 0". Cela sert le même objectif : que le fichier compilé fasse un nombre entier de secteurs de disquette.

Il ne reste plus qu'à configurer le PC virtuel pour qu'il lise le fichier généré comme une disquette : soit on lui donne une vraie disquette qui ne contient que notre fichier, soit on lui précise que la disquette est en fait un fichier image.

Il ne reste plus qu'à démarrer la machine virtuelle, et voilà ! On retrouve notre programme.

VI.6. Du massacre de disque

Notre programme a été lancé sur une machine virtuelle, et surtout, qu'il en soit ainsi pour le moment : il y a un morceau de code qui écrit sur le premier disque dur. Si on l'exécutait sur un vrai ordinateur, on écrirait là où est le système d'exploitation, ce qui est toujours une mauvaise idée. Ce petit bout de code ne fait que recopier les premiers secteurs de la disquette sur les premiers secteurs du disque dur. En enlevant la disquette de la machine virtuelle, la machine virtuelle démarre toujours, et exécute notre programme.

VI.7. Du code

Le code de ce chapitre se trouve ici :


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2011 Etienne Sauvage. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.