Wie führe ich ein Programm ohne Betriebssystem?

Wie führst du ein Programm ohne Betriebssystem aus? Können Sie Assemblierungsprogramme erstellen, die der Computer beim Start laden und ausführen kann, z. B. den Computer von einem Flash-Laufwerk booten und das Programm ausführen, das sich auf der CPU befindet?

   

Wie führst du ein Programm ohne Betriebssystem aus?

Sie platzieren Ihren Binärcode an einer Stelle, an der der processor nach dem Neustart sucht (z. B. Adresse 0 auf ARM).

Können Sie Assemblierungsprogramme 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 dem Laufwerk befindet)?

Allgemeine Antwort auf die Frage: Es kann getan werden. Es wird oft als “Bare-Metal-Programmierung” bezeichnet. Um von einem 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 dieser Festplatte müsste ebenfalls in einem bestimmten Format vorliegen. Auf einem bestimmten Dateisystem … Dies ist etwas, das normalerweise Bootloader tun. Viele ARM-Boards lassen Sie einige dieser Dinge tun. Einige haben einen Boot-Loader, der Sie bei der grundlegenden Einrichtung unterstützt.

Hier finden Sie eine gute Anleitung, wie Sie grundlegende Betriebssystem auf Raspberry PI zu tun.

Edit: Dieser Artikel und die ganze wiki.osdev.org beantwortet die meisten Ihrer Fragen http://wiki.osdev.org/Introduction

Wenn Sie nicht direkt mit der Hardware experimentieren möchten, können Sie sie auch als virtuelle Maschine mit Hypervisoren wie qemu ausführen. Sehen Sie hier, wie Sie “Hallo Welt” direkt auf virtualisierte ARM-Hardware ausführen können.

Ausführbare Beispiele

Technisch gesehen ist ein Programm, das ohne Betriebssystem läuft, ein Betriebssystem. Sehen wir uns nun an, wie Sie einige winzige Hallo-Welt-Betriebssysteme erstellen und ausführen können.

Der Code aller folgenden Beispiele ist in diesem GitHub Repo enthalten . Alles wurde auf Ubuntu 14.04 AMD64 QEMU und echte Hardware ThinkPad T430 getestet.

Bootsektor

Auf x86 können Sie am einfachsten und auf der niedrigsten Ebene einen Master Boot Sector (MBR) erstellen, bei dem es sich um einen Bootsektor handelt , und ihn dann auf einem Datenträger installieren.

Hier erstellen wir einen mit einem einzelnen printf Aufruf:

 printf '\364%509s\125\252' > main.img sudo apt-get install qemu-system-x86 qemu-system-x86_64 -hda main.img 

main.img enthält Folgendes:

  • \364 in oktal == 0xf4 in hex: die Kodierung für eine hlt statement, die die CPU anweist, nicht mehr zu arbeiten.

    Deshalb wird unser Programm nichts machen: nur starten und stoppen.

    Wir verwenden oktal, weil \x hex Zahlen nicht von POSIX angegeben werden.

    Wir könnten diese Codierung leicht erhalten mit:

     echo hlt > a.asm nasm -f bin a.asm hd a 

    aber die 0xf4 Codierung ist 0xf4 auch im Intel-Handbuch dokumentiert.

  • %509s produzieren 509 Leerzeichen. Zum Ausfüllen der Datei bis zum Byte 510 benötigt.

  • \125\252 in oktal == 0x55 gefolgt von 0xaa : magische Bytes, die von der Hardware benötigt werden. Sie müssen Bytes 511 und 512 sein.

    Wenn nicht vorhanden, behandelt die Hardware dies nicht als bootfähige Festplatte.

Beachten Sie, dass einige Zeichen bereits auf dem Bildschirm angezeigt werden, auch wenn Sie nichts tun. Diese werden von der Firmware gedruckt und dienen zur Identifikation des Systems.

Auf echter Hardware laufen

Emulatoren machen Spaß, aber Hardware ist das einzig Wahre.

  • Brennen Sie das Bild auf einen USB-Stick (wird Ihre Daten zerstören!):

     sudo dd if=main.img of=/dev/sdX 
  • Schließen Sie den USB-Anschluss an einen Computer an

  • Mach es an

  • sagen Sie es von USB booten.

    Dies bedeutet, dass die Firmware USB vor der Festplatte auswählen muss.

    Wenn dies nicht das Standardverhalten Ihres Rechners ist, drücken Sie nach dem Einschalten weiterhin Enter, F12, ESC oder andere seltsame Tasten, bis Sie ein Boot-Menü erhalten, in dem Sie vom USB booten können.

    Es ist oft möglich, die Suchreihenfolge in diesen Menüs zu konfigurieren.

Hallo Welt

Nachdem wir nun ein minimales Programm erstellt haben, gehen wir zu einer Hallo-Welt über.

Die offensichtliche Frage ist: Wie mache ich IO? Ein paar Optionen:

  • Bitten Sie die Firmware, zB BIOS oder UEFI, für uns zu tun
  • VGA: spezieller Speicherbereich, der beim Beschreiben auf den Bildschirm gedruckt wird. Kann im geschützten Modus verwendet werden.
  • schreibe einen Treiber und sprich direkt mit der Display-Hardware. Dies ist der “richtige” Weg, es zu tun: mächtiger, aber komplexer.
  • Verwenden Sie Debug-functionen von Chips. ARM nennt zum Beispiel ihr Semihosting . Auf echter Hardware erfordert es einige zusätzliche Hardware- und Software-Unterstützung, aber auf Emulatoren kann es eine kostenlose bequeme Option sein. Beispiel .

Hier werden wir ein BIOS-Beispiel machen, da es einfacher ist. Beachten Sie jedoch, dass dies nicht die robusteste Methode ist.

Hier ist der GAS-Code:

 .code16 .global _start _start: cli mov $msg, %si mov $0x0e, %ah loop: lodsb or %al, %al jz halt int $0x10 jmp loop halt: hlt msg: .asciz "hello world" .org 510 .word 0xaa55 

Neben den Standard-Benutzerland-assemblyanleitungen haben wir:

  • .code16 : weist GAS an, 16-Bit-Code auszugeben

  • cli : disable software interrupts. Diese könnten dazu führen, dass der processor nach dem hlt wieder startet

  • int $0x10 : ruft ein BIOS auf. Dies ist, was die Zeichen nacheinander druckt.

  • .org 510 und .word 0xaa55 : Platziere die magischen Bytes am Ende

Eine schnelle und schmutzige Art, dies zu kompilieren, ist mit:

 as -o main.o main.S ld --oformat binary -o main.ing -Ttext 0x7C00 main.o 

und main.img wie zuvor aus.

Es gibt zwei wichtige Flaggen hier:

  • --oformat binary : Ausgabe roher binärer Assembly-Code, --oformat binary Sie ihn nicht in einer ELF-Datei, wie dies bei regulären ausführbaren Dateien für Benutzerland der Fall ist.

  • -Ttext 0x7C00 : Wir müssen dem Linker ld mitteilen, wo der Code platziert wird, damit er auf den Speicher zugreifen kann.

    Insbesondere wird dies während der Umzugsphase verwendet. Lesen Sie hier mehr darüber.

Die bessere Methode zum Kompilieren besteht darin, ein sauberes Linker-Skript zu verwenden . Das Linker-Skript kann auch die magischen Bytes für uns platzieren.

Firmware

In Wahrheit ist Ihr Boot-Sektor nicht die erste Software, die auf der CPU des Systems ausgeführt wird.

Was eigentlich zuerst läuft, ist die sogenannte Firmware , die eine Software ist:

  • Hergestellt von den Hardwareherstellern
  • typischerweise geschlossene Quelle, aber wahrscheinlich C-basiert
  • im Nur-Lese-Speicher gespeichert und daher ohne die Zustimmung des Verkäufers schwieriger / unmöglich zu modifizieren.

Bekannte Firmwares umfassen:

  • BIOS : alte, allgegenwärtige x86-Firmware. SeaBIOS ist die Standard-Open-Source-Implementierung, die von QEMU verwendet wird.
  • UEFI : BIOS-Nachfolger, besser standardisiert, aber leistungsfähiger und unglaublich aufgebläht.
  • Coreboot : der edle Cross-Arc-Open-Source-Versuch

Die Firmware macht Dinge wie:

  • Schleife über jede Festplatte, USB, Netzwerk usw., bis du etwas Bootfähiges findest.

    Wenn wir QEMU -hda sagt -hda , dass main.img eine Festplatte ist, die mit der Hardware verbunden ist, und

    hda ist der erste, der ausprobiert wird und benutzt wird.

  • lade die ersten 512 Bytes in die RAM-Speicheradresse 0x7c00 , lege den RIP der CPU dorthin und lasse ihn laufen

  • Zeigen Sie Dinge wie das Startmenü oder BIOS-Druckaufrufe auf dem Display an

Die Firmware bietet OS-ähnliche functionen, von denen die meisten Betriebssysteme abhängen. Zum Beispiel wurde eine Python-Untergruppe portiert, um auf BIOS / UEFI zu laufen: https://www.youtube.com/watch?v=bYQ_lq5dcvM

Es kann argumentiert werden, dass Firmwares von Betriebssystemen nicht zu unterscheiden sind, und dass Firmware die einzige “echte” Bare-Metal-Programmierung ist, die man tun kann.

Wie dieser CoreOS-Entwickler es ausdrückt :

Der schwierige Teil

Wenn Sie einen PC einschalten, sind die Chips, aus denen der Chipsatz besteht (Northbridge, Southbridge und SuperIO), noch nicht richtig initialisiert. Obwohl das BIOS-ROM so weit von der CPU entfernt ist, wie es sein könnte, ist dies für die CPU zugänglich, weil es sein muss, andernfalls hätte die CPU keine statementen zum Ausführen. Dies bedeutet nicht, dass das BIOS-ROM vollständig zugeordnet ist, normalerweise nicht. Aber gerade genug wird gemappt, um den Boot-process in Gang zu bringen. Irgendwelche anderen Geräte, vergiss es einfach.

Wenn Sie Coreboot unter QEMU ausführen, können Sie mit den höheren Schichten von CoreBoot und mit Payloads experimentieren, aber QEMU bietet wenig Möglichkeiten, mit dem Startcode niedriger Ebene zu experimentieren. Zum einen funktioniert RAM einfach von Anfang an.

Post-BIOS-Anfangszustand

Wie bei vielen anderen Dingen 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 startet.

Tun Sie sich also einen Gefallen und verwenden Sie einen Initialisierungscode wie den folgenden: https://stackoverflow.com/a/32509555/895245

Register wie %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 netter als echte Hardware sind und Ihnen einen guten Anfangszustand geben. Dann, wenn Sie auf echte Hardware laufen, bricht alles.

GRUB Multiboot

Bootsektoren sind einfach, aber nicht sehr praktisch:

  • Sie können nur ein Betriebssystem pro Datenträger haben
  • Der Ladecode muss wirklich klein sein und in 512 Bytes passen. Dies könnte mit dem Int 0x13 BIOS-Aufruf getriggers werden.
  • Sie müssen selbst viel Startup machen, zB in den geschützten Modus wechseln

Aus diesen Gründen hat GRUB ein komfortableres Dateiformat namens Multiboot erstellt.

Minimal funktionierendes Beispiel: https://github.com/cirosantilli/x86-bare-metal-examples/tree/d217b180be4220a0b4a453f31275d38e697a99e0/multiboot/hello-world

Wenn Sie Ihr Betriebssystem als Multiboot-Datei vorbereiten, kann GRUB es in einem normalen Dateisystem finden.

Dies ist es, was die meisten Distributionen tun, indem sie OS-Images unter /boot ablegen.

Multiboot-Dateien sind grundsätzlich 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 grub-mkrescue in eine bootfähige Festplatte grub-mkrescue .

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 Hybridbild zu erstellen, das entweder mit ISO oder USB arbeitet. Dies kann mit grub-mkrescue ( Beispiel ) geschehen, und wird auch vom Linux-coreel gemacht, make isoimage mit isohybrid .

ARM

ARM-land hat seine eigenen Konventionen (oder mehr Konventionen, da ARM Lizenzen an Anbieter lizenziert, die es modifizieren), aber die allgemeinen Ideen sind die gleichen:

  • Sie schreiben Code in eine magische Adresse im Speicher
  • Sie machen IO mit magischen Adressen

Einige Unterschiede beinhalten:

  • IO wird gemacht, indem man direkt an magische Adressen schreibt, es gibt keine in und Ausweisungen
  • Sie müssen Ihrem Image kompilierte Closed-Source-Blobs von Herstellern hinzufügen. Diese sind etwas wie BIOS, und das ist eine gute Sache, da es die Firmware transparenter macht.

Hier sind einige Beispiele:

  • Wie bare Metal ARM-Programme erstellt und auf QEMU ausgeführt werden? QEMU freundliche UART-Interaktion
  • Wie führe ich ein C-Programm ohne Betriebssystem auf dem Raspberry Pi? Raspberry Pi Hardware freundliche LED-Interaktion

Ressourcen