Projet système : environnement

De Ensiwiki
Révision de 5 juin 2019 à 07:41 par Schmittw (discussion | contributions) (Installation : : remplacement de yaourt (plus maintenu) par yay)

Aller à : navigation, rechercher
AttentionCette page est maintenue uniquement par les enseignants. Afin de ne pas perturber le déroulement des cours, elle n'a pas vocation à être modifiée par les élèves. Mais si vous avez des modifications à proposer, merci d'en discuter ou d'envoyer un e-mail aux auteurs de la page (cf. historique)

Le projet système se déroule dans les salles affectées (cf. l'emploi du temps). La présentation détaille l'environnement. Ici, nous précisons la configuration des logiciels. Dans le cadre du projet de spécialité 2A système, nous conseillons fortement l'utilisation de Qemu pour les cycles de développement. Parfois vous aurez aussi avantage à utiliser Bochs, particulièrement lors de l'implantation de la pagination.

Conseil

Laisser tourner un noyau en machine virtuelle peut vite consommer beaucoup de ressources processeur, ce qui fait chauffer la machine de développement et peut faire "ramer" votre environnement. Il est donc conseillé d'arrêter les machines virtuelles après utilisation.

Il est possible de réduire au minimum la consommation processeur en utilisant l'instruction HLT à l'intérieur des boucles d'attente active. HLT a pour effet de mettre en pause le processeur jusqu'à ce qu'une interruption matérielle survienne. En général on entoure l'instruction HLT des instructions STI et CLI.

__asm__ __volatile__("sti");
__asm__ __volatile__("hlt");
__asm__ __volatile__("cli");

ou en utilisant les fonctions ASM fournies dans cpu.h

sti();
halt();
cli();

Qemu

Qemu est un logiciel complexe fournissant, entre autre, une machine virtuelle basée sur KVM. L'idée de cette page est de présenter l'utilisation de Qemu/KVM dans le cadre du Projet Système.

Installation

Qemu devrait normalement être disponible sur les machine Ensilinux de l'école. Sur vos machines personnelles, il suffit d'installer les paquetages qemu et qemu-kvm pour obtenir Qemu et le support KVM. Pour les personnes qui auraient une distribution un peu moins clefs en main qu'Ubuntu, n'oubliez pas d'ajouter votre utilisateur au groupe kvm.

Pour les utilisateurs d'Ubuntu:

sudo apt-get install qemu qemu-kvm qemu-common

Pour ceux d'Archlinux:

sudo pacman -S qemu

Vous pouvez compléter avec les commandes pour d'autres distributions/gestionnaires de paquets.

Cas d'utilisation

Le noyau du projet système se lance très simplement avec Qemu. Notre noyau respecte le standard Multiboot qui lui permet d'être chargé aveuglément par un loader supportant ce format, or Qemu sait charger sans aide un noyau au format Multiboot. Ainsi lancer le noyau consiste simplement à lancer la commande suivante: qemu-system-i386 -m 256 -kernel kernel/kernel.bin

  • -m <valeur> précise la taille de la mémoire vive disponible en Mo.
  • -kernel <chemin vers un binaire> indique le binaire du noyau à lancer.

Notes :

  • le noyau ne peut pas fonctionner correctement avec moins de 256 Mo de mémoire vive.
  • sur certaines distributions il faudra peut être remplacer qemu-system-i386 par qemu-kvm, tout simplement qemu ou encore qemu-system-x86_64. Les arguments de la ligne de commande restent identiques.

CentOS 6.5 de l'école

/usr/libexec/qemu-kvm -m 256 -kernel kernel/kernel.bin
VNC server running on `::1:5900'

et

vinagre [::1]:5900

Les crochets viennent de la notation d'adresse IPv6 dans une URL

Affichage VGA sous CentOS

Qemu est capable de gèrer l'affichage (pour nous l'écran VGA) de plusieurs façons:

  • directement dans une fenêtre graphique classique (sdl ou gtk)
  • dans un fenêtre texte (en mode VGA) (curses)
  • ou en utilisant spice (le même protocole que "pcvirtuel") ou vnc. Il faut alors utiliser un client d'affichage. Sous CentOS, par exemple, vous pouvez utiliser vinagre
 vinagre localhost:6666


Débug

Qemu fourni un certain nombre d'outils embarqués pour débugguer facilement le programme qu'il fait tourner. Cette section n'est pas exhaustive, vous êtes invités à lire la manpage de Qemu et/ou à compléter cette page si vous connaissez des astuces de débug supplémentaires.

Connecter un débuggueur

Pour connecter un débuggueur à un noyau, il est nécessaire que celui-ci fournisse un minimum d'outils. Ce sont les gdb-stubs, ils sont disponibles sur le site de gdb. Le point intéressant avec Qemu, c'est qu'il implémente déjà de tels stubs et il n'est pas nécessaire que le noyau le supporte lui même.

Pour les utiliser et connecter le débuggueur, il faut lancer votre noyau de la manière suivante:

qemu-system-i386 -m 256 -kernel kernel/kernel.bin -s -S
  • Les options -m et -kernel sont documentées plus haut.
  • -s démarre un serveur gdb sur le port TCP 1234.
  • -S bloque le vCPU tant que le débuggueur n'est pas connecté et commande la machine.

Note pour kvm (accélération matérielle): le qemu de la Ubuntu utilise l'accélération matérielle (kvm) par défaut. Celui de la Debian utilise kvm avec l'option -enable-kvm.Les breakpoints classiques (break) fonctionnent mal avec kvm. Vous pouvez utilisez les hbreak, les breakpoints assistés par le matériel (ici c'est qemu, le "matériel"). Par exemple pour mettre un breakpoint sur la fonction kernel_start

(gdb) hbreak kernel_start 
Hardware assisted breakpoint 1 at 0x1126fd: file start.c, line 14.
(gdb) cont
Continuing.

Breakpoint 1, kernel_start () at start.c:14
14	{
(gdb)

CentOS 6.5 de l'école : Il faut désactiver l'accélération (kvm) et utiliser un démarrage avec le boot.pxe (pseudo-carte réseau) pour pouvoir faire des breakpoints. Il faut donc récupérer le fichier boot.pxe du ZIP fournit en PSE.

Il FAUT se placer dans le répertoire avec le fichier kernel.bin (il sera chargé par le boot.pxe):

qemu-system-i386 -m 256 --s -S -no-kvm -net nic -net user,tftp=`pwd`,bootfile=boot.pxe

Mais qemu sans kvm devient alors plus permissif que le vrai matériel. Certains bugs n'apparaîtront pas.

utiliser le débogueur

Qemu est bloqué pour le moment, il vous reste à démarrer gdb (ou ddd):

$ gdb kernel/kernel.bin
GNU gdb (GDB) 7.6
[...]
Reading symbols from /home/dejeand/psys/kernel/kernel.bin...done.
(gdb)

puis à le connecter à la VM:

(gdb) target remote :1234
Remote debugging using :1234
0x0000fff0 in ?? ()

GDB est prêt, mettez en place vos breakpoints, watchpoints et continuez l'éxecution avec la commande cont.

Console/sortie débug

Une fonctionnalité intéressante de Qemu est la console de débug. Quand vous écrivez dans la mémoire de la machine pour faire de l'affichage VGA 80x25, Qemu émule la carte et vous affiche le contenu dans une fenêtre. Quand vous modifiez l'affichage (passage en mode VESA, ou en mode dessin au pixel), vous n'avez plus la possibilité d'avoir des traces.

Qemu fourni un port sur son processeur virtuel qui permet de sortir des traces directement dans le terminal. Ainsi chaque caractère émis sur ce port à l'aide d'un outb apparaît dans votre terminal. Vous pouvez ainsi débugguer même quand l'affichage est inutilisable. Pour l'activer, lancez qemu avec l'option -debugcon stdio.

Exemple :

outb('a', 0xE9);

Fait apparaître un a dans votre terminal, à vous d'adapter votre console_putbytes pour qu'il puisse réaliser celà. Attention ce n'est qu'une possiblité de débug, non un élement requis pour l'évaluation de votre noyau.

Quelques options supplémentaires

Certaines des options qui suivent pourront vous être utile:

  • -no-reboot quitte Qemu au lieu de redémarrer si il y a lieu (triples fautes, etc.).
  • -no-shutdown bloque le vCPU lors d'un arrêt de la VM au lieu de rebooter, pratique pour conserver les traces en cas du bug.
  • -display <type> change le type d'affichage pour type, utiliser le type curses permet d'obtenir un affichage VGA directement dans la console et non dans une fenêtre X, pratique pour une utilisation distante. Voir la manpage pour plus de détails.
  • -hd[abcd] <file> utiliser file comme disque dur n° (a=0 .. d=3).


Clé USB

Il est également possible de tester votre noyau sur un vrai PC, via le chargeur Grub.

Si Grub est déjà installé sur votre clé, il suffit d'ajouter la configuration suivante dans le fichier boot/grub/grub.cfg, à adapter selon le partitionnement de la clé :

menuentry "kernel.bin" {
multiboot (hd0,1)/kernel.bin
}

Sinon, nous vous proposons un programme pour installer Grub sur une clé USB.

Attention : le script d'installation ne supporte que les clés USB munies d'une table de partition. La commande mount vous permet de savoir si c'est le cas (la présence d'un chiffre à la fin du nom de périphérique) :

# Exemple de clé sans table de partition
/dev/sdb on /media/disk type vfat ...

# Exemple de clé avec table de partition
/dev/sdb1 on /media/disk type vfat ...

Il est possible de reformatter une clé avec une table de partition, mais les données qu'elle contient seront perdues.

Sur un Linux moderne, avec Grub (GPT) comme gestionnaire de boot

  • Changer la table des partitions de la clé en GPT (attention, cela efface tout)
  • Créer une partition (fat16, fat32, ext2 (à tester)) et mettre votre kernel.bin à la racine
  • Brancher la clé USB sur le poste et (re)démarrer
  • Au moment du choix de l'OS, appuyez sur la touche c, vous voilà dans la console de GRUB (attention, QWERTY)
  • Localiser votre partition avec la commande ls, généralement (hd1, gpt1)
  • Rentrez la commande multiboot (hd1,gpt1)/kernel.bin
  • Tapez la commande boot et voilà !

Installation de Grub sur la clé

  • Brancher la clé USB sur le poste de développement
  • Créer sur la clé un fichier ou un répertoire vide nommé exactement : installer_grub_ici
  • Taper dans un terminal la commande : usbgrub. Cette commande va localiser votre clé USB par le fichier marqueur créé précedemment, et y installer grub dans un répertoire nommé boot.
  • Supprimer le fichier installer_grub_ici

Attention : si votre clé était déjà bootable, il ne sera plus possible de démarrer le système qu'elle contient sans retravailler la configuration de grub (nous ne l'avons pas testé).

Tester un noyau

Une fois la clé bootable créée, on peut l'utiliser pour démarrer le noyau sur n'importe quel PC récent :

  • Copier le fichier kernel.bin à la racine de la clé
  • Déconnecter proprement (démonter) la clé USB
  • Brancher la clé sur un PC éteint
  • Démarrer le PC et faire apparaître le menu de sélection d'amorçage (F12 sur les machines de D200 / D201)
  • Choisir le périphérique correspondant à la clé (USB Device + Entrée en D20x)

On voit alors Grub qui affiche le menu de boot.

peut être démarré via le menu Applications -> Outils système -> Sun VirtualBox.

Note : si vous souhaitez utiliser VirtualBox sur votre machine personnelle sous Linux, il ne faut pas choisir la version OSE (Open Source Edition) qui n'est pas compatible avec le chargeur de noyau fourni. La version binaire fonctionne elle sans problème. Il faut également installer l'extension pack de VirtualBox pour avoir accès aux fonctionnalités PXE nécessaires au projet (voir la section Download de VirtualBox).

Création de la machine virtuelle

On crée d'abord une machine virtuelle de base.

  • Nouveau -> Suivant.
  • Choisir un nom pour la machine virtuelle. Nous choisissons de l'appeler Noyau. Système d'exploitation : Other, version : Other/Unknown. Suivant.
  • Taille de la mémoire vive : 64 Mo.
  • Décocher l'option Disque dur d'amorçage. La machine virtuelle n'a pas besoin de disque dur. Suivant. Continuer. Terminer.

Maintenant, on paramètre plus finement cette machine virtuelle.

  • Clic droit sur Noyau -> Préférences -> Système. Tout décocher dans Ordre d'amorçage et cocher Réseau.
  • Toujours dans Préférences -> Interfaces séries -> Port 1. Cocher l'activation. Port numéro : COM1. Mode port : Tuyau hôte. Cocher Créer un tuyau. Port/Chemin fichier : /tmp/vbox-psys. OK.

Finalement on installe un programme (Noyau.pxe.gz) nécessaire pour le boot du noyau par le réseau. C'est un chargeur intermédiaire, chargé par le BIOS de la machine virtuelle via un réseau virtuel, qui chargera lui-même votre noyau en mémoire de la machine virtuelle pour l'exécuter de manière compatible avec la Multiboot Specification (voir le manuel de Grub).

mkdir -p ~/.VirtualBox/TFTP
gzip -dc Noyau.pxe.gz > ~/.VirtualBox/TFTP/Noyau.pxe

Tester un noyau

Après avoir configuré la machine virtuelle comme indiqué ci-dessus, il est possible de tester un noyau en le copiant dans le répertoire TFTP.

cp kernel.bin ~/.VirtualBox/TFTP/

Il suffit de lancer la machine virtuelle pour voir le noyau démarrer.

Pour l'arrêter, il suffit de fermer la fenêtre et de choisir l'option Eteindre la machine.

Débogage

Le fichier /tmp/vbox-psys créé par VirtualBox pour le port série est une socket Unix, à laquelle gdb ne peut pas se connecter. Il faut donc la convertir en un pseudo port série. Pour cela, vous pouvez utiliser la commande socat. Après le lancement de la machine virtuelle, la commande :

socat UNIX-CONNECT:/tmp/vbox-psys PTY,link=/tmp/gdb-psys

créera le lien symbolique /tmp/gdb-psys vers un nouveau pseudo port série, dont on peut alors se servir directement avec gdb ou ddd à la place de /dev/ttyS0.

Conseil

Laisser tourner un noyau en machine virtuelle peut vite consommer beaucoup de ressources processeur, ce qui fait chauffer la machine de développement et peut faire "ramer" votre environnement. Il est donc conseillé d'arrêter les machines virtuelles après utilisation.

Il est possible de réduire au minimum la consommation processeur en utilisant l'instruction HLT à l'intérieur des boucles d'attente active. HLT a pour effet de mettre en pause le processeur jusqu'à ce qu'une interruption matérielle survienne. En général on entoure l'instruction HLT des instructions STI et CLI.

__asm__ __volatile__("sti");
__asm__ __volatile__("hlt");
__asm__ __volatile__("cli");

-->

Bochs

Bochs est un émulateur x86. Contrairement à VirtualBox et Qemu, il n'éxecute pas le code directement sur le processeur quand c'est possible, mais émule entièrement le comportement du processeur, et de la machine de manière générale. L'inconvénient majeur est une grosse perte de performances, l'avantage est qu'il est possible de suivre et de tracer aboslument tous les comportements de CPU et du matériel émulé.

Cet outil est primordial pour le développement de la mémoire virtuelle car il permet à tout moment de l'éxecution:

  • obtenir le mapping mémoire courant,
  • tracer les fautes de pages, leur adresse et leur résolution,
  • éxecuter en mode pas à pas quelque soit la situation dans le noyau, peu importe l'état des gdb-stubs par exemple.

Installation

Bochs ne sera disponible que sur vos machines personnelles, dans la mesure où il nécessite une image de disque pour fonctionner, et par conséquent les droits root pour la créer. Nous verrons ceci un peu plus loin. Il existe deux versions de Bochs dans vos dépôts, une version standard, et une version avec les outils de développement, notament le débuggueur intégré. Nous souhaitons installer cette seconde version.

Sur Ubuntu, la version proposée dans les dépôts n'est pas compilée avec le débuggueur. Il faudra donc la compiler manuellement. Pour les utilisateurs d'Archlinux, vous trouverez le package dans les dépôts community ou AUR :

yay -S bochs
  ou
yay -S bochs-gdb


Pour les autres vous pouvez vous inspirer des commandes suivantes pour compiler votre version:

$ cd /tmp
$ wget http://downloads.sourceforge.net/project/bochs/bochs/2.6/bochs-2.6.tar.gz
$ tar xzf bochs-2.6.tar.gz
$ cd bochs-2.6/
$ ./configure --enable-smp --enable-all-optimizations --with-all-libs --prefix=/opt --enable-debugger --enable-plugins --enable-pci --enable-pcidev --enable-usb --enable-3dnow --enable-debugger-gui

Puis, si tout s'est bien passé :

$ make -j n

n est jusqu'à deux fois le nombre de coeurs (threads) disponibles sur votre machine.

Enfin installez le tout (dans /opt pour garder une machine saine):

$ sudo make install

Configuration pour le projet

Emuler un système dans bochs nécessite de mettre en place un petit environnement d'éxecution. Il consiste en un fichier de configuration et une image de disque. Nous vous fournissons le nécessaire dans les paragraphes suivants.

Configuration de la machine émulée

Bochs utilises un fichier de configuration nommé bochsrc qu'il cherche à l'endroit où vous lancez l'émulateur. Le fichier suivant devrait convenir pour un projet système : bochsrc

megs: 256
boot: disk
ata0-master: type=disk, path="disk.img", mode=flat, cylinders=260, heads=16, spt=63

Quelques explications :

  1. megs: 256 quantité de mémoire alloué au système.
  2. boot: disk la machine boot à partir d'un périphérique de type disque dur.
  3. ata0-master: type=disk [...] décrit la configuration du disque dur 0, c'est un disque IDE, ayant comme base le fichier disk.img.

En lançant bochs dans le même répertoire que le fichier, il prendra cette configuration automatiquement, sinon vous pouvez utiliser :

bochs -f /chemin/vers/le/fichier/bochsrc

Utilisation d'une image de disque

Comme indiqué précédement, nous allons devoir utiliser une image de disque, un fichier qui représente le contenu d'un disque dur que Bochs va présenter comme tel au système qu'il émule. En effet Bochs n'est pas capable de booter un noyau au format Multiboot comme Qemu.

Pour cela il faut créer un fichier dont le contenu sera comme indiqué ci-dessous :

  • Un Master Boot Record (MBR) avec un loader et une table des partitions
  • Une partition primaire avec un système de fichier contenant le binaire du noyau et le fichier de configuration du loader.

Nous avons choisi d'utiliser un système de fichiers ext2, et GRUB comme loader. Comme la construction d'une telle image peut être compliquée et peu intéressante, nous vous en fournissons une : Fichier:Disk.img.tgz.

Décompressez (c'est du bzip2, l'extension est mauvaise à cause du wiki) et assurez-vous de son intégrité (utilisez le hash MD5 ci-dessous) :

$ tar xjf disk.img.tgz
$ md5sum disk.img 
78551d0692cc798a2624654c5fbd8fa2  disk.img

Une fois fait, placez l'image à l'endroit où vous lancerez bochs, ou modifiez le fichier de configuratioun pour qu'il pointe sur la bonne image.

Maintenant nous allons voir comment copier votre noyau dans l'image de disque afin d'utiliser l'émulateur. Pour cela il vous faudra les droits administrateurs pour monter et démonter l'image de disque. Ce qui explique pourquoi ceci ne peut avoir lieu sur les machines de l'école, l'utilisation de fuse-ext2 n'ayant pas encore été explorée. La séquence suivante copie le binaire du noyau dans l'image de

$ mkdir -p disk/
$ sudo mount -t ext2 -o loop,offset=1048576 disk.img disk/
$ sudo cp kernel/kernel.bin disk/kernel.bin
$ sync
$ sudo umount disk/
$ rm -r disk/

A titre d'exemple, on peut ajouter une cible pour lancer bochs dans le top-Makefile du noyau ce qui ajoute la cible suivante:

disk:
       mkdir -p $@

.PHONY: bochs
bochs: all disk
       @echo "### This target will require root access to mont disk image ! ###"
       sudo mount -t ext2 -o loop,offset=1048576 disk.img disk/
       sudo cp kernel/kernel.bin disk/kernel.bin
       sync
       sudo umount disk/
       bochs

Il ne reste plus qu'à faire make bochs pour compiler puis lancer automatiquement bochs avec une image mise à jour. La seule contrainte est l'entrée du mot de passe pour sudo.

Utilisation d'une image de cdrom sans droit root

Il est possible de construire une image de cdrom sans avoir besoin des droits root

Il faut d'abord créer un répertoire (ici iso), y mettre kernel.bin (ici dans iso/boot) et un fichier de boot grub.cfg dans iso/boot/grub/

mkdir -p iso/boot/grub
cp kernel.bin iso/boot
cp grub.cfg iso/boot/grub

Le fichier grub.cfg peut contenir les lignes suivantes:

set timeout=15
set default=0 # Set the default menu entry
menuentry "OS projet System: kernel.bin" {
  multiboot /boot/kernel.bin   # The multiboot command replaces the kernel command
  boot
}

La construction de l'image cd.iso est réalisée par grub2

grub-mkrescue -o cd.iso iso

Le bochsrc suivant boot bochs sur l'image de cdrom:

megs: 256
boot: cdrom
ata0-master: type=cdrom, path="cd.iso", status=inserted

Lancer bochs avec son débuggueur

Lorsque Bochs est compilé avec le débuggueur, celui-ci va arrêter automatiquement l'éxecution à chaque démarrage pour vous laisser le configurer, ajouter des breakpoints, etc. Vous allez donc obtenir :

$ bochs
[...]
Next at t=0
(0) [0x00000000fffffff0] f000:fff0 (unk. ctxt): jmp far f000:e05b         ; ea5be000f0
00000000000i[XGUI ] Mouse capture off
<bochs:1>

La commande cont vous permet de continuer l'éxecution telle quel.

Une fois l'émulation lancée, vous pouvez :

  • Interrompre l'éxecution pour configurer le niveau de logs en utilisant la "boite à outils" disponible dans la fenêtre de bochs.
  • Interrompre l'éxecution en faisant Ctrl+C pour obtenir le prompt. Ainsi vous configurer les points d'arrêts, les watch points, regardez les valeurs des registres, etc.

La page suivante documente à merveille l'utilisation du débuggueur intégré: Documentation du débuggueur.

Astuce de débug

Parfois au cours de votre développement, particulièrement si vous utilisez Bochs, vous obtiendrez comme unique information, l'adresse contenue dans le pointeur d'instruction au moment de la faute. Vous pouvez exploiter cette information, si faible soit-elle.

Exemple : La trace suivante est émise à Bochs:

00171331842d[CPU0 ] page fault for address 00000000 @ 001472c1

et m'indique que j'ai déréférencé NULL, à l'adresse 0x001472c1. Pour savoir où la faute à exactement eu lieu, je désassemble le binaire du noyau et je peux savoir dans quelle fonction se trouve cette adresse :

$ objdump -d kernel/kernel.bin | less
[...]
001472b0 <kernel_start>:
    1472b0:       55                      push   %ebp
    1472b1:       89 e5                   mov    %esp,%ebp
    1472b3:       83 ec 18                sub    $0x18,%esp
    1472b6:       b8 00 00 00 00          mov    $0x0,%eax
    1472bb:       c7 00 00 00 00 00       movl   $0x0,(%eax)       <------------- ICI
    1472c1:       c7 04 24 10 28 15 00    movl   $0x152810,(%esp)
    1472c8:       e8 f7 51 00 00          call   14c4c4 <puts>
[...]

A partir de ceci, on voit que l'on est dans la fonction kernel_start, peut de temps après le début, et on repère le pattern :

mov    $0x0,%eax
movl   $0x0,(%eax)

typique du : *((int*)0) = 0; que j'ai introduit pour créer la faute. Vous vouliez faire du système, vous êtes en plein dedans.