Dans cet article, nous explorons le processus technique de démarrage d’un ordinateur, en détaillant le chargement du Master Boot Record (MBR), la structure des secteurs d’un disque et l’implémentation d’un bootloader en Assembly avec un focus sur la validation de la signature de boot.

Chargement du bootloader

Lors de la mise sous tension, le matériel exécute le Power-On Self Test (POST). Après la validation des composants, le BIOS recherche des périphériques de stockage amorçables. Le premier secteur physique (les 512 premiers octets, connus sous le nom de secteur d’amorçage ou MBR) est chargé dans la mémoire RAM. Le BIOS valide l’intégrité de ce secteur en recherchant la “signature magique0xAA55 dans ses deux derniers octets ; si elle est présente, l’exécution est transférée à cette adresse et le processus de démarrage se poursuit.

⚠️ Note technique : Introduit en 1983, le MBR (Master Boot Record) a été le standard dominant pendant des décennies. Actuellement, les systèmes modernes utilisent le GPT (GUID Partition Table), qui surpasse les limitations de partitionnement et de taille de disque du MBR. À des fins didactiques, nous utiliserons le modèle MBR dans cet article.

Chargement du secteur d'amorçage

Géométrie du disque et secteurs

Pour comprendre l’organisation des données, considérez la structure d’un disque dur (HDD) conventionnel. Le disque est organisé de manière hiérarchique en secteurs, pistes (tracks) et têtes (heads).

Secteurs d'un disque dur

Le MBR (Master Boot Record) se situe dans le premier secteur physique du disque. Ce secteur possède exactement 512 octets de capacité, l’espace où est défini le premier stade (Stage 1) du chargeur d’amorçage du système d’exploitation.

Implémentation du secteur de boot

Le développement d’un bootloader est typiquement réalisé en Assembly, garantissant un contrôle direct sur le matériel et la cartographie de la mémoire. Ci-dessous, nous présentons un exemple d’implémentation :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# ***********************************************************
# Exemple de secteur de boot
# ***********************************************************

.code16
.intel_syntax noprefix
.text
.org 0x0                                        

LOAD_SEGMENT = 0x1000                     # Le chargeur de 2e stade sera chargé dans le segment 1000h
FAT_SEGMENT  = 0x0ee0                     # La FAT du disque de boot sera chargée dans le segment 0x0ee0 
                                          # (9*512 octets sous le chargeur de 2e stade)

.global main

main:
    jmp short start                       # Saute au début du code
    nop                                   # Alignement (nop) pour l'en-tête du secteur de boot

.include "bootsector.s"
.include "macros.s"

start:
  mInitSegments                           # Initialise les segments de mémoire
  mResetDiskSystem                        # Réinitialise le sous-système de disque
  mWriteString loadmsg                    # Affiche le message de chargement
  mFindFile filename, LOAD_SEGMENT        # Localise le fichier du 2e stade dans le répertoire racine
  mReadFAT FAT_SEGMENT                    # Charge la table FAT en mémoire
  mReadFile LOAD_SEGMENT, FAT_SEGMENT     # Transfère le 2e stade en mémoire RAM
  mStartSecondStage                       # Transfère le flux d'exécution au 2e stade
 
# Routine de gestion des échecs du processus de boot
bootFailure:
  mWriteString diskerror                  # Affiche un message d'erreur de disque
  mReboot                                 # Sollicite le redémarrage du système
  
.include "functions.s"
    
# Définition des données et constantes
filename:    .asciz "2NDSTAGEBIN"
rebootmsg:   .asciz "Appuyez sur n'importe quelle touche pour redémarrer.\r\n"
diskerror:   .asciz "Erreur de disque. "
loadmsg:     .asciz "Chargement de DevOS...\r\n"

root_strt:   .byte 0,0      # Offset du répertoire racine
root_scts:   .byte 0,0      # Nombre de secteurs du répertoire racine
file_strt:   .byte 0,0      # Offset du bootloader sur le disque

.fill (510-(.-main)), 1, 0  # Padding avec des zéros jusqu'à l'octet 510
BootMagic:  .int 0xAA55     # Signature magique pour la reconnaissance par le BIOS

Analyse technique et signature de boot

L’implémentation utilise des directives spécifiques pour garantir la conformité au standard MBR. Le point critique réside dans le remplissage du secteur pour atteindre la taille exacte de 512 octets (lignes 49 et 50).

Comme le volume d’instructions et de données peut varier, il est nécessaire de calculer dynamiquement le remplissage (padding) nécessaire. L’expression utilisée est : (510 - (.-main))

Composants de l’expression :

  • . (point) : Représente le compteur de localisation actuel (location counter).
  • main : L’adresse du point d’entrée initial.
  • (.-main) : Calcule le déplacement (offset) total d’octets générés jusqu’à présent.
  • 510 - (.-main) : Détermine combien d’octets il reste pour atteindre la marque de 510 octets.

Les deux derniers octets (511 et 512) sont réservés pour la signature 0xAA55. Sans cette signature, le BIOS ne reconnaîtra pas le périphérique comme amorçable.

Génération du binaire et chargement en RAM

Après l’assemblage du code source, un fichier binaire est généré. Quand l’ordinateur identifie un périphérique de démarrage, le BIOS lit le premier secteur (512 octets), copie son contenu à l’adresse physique de mémoire 0x7C00 et lance l’exécution des instructions à partir de cette adresse.

Fichier binaire en hexadécimal

Prochaines étapes et conclusion

L’initialisation d’un système d’exploitation est un processus multi-stades. Le code analysé représente le Stade 1, dont la fonction primordiale est de localiser et charger le stade subséquent en mémoire RAM, comme démontré dans la logique à partir de la ligne 23.

Dans de futurs articles, nous détaillerons les phases postérieures, incluant la transition vers le Mode Protégé et le chargement du Kernel du système.