Der Bootloader springt nicht zum Kernel-Code

8

Ich schreibe kleines Betriebssystem - für die Praxis. Ich begann mit dem Bootloader.
Ich möchte ein kleines Befehlssystem erstellen, das im 16-Bit-Realmodus läuft (für den Moment).
Ich habe einen Bootloader erstellt, der das Laufwerk zurücksetzt und dann den Sektor nach dem Bootloader lädt.
Das Problem liegt darin, dass nach jmp function tatsächlich nichts passiert.

Ich versuche nicht, den nächsten Sektor bei 0x7E00 zu laden (ich bin nicht ganz sicher, wie ich die Adresse mit es: bx ansprechen kann, so dass es ein Problem ist, glaube ich, dass seine Adresse: offset), gleich nach dem Bootloader.

Dies ist der Code:

%Vor%

Ich habe diesen Code mit VirtualBox getestet, aber tatsächlich passiert nichts, der Lesefehler wird nicht angezeigt, ebenso wie die Nachricht, die gedruckt werden sollte.

    
vakus 21.09.2015, 18:06
quelle

1 Antwort

36

Die primären Probleme mit diesem Code waren:

  1. ES: BX zeigte auf das falsche Segment: offset, um den Kernel in
  2. zu laden
  3. Falscher Sektor wurde geladen, daher war Kernel nicht das, was erwartet wurde

Der erste war in diesem Code:

%Vor%

Die Frage möchte den Sektor von der Festplatte in 0x0000:0x7E00 ( ES: BX ) laden. Dieser Code setzt das ES: BX auf 0x7E00:0x0000 , das in eine physikalische Adresse von 0x7E000 aufgelöst wird ((0x7E00 & lt; & lt; 4) + 0x0000). Ich denke, die Absicht war, 0x07E0 in ES zu laden, was eine physikalische Adresse von 0x7E00 ((0x07E0 & lt; & lt; 4) + 0x0000) ergeben würde. Sie können mehr über 16:16 Speicheradressierungsberechnungen hier erfahren. Die Multiplikation des Segments mit 16 entspricht der Verschiebung um 4 Bits.

Das zweite Problem im Code ist hier:

%Vor%

Die Nummer für den zweiten 512-Block-Sektor auf der Festplatte ist 2, nicht 1. Um also den obigen Code zu beheben, müssen Sie CL entsprechend einstellen:

%Vor%

Allgemeine Tipps zur Bootloader-Entwicklung

Andere Probleme, die den laufenden Code auf verschiedenen Emulatoren, virtuellen Maschinen und echter physischer Hardware auslösen können, sind:

  1. Wenn das BIOS zu Ihrem Code springt, können Sie sich nicht auf CS , DS , ES , SS , SP registriert gültige oder erwartete Werte. Sie sollten beim Starten des Bootloaders entsprechend eingerichtet werden. Sie können nur garantieren, dass Ihr Bootloader von der physischen Adresse 0x00007c00 geladen und ausgeführt wird und dass die Startlaufwerknummer in das Verzeichnis DL geladen wird.
  2. Setzen Sie SS: SP auf Speicher, von dem Sie wissen, dass er nicht mit dem Betrieb Ihres eigenen Codes in Konflikt steht. Das BIOS hat möglicherweise seinen Standard-Stapelzeiger irgendwo in dem ersten Megabyte verwendbaren und adressierbaren RAM platziert. Es gibt keine Garantie, wo das ist und ob es für den Code geeignet ist, den Sie schreiben.
  3. Das von lodsb , movsb etc verwendete Richtungs-Flag könnte entweder gesetzt oder gelöscht werden. Wenn das Richtungsflag falsch eingestellt ist, können SI / DI -Register möglicherweise in die falsche Richtung eingestellt werden. Verwenden Sie STD / CLD , um es in die gewünschte Richtung zu setzen (CLD = vorwärts / STD = rückwärts). In diesem Fall nimmt der Code die Vorwärtsbewegung an, daher sollte CLD verwendet werden. Mehr dazu finden Sie in einer Befehlssatzreferenz
  4. Beim Springen zu einem Kernel ist es im Allgemeinen eine gute Idee, FAR JMP zu setzen, damit CS: IP korrekt auf die erwarteten Werte gesetzt wird. Dies kann Probleme mit Kernel-Code vermeiden, die absolut nahe JMPs und CALLs innerhalb desselben Segments tun können.
  5. Wenn Sie Ihren Bootloader auf 16-Bit-Code abzielen, der auf 8086/8088-Prozessoren (UND höher) funktioniert, vermeiden Sie die Verwendung von 32-Bit-Registern im Assemblercode. Verwenden Sie AX / BX / CX / DX / DI / DI / SP / BP anstelle von EAX / EBX / ECX / EDX / ESI / EDI / ESP / EBP Obwohl dies kein Problem in dieser Frage ist, war es ein Problem für andere, die Hilfe suchen. Ein 32-Bit-Prozessor kann 32-Bit-Register im 16-Bit-Real-Modus verwenden, aber ein 8086/8088/80286 kann nicht, da sie 16-Bit-Prozessoren ohne Zugriff auf erweiterte 32-Bit-Register waren.
  6. FS und GS Segmentregister wurden zu 80386+ CPUs hinzugefügt. Vermeiden Sie sie, wenn Sie 8086/8088/80286 als Ziel festlegen möchten.

Um das erste und zweite Element aufzulösen, kann dieser Code nahe dem Start des Bootloaders verwendet werden:

%Vor%

Ein paar Dinge zu beachten. Wenn Sie den Wert des SS -Registers ändern (in diesem Fall über MOV ), soll der Prozessor die Interrupts für diese Anweisung ausschalten und sie bis nach deaktivieren die folgende Anweisung. Normalerweise müssen Sie sich keine Gedanken über die Deaktivierung von Interrupts machen, wenn Sie SS aktualisieren, unmittelbar gefolgt von einer Aktualisierung von SP . Es gibt einen Fehler in sehr frühen 8088-Prozessoren, bei dem dies nicht berücksichtigt wurde. Wenn Sie also auf die größtmögliche Anzahl von Umgebungen abzielen, ist es eine sichere Sache, diese explizit zu deaktivieren und erneut zu aktivieren. Wenn Sie nicht beabsichtigen, jemals an einem fehlerhaften 8088 zu arbeiten, können die Anweisungen CLI / STI im obigen Code entfernt werden. Ich kenne diesen Bug aus erster Hand mit meiner Arbeit, die ich Mitte der 80er Jahre auf meinem PC gemacht habe.

Die zweite Sache, die ich beachten muss, ist, wie ich den Stapel aufstelle. Für Leute, die neu zu 8088/8086 16-Bit-Montage sind, kann der Stapel eine Vielzahl von Möglichkeiten gesetzt werden. In diesem Fall setze ich den oberen Teil des Stapels (unterster Teil im Speicher) auf 0x8000 ( SS ). Ich setze dann den Stapelzeiger ( SP ) auf 0 . Wenn Sie etwas im Stapel schieben im 16-Bit-Real-Modus dekrementiert der Prozessor zuerst den Stack-Zeiger um 2 und dann platziert ein 16-Bit WORD an dieser Position.Somit würde der erste Schub zum Stapel bei 0x0000-2 = 0xFFFE (-2) liegen. Sie hätten dann ein SS: SP , das wie 0x8000:0xFFFE aussieht. In diesem Fall läuft der Stapel von 0x8000:0x0000 bis 0x8000:0xFFFF .

Wenn Sie mit dem Stack arbeiten, der auf einem 8086 läuft (gilt nicht für 80286,80386+ Prozessoren), ist es eine gute Idee, den Stack-Pointer ( SP ) auf eine gerade Zahl zu setzen. Wenn Sie auf dem ursprünglichen 8086 SP auf eine ungerade Zahl setzen, würden Sie sich eine 4-Takt-Strafe zulegen Jeder Zugriff auf Stapelspeicher. Da der 8088 einen 8-Bit-Datenbus hatte, gab es diesen Nachteil nicht, aber das Laden eines 16-Bit Wortes auf 8086 dauerte 4 Taktzyklen, während der 8088 (zwei 8-Bit-Speicher) 8 Taktzyklen benötigte liest).

Schließlich, wenn Sie CS: IP explizit so festlegen möchten, dass CS bis zu dem Zeitpunkt, zu dem JMP abgeschlossen ist, richtig eingestellt ist Ihr Kernel), dann wird empfohlen, FAR JMP ( Siehe Operationen, die die Segmentregister beeinflussen / < em> FAR springen ). In der NASM-Syntax würde die JMP wie folgt aussehen:

%Vor%

Einige (zB MASM / MASM32) Assembler haben keine direkte Unterstützung um einen FAR JMP zu kodieren, so wie es manuell gemacht werden kann:

%Vor%

Wenn Sie GNU Assembler verwenden, sieht es so aus:

%Vor%     
Michael Petch 21.09.2015, 21:50
quelle