J'ai essayé de créer un chargeur de démarrage x86 qui peut démarrer vmlinux avec Rust

Depuis que j'ai commencé à étudier Rust, j'ai créé un chargeur de démarrage x86 (auto-proclamé: Krabs) qui parle le protocole de démarrage Linux pendant les vacances d'hiver. Dans cet article, j'aimerais écrire sur la motivation pour le développement, les fonctionnalités et le mécanisme des Krabs que j'ai créés, et ce qui m'a fait plaisir pendant le développement.

Qu'est-ce que Krabs

Krabs est un chargeur de chaîne avec une configuration de fusée en 4 étapes pour x86 / x86_64 (BIOS hérité) écrit en Rust. Vous pouvez démarrer le noyau au format ELF compressé avec bzip2. Après avoir décompressé l'image compressée bzip2 et réorganisé l'image ELF qui a été extraite ensuite, le noyau démarre. En interne, il utilise la bibliothèque C libbzip2, mais tout le reste est écrit en Rust.

GitHub - ellbrid/krabs: An x86 bootloader written in Rust.

Il présente les caractéristiques suivantes.

Voici un exemple de démarrage de vmlinux 64 bits qui utilise la ligne de commande du noyau et initrd.

./tools/build.sh -k vmlinux -i initramfs.cpio.gz -c "clocksource=tsc" disk.img 
qemu-system-x86_64 --hda disk.img -m 1G

cmdline.gif

Motivation pour faire des Krabs

Le but est de pratiquer Rust et de vous familiariser avec Linux. Je pensais que le codage au niveau inférieur à la pile du système d'exploitation pourrait être rendu plus moderne en utilisant Rust. Aussi. Du processus au démarrage du noyau Linux, je voulais aussi extraire uniquement le minimum d'essence nécessaire et enfin créer un environnement de démarrage où il n'y a pas de boîte noire pour moi. Fondamentalement, l'objectif était de supprimer les pièces déroutantes et obstruées suivantes.

Dans cet esprit, j'ai abandonné la lecture du bootloader et le suivi du code bzImage, et j'ai décidé d'écrire le bootloader moi-même dans Rust.

Comment ça fonctionne

Mécanisme de démarrage du noyau Linux

Regarder le mécanisme de démarrage du noyau Linux à partir d'un chargeur de démarrage bzImage ou GRUB peut être difficile, mais la réalité est étonnamment simple. La chose fondamentale est deux points. Extraire l'image compressée, extraire l'image au format ELF, la déplacer selon l'en-tête du programme et Le protocole de démarrage Linux / x86 Effectuez le traitement d'initialisation conformément à (/x86/boot.html) et définissez les paramètres. Seulement ça.

Cela peut sembler incroyablement facile, mais spécifiquement, les quatre types de traitement d'initialisation suivants sont effectués.

** Initialisation matérielle: **

** Initialisation du logiciel: **

** Communiquer avec le noyau: **

** Emplacement de l'image: **

Krabs gère le traitement ci-dessus avec un programme divisé en quatre étapes.

Structure et vue d'ensemble de Krabs

  1. stage1:
    stage1 est un programme de 446 octets écrit dans le secteur de démarrage. Les registres de segment («CS», «DS», «ES», «SS») sont mis à «0x07C0» et le pointeur de pile («ESP») est initialisé à «0xFFF0». Après cela, chargez stage2 à l'adresse 0x07C0: 0x0200 et passez à l'adresse 0x07C0: 0x0280. A la fin de 2 octets de stage1, il y a une zone pour stocker la longueur de secteur (512 octets) du programme stage2. Même Rust peut écrire un chargeur de 1er étage qui tient dans 446 octets!
  2. stage2:
    Chargez stage3 à l'adresse 0x07C0: 0x6000. L'image du noyau compressée bzip2 est chargée à l'adresse de la zone de mémoire étendue 0x0350_0000 et initrd est chargée à 0x0560_0000. Nous avons utilisé un tampon de piste de 4K octets à partir de l'adresse 0x07C0: 0xEE00 pour transférer ces fichiers. Stockez temporairement ce que vous avez lu sur le disque ici, puis utilisez la fonction BIOS ʻINT 15h 0x87hpour le transférer à l'adresse appropriée. Après avoir chargé les images de noyau stage3, initrd et compressées, passez à l'adresse0x07C0: 0x6000`. La ligne de commande du noyau se trouve dans la zone de 120 octets à partir de l'adresse «0x280».
  3. stage3 + stage4:
    Les étapes 3 et 4 peuvent utiliser la zone bss, elles doivent donc prendre en charge la suppression de zéro de la section .bss. Après une série d'initialisations matérielles et logicielles, préparez les informations de la page zéro de «0x07C0: 0x0000» à «0x07C0: 0x0FFF». Activez la ligne A20, changez le bus d'adresse sur 32 bits et passez en mode protégé. Appelez la fonction de décompression bzip2 pour restaurer l'image du noyau ELF compressée bzip2 à l'adresse mémoire étendue «0x100000» ou version ultérieure. Ensuite, analysez et chargez le fichier ELF32 / ELF64. Si la cible est ELF64, définissez le tableau de la page de démarrage 4G et passez en mode long. Enfin, passez au point d'entrée et démarrez le noyau. À ce stade, définissez l'adresse physique (0x00007C00) des informations Zero Page préparées dans la mémoire inférieure du registre ESI ou RSI.
  4. plankton:
    plancton est une bibliothèque commune de stage1 à stage4.

Structure du disque

Krabs prend en charge les disques durs et les SSD, mais vous devez avoir un MBR. En outre, l'une des partitions doit avoir l'indicateur de démarrage défini. Stage3,4, kernel et initrd sont stockés dans la partition de démarrage avec le drapeau de démarrage.

layout.png

Supplément

Au fait, vous vous demandez peut-être pourquoi il prend en charge le BIOS hérité, alors laissez-moi ajouter ceci. Seul le BIOS hérité est pris en charge cette fois pour les trois raisons suivantes.

Impressions que j'ai écrites à Rust

Rust est beaucoup plus facile que C pour écrire du code de bas niveau. Telle est mon impression personnelle.

Le sentiment de sécurité lorsque la compilation passe est incroyable

Même si un problème survient, il suffit de douter de la partie «non sûre». Cette fois, c'était vrai.

La meilleure façon de penser les packages et les modules

Vous n'avez pas à être frustré par le fichier objet avec lequel créer un lien, comme C.

Code moderne facile à appliquer

Le code de bas niveau de no_std est également relativement moderne. De plus, je n'ai pas à m'inquiéter autant de ne pas pouvoir l'utiliser ou de ne pas pouvoir l'utiliser. C'est un sentiment, mais la vitesse de développement peut être plus rapide que C? Mieux encore, Rust est amusant à écrire. Il est bon d'écrire en pensant au type. Il est également amusant d'utiliser diverses fonctions.

Le chargeur à chaîne peut être écrit même avec Rust

Je t'appellerai. 16 bits / 32 bits peuvent être chargés par le chargeur de chaîne sans trop d'inconvénients. En raison de la structure du chargeur à chaîne, il est nécessaire de décrire la partie non sécurisée pour passer à l'étape suivante, mais il est bon que la technique souvent utilisée en C puisse être utilisée telle quelle dans la partie non sécurisée. J'ai pensé. Cependant, soyez prudent avec les macros et les fermetures à l'intérieur du secteur de démarrage. J'ai l'impression que la taille est facile à exploser.

Utilisation et mise à disposition des actifs de C

Je pense qu'il est très merveilleux que FFI puisse utiliser les actifs de C sans trop s'en rendre compte. J'utilise libbzip2 en interne cette fois, mais c'était facile à utiliser depuis Rust. Au contraire, il était facile de fournir à C les actifs de Rust. Vous avez besoin de malloc pour utiliser libbzip2, mais j'ai pu fournir au côté C une implémentation facile dans Rust. /src/stage_4th/src/bz2d.rs#L45).

Où j'étais accro

Pour définir la table des pages, alignez le Linker Script et [struct Attributes]( J'ai essayé de le configurer sur https://doc.rust-lang.org/reference/type-layout.html#representations), mais aucun d'entre eux n'a fonctionné. .. .. (Il semblait que les autres structures de données avaient été corrompues). L'alignement de Rust est laissé tel quel, se demandant si quelque chose ne va pas. En fin de compte, j'ai sécurisé manuellement la zone où je voulais définir le tableau des pages et y solidifié l'adresse. Exemple

Un super grand ingénieur m'a aidé!

Pendant un certain temps après avoir été en mesure de créer Krabs et de démarrer un simple noyau de style ELF, vmlinux n'a pas pu démarrer correctement pour une raison quelconque et le développement s'est arrêté pendant un court laps de temps. Pendant ce temps, j'ai écrit README en anglais, testé des exemples d'opérations simples, pris en charge le mode long et annoncé en anglais sur Twitter. Je me demandais pourquoi cela ne fonctionnait pas tous les jours, et un jour, alors que je chargeais la source de bzImage, j'étais très heureux.

Dans un tweet que j'ai murmuré en japonais avec désinvolture, quel super grand ingénieur d'AWS @msw et de l'arbre du noyau Linux x86 / x86-64 est célèbre comme mainteneur de longue date @LinuxHPA (Hans Peter Anvin --Wikipedia) a répondu et m'a donné des conseils! J'étais si heureuse d'être si heureuse. Grâce à ces conseils, le problème a été résolu aussitôt, et j'ai renouvelé ma détermination à ne pas supporter la spécification multi-boot (je n'aime pas vraiment la spécification multi-boot, qui embarque à l'origine des paramètres dans le noyau).

advice2.png

Cette fois, il a répondu au japonais, mais j'ai été connecté après avoir reçu une réaction de ce qui était initialement annoncé en anglais. J'ai de nouveau pensé qu'il était important de l'envoyer en anglais.

De plus, si vous murmurez en anglais, je pense que la fréquence de réception de DM directement sur Twitter et d'envoi de messages d'assistance par réponse augmentera. Ce sera très encourageant. Je voudrais introduire un message heureux. L'écriture d'un OS dans Rust m'a également envoyé un message.

... c'est un gaspillage de finir avec ça, donc pour le plaisir de faire l'expérience de Krabs, j'aimerais couvrir un exemple de construction finale d'un système Linux minimal et de le démarrer avec Krabs.

Démarrons Linux minimal

Je pense que ce sera utile. (Par la suite, je travaille sur CentOS7)

Faisons un vmlinux minimal!

1: Apportez la source Linux.

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.5.2.tar.gz
tar xf linux-5.5.2.tar.gz 
cd linux-5.5.2

2: Définissez la configuration Linux.

make allnoconfig
make menuconfig

Utilisez les paramètres suivants.

64-bit kernel ---> yes
General setup ---> Initial RAM filesystem and RAM disk (initramfs/initrd) support ---> yes
General setup ---> Configure standard kernel features ---> Enable support for printk ---> yes
Executable file formats / Emulations ---> Kernel support for ELF binaries ---> yes
Executable file formats / Emulations ---> Kernel support for scripts starting with #! ---> yes
Enable the block layer ---> yes
Device Drivers ---> Generic Driver Options ---> Maintain a devtmpfs filesystem to mount at /dev ---> yes
Device Drivers ---> Generic Driver Options ---> Automount devtmpfs at /dev, after the kernel mounted the rootfs ---> yes
Device Drivers ---> Character devices ---> Enable TTY ---> yes
Device Drivers ---> Character devices ---> Serial drivers ---> 8250/16550 and compatible serial support ---> yes
Device Drivers ---> Character devices ---> Serial drivers ---> Console on 8250/16550 and compatible serial port ---> yes
Device Drivers ---> Block devices ---> yes
Device Drivers ---> PCI Support --> yes
Device Drivers ---> Serial ATA and Parallel ATA drivers (libata) ---> yes
Device Drivers ---> Serial ATA and Parallel ATA drivers (libata) ---> Intel ESB, ICH, PIIX3, PIIX4 PATA/SATA support ---> yes
Device Drivers ---> Serial ATA and Parallel ATA drivers (libata) ---> Generic ATA support ---> yes   
Device Drivers ---> SCSI device support ---> SCSI disk support
File systems ---> The Extended 4 (ext4) filesystem ---> yes
File systems ---> Pseudo filesystems ---> /proc file system support ---> yes
File systems ---> Pseudo filesystems ---> sysfs file system support ---> yes

Sinon, j'ai préparé ma configuration recommandée avec ce qui précède déjà défini, alors mettez-le dans .config. Je vais le copier.

wget https://raw.githubusercontent.com/ellbrid/krabs/master/resources/.config -O .config
make menuconfig

3: Construisez vmlinux.

make vmlinux

4: Vous avez vmlinux dans votre répertoire actuel.

Faisons des initramfs!

1: Tout d'abord, créez le répertoire . / Src / initramfs et construisez le répertoire de base ici.

cd ..
mkdir --parents src/initramfs/{bin,dev,etc,lib,lib64,mnt/root,proc,root,sbin,sys}

2: Copiez également les nœuds de périphérique de base.

	1.	sudo cp --archive /dev/{null,console,tty,tty[0-4],sda,sda[1-8],mem,kmsg,random,urandom,zero} src/initramfs/dev/

3: Utilisez busybox au lieu d'installer une bibliothèque dynamique et de créer un environnement.

curl -L 'https://www.busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64' > src/initramfs/bin/busybox
sudo chmod +x src/initramfs/bin/busybox
./src/initramfs/bin/busybox --list | sed 's:^:src/initramfs/bin/:' | xargs -n 1 ln -s busybox

4: Préparez le script ʻinit`.

cat >> src/initramfs/init << EOF
#!/bin/sh
mount -t devtmpfs  devtmpfs  /dev
mount -t proc      proc      /proc
mount -t sysfs     sysfs     /sys
sleep 2
cat <<END
Boot took $(cut -d' ' -f1 /proc/uptime) seconds
                                             
_____           _        __    _             
|   __|___ ___ _| |_ _   |  |  |_|___ _ _ _ _ 
|__   | .'|   | . | | |  |  |__| |   | | |_'_|
|_____|__,|_|_|___|_  |  |_____|_|_|_|___|_,_|
                  |___|                       


Welcome to Sandy Linux
END
exec sh
EOF
sudo chmod +x src/initramfs/init

5: Créez des initramfs.

cd src/initramfs
find . | cpio -o -H newc | gzip > ../../initramfs.cpio.gz

Faisons une image disque!

1: Créez un fichier image avec qemu-img. Vous pouvez également utiliser dd.

qemu-img create disk.img 512M

2: Créez une partition avec fdisk.

1st partition:

Command (m for help): n
Partition type:
   p   primary (0 primary, 0 extended, 4 free)
   e   extended
Select (default p): p
Partition number (1-4, default 1): 1
First sector (2048-1048575, default 2048): 2048
Last sector, +sectors or +size{K,M,G} (2048-1048575, default 1048575): 206848
Partition 1 of type Linux and of size 100 MiB is set

Créez un indicateur de démarrage dans la première partition:

Command (m for help): a
Selected partition 1

2nd partition:

Command (m for help): n
Partition type:
   p   primary (1 primary, 0 extended, 3 free)
   e   extended
Select (default p): p
Partition number (2-4, default 2): 
First sector (206849-1048575, default 208896): 
Using default value 208896
Last sector, +sectors or +size{K,M,G} (208896-1048575, default 1048575): 
Using default value 1048575
Partition 2 of type Linux and of size 410 MiB is set

write out:

Command (m for help): w
The partition table has been altered!
Syncing disks.

3: Créez un système de fichiers ext4 dans la deuxième partition

$ sudo kpartx -av disk.img 
lsblk
NAME            MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sr0              11:0    1 1024M  0 rom  
loop0             7:0    0  512M  0 loop 
├─loop0p1       253:2    0  100M  0 part 
└─loop0p2       253:3    0  410M  0 part 
$ sudo mkfs.ext4 /dev/mapper/loop0p2
$ sudo kpartx -d disk.img 

Commencez avec Krabs!

Exécutez simplement la commande suivante: Cela compressera bzip2 vmlinux et l'écrira sur disk.img avec le chargeur de démarrage.

$ pwd
path/to/krabs
$ ./tools/build.sh -k path/to/vmlinux -i path/to/initramfs.cpio.gz path/to/disk.img 

Commençons!

$ qemu-system-x86_64 --hda disk.img -m 1G

cmdline.gif

Épilogue

Au fait, dans un commentaire de reddit, on m'a demandé pourquoi je n'utilisais pas xz ou gzip. Je n'y ai pas trop réfléchi, mais la raison en est qu'il était facile d'intégrer bzip2. Une autre raison est que j'ai hâte d'entendre les nouvelles concernant le portage de bzip2 sur Rust. Cependant, comme vous pouvez le voir de ceux qui ont essayé l'exemple ci-dessus de démarrage de Minimal Linux, bzip2 est très lent. Par conséquent, il est possible de migrer vers gzip ou xz à l'avenir.

Au fait, j'aime Sponge Bob et emprunte le nom de ce chargeur de démarrage à M. Kani et Plankton. Mon objectif secret est d'utiliser Krabs au lieu de GRUB sur le Linux que je démarre. (Je prévois de commencer à créer mon propre système d'exploitation nommé sponge).

Je vise également à créer une AMI Linux blague originale en utilisant ce chargeur de démarrage sur Amazon EC2 (également connu sous le nom de Sandy Linux AMI lol). Je me suis demandé si je devais utiliser le systemd [rustysd] de Rust (https://github.com/KillingSpark/rustysd) pour init pour Rust's coreutils. penser. Est-ce que ssh est cool avec this? Vos rêves se répandront.

De plus, j'ai trouvé que la star sur github était vraiment sympa, alors j'ai décidé de jouer de plus en plus à partir de maintenant.

Merci d'avoir lu jusqu'au bout. Si vous avez des likes, des commentaires, des conseils, des problèmes, des pull requests, etc., n'hésitez pas à nous contacter.

GitHub - ellbrid/krabs: An x86 bootloader written in Rust.

Recommended Posts

J'ai essayé de créer un chargeur de démarrage x86 qui peut démarrer vmlinux avec Rust
Une histoire sur un amateur faisant une rupture de bloc avec python (kivy) ②
Une histoire sur un amateur faisant une rupture de bloc avec python (kivy) ①
L'histoire de la création d'une partition de type Hanon avec Python
Une histoire sur l'installation de matplotlib à l'aide de pip avec une erreur
Une histoire sur la création d'une courte chanson par hasard avec Sudachi Py
L'histoire de la création d'un module qui ignore le courrier avec python
Une histoire sur l'apprentissage automatique avec Kyasuket
Notes pour créer des figures pouvant être publiées dans des revues avec matplotlib
Une histoire sur la création d'un environnement IDE avec WinPython sur un ancien système d'exploitation Windows.
L'histoire de la création d'une application Web qui enregistre des lectures approfondies avec Django
[Hackason] À propos de la création d'un outil pouvant être imprimé sur Raspberry Pi [Outil pratique]
Une histoire sur la façon dont les utilisateurs de Windows 10 ont créé un environnement pour utiliser OpenCV3 avec Python 3.5
Une histoire sur une erreur lors du chargement d'un modèle TensorFlow créé avec Google Colab localement
Histoire de l'utilisation du jeton logiciel de Resona avec 1Password
Une histoire de prédiction du taux de change avec Deep Learning
Une histoire qui a eu du mal avec l'ensemble commun HTTP_PROXY = ~
Une histoire d'essayer un monorepo (Golang +) Python avec Bazel
L'histoire de la gestion de theano avec TSUBAME 2.0
L'histoire de la construction d'un serveur de cache PyPI (avec Docker) et de me rendre un peu heureux à nouveau
Une histoire qui a trébuché lorsque j'ai créé un bot de chat avec Transformer
Une histoire de compétition avec un ami dans Othello AI Preparation
Une histoire sur la façon de traiter le problème CORS
Une histoire sur une guerre lorsque deux nouveaux arrivants ont développé une application
Créez une API Web capable de fournir des images avec Django
J'ai créé un plug-in qui peut faire "Daruma-san tombé" avec Minecraft
Faisons un diagramme sur lequel on peut cliquer avec IPython
L'histoire de la création d'un bot de boîte à questions avec discord.py
Une histoire à propos d'un débutant en python coincé avec aucun module nommé'ttp.server '
À propos du fait que le résumé de la torche peut être vraiment utilisé lors de la construction d'un modèle avec Pytorch
[Google Photo & Slack Photo Bot] Une histoire sur la création d'un bot qui acquiert une photo dans Google Photo et l'envoie à Slack.
Une histoire et sa mise en œuvre selon laquelle des données arbitraires a1 * a2 peuvent être représentées par un réseau de neurones ReLU à 3 couches avec des neurones intermédiaires a1 et a2 sans erreur.