Analyse complète du système de fichiers en langage assembleur pour le développement d'un système d'exploitation

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.

Étiquettes: système de fichiers FAT12 assembleur développement système os

Publié le 13 juillet à 08h24