Jour 2 : Apprentissage du langage assembleur et introduction à Makefile
Cet article se concentre sur l'analyse des paramètres liés au système de fichiers dans le code source.
Évolution de l'apprentissage
Comparons d'abord le code du jour 1, qui utilisait simplement des instructions DB, DW, DD et RESB :
; hello-os
; TAB=4
; Code standard pour disquette au format FAT12
DB 0xeb, 0x4e, 0x90
DB "HELLOIPL" ; Nom du secteur de démarrage (8 octets)
DW 512 ; Taille de chaque secteur (doit être 512 octets)
DB 1 ; Taille du cluster (doit être 1 secteur)
DW 1 ; Position de départ du FAT (généralement le premier secteur)
DB 2 ; Nombre de FAT (doit être 2)
DW 224 ; Taille du répertoire racine (généralement 224 entrées)
DW 2880 ; Taille du disque (doit être 2880 secteurs pour 1440*1024/512)
DB 0xf0 ; Type de disque (doit être 0xf0)
DW 9 ; Longueur du FAT (doit être 9 secteurs)
DW 18 ; Nombre de secteurs par piste (doit être 18)
DW 2 ; Nombre de têtes (doit être 2)
DD 0 ; Pas de partition, doit être 0
DD 2880 ; Réécriture de la taille du disque
DB 0,0,0x29 ; Signification inconnue (fixe)
DD 0xffffffff ; (Probablement) numéro d'étiquette de volume
DB "HELLO-OS " ; Nom du disque (doit faire 11 octets, complété par des espaces)
DB "FAT12 " ; Nom du format de disque (doit faire 8 octets, complété par des espaces)
RESB 18 ; Laisser 18 octets vides
; Corps du programme
DB 0xb8, 0x00, 0x00, 0x8e, 0xd0, 0xbc, 0x00, 0x7c
DB 0x8e, 0xd8, 0x8e, 0xc0, 0xbe, 0x74, 0x7c, 0x8a
DB 0x04, 0x83, 0xc6, 0x01, 0x3c, 0x00, 0x74, 0x09
DB 0xb4, 0x0e, 0xbb, 0x0f, 0x00, 0xcd, 0x10, 0xeb
DB 0xee, 0xf4, 0xeb, 0xfd
; Section d'affichage d'informations
DB 0x0a, 0x0a ; Saut de ligne deux fois
DB "hello, world"
DB 0x0a ; Saut de ligne
DB 0
RESB 0x1fe-$ ; Remplir avec 0x00 jusqu'à 0x001fe
DB 0x55, 0xaa
; Sortie au-delà du secteur de démarrage
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
Voici maintenant le code du jour 2 (utilise de vraies instructions assembleur, le fichier compilé est identique à celui du jour 1, vérifié avec un outil de comparaison binaire) :
; hello-os
; TAB=4
ORG 0x7c00 ; Spécifier l'adresse de chargement du programme
; Code standard pour disquette au format FAT12
JMP point_entree
DB 0x90
DB "HELLOIPL" ; Nom du secteur de démarrage (8 octets)
DW 512 ; Taille de chaque secteur (doit être 512 octets)
DB 1 ; Taille du cluster (doit être 1 secteur)
DW 1 ; Position de départ du FAT (généralement le premier secteur)
DB 2 ; Nombre de FAT (doit être 2)
DW 224 ; Taille du répertoire racine (généralement 224 entrées)
DW 2880 ; Taille du disque (doit être 2880 secteurs pour 1440*1024/512)
DB 0xf0 ; Type de disque (doit être 0xf0)
DW 9 ; Longueur du FAT (doit être 9 secteurs)
DW 18 ; Nombre de secteurs par piste (doit être 18)
DW 2 ; Nombre de têtes (doit être 2)
DD 0 ; Pas de partition, doit être 0
DD 2880 ; Réécriture de la taille du disque
DB 0,0,0x29 ; Signification inconnue (fixe)
DD 0xffffffff ; (Probablement) numéro d'étiquette de volume
DB "HELLO-OS " ; Nom du disque (doit faire 11 octets, complété par des espaces)
DB "FAT12 " ; Nom du format de disque (doit faire 8 octets, complété par des espaces)
RESB 18 ; Laisser 18 octets vides
; Corps du programme
point_entree:
MOV AX,0 ; Initialisation des registres
MOV SS,AX
MOV SP,0x7c00
MOV DS,AX
MOV ES,AX
MOV SI,message
boucle_affichage:
MOV AL,[SI]
ADD SI,1 ; Incrémenter SI
CMP AL,0
JE fin
MOV AH,0x0e ; Afficher un caractère
MOV BX,15 ; Couleur du caractère
INT 0x10 ; Appeler le BIOS de la carte graphique
JMP boucle_affichage
fin:
HLT ; Arrêter le CPU en attente d'instruction
JMP fin ; Boucle infinie
message:
DB 0x0a, 0x0a ; Saut de ligne deux fois
DB "hello, world"
DB 0x0a ; Saut de ligne
DB 0
RESB 0x7dfe-$ ; Remplir avec 0x00 jusqu'à 0x001fe
DB 0x55, 0xaa
; Sortie au-delà du secteur de démarrage
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 4600
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
RESB 1469432
Remarque : La ligne RESB 0x7dfe-$ ; Remplir avec 0x00 jusqu'à 0x001fe est correcte, le binaire compilé correspond bien à celui du jour 1.
Notre objectif est de comprendre la relation entre le code assembleur du jour 2 et celui du jour 1, par exemple comment MOV AX,0 est exprimé en binaire comme DB 0xb8, 0x00, 0x00.
Comprendre l'instruction ORG
L'instruction ORG 0x7c00 spécifie l'adresse de chargement du programme binaire compilé. Cela permet au code ultérieur d'utiliser des adresses relatives. L'adresse compilée = adresse de départ + adresse relative.
L'avantage d'utiliser ORG 0x7c00 est que le code peut être écrit en utilisant des adresses relatives, et l'adresse de départ est spécifiée par ORG. Ainsi, l'adreesse compilée = adresse de départ + adresse relative. Cela devient particulièrement utile lorsque l'on utilise diverses adresses complexes.
Cette conception permet à la carte mère de charger notre fichier helloos.img à l'adresse mémoire 0x7c00.
On peut supposer que la version du jour 1 utilisait des adresses relatives, où le début de helloos.img était représenté par 0x0000 dans le source, donc l'adresse mémoire où il était chargé était déterminée par l'assembleur ou le BIOS de la carte mère.
La version du jour 2 utilise des adresses absolues, spécifiant que helloos.img doit être chargé à l'adresse mémoire 0x7c00.
Différences entre les versions
MOV AX, 0 et DB 0xb8, 0x00, 0x00 sont équivalents. L'instruction MOV correspond à 0xb8 en binaire.
Informations sur FAT
La séquence 0xeb 0x4e 0x90 correspond à une instruction JMP SHORT en assembleur x86. L'assembleur NASK simplifie JMP SHORT en JMP. 0x90 est une instruction NOP (ne rien faire) en assembleur x86, tandis que 0xE9 est une instruction JMP NEAR.
Cela signifie que la version du jour 1 est équivalente à JMP 0x58; NOP; (première forme du tableau), tandis que la version du jour 2 est équivalente à JMP point_entree (deuxième forme du tableau).
L'étiquette "point_entree" fonctionne comme une étiquette en C, c'est-à-dire un marqueur d'adresse mémoire facile à mémoriser pour les humains. L'adresse réelle = adresse de départ spécifiée par ORG (0x7c00) + décalage de l'étiquette dans le programme.
Analyse complète
Examinons le secteur de démarrage du jour 1. La séquence 0xeb 0x4e 0x90, combinée à l'analyse des partitions FAT, indique qu'en x86, il s'agit de JMP 0x4e. Les octets de 0x3e à 0x4f sont tous des zéros, et 0x4e se situe exactement devant les deux premiesr 0x00 du corps du programme.
Dans la version du jour 2, le corps du programme est écrit comme "point_entree", donc l'instruction JMP point_entree a la même fonction que 0xeb 0x4e 0x90.
Les éléments suivants sous JMP point_entree sont les paramètres du secteur de partition FAT12. Ces paramètres sont des normes de l'industrie informatique et ne peuvent généralement pas être modifiés arbitrairement.
Structure du BPB (BIOS Parameter Block)
Le BPB (BIOS Parameter Block) dans le secteur zéro stocke les informations de paramètres du système de fichiers. Voici les paramètres correspondants dans notre code :
| Décalage | Zone du BPB | Valeur dans helloos | Taille | Description de la valeur |
|---|---|---|---|---|
| 0xB | BPB_BytesPersec | 512 | 2 bits | Peut être 512, 1024, 2048, 4096. Utilisation de disquette, donc 512 |
| 0xC | BPB_SecPerClus | 1 | 1 bit | 2^n, (n>=0) |
| 0xD | BPB_RsvdSecCnt | 1 | 2 bits | Fixé à 1 pour FAT12/16 |
| 0x10 | BPB_NumFATs | 2 | 1 bit | Nombre de copies FAT, recommandé 2 |
| 0x11 | BPB_RootEntCnt | 224 | 2 bits | Nombre d'entrées du répertoire racine |
| 0x13 | BPB_TotSec16 | 2880 | 2 bits | Nombre total de secteurs |
| 0x15 | BPB_Media | 0xf0 | 1 bit | Support de stockage amovible, souvent 0xF0 |
| 0x16 | BPB_FATSz16 | 9 | 2 bits | Nombre de secteurs par table FAT |
| 0x18 | BPB_SecPerTrk | 18 | 2 bits | Secteurs par piste, utilisé pour l'interruption 0x13 |
| 0x1A | BPB_NumHeads | 2 | 2 bits | Nombre de têtes, utilisé pour l'interruption 0x13 |
| 0x1C | BPB_HiddSec | 0 | 4 bits | Secteurs cachés avant la partition FAT |
| 0x20 | BPB_TotSec32 | 2880 | 4 bits | Nombre total de secteurs si >10000 |
Implications pour le développement
En analysant ces informations, nous comprenons mieux la structure d'un système de fichiers FAT12. Cette compréhension nous permet non seulement de travailler avec FAT12, mais aussi de développer des secteurs de démarrage pour FAT32, voire pour d'autres systèmes de fichiers comme exFAT, ext ou NTFS.
La première partie concerne le secteur de démarrage et le système de fichiers (dans notre exemple, FAT12). La deuxième partie contient le système et le programme (HelloWorld).
Nous pouvons maintenant nous concentrer sur la deuxième partie : "Jour 2 (après-midi) : Comprendre comment écrire un programme HelloWorld en assembleur".
Ressources supplémentaires
Documentation sur NASM : - L'instruction ORG : spécifie un décalage ajouté à toutes les références d'adresse internes dans une section. - Tutoriel NASM en français
Ressources complémentaires sur FAT : - Documentation FAT de Microsoft - Spécification exFAT - Analyse FAT32 en chinois (parties 1-6)
Le problème de exFAT
Il est à noter que la conception de partition exFAT inclut deux tables d'allocation (selon la spécification exFAT). Cependant, lorsque vous formatez une partition exFAT sous Windows, seule une table d'allocation est créée (pas de table de sauvegarde), tandis que la mise en forme d'une partition FAT32 sous Windows crée deux tables d'allocation.
La documentation exFAT indique que exFAT prend en nativement deux tables d'allocation, mais Microsoft estime que les supports de stockage flash n'ont pas besoin de deux tables d'allocation. En pratique, les pertes de données sur les supports flash dus à exFAT avec une seule table d'allocation sont fréquentes.
C'est probablement la raison pour laquelle on dit que exFAT est sujet à la perte de données. Bien que exFAT prenne en charge nativement deux tables d'allocation, les outils de formatage de Windows, de DiskGenius et de mkexfat sous Linux ne fournissent qu'une table d'allocation pour exFAT.