Teil 8 - Ein erstes Programm — Änderungen 8242 → 11394

Zeile 12: Zeile 12:
 
Wir gehen daher erst einmal wieder einen Schritt zurück und schreiben direkt in den Videospeicher. Wir werden das weiter unten dann ordentlich machen. Wir erstellen also zunächst einmal eine Datei  ''test.c'' in einem anderen Verzeichnis (die Makefile des Kernels sammelt alle .c-Dateien im Kernelverzeichnis und seinen Unterverzeichnissen auf, und das wollen wir ja gerade nicht):
 
Wir gehen daher erst einmal wieder einen Schritt zurück und schreiben direkt in den Videospeicher. Wir werden das weiter unten dann ordentlich machen. Wir erstellen also zunächst einmal eine Datei  ''test.c'' in einem anderen Verzeichnis (die Makefile des Kernels sammelt alle .c-Dateien im Kernelverzeichnis und seinen Unterverzeichnissen auf, und das wollen wir ja gerade nicht):
  
−
<c>#include <stdint.h>
+
<syntaxhighlight lang=c>#include <stdint.h>
  
 
uint16_t* videomem = (uint16_t*) 0xb8000;
 
uint16_t* videomem = (uint16_t*) 0xb8000;
Zeile 24: Zeile 24:
  
 
     while(1);
 
     while(1);
−
}</c>
+
}</syntaxhighlight>
  
 
Das Programm sollte also den String "012" ausgeben. Ans Ende setzen wir eine Endlosschleife, damit der Prozessor beim Ausführen nicht über das Ende des Programms hinausläuft und irgendwelches Zeug ausführt, das dort zufällig steht.
 
Das Programm sollte also den String "012" ausgeben. Ans Ende setzen wir eine Endlosschleife, damit der Prozessor beim Ausführen nicht über das Ende des Programms hinausläuft und irgendwelches Zeug ausführt, das dort zufällig steht.
Zeile 89: Zeile 89:
 
Mit dieser Information können wir jetzt ''init_multitasking'' umschreiben: Falls ein Multibootmodul übergeben worden ist, starten wir nicht die vier Testtasks, sondern dieses eine Modul. Der Bootloader legt uns dieses Modul allerdings einfach irgendwo hin, während das Programm davon ausgeht, an 2 MB geladen zu werden. Wir müssen es also erst einmal dorthin kopieren.
 
Mit dieser Information können wir jetzt ''init_multitasking'' umschreiben: Falls ein Multibootmodul übergeben worden ist, starten wir nicht die vier Testtasks, sondern dieses eine Modul. Der Bootloader legt uns dieses Modul allerdings einfach irgendwo hin, während das Programm davon ausgeht, an 2 MB geladen zu werden. Wir müssen es also erst einmal dorthin kopieren.
  
−
<c>void init_multitasking(struct multiboot_info* mb_info)
+
<syntaxhighlight lang=c>void init_multitasking(struct multiboot_info* mb_info)
 
{
 
{
 
     if (mb_info->mbs_mods_count == 0) {
 
     if (mb_info->mbs_mods_count == 0) {
Zeile 112: Zeile 112:
 
         init_task(load_addr);
 
         init_task(load_addr);
 
     }
 
     }
−
}</c>
+
}</syntaxhighlight>
  
 
Wichtig ist jetzt, dass die Speicherverwaltung die entsprechenden Speicherbereiche nicht schon für eine andere Verwendung hergegeben hat. Spätestens an dieser Stelle müssen wir ''pmm_init'' also entsprechend erweitern:
 
Wichtig ist jetzt, dass die Speicherverwaltung die entsprechenden Speicherbereiche nicht schon für eine andere Verwendung hergegeben hat. Spätestens an dieser Stelle müssen wir ''pmm_init'' also entsprechend erweitern:
  
−
<c>    ...
+
<syntaxhighlight lang=c>    ...
  
 
     /*
 
     /*
Zeile 137: Zeile 137:
 
         }
 
         }
 
     }
 
     }
−
}</c>
+
}</syntaxhighlight>
  
 
Das war's im Prinzip schon, jetzt müssen wir das Modul nur noch testen:
 
Das war's im Prinzip schon, jetzt müssen wir das Modul nur noch testen:
Zeile 148: Zeile 148:
 
Als Syscall benutzen wir den Interrupt 0x30 (das ist nach Exceptions und IRQs der erste freie). Falls noch nicht geschehen, muss dieser Interrupt in die IDT eingetragen und ein passender Interrupt-Stub angelegt werden. In ''handle_interrupt'' lassen wir für Interrupt 0x30 eine neue Funktion syscall aufrufen:
 
Als Syscall benutzen wir den Interrupt 0x30 (das ist nach Exceptions und IRQs der erste freie). Falls noch nicht geschehen, muss dieser Interrupt in die IDT eingetragen und ein passender Interrupt-Stub angelegt werden. In ''handle_interrupt'' lassen wir für Interrupt 0x30 eine neue Funktion syscall aufrufen:
  
−
<c>struct cpu_state* syscall(struct cpu_state* cpu)
+
<syntaxhighlight lang=c>struct cpu_state* syscall(struct cpu_state* cpu)
 
{
 
{
 
     switch (cpu->eax) {
 
     switch (cpu->eax) {
Zeile 157: Zeile 157:
  
 
     return cpu;
 
     return cpu;
−
}</c>
+
}</syntaxhighlight>
  
 
In eax sollen Aufrufer die Funktionsnummer übergeben (und als Funktion 0 definieren wir eine Funktion, die ein einzelnes Zeichen ausgibt) und die Parameter werden in den übrigen Registern übergeben. Stattdessen könnte man natürlich auch andere Aufrufkonventionen benutzen und Parameter z.B. über den Stack übergeben. Außerdem geben wir hier gleich von Anfang an einen neuen CPU-Zustand zurück, um Syscalls zu erlauben, die aktiv den aktuellen Task wechseln.
 
In eax sollen Aufrufer die Funktionsnummer übergeben (und als Funktion 0 definieren wir eine Funktion, die ein einzelnes Zeichen ausgibt) und die Parameter werden in den übrigen Registern übergeben. Stattdessen könnte man natürlich auch andere Aufrufkonventionen benutzen und Parameter z.B. über den Stack übergeben. Außerdem geben wir hier gleich von Anfang an einen neuen CPU-Zustand zurück, um Syscalls zu erlauben, die aktiv den aktuellen Task wechseln.
  
 
Das Programm muss natürlich auch noch entsprechend angepasst werden, den Syscall zu verwenden - der Aufruf sieht ungefähr aus wie in [[Teil_3_-_Trockenübungen|Teil 3]] für Linux. Um sicherzugehen, dass wir die neue Version sehen, ändern wir auch gleich noch die Anzahl der ausgegebenen Ziffern:
 
Das Programm muss natürlich auch noch entsprechend angepasst werden, den Syscall zu verwenden - der Aufruf sieht ungefähr aus wie in [[Teil_3_-_Trockenübungen|Teil 3]] für Linux. Um sicherzugehen, dass wir die neue Version sehen, ändern wir auch gleich noch die Anzahl der ausgegebenen Ziffern:
−
<c>for (i = 0; i < 5; i++) {
+
<syntaxhighlight lang=c>for (i = 0; i < 5; i++) {
 
     asm("int $0x30" : : "a" (0), "b" ('0' + i));
 
     asm("int $0x30" : : "a" (0), "b" ('0' + i));
−
}</c>
+
}</syntaxhighlight>
  
 
== ELF ==
 
== ELF ==
Zeile 195: Zeile 195:
 
Zunächst brauchen wir eine Headerdatei ''elf.h'', die uns die nötigen Datenstrukturen - den ELF-Header und die Program Header - zur Verfügung stellt:
 
Zunächst brauchen wir eine Headerdatei ''elf.h'', die uns die nötigen Datenstrukturen - den ELF-Header und die Program Header - zur Verfügung stellt:
  
−
<c>#ifndef ELF_H
+
<syntaxhighlight lang=c>#ifndef ELF_H
 
#define ELF_H
 
#define ELF_H
  
Zeile 230: Zeile 230:
 
} __attribute__((packed));
 
} __attribute__((packed));
  
−
#endif</c>
+
#endif</syntaxhighlight>
  
 
Damit lässt sich dann ein kleiner ELF-Loader bauen - er bringt die Datei irgendwie in den Speicher und startet einen neuen Task:
 
Damit lässt sich dann ein kleiner ELF-Loader bauen - er bringt die Datei irgendwie in den Speicher und startet einen neuen Task:
  
−
<c>void init_elf(void* image)
+
<syntaxhighlight lang=c>void init_elf(void* image)
 
{
 
{
 
     /*
 
     /*
Zeile 273: Zeile 273:
  
 
     init_task((void*) header->entry);
 
     init_task((void*) header->entry);
−
}</c>
+
}</syntaxhighlight>
  
 
Aus der Blickwinkel der Sicherheit ist er eine ziemliche Katastrophe (wie die FIXME-Kommentare zeigen), aber viel schlechter als der bisherige Loader für flache Binaries ist er auch nicht. Die Länge des Images zu prüfen ist einfach genug, dass ich es nicht weiter beschreiben muss, und wohin die Dateien geladen werden, ändert sich ohnehin wieder, sobald Paging ins Spiel kommt.
 
Aus der Blickwinkel der Sicherheit ist er eine ziemliche Katastrophe (wie die FIXME-Kommentare zeigen), aber viel schlechter als der bisherige Loader für flache Binaries ist er auch nicht. Die Länge des Images zu prüfen ist einfach genug, dass ich es nicht weiter beschreiben muss, und wohin die Dateien geladen werden, ändert sich ohnehin wieder, sobald Paging ins Spiel kommt.
  
 
In ''init_multitasking'' müssen wir die neue Funktion natürlich auch noch aufrufen:
 
In ''init_multitasking'' müssen wir die neue Funktion natürlich auch noch aufrufen:
−
<c>    ...
+
<syntaxhighlight lang=c>    ...
 
     } else {
 
     } else {
 
         /*
 
         /*
Zeile 287: Zeile 287:
 
         init_elf((void*) modules[0].mod_start);
 
         init_elf((void*) modules[0].mod_start);
 
     }
 
     }
−
     ...</c>
+
     ...</syntaxhighlight>
  
 
Damit haben wir einen Kernel, der eine ELF-Datei ausführen kann.
 
Damit haben wir einen Kernel, der eine ELF-Datei ausführen kann.