; Copyright (c) 2006-2009 Frédéric Feret ; Tous droits réservés. ; Ce secteur de boot permet le chargement du fichier image du système depuis ; un CD-ROM utilisant le standard ISO 9660 (appelé aussi standard "El Torito") ; en mode "no emulation", c'est-à-dire sans utiliser une image de disquette en ; guise de secteur de boot. ; ; Le secteur de boot est conçu pour charger le fichier OSIMAGE à la racine du ; disque. En cas d'abscence de ce fichier, le secteur de boot affichera un message ; d'erreur à l'écran. L'utilisateur devra donc appuyer sur une touche du clavier, ; pour relancer le processus de boot du BIOS. ; ; Ce secteur de boot supporte les disques comportant plusieurs sessions de ; gravure. En revanche, seul le format Joliet pour les noms de fichiers est ; supporté. cpu 386 org 0x0000 use16 ;****************************************************************************** ; Fonctions ; _start ; ; Il s'agit de la fonction principale du secteur de boot, qui permet le chargement ; du fichier OSIMAGE en mémoire à l'adresse 0000:8000. ; ; Paramètres : ; DL numéro du disque de démarrage. boot_start: cli ; Le BIOS a chargé notre secteur de boot à l'adresse 0000:7C00. Nous allons ; donc déplacer le code du secteur de boot à l'adresse 9000:0000. cld xor ax, ax mov ds, ax mov si, 0x7C00 ; DS:SI = 0000:7C00. mov ax, 0x9000 mov es, ax xor di, di ; ES:DI = 9000:0000. mov cx, 0x0800 ; 2048 octets. rep movsb ; Saute à la nouvelle adresse. jmp 0x9000:.flush_cs .flush_cs: ; Réinitialise les registres de segment. mov ax, cs mov ds, ax xor ax, ax mov es, ax mov ss, ax ; Réinitialise la pile. mov sp, 0x7C00 ; Sauvegarde le numéro du disque de démarrage passée en argument par le BIOS. mov byte [drive_number], dl sti ; Tout d'abord, nous allons vérifier si le processeur supporte le mode protégé ; 32 bits. Cela est indispensable pour le système d'exploitation, mais aussi ; pour le secteur de boot, qui manipule des informations sur 32 bits. Pour cela, ; nous allons vérifier si le modèle du processeur est un 386, en manipulant les ; bits IOPL du registre FLAGS. pushf pop ax ; FLAGS -> AX or ax, 0x3000 ; met les bits IOPL à 1. push ax popf ; AX -> FLAGS pushf pop ax ; FLAGS -> AX test ax, 0x3000 ; vérifie si les bits IOPL sont toujours à 1. jnz .is_386 ; c'est un 386. ; Le processeur est un 286 ou un modèle encore plus ancien. On affiche un ; message d'erreur à l'écran, puis on invîte l'utilisateur à presser une touche ; du clavier pour redémarrer l'ordinateur. mov si, msg_no_386 call print_string jmp reboot .is_386: ; Récupérer, auprès du BIOS, la taille en octets d'un secteur. mov word [disk_packet], 0x1A mov ah, 0x48 xor al, al mov si, disk_packet mov dl, byte [drive_number] int 0x13 jc .assume_2k_sector mov ax, word [disk_packet + 0x18] mov word [bytes_per_sect], ax jmp short .next .assume_2k_sector: ; Une erreur est survenue lors de la récupération de la taille d'un secteur. ; Dans ce cas, on affiche un message d'avertissement à l'écran, puis on assume ; le fait qu'un secteur sur le disque fasse 2 kilo-octets. mov word [bytes_per_sect], 0x0800 mov si, msg_assume_2k_sector call print_string .next: ; Pour pouvoir charger le fichier OSIMAGE (qui doit être situé à la racine du ; disque), nous devons parcourir toutes les sessions du disque. Chaque session ; contient comme en-tête un descripteur de volume et une arborescence de fichiers ; et de répertoires. Nous allons donc récupérer des informations sur le répertoire ; racine (sa taille et son emplacement) de chaque session, charger ce répertoire ; en mémoire, et regarder si le nom de fichier du noyau se trouve dans ce répertoire. ; Si le fichier n'est pas trouvé, on passe à la session suivante. Si toutes les ; sessions ont été parcourues, et que le nom du fichier n'a pas été trouvé, on en ; conclut que le noyau ne se trouve pas sur le disque. mov eax, 16 ; le premier descripteur de volume se trouve au ; 17ème secteur du disque. .get_next_desc: ; Charge le secteur en mémoire à l'adresse 0000:1000. mov dword [desc_sector], eax mov bx, 0x1000 mov cx, 1 call read_sectors ; Vérifie la présence de la signature "\2CD001" au début du descripteur. mov si, desc_header mov di, 0x1000 mov cx, 6 rep cmpsb je .found_desc ; Vérifie si nous avons parcouru tous les descripteurs de volume. cmp byte [es:0x1000], 0xFF jne .next_desc ; oui, on en conclut que le noyau n'est pas sur le ; disque. ; Nous avons parcouru toutes les sessions du disque, et le fichier OSIMAGE ; n'a pas été trouvé. Dans ce cas, on affiche un message d'erreur à l'écran, ; puis on invîte l'utilisateur à presser une touche du clavier pour redémarrer ; l'ordinateur. mov si, msg_file_not_found call print_string jmp reboot .next_desc: ; Détermine le numéro du prochain secteur à charger. mov eax, dword [desc_sector] inc eax jmp .get_next_desc .found_desc: ; Nous venons de charger un descripteur de volume d'une session en mémoire. ; Nous allons vérifier si cette session prend en charge le format Joliet pour ; le stockage des noms de fichiers et de répertoire. Avec ce format, les noms ; de fichiers ne sont pas limités à 11 caractères (8 pour le nom et 3 pour ; l'extension du fichier) et sont codés en Unicode. Le secteur de boot ne ; reconnait que ce format. Dans le cas contraire, on ignore la session. mov di, 0x1058 mov si, joliet_signature mov cx, 3 rep cmpsb jne .next_desc ; Nous avons trouvé une session qui prend en charge le format Joliet. Nous ; pouvons donc récupérer la taille et l'emplacement du répertoire racine. mov eax, dword [es:0x109E] mov dword [root_dir_start], eax mov eax, dword [es:0x10A6] mov dword [root_dir_size], eax ; Calcule le nombre de secteurs à charger. movzx ebx, word [bytes_per_sect] div ebx cmp edx, 0 je .next2 inc eax .next2: mov word [root_dir_sectors], ax ; Charge le contenu du répertoire racine en mémoire à l'adresse 0000:1000. mov eax, dword [root_dir_start] mov bx, 0x1000 mov cx, word [root_dir_sectors] call read_sectors ; Nous allons maintenant rechercher dans le répertoire racine que nous venons ; de charger le fichier OSIMAGE. Si le fichier a été trouvé, nous sauvegarderons ; le numéro du secteur ou se situe le répertoire racine sur le disque, ainsi que ; sa taille en octets. Dans le cas contraire, on passera à la session suivante. mov di, 0x1000 .search_file: add di, 25 ; Vérifie si l'entrée du répertoire racine pointée par ES:DI désigne bien ; un fichier. cmp byte [es:di], 0 jne .next_entry push di add di, 8 ; avance DI vers le nom du fichier. mov cx, osimage_size mov si, osimage ; nom du fichier à charger (OSIMAGE en Unicode). rep cmpsb ; vérification... pop di je .found_file ; fichier trouvé ? .next_entry: add di, 7 movzx ax, byte [es:di] add di, ax .loop_next_entry: inc di cmp byte [es:di], 0 je .loop_next_entry ; Vérifie si nous avons parcouru le contenu de tout le répertoire racine. mov eax, dword [root_dir_size] add eax, 0x1000 cmp di, ax jb .search_file ; Le fichier OSIMAGE n'a pas été trouvé dans le répertoire racine. Dans ce cas, ; on passe à la prochaine session du disque. jmp .next_desc .found_file: sub di, 25 ; Le fichier OSIMAGE est bien présent dans le répertoire racine. Nous allons ; donc récupérer le numéro du secteur ou se situe le fichier sur le disque, ; ainsi que sa taille en octets. Ensuite, nous vérifierons si la taille de ce ; fichier ne dépasse pas 512 kilo-octets. En effet, ce fichier sera situé en ; mémoire conventionnelle à l'adresse 1000:0000. mov eax, dword [es:di + 2] mov dword [file_start], eax mov eax, dword [es:di + 10] mov dword [file_size], eax cmp eax, 524288 jbe .file_size_ok ; La taille du fichier OSIMAGE dépasse les 512 kilo-octets. On affiche un message ; d'erreur à l'écran, puis on invîte l'utilisateur à presser une touche du clavier ; pour redémarrer l'ordinateur. mov si, msg_file_too_large call print_string jmp reboot .file_size_ok: ; Calcule le nombre de secteurs à charger. movzx ebx, word [bytes_per_sect] div ebx cmp edx, 0 je .next3 inc eax .next3: mov word [file_sectors], ax ; Charge le noyau en mémoire à l'adresse 1000:0000. mov eax, dword [file_start] mov bx, 0x1000 mov es, bx xor bx, bx mov cx, word [file_sectors] call read_sectors ; Il ne reste plus qu'a exécuter le code du noyau en sautant à son adresse. ; On n'oublie pas de passer comme argument au noyau, le numéro du disque de ; démarrage, attribuée par le BIOS, dans DL. mov dl, byte [drive_number] xor si, si jmp 0x1000:0x0000 ; read_sectors ; ; Permet de charger un ou plusieurs secteurs en mémoire à une adresse donnée. ; ; Paramètres : ; EAX numéro du secteur logique de départ. ; CX nombre de secteurs à charger. ; ES:BX adresse mémoire de destination. ; ; Retour : ; FLAGS.CF mis à 1 si erreur. read_sectors: ; Pour demander au BIOS de charger les secteurs, nous devons construire ; un paquet de données qui contiendra le nombre de secteur à charger, le ; numéro du secteur de départ ainsi que l'adresse de destination. L'adresse ; de ce paquet sera ensuite passée au BIOS, qui chargera les secteurs en ; mémoire. mov byte [disk_packet], 0x10 mov word [disk_packet + 2], 1 ; on ne charge qu'un secteur à la fois. ; Inscrit l'adresse de destination dans le paquet. Note : cette adresse doit ; être sous la forme segment:offset. mov word [disk_packet + 4], bx mov word [disk_packet + 6], es ; Inscrit le numéro du secteur logique de départ. Cette information étant ; est codée sur 64 bits dans le paquet. Mais ici nous ne pouvons pas traiter ; de nombre supérieur à 32 bits. Par conséquent, le secteur à charger doit ; être situé dans les 4 premiers giga-octets du disque (limitation uniquement ; présente sur les DVD). mov dword [disk_packet + 8], eax mov dword [disk_packet + 12], 0 .loop_read: ; Charge le secteur en mémoire. mov ah, 0x42 xor al, al mov si, disk_packet mov dl, byte [drive_number] int 0x13 jnc .next ; erreur de lecture ? ; Une erreur de lecture est survenue. On affiche un message d'erreur à ; l'écran, puis on invîte l'utilisateur à presser une touche du clavier pour ; redémarrer l'ordinateur. mov si, msg_read_error call print_string jmp reboot .next: ; Détermine le numéro du prochain secteur à charger. inc dword [disk_packet + 8] ; Met à jour l'adresse de destination. Plutôt que d'incrémenter l'offset, ; nous allons incrémenter le segment. mov ax, word [bytes_per_sect] shr ax, 4 add word [disk_packet + 6], ax loop .loop_read ; charge le prochain secteur. .end: ret ; print_string ; ; Affiche une chaîne de caractères à l'écran, à la position courante du ; curseur. ; ; Paramètres : ; DS:SI chaîne de caractères terminée par '\0'. print_string: push ax push bx push si .print_char: ; Lit un caractère. lodsb ; Vérifie s'il on a atteint la fin de la chaîne de caractères. cmp al, 0 je .end ; Affiche le caractère à l'écran. mov ah, 0x0E mov bx, 0x07 int 0x10 jmp short .print_char .end: pop si pop bx pop ax ret ; reboot ; ; Permet de relancer le processus de démarrage du BIOS. Cette méthode permet ; de ne pas redémarrer complètement l'ordinateur. reboot: mov si, msg_reboot call print_string ; Attend qu'une touche du clavier soit préssée. xor ax, ax int 0x16 ; Redémarrage... int 0x19 ;****************************************************************************** ; Messages msg_no_386: db "Error: 386 or higher processor required.", 13, 10, 0 msg_assume_2k_sector: db "Error: failed to get sector size, assume 2 Kb.", 13, 10, 0 msg_file_not_found: db "Error: OS image not found.", 13, 10, 0 msg_file_too_large: db "Error: OS image is too large (> 512 Kb).", 13, 10, 0 msg_read_error: db "Error: cannot read on disk.", 13, 10, 0 msg_reboot: db "Press any key to restart your computer.", 13, 10, 0 ;****************************************************************************** ; Données desc_header: db 0x02, "CD001" joliet_signature: db 0x25, 0x2F, 0x45 osimage: db 0, 'o', 0, 's', 0, 'i', 0, 'm', 0, 'a', 0, 'g', 0, 'e' osimage_size equ ($ - osimage) bytes_per_sect: dw 0 drive_number: db 0 root_dir_size: dd 0 root_dir_sectors: dw 0 root_dir_start: dd 0 file_sectors: dw 0 file_start: dd 0 file_size: dd 0 desc_sector: dd 0 disk_packet: times 0x20 db 0 ; Le secteur de boot doit avoir une taille éxacte de 2048 octets. Dans ce cas, ; nous devons remplir l'espace restant par des octets à 0, afin que le secteur ; de boot, une fois compilé, ait la taille que nous voulons. times (2046 - ($ - boot_start)) db 0 ; Pour que le BIOS charge notre secteur de boot, les deux derniers octets de ce ; dernier doit contenir la valeur 0xAA55. dw 0xAA55