Les principaux outils
Le but de cette section est de décrire brièvement les outils utilisés pour compiler et exécuter ManuX .
L’essentiel du code de ManuX est écrit en C, et un peu d’assembleur est nécessaire ici ou là, …Le processus de compilation est évidemment automatisé avec l’outil make. Je vais décire sommairement cette étape dans la sous-section [subsection:compilation].
ManuX peut ensuite être exécuté sur une machine réelle, mais il est évidemment possible d’utiliser un émulateur. Nous évoquerons les possibilités dans la sous-section [subsection:execution].
Le débugage est un élément important dans la phase de développement d’un logiciel, et il se révèle parfois un peu délicat lorsqu’il s’agit de développer un noyau. Nous évoquerons des outils dans les sous-sections 4.1 et 4.2.
Le compilateur C
Le compilateur GCC est utilisé, la version 11.3.0 avec une cible
x86_64-linux-gnu fonctionne parfaitement. Je n’ai jamais eu de
problème avec aucune version de GCC jusqu’à présent.
L’assembleur
J’utilise NASM[1] pour les quelques fichiers écrits en assembleur, dans sa version 2.14.
La compilation
La commpilation générale du noyau est fondée sur l’outil make, et
j’utilise la version 4.2.1 de gnumake.
Les scripts
Quelques outils de scripts tels que awk sont utilisés, rien de très
original, et tout système Linux correctement installé devrait fournir ce
qui est nécessaire.
La gestion des fichiers iso
Les fichiers iso son générés par la commande grub-mkrescue qui
utilise xorriso.
L’exécution dans un émulateur
Bien sûr, l’idée n’est pas de faire fonctionner ManuX sur du matériel réel, même si c’est tout à fait possible, mais plutôt dans un émulateur.
J’utilise pour cela Qemudans sa version 3.1.0. On peut également
utiliser bochs ainsi que VirtualBox.
La documentation
La maigre documentation (que vous êtes en train de lide) est écrite essentiellement en LaTeX. N’importe quelle version convient parfaitement.
J’utilise également DoxyGen et mkdocs pour générer ensuite une version navigable en ligne.
Compilation de ManuX
Le Makefile principal défini un certain nombre de cibles utilisables
pour compiler et exécuter ManuX.
La création du noyau
Sans grande surprise, on compile avec la commande
make
Différentes cibles peuvent être créées
-
manuxpermet de compiler une image du noyau ; -
runpermet de lancer un émulateur (par défaut pour le moment c’est Qemu) ; -
cleanpermet d’effacer tous les fichiers issus de compilation ; -
isopermet de créer une image iso.
Gestion de configurations multiples
L’essentiel de la configuration de ManuX se fait dans le fichier
include/manux/config.h
(dox) . Il est cependant possible de
demander à maked’utiliser un fichier différent.
Le répertoire multiconf contient différents fichiers de configuration.
Chacun de ces fichiers permet alors de générer une version différente de
ManuX.
Ainsi, le fichier
multiconf/printk.h
(dox) définit la configuration permettant
de compiler un noyau ManuX montrant l’utilsation de printk. On le
compile de la façon suivante
make CFG=printk
Il peut alors être exécuté par exemple avec
make run
Création d’un fichier iso
Il est également possible de créer un fichier isocontenant une ou plusieurs configuration de ManuX.
Pour créer un fichier isocontenant la version actuellement définie dans
le fichier include/manux/config.h
(dox) on utilisera simplement
make iso
L’exécution pourra alors se faire via la commande
make runiso
Il est également possible de générer un fichier isocontenant toutes les
versions de ManuX décrites par les fichiers du répertoire multiconf de
la façon suivante
make multiso
On l’exécutera de la même façon :
make runiso
L’édition de liens
Afin d’assembler tous les éléments en un seul fichier noyau.elf qui
pourra ensuite être utilisé par Multiboot, nous allons utiliser
l’éditeur de liens ld. Le comportement de ld est conditionné par un
fichier qui s’appelle un linker script. Un tel script décrit en
particulier comment les différentes sections des fichiers d’entrée sont
disposées dans le fichier de sortie et comment la mémoire est organisée.
Le code suivant montre un exemple simple de script :
ENTRY(start)
SECTIONS {
. = 1M
.boot :
{
*(.multiboot)
}
adresseDebutManuX = .;
.text : {
*(.text)
}
.rodata : {
*(.rodata)
}
.data : {
*(.data)
}
.bss : {
*(.bss)
}
}
Dans ce script, nous avous défini le nom du point d’entrée (ici
start), et nous avons décrit un certain nombre de sections avec leur
ordre d’apparition dans le fichier de sortie et leur contenu. Vous
remarquerez également que l’on utilise la notation “.” pour spécifier
l’adresse courante et que cette adresse peut être consultée mais
également modifiée. Ainsi la ligne . = 1M stipule que l’on se place )
l’adresse 1M. D’un autre côté la ligne adresseDebutManuX = . stipule
que l’on enregistre dans la variable adresseDebutManuX la valeur de
l’adresse courante (celle du début de la section . text donc).
Exécution de ManuX
Le but n’est évidemment pas de faire tourner cette merveille sur du vrai matériel, mais plutôt dans un émulateur.
L’émulateur Qemu
C’est maintenant Qemu[2] que j’utilise, par exemple de la façon suivante
qemu-system-i386 -drive format=raw,file=manux,index=0,if=floppy -m 64M
C’est d’ailleurs la commande que lance
make runfloppy
Une autre façon de démarrer ManuX est la suivante
qemu-system-i386 -kernel noyau.elf -m 64M
C’est ce que fait la commande
make run
Mais on préférera probablement passer par une image ISO :
qemu-system-i386 -m 64M -cdrom manux.iso
C’est ce que fait la commande
make runiso
De nombreuses options sont disponibles, mais ce n’est pas le moment d’en parler !
L’émulateur Bochs
J’utilisais initialement cet outil, mais les fichiers de configuration ne semblent plus convenir aux dernières versions de Bochs[3] il faudrait les mettre à jour !
Le débugage de ManuX
Le débugage “interne”
Ce que j’appelle ici le débugage interne, c’est tous les outils que l’on peut utiliser dans le code lui-même pour essayer de comprendre ce qu’il s’y passe.
Nous avons pour le moment quelques outils de base, copieusement bugués eux-mêmes (!).
La fonction printk_debug
Elle permet de produire des messages d’erreur qui seront envoyés de
façon sélective en fonction de la valeur de masqueDebugageConsole et
de masqueDebugageFichier
Elle utilise pour cela les fonctionalités de la fonction printk() et
du journal, ce qui permet d’envoyer les messages de debug sur la console
et/ou dans un fichier.
Les masques de débogages
Deux masques de débogage sont définis : masqueDebugageConsole et
masqueDebugageFichier. Ils sont définies dans
Si le paramètre de compilation MANUX_DEBUG_VAR n’est pas défini, ces
deux masques sont définis comme des macros. On ne peut donc pas trop
jouer avec en cours d’exécution du noyau, …
En revanche, si MANUX_DEBUG_VAR est définie, c’est un nouveau monde
qui s’ouvre à nous ! Ces variables sont en effet déclarées et
initialisées dans noyau/debug.c
(dox) , et elles peuvent alors être
modifiées durant l’exécution du noyau.
Une utilisation particulièrement intéressante pourra en être faite au travers du registre (voir la doc) et des paramètres de boot, car il est alors possible de définir la valeur de ces masque via une option du bootloader.
La macro assert
Elle est définie dans le fichier
include/manux/debug.h
(dox)
et elle permet de vérifier lors de l’exécution qu’une propostion est vérifiée ou non. Si elle ne l’est pas, un message est affiché.
Le handler handlerPanique
La fonction handlerPanique est définie dans le fichier
include/manux/debug.h
(dox) et permet d’afficher un état du
système qui peut être utile à des fin de débugage.
Le fichier noyau/symbols
Il va surtout nous servir pour aider au débugage externe, mais il est généré à la compilation du noyau.
Le débugage “externe”
Une technique pour débuguer est d’utiliser qemu couplé avec gdb.
Pour cela, je lance par exemple qemu de la façon suivante
qemu-system-i386 -drive format=raw,file=manux,index=0,if=floppy -rtc base\=localtime -m 64M -gdb tcp::1234 -S
Je lance ensuite gdb dans un autre terminal avec un .gdbinit qui
contient :
target remote :1234
dir noyau
dir lib
file noyau/noyau.elf
La commande dir permet de dire à gdb où trouver les fichiers sources
et la command file lui permet de consulter le binaire du noyau pour y
trouver l’adresse des fonctions et variables (ici en fait, c’est une
autre compilation du noyau au format elf !).
Exécution
Quelques commandes de base pour l’exécution de code dans gdb
-
runpermet de lancer l’exécution d’un programme (inutilisable ici étant donné notre fonctionnement : on débogue à distance) ; -
continuepermet de poursuivre l’exécution jusqu’au prochain point d’arrêt/bug/fin/…; -
stepavance d’une instruction dans le code ; -
next -
breakpoint fonction -
breakpoint fichier:ligne
Affichage des registres
Il peut être intéressant de consulter le contenu des registres
info registersdonne le contenu des principaux registres.
Affichage des variables
Gdb permet également de consulter le contenu des variables :
p totoaffiche le contenu de la variabletoto.
Désassemblage du code
Une fonctionnalité intéressante est de pouvoir observer le code assembleur, ce qui peut permettre de comprendre certains comportements étranges, tels que les conséquences de choix d’optimisation du compilateur :
disassemble foncaffiche le code assembleur de la fonctionfonc.
Références
“Qemu home page,” 2022. Available: https://www.qemu.org/.
The Bochs Project. “Bochs home page,” 2021. Available: https://bochs.sourceforge.io/.
The NASM development team. “NASM home page,” 2015. Available: https://www.nasm.us/.
[1] The NASM development team, “NASM home page,” 2015, Available: https://www.nasm.us/.
[2] “Qemu home page,” 2022, Available: https://www.qemu.org/.
[3] The Bochs Project, “Bochs home page,” 2021, Available: https://bochs.sourceforge.io/.