Wie kann man ein Programm alleine ausführen, ohne dass ein Betriebssystem ausgeführt wird? Können Sie Assembly-Programme erstellen, die der Computer beim Start laden und ausführen kann, z. B. den Computer von einem Flash-Laufwerk starten und das Programm ausführen, das sich auf der CPU befindet?
assembly
x86
operating-system
bootloader
osdev
user2320609
quelle
quelle
Antworten:
Sie platzieren Ihren Binärcode an einer Stelle, nach der der Prozessor nach dem Neustart sucht (z. B. Adresse 0 auf ARM).
Allgemeine Antwort auf die Frage: Es kann getan werden. Es wird oft als "Bare-Metal-Programmierung" bezeichnet. Um vom Flash-Laufwerk zu lesen, möchten Sie wissen, was USB ist, und Sie möchten einen Treiber haben, der mit diesem USB funktioniert. Das Programm auf diesem Laufwerk müsste auch in einem bestimmten Format vorliegen, in einem bestimmten Dateisystem ... Dies ist etwas, was Bootloader normalerweise tun, aber Ihr Programm könnte einen eigenen Bootloader enthalten, so dass es in sich geschlossen ist, wenn die Firmware dies nur tut Laden Sie einen kleinen Codeblock.
Auf vielen ARM-Boards können Sie einige dieser Dinge tun. Einige haben Bootloader, die Ihnen bei der Grundeinstellung helfen.
Hier finden Sie ein großartiges Tutorial zum Ausführen eines grundlegenden Betriebssystems auf einem Raspberry Pi.
Bearbeiten: Dieser Artikel und die gesamte wiki.osdev.org werden die meisten Ihrer Fragen beantworten http://wiki.osdev.org/Introduction
Wenn Sie nicht direkt mit Hardware experimentieren möchten, können Sie diese auch als virtuelle Maschine mit Hypervisoren wie qemu ausführen. Sehen Sie, wie „Hallo Welt“ direkt auf virtualisierten ARM Hardware laufen hier .
quelle
Ausführbare Beispiele
Lassen Sie uns einige winzige Bare-Metal-Hallo-Welt-Programme erstellen und ausführen, die ohne Betriebssystem ausgeführt werden:
Wir werden sie auch so oft wie möglich auf dem QEMU-Emulator ausprobieren, da dies sicherer und bequemer für die Entwicklung ist. Die QEMU-Tests wurden auf einem Ubuntu 18.04-Host mit der vorgefertigten QEMU 2.11.1 durchgeführt.
Der Code aller folgenden x86-Beispiele und mehr ist in diesem GitHub-Repo enthalten .
So führen Sie die Beispiele auf realer x86-Hardware aus
Denken Sie daran, dass das Ausführen von Beispielen auf realer Hardware gefährlich sein kann, z. B. wenn Sie versehentlich Ihre Festplatte löschen oder die Hardware blockieren: Tun Sie dies nur auf alten Computern, die keine kritischen Daten enthalten! Oder noch besser, verwenden Sie billige Einweg-Devboards wie den Raspberry Pi (siehe ARM-Beispiel unten).
Für einen typischen x86-Laptop müssen Sie Folgendes tun:
Brennen Sie das Bild auf einen USB-Stick (zerstört Ihre Daten!):
Schließen Sie den USB an einen Computer an
Mach es an
Sagen Sie ihm, er soll vom USB booten.
Dies bedeutet, dass die Firmware vor der Festplatte USB auswählt.
Wenn dies nicht das Standardverhalten Ihres Computers ist, drücken Sie nach dem Einschalten die Eingabetaste, F12, ESC oder andere seltsame Tasten, bis Sie ein Startmenü erhalten, in dem Sie auswählen können, ob Sie vom USB-Gerät booten möchten.
In diesen Menüs ist es häufig möglich, die Suchreihenfolge zu konfigurieren.
Auf meinem T430 sehe ich beispielsweise Folgendes.
Nach dem Einschalten muss ich die Eingabetaste drücken, um das Startmenü aufzurufen:
Dann muss ich hier F12 drücken, um den USB als Startgerät auszuwählen:
Von dort aus kann ich den USB wie folgt als Startgerät auswählen:
Um alternativ die Startreihenfolge zu ändern und den USB-Anschluss mit höherer Priorität auszuwählen, damit ich ihn nicht jedes Mal manuell auswählen muss, drücke ich im Bildschirm "Startup Interrupt Menu" auf F1 und navigiere dann zu:
Bootsektor
Unter x86 können Sie am einfachsten und niedrigsten Ebene einen Master Boot Sector (MBR) erstellen , bei dem es sich um eine Art Bootsektor handelt , und ihn dann auf einer Festplatte installieren.
Hier erstellen wir eine mit einem einzigen
printf
Aufruf:Ergebnis:
Beachten Sie, dass auch ohne etwas zu tun bereits einige Zeichen auf dem Bildschirm gedruckt sind. Diese werden von der Firmware gedruckt und dienen zur Identifizierung des Systems.
Und auf dem T430 erhalten wir nur einen leeren Bildschirm mit einem blinkenden Cursor:
main.img
enthält Folgendes:\364
in octal ==0xf4
in hex: die Codierung für einehlt
Anweisung, die die CPU anweist, nicht mehr zu arbeiten.Daher wird unser Programm nichts tun: nur starten und stoppen.
Wir verwenden Oktal, da
\x
Hex-Zahlen von POSIX nicht angegeben werden.Wir könnten diese Codierung leicht erhalten mit:
welche Ausgänge:
Aber es ist natürlich auch im Intel-Handbuch dokumentiert.
%509s
509 Räume produzieren. Muss die Datei bis Byte 510 ausfüllen.\125\252
in oktal ==0x55
gefolgt von0xaa
.Dies sind 2 erforderliche magische Bytes, die die Bytes 511 und 512 sein müssen.
Das BIOS durchsucht alle unsere Datenträger nach bootfähigen Datenträgern und berücksichtigt nur bootfähige Datenträger mit diesen beiden magischen Bytes.
Wenn nicht vorhanden, behandelt die Hardware dies nicht als bootfähige Festplatte.
Wenn Sie kein
printf
Meister sind, können Sie den Inhalt vonmain.img
mit bestätigen:welches das erwartete zeigt:
Wo
20
ist ein Leerzeichen in ASCII.Die BIOS-Firmware liest diese 512 Bytes von der Festplatte, speichert sie im Speicher und setzt den PC auf das erste Byte, um sie auszuführen.
Hallo Welt Boot Sektor
Nachdem wir ein minimales Programm erstellt haben, gehen wir zu einer Hallo-Welt.
Die offensichtliche Frage ist: Wie mache ich IO? Einige Optionen:
Bitten Sie die Firmware, zB BIOS oder UEFI, dies für uns zu tun
VGA: Spezieller Speicherbereich, der beim Schreiben auf den Bildschirm gedruckt wird. Kann im geschützten Modus verwendet werden.
Schreiben Sie einen Treiber und sprechen Sie direkt mit der Display-Hardware. Dies ist der "richtige" Weg, dies zu tun: leistungsfähiger, aber komplexer.
serielle Schnittstelle . Dies ist ein sehr einfaches standardisiertes Protokoll, das Zeichen von einem Host-Terminal sendet und empfängt.
Auf Desktops sieht es so aus:
Quelle .
Es ist leider auf den meisten modernen Laptops nicht verfügbar, aber es ist der übliche Weg für Entwicklungsboards, siehe die ARM-Beispiele unten.
Dies ist wirklich eine Schande, da solche Schnittstellen zum Debuggen des Linux-Kernels zum Beispiel sehr nützlich sind .
Verwenden Sie die Debug-Funktionen von Chips. ARM nennt zum Beispiel ihr Semihosting . Auf echter Hardware ist zusätzliche Hardware- und Softwareunterstützung erforderlich, auf Emulatoren kann dies jedoch eine kostenlose und bequeme Option sein. Beispiel .
Hier machen wir ein BIOS-Beispiel, da es auf x86 einfacher ist. Beachten Sie jedoch, dass dies nicht die robusteste Methode ist.
Netz
GitHub stromaufwärts .
link.ld
Zusammenbauen und verknüpfen mit:
Ergebnis:
Und auf dem T430:
Getestet auf: Lenovo Thinkpad T430, UEFI BIOS 1.16. Auf einem Ubuntu 18.04-Host generierte Festplatte.
Neben den Standard-Montageanleitungen für Userland haben wir:
.code16
: Weist GAS an, 16-Bit-Code auszugebencli
: Software-Interrupts deaktivieren. Dadurch könnte der Prozessor nach dem erneut gestartet werdenhlt
int $0x10
: führt einen BIOS-Aufruf durch. Dies ist es, was die Zeichen einzeln druckt.Die wichtigen Link-Flags sind:
--oformat binary
: Geben Sie rohen binären Assembly-Code aus und wickeln Sie ihn nicht in eine ELF-Datei ein, wie dies bei regulären ausführbaren Benutzerlanddateien der Fall ist.Machen Sie sich mit dem Verschiebungsschritt des Verlinkens vertraut, um den Linker-Skript-Teil besser zu verstehen: Was machen Linker?
Cooler x86 Bare-Metal-Programme
Hier sind einige komplexere Bare-Metal-Setups, die ich erreicht habe:
Verwenden Sie C anstelle von Assembly
Zusammenfassung: Verwenden Sie GRUB Multiboot, um viele lästige Probleme zu lösen, an die Sie nie gedacht haben. Siehe den folgenden Abschnitt.
Die Hauptschwierigkeit bei x86 besteht darin, dass das BIOS nur 512 Bytes von der Festplatte in den Speicher lädt und Sie diese 512 Bytes wahrscheinlich in die Luft jagen, wenn Sie C verwenden!
Um dies zu lösen, können wir einen zweistufigen Bootloader verwenden . Dadurch werden weitere BIOS-Aufrufe ausgeführt, die mehr Bytes von der Festplatte in den Speicher laden. Hier ist ein minimales Beispiel für eine Assembly der Stufe 2 von Grund auf mit den BIOS-Aufrufen von int 0x13 :
Alternative:
-kernel
Option, mit der eine gesamte ELF-Datei in den Speicher geladen wird. Hier ist ein ARM-Beispiel, das ich mit dieser Methode erstellt habe .kernel7.img
, ähnlich wie bei QEMU-kernel
.Hier ist nur zu Bildungszwecken ein einstufiges minimales C-Beispiel :
Haupt c
Eintrag.S
linker.ld
Lauf
C Standardbibliothek
Es macht mehr Spaß, wenn Sie jedoch auch die C-Standardbibliothek verwenden möchten, da wir nicht über den Linux-Kernel verfügen, der einen Großteil der Funktionen der C-Standardbibliothek über POSIX implementiert .
Einige Möglichkeiten, ohne auf ein vollwertiges Betriebssystem wie Linux zuzugreifen, sind:
Schreibe dein Eigenes. Am Ende sind es nur ein paar Header und C-Dateien, oder? Richtig??
Newlib
Detailliertes Beispiel unter: /electronics/223929/c-standard-libraries-on-bare-metal/223931
Newlib implementiert alle langweiligen Nicht-OS - spezifische Dinge für Sie, zum Beispiel
memcmp
,memcpy
usw.Anschließend erhalten Sie einige Stubs, mit denen Sie die Syscalls implementieren können, die Sie selbst benötigen.
Zum Beispiel können wir
exit()
ARM durch Semihosting implementieren mit:wie in diesem Beispiel gezeigt .
Sie können beispielsweise
printf
zu den UART- oder ARM-Systemen umleiten oderexit()
mit Semihosting implementieren .eingebettete Betriebssysteme wie FreeRTOS und Zephyr .
Mit solchen Betriebssystemen können Sie in der Regel die vorbeugende Planung deaktivieren, sodass Sie die volle Kontrolle über die Laufzeit des Programms haben.
Sie können als eine Art vorimplementierte Newlib angesehen werden.
GNU GRUB Multiboot
Bootsektoren sind einfach, aber nicht sehr praktisch:
Aus diesen Gründen hat GNU GRUB ein bequemeres Dateiformat namens Multiboot erstellt.
Minimales Arbeitsbeispiel: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world
Ich verwende es auch in meinem GitHub-Beispiel-Repo , um alle Beispiele problemlos auf realer Hardware ausführen zu können, ohne den USB millionenfach zu brennen.
QEMU-Ergebnis:
T430:
Wenn Sie Ihr Betriebssystem als Multiboot-Datei vorbereiten, kann GRUB es in einem regulären Dateisystem finden.
Dies ist, was die meisten Distributionen tun, indem sie OS-Images unterlegen
/boot
.Multiboot-Dateien sind im Grunde eine ELF-Datei mit einem speziellen Header. Sie werden von GRUB unter folgender Adresse angegeben: https://www.gnu.org/software/grub/manual/multiboot/multiboot.html
Sie können eine Multiboot-Datei mit in eine bootfähige Festplatte verwandeln
grub-mkrescue
.Firmware
In Wahrheit ist Ihr Bootsektor nicht die erste Software, die auf der CPU des Systems ausgeführt wird.
Was tatsächlich zuerst läuft, ist die sogenannte Firmware , bei der es sich um eine Software handelt:
Bekannte Firmwares sind:
Die Firmware macht Dinge wie:
Durchlaufen Sie jede Festplatte, jeden USB-Stick, jedes Netzwerk usw., bis Sie etwas Bootfähiges finden.
Wenn wir QEMU ausführen,
-hda
heißt es, dassmain.img
es sich um eine an die Hardware angeschlossene Festplattehda
handelt, die als erste ausprobiert wird und verwendet wird.Laden Sie die ersten 512 Bytes in die RAM-Speicheradresse
0x7c00
, legen Sie den RIP der CPU dort ab und lassen Sie ihn laufenZeigen Sie Dinge wie das Startmenü oder BIOS-Druckaufrufe auf dem Display an
Die Firmware bietet betriebssystemähnliche Funktionen, von denen die meisten Betriebssysteme abhängen. Beispielsweise wurde eine Python-Teilmenge für die Ausführung unter BIOS / UEFI portiert: https://www.youtube.com/watch?v=bYQ_lq5dcvM
Es kann argumentiert werden, dass Firmwares nicht von Betriebssystemen zu unterscheiden sind und dass Firmware die einzige "echte" Bare-Metal-Programmierung ist, die man machen kann.
Wie dieser CoreOS-Entwickler es ausdrückt :
Post BIOS-Anfangszustand
Wie viele Dinge in der Hardware ist die Standardisierung schwach, und eines der Dinge, auf die Sie sich nicht verlassen sollten, ist der Anfangszustand der Register, wenn Ihr Code nach dem BIOS ausgeführt wird.
Tun Sie sich selbst einen Gefallen und verwenden Sie einen Initialisierungscode wie den folgenden: https://stackoverflow.com/a/32509555/895245
Register mögen
%ds
und%es
haben wichtige Nebenwirkungen, daher sollten Sie sie auf Null setzen, auch wenn Sie sie nicht explizit verwenden.Beachten Sie, dass einige Emulatoren besser als echte Hardware sind und Ihnen einen guten Ausgangszustand bieten. Wenn Sie dann auf echter Hardware laufen, bricht alles zusammen.
El Torito
Format, das auf CDs gebrannt werden kann: https://en.wikipedia.org/wiki/El_Torito_%28CD-ROM_standard%29
Es ist auch möglich, ein Hybrid-Image zu erstellen, das entweder auf ISO oder USB funktioniert. Dies kann mit
grub-mkrescue
( Beispiel ) erfolgen und wird auch vom Linux-Kernel beimake isoimage
Verwendung durchgeführtisohybrid
.ARM
In ARM sind die allgemeinen Ideen dieselben.
Es gibt keine allgemein verfügbare halbstandardisierte vorinstallierte Firmware wie das BIOS, die wir für die E / A verwenden können. Die zwei einfachsten Arten von E / A, die wir ausführen können, sind:
Ich habe hochgeladen:
Einige einfache QEMU C + Newlib- und Raw-Assembly-Beispiele hier auf GitHub .
Das Beispiel prompt.c nimmt beispielsweise Eingaben von Ihrem Host-Terminal entgegen und gibt die Ausgabe über den gesamten simulierten UART zurück:
Siehe auch: Wie erstelle ich Bare-Metal-ARM-Programme und führe sie auf QEMU aus?
Ein vollautomatisches Raspberry Pi-Blinker-Setup unter: https://github.com/cirosantilli/raspberry-pi-bare-metal-blinker
Siehe auch: Wie führe ich ein C-Programm ohne Betriebssystem auf dem Raspberry Pi aus?
Um die LEDs auf QEMU zu "sehen", müssen Sie QEMU aus dem Quellcode mit einem Debug-Flag kompilieren: /raspberrypi/56373/is-it-possible-to-get-the-state-of- the-leds-and-gpios-in-a-qemu-emulation-like-t
Als nächstes sollten Sie eine UART-Hallo-Welt ausprobieren. Sie können mit dem Blinker-Beispiel beginnen und den Kernel durch diesen ersetzen: https://github.com/dwelch67/raspberrypi/tree/bce377230c2cdd8ff1e40919fdedbc2533ef5a00/uart01
Lassen Sie den UART zuerst mit Raspbian arbeiten, wie ich unter /raspberrypi/38/prepare-for-ssh-without-a-screen/54394#54394 erklärt habe. Es sieht ungefähr so aus:
Stellen Sie sicher, dass Sie die richtigen Pins verwenden, sonst können Sie Ihren UART-zu-USB-Konverter brennen. Ich habe es bereits zweimal durch Kurzschließen von Masse und 5 V gemacht ...
Stellen Sie schließlich vom Host aus eine Verbindung zur Seriennummer her:
Für den Raspberry Pi verwenden wir eine Micro-SD-Karte anstelle eines USB-Sticks, um unsere ausführbare Datei zu enthalten, für die Sie normalerweise einen Adapter benötigen, um eine Verbindung zu Ihrem Computer herzustellen:
Vergessen Sie nicht, den SD-Adapter wie folgt zu entsperren: /ubuntu/213889/microsd-card-is-set-to-read-only-state-how-can-i-write-data -on-it / 814585 # 814585
https://github.com/dwelch67/raspberrypi sieht aus wie das beliebteste Bare-Metal-Raspberry-Pi-Tutorial, das heute verfügbar ist.
Einige Unterschiede zu x86 sind:
IO erfolgt durch direktes Schreiben an magische Adressen, es gibt keine
in
undout
Anweisungen.Dies wird als speicherabgebildete E / A bezeichnet .
Für echte Hardware wie den Raspberry Pi können Sie die Firmware (BIOS) selbst zum Disk-Image hinzufügen.
Das ist gut so, denn dadurch wird die Aktualisierung dieser Firmware transparenter.
Ressourcen
quelle
Betriebssystem als Inspiration
Das Betriebssystem ist auch ein Programm , sodass wir auch unser eigenes Programm erstellen können, indem wir die Funktionen eines der kleinen Betriebssysteme von Grund auf neu erstellen oder ändern (einschränken oder hinzufügen) und es dann während des Startvorgangs ausführen (unter Verwendung eines ISO-Images ). .
Diese Seite kann beispielsweise als Ausgangspunkt verwendet werden:
So schreiben Sie ein einfaches Betriebssystem
Hier passt das gesamte Betriebssystem vollständig in einen 512-Byte-Bootsektor ( MBR )!
Ein solches oder ein ähnliches einfaches Betriebssystem kann verwendet werden, um ein einfaches Framework zu erstellen, das uns Folgendes ermöglicht:
Es gibt jedoch viele Möglichkeiten. Um beispielsweise ein größeres Betriebssystem für die x86-Assemblersprache zu sehen, können wir das Betriebssystem MykeOS , x86 kennenlernen, das ein Lernwerkzeug ist , um die einfache Arbeit mit 16-Bit-Betriebssystemen im Real-Modus mit gut kommentiertem Code und umfassender Dokumentation zu demonstrieren .
Bootloader als Inspiration
Andere gängige Arten von Programmen, die ohne das Betriebssystem ausgeführt werden, sind auch Bootloader . Wir können ein Programm erstellen, das von einem solchen Konzept inspiriert ist, beispielsweise über diese Website:
So entwickeln Sie Ihren eigenen Bootloader
Der obige Artikel stellt auch die grundlegende Architektur eines solchen Programms vor :
Wie wir sehen können, ist diese Architektur sehr flexibel und ermöglicht es uns, jedes Programm zu implementieren , nicht unbedingt einen Bootloader.
Insbesondere wird gezeigt, wie die "Mixed Code" -Technik verwendet wird, mit der Konstruktionen auf hoher Ebene (aus C oder C ++ ) mit Befehlen auf niedriger Ebene (aus Assembler ) kombiniert werden können . Dies ist eine sehr nützliche Methode, aber wir müssen uns daran erinnern:
Der Artikel zeigt auch, wie Sie das erstellte Programm in Aktion sehen und wie Sie es testen und debuggen.
UEFI-Anwendungen als Inspiration
In den obigen Beispielen wurde die Tatsache verwendet, dass der Sektor-MBR auf das Datenmedium geladen wurde. Wir können jedoch tiefer in die Tiefe vordringen, indem wir beispielsweise die UEFI- Anwendungen verwenden :
Wenn wir mit der Erstellung solcher Programme beginnen möchten , können wir beispielsweise mit diesen Websites beginnen:
Programmierung für EFI: Erstellen eines "Hello, World" -Programms / UEFI-Programmierung - Erste Schritte
Erkundung von Sicherheitsproblemen als Inspiration
Es ist bekannt, dass eine ganze Gruppe schädlicher Software (Programme) ausgeführt wird, bevor das Betriebssystem gestartet wird .
Eine große Gruppe von ihnen arbeitet im MBR-Sektor oder in UEFI-Anwendungen, genau wie die oben genannten Lösungen, aber es gibt auch solche, die einen anderen Einstiegspunkt verwenden, z. B. den Volume Boot Record (VBR) oder das BIOS :
oder vielleicht auch eine andere.
Angriffe vor dem Systemstart
Verschiedene Möglichkeiten zum Booten
Ich denke auch, dass es in diesem Zusammenhang auch erwähnenswert ist, dass es verschiedene Formen des Bootens des Betriebssystems (oder des dafür vorgesehenen ausführbaren Programms) gibt . Es gibt viele, aber ich würde zu achten wie aus dem Netz zu laden , den Code mit Netzwerk - Boot - Option ( PXE ), die uns das Programm auf dem Computer ausgeführt werden können , unabhängig von dessen Betriebssystem und auch unabhängig von einem Speichermedium , das ist direkt mit dem Computer verbunden:
Was ist Network Booting (PXE) und wie können Sie es verwenden?
quelle