| Zeile 65: | Zeile 65: | ||
Und irgendwo in ''ClearScreen'' schmiert dann der Code ab, weil der Stack eben nach '''unten''' wächst und die GDT überschreibt. Also immer vorsichtig sein mit dem Stack, insbesondere wenn man irgendwann für jeden Prozess einen eigenen hat. | Und irgendwo in ''ClearScreen'' schmiert dann der Code ab, weil der Stack eben nach '''unten''' wächst und die GDT überschreibt. Also immer vorsichtig sein mit dem Stack, insbesondere wenn man irgendwann für jeden Prozess einen eigenen hat. | ||
| − | == | + | == Hardware == |
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
| − | |||
=== Defekter Sektor === | === Defekter Sektor === | ||
Es kann auch durchaus vorkommen das ein Sektor der Diskette, die zu zum Testen verwendest, defekt ist. Dieser Fehler ist besonders dann schwer zu finden, wenn man sein System primär auf realer Hardware testet. | Es kann auch durchaus vorkommen das ein Sektor der Diskette, die zu zum Testen verwendest, defekt ist. Dieser Fehler ist besonders dann schwer zu finden, wenn man sein System primär auf realer Hardware testet. | ||
== C-spezifische Fehler == | == C-spezifische Fehler == | ||
| − | |||
| − | |||
=== volatile === | === volatile === | ||
Der Compiler hat die angenehme Eigenschaft den Code um sinnlose Passagen zu erleichtern. Aber manchmal ist das eben nicht hilfreich, vor allem wenn man inline-Assembler verwendet, wird dieser oft wegoptimiert. Dagegen hilft das ''volatile'' Schlüsselwort. Es fordert den Compiler dazu auf, den entsprechenden Code nicht zu optimieren: | Der Compiler hat die angenehme Eigenschaft den Code um sinnlose Passagen zu erleichtern. Aber manchmal ist das eben nicht hilfreich, vor allem wenn man inline-Assembler verwendet, wird dieser oft wegoptimiert. Dagegen hilft das ''volatile'' Schlüsselwort. Es fordert den Compiler dazu auf, den entsprechenden Code nicht zu optimieren: | ||
| Zeile 118: | Zeile 94: | ||
</c> | </c> | ||
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)) === | ||
| + | Und wieder macht uns der Compiler ein schönes Codekonstrukt kaputt, weil er structs "optimiert". Folgender Code erstellt einen IDT-Eintrag: | ||
| + | <c> | ||
| + | VOID idt_set_entry(uint8 n, unit16 sel, void* handler, uint8 flags) | ||
| + | { | ||
| + | idt_entry[n].resreved = 0; | ||
| + | idt_entry[n].flags = flags; | ||
| + | idt_entry[n].sel = sel; | ||
| + | idt_entry[n].baseLo = (handler) & 0x0000FFFF; | ||
| + | idt_entry[n].baseHi = (handler) >> 16; | ||
| + | return; | ||
| + | } | ||
| + | </c> | ||
| + | Diese (fehlerfreie) Funktion greift dabei auf ein Array aus folgendem struct zu: | ||
| + | |||
| + | <c> | ||
| + | struct idt_entry | ||
| + | { | ||
| + | unit16 baseLo; | ||
| + | uint16 sel; | ||
| + | uint8 always0; //reserved | ||
| + | uint8 flags; | ||
| + | uint16 baseHi; | ||
| + | } | ||
| + | </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: | ||
| + | <c> | ||
| + | struct idt_entry | ||
| + | { | ||
| + | //... | ||
| + | }__attribute__((packed)); | ||
| + | </c> | ||
| + | Das angehängte ''__attribute__((packed))'' vehindert, dass der Compiler die Elemente ausrichtet, wodurch schließlich der Code funktioniert. | ||
=== Nur ein kleines Zeichen… === | === Nur ein kleines Zeichen… === | ||
| Zeile 125: | Zeile 134: | ||
==== „==“ ist ungleich „=“ ==== | ==== „==“ ist ungleich „=“ ==== | ||
| + | Es ist eigentlich ein totaler Anfängerfehler, in Bedingungen das Zuweisungszeichen '=' mit dem Vergleichsoperator '==' zu verwechseln. Also bitte aufpassen! | ||
| + | |||
==== Zahlensysteme ==== | ==== Zahlensysteme ==== | ||
Die Page Size Extension ist eine tolle Sache, also aktivieren wir sie doch mal. Dafür einfach in cr4 das 4. Bit setzen. Ganz simpel: | Die Page Size Extension ist eine tolle Sache, also aktivieren wir sie doch mal. Dafür einfach in cr4 das 4. Bit setzen. Ganz simpel: | ||
| Zeile 202: | Zeile 213: | ||
Falls nichts davon zutrifft, könntest du die Objektdatei, die die deiner Meinung nach entsprechende Funktion enthalten soll, disassemblieren und schauen, ob ein entsprechender Symbolname auftaucht. Alternativ kannst du dir auch mit dem Befehl ''readelf'' die Symbole der Objektdatei anzeigen lassen: | Falls nichts davon zutrifft, könntest du die Objektdatei, die die deiner Meinung nach entsprechende Funktion enthalten soll, disassemblieren und schauen, ob ein entsprechender Symbolname auftaucht. Alternativ kannst du dir auch mit dem Befehl ''readelf'' die Symbole der Objektdatei anzeigen lassen: | ||
readelf -s objectfile.o | readelf -s objectfile.o | ||
| + | |||
| + | === 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: | ||
| + | OUTPUT_FORMAT("binary") | ||
| + | INPUT("kernel.o") | ||
| + | ENTRY(_main) | ||
| + | SECTIONS { | ||
| + | .text : { | ||
| + | *(.text*) | ||
| + | } | ||
| + | .data : { | ||
| + | *(.data*) | ||
| + | } | ||
| + | .bss : { | ||
| + | *(.bss*) | ||
| + | } | ||
| + | } | ||
| + | </pre> | ||
| + | - die CPU verabschieded sich, da man die Ladeadresse auch im Linkerscript berücksichtigen muss (die Standartadresse ist 0x00, aber das stimmt ja hier nicht). Desshalb: | ||
| + | OUTPUT_FORMAT("binary") | ||
| + | INPUT("kernel.o") | ||
| + | ENTRY(_main) | ||
| + | SECTIONS { | ||
| + | .text (0x10000): { | ||
| + | *(.text*) | ||
| + | } | ||
| + | .data : { | ||
| + | *(.data*) | ||
| + | } | ||
| + | .bss : { | ||
| + | *(.bss*) | ||
| + | } | ||
| + | } | ||
| + | </pre> | ||
=== rodata steht am Anfang === | === rodata steht am Anfang === | ||
| − | + | Über folgenden Fehler stolpert man meistens gleich zu Beginn, wenn man im Kernel nur ein wenig Text ausgeben möchte. Nehmen wir beispielsweise folgenden kleinen Kernel: | |
| − | + | <c> | |
| − | + | void main() | |
| + | { | ||
| + | clrscr; | ||
| + | kprintf("Hallo Welt!"); | ||
| + | for(;;); | ||
| + | } | ||
| + | </c> | ||
| + | |||
| + | Der Kernel wird mit folgendem Linkerscript gelinkt: | ||
| + | <pre> | ||
| + | OUTPUT_FORMAT("binary") | ||
| + | INPUT("kernel.o") | ||
| + | ENTRY(_main) | ||
| + | SECTIONS | ||
| + | { | ||
| + | .text (0x10000): | ||
| + | { | ||
| + | *(.text*) | ||
| + | } | ||
| + | .data : | ||
| + | { | ||
| + | *(.data*) | ||
| + | } | ||
| + | .bss : | ||
| + | { | ||
| + | *(.bss*) | ||
| + | } | ||
| + | } | ||
| + | </pre> | ||
| + | |||
| + | 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 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 zu 'start.o', könnte so aussehen: | ||
| + | |||
| + | <asm> | ||
| + | extern _main ;das ist unser Einsprunglabel im eigentlichen Kernel | ||
| + | global start | ||
| + | |||
| + | start: | ||
| + | jmp _main | ||
| + | </asm> | ||
| + | |||
| + | Dann noch das Linkerscript modifizieren und fertig: | ||
| + | <pre> | ||
| + | OUTPUT_FORMAT("binary") | ||
| + | INPUT("kernel.o","start.o") | ||
| + | ENTRY(start) | ||
| + | SECTIONS | ||
| + | { | ||
| + | .text (0x10000): | ||
| + | { | ||
| + | *start.o(*) | ||
| + | *(.text*) | ||
| + | } | ||
| + | .data : | ||
| + | { | ||
| + | *(.data*) | ||
| + | } | ||
| + | .bss : | ||
| + | { | ||
| + | *(.bss*) | ||
| + | } | ||
| + | } | ||
| + | </pre> | ||