Ich habe versucht, einen x86-Bootloader zu erstellen, der vmlinux mit Rust booten kann

Seit ich Rust studiere, habe ich einen x86-Bootloader (selbsternannt: Krabs) erstellt, der in den Winterferien das Linux-Bootprotokoll spricht. In diesem Artikel möchte ich über die Motivation für die Entwicklung, die Merkmale und Mechanismen der von mir erstellten Krabben und darüber, worüber ich mich während der Entwicklung gefreut habe, schreiben.

Was ist Krabben?

Krabs ist ein Kettenlader mit einer 4-stufigen Raketenkonfiguration für x86 / x86_64 (Legacy BIOS), geschrieben in Rust. Sie können den mit bzip2 komprimierten Kernel im ELF-Format starten. Nachdem Sie das komprimierte bzip2-Image dekomprimiert und das als nächstes extrahierte ELF-Image neu angeordnet haben, wird der Kernel gestartet. Intern wird die Bibliothek libbzip2 C verwendet, aber alles andere ist in Rust geschrieben.

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

Es hat die folgenden Funktionen.

Das folgende Beispiel zeigt das Booten von 64-Bit-VMlinux unter Verwendung der Kernel-Befehlszeile und von 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, Krabben zu machen

Ziel ist es, Rust zu üben und sich mit Linux vertraut zu machen. Ich dachte, dass die Codierung auf der Ebene unterhalb des Betriebssystemstapels durch die Verwendung von Rust moderner gestaltet werden könnte. Ebenfalls. Vom Prozess bis zum Booten des Linux-Kernels wollte ich auch nur die minimal notwendige Essenz extrahieren und schließlich eine Boot-Umgebung erstellen, in der es für mich keine Black Box gibt. Grundsätzlich bestand das Ziel darin, die folgenden verwirrenden und verstopften Teile zu entfernen.

In diesem Sinne gab ich das Lesen des Bootloaders und das Verfolgen des bzImage-Codes auf und beschloss, den Bootloader selbst in Rust zu schreiben.

Wie es funktioniert

Linux-Kernel-Boot-Mechanismus

Es kann schwierig sein, den Linux-Kernel-Bootmechanismus von einem bzImage- oder GRUB-Bootloader aus zu betrachten, aber die Realität ist überraschend einfach. Das Grundlegende sind zwei Punkte. Extrahieren Sie das komprimierte Image, extrahieren Sie das ELF-Format-Image, verschieben Sie es gemäß dem Programm-Header und Das Linux / x86-Boot-Protokoll. Führen Sie die Initialisierungsverarbeitung gemäß (/x86/boot.html) durch und legen Sie die Parameter fest. Nur das.

Es mag unglaublich einfach erscheinen, aber speziell werden die folgenden vier Arten der Initialisierungsverarbeitung ausgeführt.

** Hardware-Initialisierung: **

** Software-Initialisierung: **

** Kommunikation mit dem Kernel: **

** Bildplatzierung: **

Krabs erledigt die obige Verarbeitung mit einem Programm, das in vier Stufen unterteilt ist.

Struktur und Überblick über Krabben

  1. stage1:
    stage1 ist ein 446-Byte-Programm, das in den Bootsektor geschrieben wurde. Die Segmentregister ("CS", "DS", "ES", "SS") werden auf "0x07C0" gesetzt und der Stapelzeiger ("ESP") wird auf "0xFFF0" initialisiert. Laden Sie danach Stufe 2 auf die Adresse "0x07C0: 0x0200" und springen Sie zur Adresse "0x07C0: 0x0280". Am Ende 2 Bytes von Stufe 1 befindet sich ein Bereich zum Speichern der Sektorlänge (512 Bytes) des Programms Stufe 2. Sogar Rust kann einen Lader der ersten Stufe schreiben, der in 446 Bytes passt!
  2. stage2:
    Laden Sie Stufe 3 unter der Adresse "0x07C0: 0x6000". Das komprimierte Kernel-Image von bzip2 wird unter der Adresse "0x0350_0000" für den erweiterten Speicherbereich und initrd unter "0x0560_0000" geladen. Wir haben einen 4K-Byte-Spurpuffer von der Adresse "0x07C0: 0xEE00" verwendet, um diese Dateien zu übertragen. Speichern Sie hier vorübergehend, was Sie von der Festplatte gelesen haben, und übertragen Sie es dann mit der BIOS-Funktion "INT 15h" (0x87h) an die entsprechende Adresse. Springen Sie nach dem Laden der Stage3-, Initrd- und komprimierten Kernel-Images zur Adresse "0x07C0: 0x6000". Die Kernel-Befehlszeile befindet sich im Bereich von 120 Bytes ab der Adresse "0x280".
  3. stage3 + stage4:
    Stufe 3 und Stufe 4 können den BSS-Bereich verwenden und müssen das Null-Löschen des Abschnitts ".bss" unterstützen. Bereiten Sie nach einer Reihe von Hardware- und Software-Initialisierungen die Nullseiteninformationen von "0x07C0: 0x0000" bis "0x07C0: 0x0FFF" vor. Aktivieren Sie die A20-Leitung, ändern Sie den Adressbus auf 32 Bit und wechseln Sie in den geschützten Modus. Rufen Sie die bzip2-Dekomprimierungsfunktion auf, um das bzip2-komprimierte ELF-Kernel-Image auf die erweiterte Speicheradresse "0x100000" oder höher wiederherzustellen. Analysieren und laden Sie dann die ELF32 / ELF64-Datei. Wenn das Ziel ELF64 ist, legen Sie die 4G-Startseitentabelle fest und wechseln Sie in den Langmodus. Zum Schluss springen Sie zum Einstiegspunkt und starten den Kernel. Stellen Sie zu diesem Zeitpunkt die physikalische Adresse (0x00007C00) der im unteren Speicher des ESI- oder RSI-Registers vorbereiteten Nullseiteninformationen ein.
  4. plankton:
    Plankton ist eine Bibliothek, die Stufe 1 bis Stufe 4 gemeinsam hat.

Festplattenstruktur

Krabs unterstützt Festplatten und SSDs, Sie müssen jedoch über einen MBR verfügen. Außerdem muss für eine der Partitionen das Startflag gesetzt sein. Stage3,4, Kernel und initrd werden mit dem Bootflag in der Boot-Partition gespeichert.

layout.png

Ergänzung

Übrigens fragen Sie sich vielleicht, warum es das ältere BIOS unterstützt. Lassen Sie mich dies hinzufügen. Dieses Mal wird aus den folgenden drei Gründen nur das ältere BIOS unterstützt.

Eindrücke, die ich in Rust geschrieben habe

Rust ist viel einfacher als C, um Code auf niedriger Ebene zu schreiben. Das ist mein persönlicher Eindruck.

Das Gefühl der Sicherheit beim Bestehen der Zusammenstellung ist erstaunlich

Selbst wenn ein Problem auftritt, müssen Sie wirklich nur an dem "unsicheren" Teil zweifeln. Diesmal war das wahr.

Die beste Art, über Pakete und Module nachzudenken

Sie müssen nicht frustriert sein, mit welcher Objektdatei Sie wie C verknüpfen möchten.

Einfach anzuwendender moderner Code

Der Low-Level-Code von no_std ist ebenfalls relativ modern. Außerdem muss ich mir nicht so viele Sorgen machen, dass ich es nicht benutzen kann oder nicht benutzen kann. Es ist ein Gefühl, aber die Entwicklungsgeschwindigkeit kann schneller als C sein? Das Beste ist, dass es Spaß macht, Rust zu schreiben. Es ist gut, mit dem Typ zu schreiben. Es macht auch Spaß, verschiedene Funktionen zu nutzen.

Kettenlader kann auch mit Rust geschrieben werden

Ich werde dich anrufen. 16bit / 32bit können vom Kettenlader ohne große Unannehmlichkeiten geladen werden. Aufgrund der Struktur des Kettenladers ist es notwendig, den unsicheren Teil zu beschreiben, um zur nächsten Stufe zu gelangen. Es ist jedoch gut, dass die in C häufig verwendete Technik genauso verwendet werden kann wie im unsicheren Teil. Ich dachte. Seien Sie jedoch vorsichtig mit Makros und Schließungen im Bootsektor. Ich habe das Gefühl, dass die Größe leicht zu explodieren ist.

Nutzung und Bereitstellung der Vermögenswerte von C.

Ich finde es sehr wunderbar, dass FFI die Vermögenswerte von C ohne viel Bewusstsein nutzen kann. Dieses Mal verwende ich libbzip2 intern, aber es war einfach von Rust aus zu verwenden. Im Gegenteil, es war einfach, C mit Rusts Vermögen zu versorgen. Sie benötigen malloc, um libbzip2 zu verwenden, aber ich konnte der C-Seite eine einfache Implementierung in Rust bieten. /src/stage_4th/src/bz2d.rs#L45).

Wo ich süchtig war

Richten Sie zum Festlegen der Seitentabelle das Linker-Skript und die [Strukturattribute]( Ich habe versucht, es unter https://doc.rust-lang.org/reference/type-layout.html#representations festzulegen, aber keiner von ihnen hat funktioniert. .. .. (Es sah so aus, als wären die anderen Datenstrukturen beschädigt). Rusts Ausrichtung bleibt unverändert und fragt sich, ob etwas nicht stimmt. Am Ende habe ich den Bereich, in dem ich die Seitentabelle festlegen wollte, manuell gesichert und die Adresse dort verfestigt. Beispiel

Ein super großer Ingenieur hat mir geholfen!

Für einige Zeit, nachdem ich Krabs erstellen und einen einfachen Kernel im ELF-Stil booten konnte, konnte vmlinux aus irgendeinem Grund nicht richtig booten und die Entwicklung wurde für kurze Zeit gestoppt. Während dieser Zeit schrieb ich README auf Englisch, testete einfache Betriebsbeispiele, unterstützte den Langmodus und bewarb auf Englisch auf Twitter. Ich fragte mich, warum es nicht jeden Tag funktionierte und eines Tages, als ich die Quelle von bzImage lud, passierte etwas sehr Glückliches.

In einem Tweet, den ich beiläufig auf Japanisch murmelte, ist ein super großer Ingenieur von AWS @msw und x86 / x86-64 Linux-Kernelbaum als langjähriger Betreuer [@LinuxHPA] bekannt. (https://twitter.com/LinuxHPA) (Hans Peter Anvin - Wikipedia) antwortete und gab mir Ratschläge! Ich war so glücklich, dass ich so glücklich war. Dank dieses Ratschlags wurde das Problem sofort gelöst und ich erneuerte meine Entschlossenheit, die Multi-Boot-Spezifikation nicht zu unterstützen (die Multi-Boot-Spezifikation, die ursprünglich Parameter in den Kernel einbettet, gefällt mir nicht wirklich).

advice2.png

Diesmal antwortete er auf Japanisch, aber ich war verbunden, nachdem ich eine Reaktion von dem erhalten hatte, was ursprünglich auf Englisch beworben wurde. Ich dachte noch einmal, dass es wichtig ist, es auf Englisch zu senden.

Wenn Sie auf Englisch murmeln, wird sich die Häufigkeit des Empfangs von DMs direkt auf Twitter und des Versendens von Supportnachrichten per Antwort erhöhen. Es wird sehr ermutigend sein. Ich möchte eine fröhliche Nachricht vorstellen. Das Schreiben eines Betriebssystems in Rust hat mir auch eine Nachricht gesendet.

... es ist eine Verschwendung, damit zu enden. Um Krabs kennenzulernen, möchte ich ein Beispiel für das endgültige Erstellen eines minimalen Linux-Systems und das Booten mit Krabs behandeln.

Lassen Sie uns Minimal Linux booten

Ich denke es wird hilfreich sein. (Im Folgenden arbeite ich an CentOS7)

Machen wir einen minimalen vmlinux!

1: Bringen Sie die Linux-Quelle mit.

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: Stellen Sie die Linux-Konfiguration ein.

make allnoconfig
make menuconfig

Verwenden Sie die folgenden Einstellungen.

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

Alternativ habe ich meine empfohlene Konfiguration vorbereitet, wobei das oben Gesagte bereits festgelegt wurde. Geben Sie dies also in ".config" ein. Ich werde es kopieren.

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

3: Erstellen Sie vmlinux.

make vmlinux

4: Sie haben vmlinux in Ihrem aktuellen Verzeichnis.

Lass uns initramfs machen!

1: Erstellen Sie zuerst das Verzeichnis . / Src / initramfs und erstellen Sie hier das Basisverzeichnis.

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

2: Kopieren Sie auch die grundlegenden Geräteknoten.

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

3: Verwenden Sie Busybox, anstatt eine dynamische Bibliothek zu installieren und eine Umgebung zu erstellen.

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: Bereiten Sie das Init-Skript vor.

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: Erstellen Sie initramfs.

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

Machen wir ein Disk-Image!

1: Erstellen Sie eine Bilddatei mit qemu-img. Sie können auch dd verwenden.

qemu-img create disk.img 512M

2: Erstellen Sie eine Partition mit 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

Erstellen Sie ein Boot-Flag in der ersten 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: Erstellen Sie ein ext4-Dateisystem in der zweiten 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 

Beginnen Sie mit Krabs!

Führen Sie einfach den folgenden Befehl aus: Dadurch komprimiert bzip2 vmlinux und schreibt es zusammen mit dem Bootloader auf disk.img.

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

Lasst uns beginnen!

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

cmdline.gif

Nachwort

Übrigens wurde ich in einem Kommentar von reddit gefragt, warum ich weder xz noch gzip verwendet habe. Ich habe nicht zu viel darüber nachgedacht, aber der Grund ist, dass es einfach war, bzip2 einzubringen. Ich habe auch Neuigkeiten über die Portierung von bzip2 nach Rust gehört, was einer der Gründe ist, warum ich mich darauf freue. Wie Sie jedoch denjenigen entnehmen können, die das obige Beispiel für das Booten von Minimal Linux ausprobiert haben, ist bzip2 sehr langsam. Daher besteht die Möglichkeit, in Zukunft auf gzip oder xz zu migrieren.

Übrigens mag ich Sponge Bob und leihe mir den Namen dieses Bootloaders von Mr. Kani und Plankton aus. Mein geheimes Ziel ist es, Krabs anstelle von GRUB auf dem Linux zu verwenden, das ich boote. (Ich plane, mein eigenes Betriebssystem namens Sponge zu erstellen).

Ich möchte mit diesem Bootloader auf Amazon EC2 (auch bekannt als Sandy Linux AMI lol) auch einen originellen Linux-AMI-Witz erstellen. Ich fragte mich, ob ich Rusts systemd rustysd für init für Rusts coreutils verwenden sollte. nachdenken über. Ist ssh cool mit this? Deine Träume werden sich verbreiten.

Außerdem fand ich, dass der Stern auf Github wirklich schön war, also beschloss ich, von nun an immer mehr zu spielen.

Vielen Dank für das Lesen bis zum Ende. Wenn Sie Likes, Kommentare, Ratschläge, Probleme, Pull-Anfragen usw. haben, zögern Sie bitte nicht, uns zu kontaktieren.

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

Recommended Posts

Ich habe versucht, einen x86-Bootloader zu erstellen, der vmlinux mit Rust booten kann
Eine Geschichte über einen Amateur, der mit Python (Kivy) einen Blockbruch macht ②
Eine Geschichte über einen Amateur, der mit Python (Kivy) einen Blockbruch macht ①
Die Geschichte, mit Python eine Hanon-ähnliche Partitur zu machen
Eine Geschichte über die Installation von matplotlib mit pip mit einem Fehler
Eine Geschichte über das zufällige Erstellen eines kurzen Songs mit Sudachi Py
Die Geschichte, ein Modul zu erstellen, das E-Mails mit Python überspringt
Eine Geschichte über maschinelles Lernen mit Kyasuket
Hinweise zum Erstellen von Zahlen, die mit matplotlib in Zeitschriften veröffentlicht werden können
Eine Geschichte zum Erstellen einer IDE-Umgebung mit WinPython unter einem alten Windows-Betriebssystem.
Die Geschichte der Erstellung einer Webanwendung, die umfangreiche Lesungen mit Django aufzeichnet
[Hackason] Über das Erstellen eines Werkzeugs, das auf Raspberry Pi gedruckt werden kann [Praktisches Werkzeug]
Eine Geschichte darüber, wie Windows 10-Benutzer eine Umgebung für die Verwendung von OpenCV3 mit Python 3.5 erstellt haben
Eine Geschichte über einen Fehler beim Laden eines TensorFlow-Modells, das lokal mit Google Colab erstellt wurde
Geschichte der Verwendung von Resonas Software-Token mit 1Password
Eine Geschichte über die Vorhersage des Wechselkurses mit Deep Learning
Die Geschichte, eine harte Zeit mit der gemeinsamen Menge HTTP_PROXY = ~ zu haben
Eine Geschichte über das Ausprobieren eines (Golang +) Python-Monorepo mit Bazel
Die Geschichte, wie theano mit TSUBAME 2.0 verwaltet wurde
Die Geschichte, einen PyPI-Cache-Server (mit Docker) aufzubauen und mich wieder ein wenig glücklich zu machen
Eine Geschichte, die stolperte, als ich mit Transformer einen Chat-Chat-Bot erstellte
Eine Geschichte über den Wettbewerb mit einem Freund in Othello AI Preparation
Eine Geschichte über den Umgang mit dem CORS-Problem
Eine Geschichte über einen Krieg, als zwei Neuankömmlinge eine App entwickelten
Erstellen Sie eine Web-API, die Bilder mit Django liefern kann
Ich habe ein Plug-In erstellt, das "Daruma-san Fell" mit Minecraft ausführen kann
Lassen Sie uns ein Diagramm erstellen, auf das mit IPython geklickt werden kann
Die Geschichte, wie man mit discord.py einen Fragenkasten-Bot erstellt
Eine Geschichte über einen Python-Anfänger, der mit dem No-Modul'http.server 'feststeckt.
Über die Sache, dass Fackelzusammenfassung wirklich verwendet werden kann, wenn ein Modell mit Pytorch erstellt wird
[Google Photo & Slack Photo Bot] Eine Geschichte über das Erstellen eines Bots, der ein Foto in Google Photo erfasst und an Slack sendet.
Eine Geschichte und ihre Implementierung, dass beliebige a1 * a2-Daten durch ein dreischichtiges neuronales ReLU-Netzwerk mit a1- und a2-Zwischenneuronen ohne Fehler dargestellt werden können.