| Zeile 95: | Zeile 95: | ||
Num wird bei jedem Schleifendurchlauf der Wert von key_pressed neu geladen und getestet. | Num wird bei jedem Schleifendurchlauf der Wert von key_pressed neu geladen und getestet. | ||
=== __attribute__((packed)) === | === __attribute__((packed)) === | ||
| − | Und wieder macht uns der Compiler ein schönes Codekonstrukt kaputt, weil er structs | + | Und wieder macht uns der Compiler ein schönes Codekonstrukt kaputt, weil er structs „optimiert“. Folgender Code erstellt einen IDT-Eintrag: |
<c> | <c> | ||
| − | VOID idt_set_entry( | + | VOID idt_set_entry(uint8_t n, uint16_t sel, void* handler, uint8_t flags) |
{ | { | ||
idt_entry[n].resreved = 0; | idt_entry[n].resreved = 0; | ||
idt_entry[n].flags = flags; | idt_entry[n].flags = flags; | ||
idt_entry[n].sel = sel; | idt_entry[n].sel = sel; | ||
| − | idt_entry[n].baseLo = (handler) & 0x0000FFFF; | + | idt_entry[n].baseLo = (uintptr_t)(handler) & 0x0000FFFF; |
| − | idt_entry[n].baseHi = (handler) >> 16; | + | idt_entry[n].baseHi = (uintptr_t)(handler) >> 16; |
return; | return; | ||
} | } | ||
| Zeile 112: | Zeile 112: | ||
struct idt_entry | struct idt_entry | ||
{ | { | ||
| − | + | uint16_t baseLo; | |
| − | + | uint16_t sel; | |
| − | + | uint8_t always0; //reserved | |
| − | + | uint8_t flags; | |
| − | + | uint16_t baseHi; | |
| − | } | + | }; |
</c> | </c> | ||
| − | Und genau an dieser Stelle produziert der Compiler mit seiner Optimierung einen Fehler und die CPU wird sich mit einem freundlichen Triple-Fault verabschieden. Denn der Compiler hat die Eigenart, die Elemente eines structs an DWORD-Grenzen auszurichten, weil dann die CPU schneller arbeiten kann. Das hat in diesem Fall aber fatale Folgen, denn aus dem angegebenen WORDs/BYTEs macht der Compiler einfach mal DWORDs. Und damit ist natürlich der IDT-Eintrag komplett falsch. Glücklicherweise gibt es aber eine ganz einfache Lösung: | + | Und genau an dieser Stelle produziert der Compiler mit seiner Optimierung einen Fehler und die CPU wird sich mit einem freundlichen Triple-Fault verabschieden. Denn der Compiler hat die Eigenart, die Elemente eines structs an DWORD-Grenzen auszurichten (auf i386), weil dann die CPU schneller arbeiten kann. Das hat in diesem Fall aber fatale Folgen, denn aus dem angegebenen WORDs/BYTEs macht der Compiler einfach mal DWORDs. Und damit ist natürlich der IDT-Eintrag komplett falsch. Glücklicherweise gibt es aber eine ganz einfache Lösung: |
<c> | <c> | ||
struct idt_entry | struct idt_entry | ||
| Zeile 134: | Zeile 134: | ||
==== „==“ ist ungleich „=“ ==== | ==== „==“ ist ungleich „=“ ==== | ||
| − | Es ist eigentlich ein totaler Anfängerfehler, in Bedingungen das Zuweisungszeichen | + | Es ist eigentlich ein totaler Anfängerfehler, in Bedingungen das Zuweisungszeichen „=“ mit dem Vergleichsoperator „==“ zu verwechseln. Also bitte aufpassen! |
==== Zahlensysteme ==== | ==== Zahlensysteme ==== | ||
| Zeile 153: | Zeile 153: | ||
==== Wie ging nochmal dieses NOT?! ==== | ==== Wie ging nochmal dieses NOT?! ==== | ||
Ganz einfache Aufgabe: Alle Bits in einer Maske umkippen, also ein logisches NOT. Wie hieß nochmal der Operator? Achja: | Ganz einfache Aufgabe: Alle Bits in einer Maske umkippen, also ein logisches NOT. Wie hieß nochmal der Operator? Achja: | ||
| − | + | <c>int gekippt = !0xFA;</c> | |
Leider falsch geraten. Das Ausrufezeichen liefert nur Null (false) und Eins (true), egal welchen Operanden man ihm liefert. Zweiter Versuch: | Leider falsch geraten. Das Ausrufezeichen liefert nur Null (false) und Eins (true), egal welchen Operanden man ihm liefert. Zweiter Versuch: | ||
| − | + | <c>int gekippt = -0xFA;</c> | |
Nein, wieder falsch. Das Minuszeichen negiert den Wert doch nur, bildet also das Zweierkomplement. Richtig gewsen wäre: | Nein, wieder falsch. Das Minuszeichen negiert den Wert doch nur, bildet also das Zweierkomplement. Richtig gewsen wäre: | ||
| − | + | <c>int gekippt = ~0xFA;</c> | |
Diese Operation liefert das gewünschte 0x05. | Diese Operation liefert das gewünschte 0x05. | ||
| Zeile 215: | Zeile 215: | ||
=== Ladeadresse nicht berücksichtigt === | === Ladeadresse nicht berücksichtigt === | ||
| − | Ein kleiner aber gemeiner Fehler: Man hat einen schönen Kernel, der von dem genauso schönen Bootloader in den RAM geladen wird z.B. nach 0x10000. Dieser wird dann gelinkt: | + | Ein kleiner aber gemeiner Fehler: Man hat einen schönen Kernel, der von dem genauso schönen Bootloader in den RAM geladen wird, z. B. nach 0x10000. Dieser wird dann gelinkt: |
| + | <pre> | ||
OUTPUT_FORMAT("binary") | OUTPUT_FORMAT("binary") | ||
INPUT("kernel.o") | INPUT("kernel.o") | ||
| Zeile 231: | Zeile 232: | ||
} | } | ||
</pre> | </pre> | ||
| − | + | – die CPU verabschiedet sich, da man die Ladeadresse auch im Linkerscript berücksichtigen muss (die Standardadresse ist 0x00, aber das stimmt ja hier nicht). Deshalb: | |
| + | <pre> | ||
OUTPUT_FORMAT("binary") | OUTPUT_FORMAT("binary") | ||
INPUT("kernel.o") | INPUT("kernel.o") | ||
| Zeile 281: | Zeile 283: | ||
</pre> | </pre> | ||
| − | Funktioniert natürlich nicht. Interessanterweise führt das Ändern des Textes zu ganz anderen Fehlern. Die Begründung ist simpel: Der Text ( | + | Funktioniert natürlich nicht. Interessanterweise führt das Ändern des Textes zu ganz anderen Fehlern. Die Begründung ist simpel: Der Text („Hallo Welt!“) landet nicht etwa in der .data-Sektion sondern in der sogenannten .rodata-Sektion. Und da wir diese hier nicht im Linkerscript angeben, wird sie einfach an den Anfang der Ausgabedatei gesetzt. Das führt natürlich zwangsweise zu Fehlern, weil ein Text ja kein Code ist. Es gibt nun zwei Möglichkeiten: |
# Man ergänzt das Linkerscript um die .rodata-Sektion ''oder noch besser'' | # Man ergänzt das Linkerscript um die .rodata-Sektion ''oder noch besser'' | ||
| − | # Man schaltet eine Assemblerdatei vor den eigentlichen Kernel, die zum Label | + | # Man schaltet eine Assemblerdatei vor den eigentlichen Kernel, die zum Label main springt. Da man irgendann sowieseo Assembler benötigt, bietet es sich an, das gleich hiermit zu erledigen. |
| − | Diese Assembler-Datei, die dann assembliert wird | + | Diese Assembler-Datei, die dann zu „start.o“ assembliert wird, könnte so aussehen: |
<asm> | <asm> | ||
| − | extern | + | extern main ;das ist unser Einsprunglabel im eigentlichen Kernel |
global start | global start | ||
start: | start: | ||
| − | jmp | + | jmp main |
</asm> | </asm> | ||