Lowlevel

Lowlevel => OS-Design => Thema gestartet von: ehenkes am 26. April 2009, 19:38

Titel: Programm laden und ausführen
Beitrag von: ehenkes am 26. April 2009, 19:38
Ich habe ein Task-Modul und eine RAM Disk. Wenn ich dort nun eine ausführbare Datei habe, z.B. im COM-Format o.ä., was ist dann der einfachste Weg dieses "Programm" laufen zu lassen? Gibt es dazu ein Tutorial, habe ich bisher nicht gesehen? 
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 26. April 2009, 20:02
Ich gehe einmal davon aus, daß Multitasking schon funktioniert, d.h. die obligatorischen zwei Kerneltasks, die "a" und "b" ausgeben, laufen soweit. Wenn nein, wäre das erstmal ein erster Schritt.

Bei .com-Dateien von DOS ist der Rest einfach: Das sind flache Binaries, sobald sie im Speicher liegen, muß man also nur noch einen Task erstellen, die Segmentregister für den Task richtig setzen und eip auf das erste Byte der Datei zeigen lassen. Das eigentliche Reinspringen in den Code sollte dann der Interrupthandler übernehmen, der das auch schon bei den Kerneltasks gemacht hat.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 26. April 2009, 20:18
Danke für den Hinweis. Einen laufenden Scheduler habe ich noch nicht, da gibt es noch GPF (siehe anderen Thread). Aber das Wechseln von Tasks (an der richtigen Stelle in einer while-Schleife) klappt schon gut (ge"forkte" Kernel-Tasks, da nehme ich die PID als "Farbgeber" mittels setcolor(PID,0) beim Auslesen der KeyQueue :-D )
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 16. May 2009, 01:13
Nachdem Multitasking nun läuft: wo bekommt man ein kleines Beispiel-Programm her? Das könnte man ja über den incbin-Trick ins Memory linken und mit memcpy an eine ausgewählte Stelle senden und dort starten.
Nachdem ich diesen Kernel-Stack-Mechanismus und das Setzen von esp und eip hoffentlich verstanden habe, wie führt tyndur ein Programm aus?
Titel: Re: Programm laden und ausführen
Beitrag von: bluecode am 16. May 2009, 11:24
Nachdem Multitasking nun läuft: wo bekommt man ein kleines Beispiel-Programm her? Das könnte man ja über den incbin-Trick ins Memory linken und mit memcpy an eine ausgewählte Stelle senden und dort starten.
Das wäre ein erster Anfang. Macht aber nach ein paar Minuten nichtmehr viel Spaß. :-D


Zitat
Nachdem ich diesen Kernel-Stack-Mechanismus und das Setzen von esp und eip hoffentlich verstanden habe, wie führt tyndur ein Programm aus?
Die ersten Programme/Treiber (tyndur & lightOS sind Mikrokernel) werden von Grub in den RAM geladen. Grub übergibt einem nach dem Laden eine Tabelle mit (unter anderem) den Startadressen, Längen und Namen der geladenen Module. Die Module sind bei beiden Kerneln ELF-Dateien. Für jedes dieser Programme wird nach dem initialisieren des Kernels ein Prozess mit einem Thread erstellt. Dann muss vielleicht noch ein bisschen ruminitialisiert werden, aber anschließend wird zum ersten Thread geswitcht und damit das ges. Multitasking angeschoben.

ELF hat seine Spezifkation zB hier gelinkt (http://en.wikipedia.org/wiki/Executable_and_Linkable_Format). Auch für PE/COFF gibt es Spezifikationen, nämlich hier (http://www.microsoft.com/whdc/system/platform/firmware/PECOFF.mspx).
Unser Wiki hat auch einen Artikel zu ELF (http://lowlevel.brainsware.org/wiki/index.php/Executable_and_Linking_Format) und ein bisschen was zu PE (http://lowlevel.brainsware.org/wiki/index.php/PE).
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 16. May 2009, 12:59
Zitat
Das wäre ein erster Anfang.
Das würde ich gerne erstmal aufgreifen. Spaß macht zunächst das, was man plant, umsetzt und dann auch wirklich so oder ähnlich funktioniert. Daher würde ich diesen "winzigen" Schritt gehen. Ich habe mir Folgendes überlegt. In kernel.map erhalte ich ja bereits einige Einsprünge für im Kernel bereits enthaltene Funktionen. Ein einfaches Beispiel ist settextcolor(Schriftfarbe, Hintergrundfarbe), das laut kernel.map wie folgt liegt:
0x00008614                _settextcolorIch habe also mit lb 0x00008614 einen Breakpoint gesetzt und mir das in Bochs Debugger mit s 1 angeschaut:
Zitat
Next at t=145559835 (0) [0x00008614] 0008:0000000000008614 (unk. ctxt): push ebp                  ;55
Next at t=145559836 (0) [0x00008615] 0008:0000000000008615 (unk. ctxt): mov ebp, esp              ;89e5
Next at t=145559837 (0) [0x00008617] 0008:0000000000008617 (unk. ctxt): mov al, byte ptr ss:[ebp+0xc] ; 8a450c
Next at t=145559838 (0) [0x0000861a] 0008:000000000000861a (unk. ctxt): shl eax, 0x04             ;c1e004
Next at t=145559839 (0) [0x0000861d] 0008:000000000000861d (unk. ctxt): mov dl, byte ptr ss:[ebp+0x8] ; 8a5508
Next at t=145559840 (0) [0x00008620] 0008:0000000000008620 (unk. ctxt): and edx, 0x0000000f       ;83e20f
Next at t=145559841 (0) [0x00008623] 0008:0000000000008623 (unk. ctxt): or eax, edx               ;09d0
Next at t=145559842 (0) [0x00008625] 0008:0000000000008625 (unk. ctxt): mov byte ptr ds:0xcbf4, al ; a2f4cb0000
Next at t=145559843 (0) [0x0000862a] 0008:000000000000862a (unk. ctxt): pop ebp                   ;5d
Next at t=145559844 (0) [0x0000862b] 0008:000000000000862b (unk. ctxt): ret                       ;c3

mit objdump erhalte ich folgenden Code:
00000044 <_settextcolor>:
  44: 55                    push   %ebp
  45: 89 e5                mov    %esp,%ebp
  47: 8a 45 0c              mov    0xc(%ebp),%al
  4a: c1 e0 04              shl    $0x4,%eax
  4d: 8a 55 08              mov    0x8(%ebp),%dl
  50: 83 e2 0f              and    $0xf,%edx
  53: 09 d0                or     %edx,%eax
  55: a2 54 05 00 00        mov    %al,0x554
  5a: 5d                    pop    %ebp
  5b: c3                    ret   
Wäre dies ein geeignetes überschaubares "ausführbares binäres Programm", das man als weitere Tasks mit verschiedenen Farben konfigurieren und zwischen den anderen Tasks, die Texte ausgeben, ausführen könnte. Hierbei müsste man jeweils die beiden Argumente für Vordergrund- und Hintergrund-Farbe auf dem Stack anbieten.
Worauf muss man bei create_task hierbei achten? So sieht dies momentan aus:
void create_task(void* entry)
{
    page_directory_t* directory = clone_directory(current_directory);
    task_t* new_task            = (task_t*)k_malloc(sizeof(task_t),0,0);

    new_task->id  = next_pid++;
    new_task->page_directory = directory;
    new_task->kernel_stack   = k_malloc(KERNEL_STACK_SIZE,1,0)+KERNEL_STACK_SIZE;
    new_task->next = 0;

    task_t* tmp_task = (task_t*)ready_queue;
    while (tmp_task->next)
        tmp_task = tmp_task->next;
    tmp_task->next = new_task; // ... and extend it

    ULONG* kernel_stack = (ULONG*) new_task->kernel_stack;

    //*(--kernel_stack) = 0x23; // ss
    //*(--kernel_stack) = USER_STACK_VIRT + USER_STACK_SIZE - 0x4;

    *(--kernel_stack) = 0x0202; // eflags = interrupts aktiviert und iopl = 0
    *(--kernel_stack) = 0x08;   // cs
    *(--kernel_stack) = (ULONG)entry; // eip

    *(--kernel_stack) = 0; // interrupt nummer
    *(--kernel_stack) = 0; // error code

    // general purpose registers
    //*(--kernel_stack) = 0; // eins von denen ist esp
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;

    // segment registers
    *(--kernel_stack) = 0x10;
    *(--kernel_stack) = 0x10;
    *(--kernel_stack) = 0x10;
    *(--kernel_stack) = 0x10;

    new_task->ebp = 0xd00fc0de;
    new_task->esp = (ULONG)kernel_stack;
    new_task->eip = (ULONG)irq_tail;
}
Wo packt man da die beiden Argumente für die Farben hin?

Titel: Re: Programm laden und ausführen
Beitrag von: bluecode am 16. May 2009, 13:04
Zitat
Zitat
Das wäre ein erster Anfang.
Das würde ich gerne erstmal aufgreifen. Spaß macht zunächst das, was man plant, umsetzt und dann auch wirklich so oder ähnlich funktioniert. Daher würde ich diesen "winzigen" Schritt gehen.
Jo klar, das war auch mein erster Schritt. :wink:  Ich nehme an in tyndur war das auch nicht anders.


ganz am Anfang, also vor
    *(--kernel_stack) = 0x0202; // eflags = interrupts aktiviert und iopl = 0
Bei dem ret der Funktion (settextcolor) wirst du allerdings sehr wahrscheinlich eine Exception kriegen, da die Rücksprungadresse ja nicht auf dem Stack liegt.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 16. May 2009, 13:57
Zitat
Bei dem ret der Funktion (settextcolor) wirst du allerdings sehr wahrscheinlich eine Exception kriegen, da die Rücksprungadresse ja nicht auf dem Stack liegt.
Ich habe folgendes gemacht:
void create_task2(void* entry, ULONG arg1, ULONG arg2)
{
    //...
    *(--kernel_stack) = arg2; // arg2
    *(--kernel_stack) = arg1; // arg1

    *(--kernel_stack) = 0x0202; // eflags = interrupts aktiviert und iopl = 0
    //...
}

vor main:
void moo()
{
  while(TRUE)
  {
      settextcolor(2,0);
      printformat("MOO %d", getpid()); // <-- kuh
      settextcolor(15,0);
  }
}

void baa()
{
  while(TRUE)
  {
      settextcolor(4,0);
      printformat("BAA %d", getpid()); // <- schaf
      settextcolor(15,0);
  }
}

void color(ULONG a, ULONG b)
{
    settextcolor(a,b);
    printformat("%d", getpid()); // <- Farbe
    settextcolor(15,0);
}

in main:
create_task(moo);
create_task(baa);
create_task2(color, 5,2);

ergibt folgendes:
task baa und task moo geben aus, dann schaltet die Hintergrundfarbe auf Türkis(5) um (wahrscheinlich vorne/hinten falsch übergeben?), man sieht eine Ausgabe (PID: grüne 4 auf türkis Hintergrund), dann page fault.

Wie bekomme ich die richtige Rücksprungadresse auf den Stack? So ga z verstehe ich das aber nicht, bei baa und moo (siehe oben) klappt das doch auch.  :?
 
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 16. May 2009, 14:03
Sorry, hatte das while(1) vergessen, dann klappt das auch mit der Übergabe von 2 Parametern:
void color(ULONG a, ULONG b)
{
  while(TRUE)
  {
    settextcolor(a,b);
    printformat("%d", getpid()); // <- Farbe
    settextcolor(15,0);
  }
}

Zitat
OO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO1111111111111111111111111111111111111111111
OO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2M
OO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2M
OO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2M3BAA 3BAA 3BAA 3BAA 3BAA 3BAA
3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA
3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA
3BAA 444444444444444444444444444444444444444444444444444444444444444444444444444
44444444444444444444444444444444444444444444444444444444444444444444444444444444
44444444444444444444444444444444444444444444444
                                               111111111111111111111111111111111
11111111111111111111111111                     111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
Farben vorne hinten sind ja erst mal egal.  :-D
So ist die Reihenfolge
    *(--kernel_stack) = arg1; // arg1
    *(--kernel_stack) = arg2; // arg2
richtig. (... beim Abholen will C das von hinten nach vorne)
Die Hintergrundfarbe, also Argument2 kommt allerdings nicht an, es ist immer türkis.  :? Das verstehe ich nicht.

Das war sozusagen ein erster Schritt auf das settextcolor zu. Das ist im Prinzip wie bei syscalls, wo man Makros schreibt für syscalls mit verschidener Anzahl an Argumenten.

Kann man da eine create_task schreiben für eine variable Anzahl an Argumenten hinter entry, also von 0 bis n?

James Molloy hat das in seinem Tutorial zu syscalls (chapter 10) ziemlich kompliziert gemacht:

#define DECL_SYSCALL0(fn) int syscall_##fn();
#define DECL_SYSCALL1(fn,p1) int syscall_##fn(p1);
#define DECL_SYSCALL2(fn,p1,p2) int syscall_##fn(p1,p2);
#define DECL_SYSCALL3(fn,p1,p2,p3) int syscall_##fn(p1,p2,p3);
#define DECL_SYSCALL4(fn,p1,p2,p3,p4) int syscall_##fn(p1,p2,p3,p4);
#define DECL_SYSCALL5(fn,p1,p2,p3,p4,p5) int syscall_##fn(p1,p2,p3,p4,p5);

#define DEFN_SYSCALL0(fn, num) \
int syscall_##fn() \
{ \
 int a; \
 asm volatile("int $0x80" : "=a" (a) : "0" (num)); \
 return a; \
}
etc.

Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 16. May 2009, 14:36
Obwohl ich das mit der falschen Hintergundfarbe noch nicht verstanden habe, machte ich den nächsten Schritt mit der Folge einer GPF:

kernel.map: 0x00008664  _settextcolor

    create_task(moo);
    create_task(baa);
    create_task2(0x00008664,4,2); //stimmt das so?
Jetzt dürften wir bei deiner Vorhersage sein. Wie kann ich die GPF umgehen? Denn wenn ich das so nicht schaffe, wie soll ich dann Programme ausführen?


Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 16. May 2009, 15:22
Wie kann ich die GPF umgehen? Denn wenn ich das so nicht schaffe, wie soll ich dann Programme ausführen?
Einfach nicht die Funktionen als Task aufrufen. Die Funktion weiß nichts davon, dass du die missbrauchst. Außerdem solltest du kein "ret" an dieser Stelle haben, sondern einen Systemaufruf, um den Prozess zu beenden.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 16. May 2009, 15:37
Zitat
Die Funktion weiß nichts davon, dass du die missbrauchst.
Von diesem Vorwurf distanziere ich mich ausdrücklich, denn es ist Aufgabe einer Funktion zu funktionieren.  :-D :-D :-D

Mir gefällt gerade auf, dass das vorher gehende Beispiel auch nur durch die while-Schleife rund um settextcolor(...) geklappt hat.  :roll:

Also mache ich das wie folgt:
void moo(){...}
void baa() {...}

void surprise(ULONG a, ULONG b) // die Parameter werden nicht von settextcolor verwendet??
{
  while(TRUE)  {printformat("%d", getpid()); settextcolor(15,0);}
}

int main()
{
   //...
    create_task(moo);
    create_task(baa);
    create_task2(surprise,4,2);
   //...
    while(TRUE) {settextcolor(15,0); printformat("%d", getpid());}
    return 0;
}
... und das läuft zumindest, allerdings, ohne dass die "missbrauchte" Funktion ihre Parameter verwendet. Wie schaffe ich das?  :evil:

Ach quatsch jetzt ist die Addresse ja völlig weg.  :-D

So gibt es wieder einen GPF:
void surprise(ULONG a, ULONG b)
{
  while(TRUE)
  {
      asm("jmp 0x00008664");
      printformat("%d", getpid());
      settextcolor(15,0);
  }
}
Wie kann ich settextcolor verwenden, ohne es aufzurufen?
Geht das irgendwie mit Funktions-Pointer und der Addresse (da bin ich ganz schwach)?

Wenn es mit jmp nicht geht, nimmt man eben call:
void surprise(ULONG a, ULONG b)
{
  while(TRUE)
  {
      asm("call 0x00008664");
      printformat("%d", getpid());
      settextcolor(15,0);
  }
}
Dann passiert etwas ganz Merkwürdiges: es läuft (siehe unten Ausdruck mittels copy in Bochs), aber Vorder- und Hintergrundfarbe sind beide schwarz, also beide Parameter leider noch 0. Gibt es da eine Hilfe, dass die Funktion weiß, dass sie da was abzuholen hat?  :evil:
Zitat
2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 444444444444444444444444444444444444444444444444444444444444444444444444444
44444444444444444444444444444444444444444444444444444444444444444444444444444444
44444444444444444444444444444444444444444444411111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
Na, da müssen wir die Parameter wohl erneut pushen?  :?
void surprise(ULONG a, ULONG b)
{
  while(TRUE)
  {
      asm volatile("push %0" : "=m" (b));
      asm volatile("push %0" : "=m" (a));
      asm volatile("call 0x00008674");
      printformat("%d", getpid());
      settextcolor(15,0);
  }
}
(volatile kann man auch weg lassen)
Das funktioniert sogar, aber nur kurz:
Zitat
2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO
 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MO  2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO
 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA
 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA
 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA 3BAA444444444444444444444444444444444444444444444
44444444444444444444444444444444444444444444444444444444444444444444444444444444
44444444444444444444444444444444444444444444444444444444444444444444444444444444
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
111111111111
             2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2M
OO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2M
OO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2MOO 2

TSS log:
esp0: 4010A000h ss0: 00000010h cr3: 00000000h eip: 00000000h eflags: 00000000h e
ax: 00000000h ecx: 00000000h edx: 00000000h ebx: 00000000h esp: 401096BCh ebp: 0
0000000h esi: 00000000h edi: 00000000h es: 00000010h cs: 00000008h ss: 00000010h
 ds: 00000010h fs: 00000010h gs: 00000010h

err_code: 00000034h address(eip): 0000849Bh
edi: 000084B0h esi: 00000005h ebp: 401056FCh eax: 401096BCh ebx: 000007BBh ecx:
000B8000h edx: 00000020h
cs: 8 ds: 16 es: 16 fs: 35031 gs 38620 ss 35031
int_no 13 eflags 00010006h useresp 00000034h

General Protection Fault >>> Exception. System Halted! <<<
:| Ist mir schon klar, dass das alles ziemlich wild ist, aber der Übergang 4->1 hat sogar geklappt. Seltsam, dass es nun ausgerechnet bei 2->3 nicht klappt. Beim ersten Durchgang geht dies problemlos, erst nach dem Einschalten dieser Überraschungsfunktion kommt der Tasswitch-Mechanismus aus dem Takt. Da staune ich jetzt wirklich. Ihr habt da sicher eine Idee.  :-)

Bei einem weiteren Versuch wieder Abbruch zwischen task 2 und 3, wie man sieht allerdings mit anderen Werten im Stack:
Zitat
2MOO 2MOO 2MOO 2MOO 2MO
TSS log:
esp0: 4010A000h ss0: 00000010h cr3: 00000000h eip: 00000000h eflags: 00000000h eax: 00000000h ecx: 00000000h edx: 00000000h ebx: 00000000h esp: 401096C0h ebp: 00000000h esi: 00000000h edi: 00000000h es: 00000010h cs: 00000008h ss: 00000010h ds: 00000010h fs: 00000010h gs: 00000010h

err_code: 0000D7F4h address(eip): 00008499h edi: 000084B0h esi: 00000220h ebp: 401056FCh eax: 401096C0h ebx: 00000AC7h ecx:
000B84D9h edx: 00000020h cs: 8 ds: 16 es: 16 fs: 16 gs 0 ss 34135
int_no 13 eflags 00010016h useresp 4010D7F4h

General Protection Fault >>> Exception. System Halted! <<<
Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 16. May 2009, 17:27
Zitat
Bei dem ret der Funktion (settextcolor) wirst du allerdings sehr wahrscheinlich eine Exception kriegen, da die Rücksprungadresse ja nicht auf dem Stack liegt.
Ich habe folgendes gemacht:
void create_task2(void* entry, ULONG arg1, ULONG arg2)
{
    //...
    *(--kernel_stack) = arg2; // arg2
    *(--kernel_stack) = arg1; // arg1

    *(--kernel_stack) = 0x0202; // eflags = interrupts aktiviert und iopl = 0
    //...
}
Eine C-Funktion erwartet vor den Argumenten noch die Rücksprungadresse auf dem Stack. Wenn die Funktion nicht zurückkehrt, kann das natürlich ein beliebiger Wert sein.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 16. May 2009, 18:49
Bei call wird die Rücksprungadresse doch automatisch auf dem Stack abgelegt?

kernel.map:
0x00008330   _surprise
0x00008484   _settextcolor

void surprise(ULONG a, ULONG b)
{
  while(TRUE)
  {
      asm ("push %0" : "=m" (b));
      asm ("push %0" : "=m" (a));
      asm ("call 0x00008484");
      printformat("%d", getpid());
      settextcolor(15,0);
  }
}

Normaler Aufruf der Funktion settextcolor(15,0) in task1:
Zitat
| STACK 0x001fffdc [0x00008423]
 | STACK 0x001fffe0 [0x0000000f]
 | STACK 0x001fffe4 [0x00000000]

Normaler Aufruf der Funktion settextcolor(15,0) in task2:
Zitat
| STACK 0x401057e0 [0x000082e9]
 | STACK 0x401057e4 [0x0000000f]
 | STACK 0x401057e8 [0x00000000]

Normaler Aufruf der Funktion settextcolor(4,0) in task3:
Zitat
| STACK 0x401097e0 [0x00008308]
 | STACK 0x401097e4 [0x00000004]
 | STACK 0x401097e8 [0x00000000]

Aufruf der Funktion settextcolor(...) in surprise über asm ("call 0x00008484"); in task 4:
Zitat
| STACK 0x4010d7b0 [0x00008341]
 | STACK 0x4010d7b4 [0x00000004]
 | STACK 0x4010d7b8 [0x123890ab]
 | STACK 0x4010d7bc [0x00000004]
 ... immer abwechselnd so weiter ...
 | STACK 0x4010d7ec [0x00008330]
Hier sieht man nur türkis, die Zahl 4 sieht man nicht.

Zitat
| STACK 0x4010d670 [0x00008363]
 | STACK 0x4010d674 [0x0000000f]
 | STACK 0x4010d678 [0x00000000]
Hier sieht man türkis Hintergrund und in rot die Zahl 4.
Das ist die Umschaltung auf  weiß/schwarz nach Ausgabe der Zahl:
printformat("%d", getpid()); settextcolor(15,0); innerhalb surpirse(...)

Hier gibt es also zwei Zustände. Der hintere ist o.k., der erste nicht. 
Da stört dieses 0x123890ab an der Stelle der Hintergrundfarbe.
Nun verstehe ich auch den Blink-Vorgang der Zahl 4.  :-D

Beim zweiten Durchgang durch Task 2 ist alles o.k.:
Umschalten auf Grün:
Zitat
| STACK 0x401057e0 [0x000082ca]
 | STACK 0x401057e4 [0x00000002]
 | STACK 0x401057e8 [0x00000000]
Umschalten auf weiß:
Zitat
| STACK 0x401057e0 [0x000082e9]
 | STACK 0x401057e4 [0x0000000f]
 | STACK 0x401057e8 [0x00000000]

Plötzlich kommt zwischen 2 und 3 etwas task 4 dazwischen!  :? Chaos :?
Task 3 läuft dann aber weiter und schaltet dann auch wieder auf 4 mit den zwei Zuständen:
falsch:
Zitat
| STACK 0x4010ca08 [0x00008341]
 | STACK 0x4010ca0c [0x00000004]
 | STACK 0x4010ca10 [0x123890ab]
richtig:
Zitat
| STACK 0x4010c9f8 [0x00008363]
 | STACK 0x4010c9fc [0x0000000f]
 | STACK 0x4010ca00 [0x00000000]
Na immerhin liegt ein while(1) zwischen den Parametern und meinem Push-Vorgang:

void surprise(ULONG a, ULONG b)
{
  while(TRUE)
  {

      asm ("push %0" : "=m" (b));
      asm ("push %0" : "=m" (a));

So geht das mehrere Durchläufe weiter. Es erfolgt kein Absturz.
Ich breche den Debug-Vorgang ab.

Lasse ich es normal laufen, kommt es genau bis 1-2-3-4-1-2-GPF.
Hat also auch etwas mit Geschwindigkeit zu tun, also dynamische Prozesse.

Kannst Du mir einen Tipp geben, wie die beiden Parameter sicher die while-Schleife überleben?
Was ist dieses 0x123890ab? Sieht "magic" aus. Ich habe gesucht:
#define HEAP_MAGIC  0x123890ABDie Kernel-Stacks befinden sich ja auf dem Heap. Liegt daran, dass k_malloc Platz auf dem Heap sucht, sobald einer da ist (Erbe von James Molloy's Code). Ich habe nach diesem Wert in kheap.h/kheap.c gesucht:
Die Zahl wird dort als magische Zahl in Header/Footer von Holes verwendet.
Es wird im Heap-Code als Sanity-Check verwendet:
    // Sanity checks.
    ASSERT(header->magic == HEAP_MAGIC);
    ASSERT(footer->magic == HEAP_MAGIC);
Da kommt also in task 4 etwas quer gehuscht.  :-o
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 16. May 2009, 19:18
Es lag natürlich mal wieder an meinen gigantischen Fähigkeiten bezüglich AT&T inline Assembler Syntax. Wer hier auch Probleme hat, dies ist die richtige Stelle zum Lesen: http://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html#s3

Die Grenze zur while-Schleife hatte ich auch noch falsch gezogen. So läuft es erstmal durch wie gewollt, also ohne GPF in der zweiten Runde.
Aber unten das Debugging zeigt, dass da noch dieses üble Grundproblem besteht:

void surprise(ULONG a, ULONG b)
{
  asm volatile ("push %0" :/* no output registers */: "r" (b));
  asm volatile ("push %0" :/* no output registers */: "r" (a));
  while(TRUE)
  {
      asm volatile ("call 0x00008484");
      printformat("%d", getpid());
      settextcolor(15,0);
  }
}
Aber alles hat sein Gutes. Der Debug-Prozess oben hat mir mehr Einsichten in die Abläufe auf dem Stack rund um C-Funktionen und Parameter gegeben.

Nimmt man besser "r" oder "m"? Es geht beides.

0x00008484  _settextcolor
Debugger in task 4:

schlechter Zustand: magic heap number (k.A. wo die da herkommt) geistert in Stack herum und verdrängt die Hintergrundfarbe. Rücksprungadresse und Vordergrundfarbe (rot=4) sind vorhanden.
| STACK 0x4010d7e0 [0x00008343]
 | STACK 0x4010d7e4 [0x00000004]
 | STACK 0x4010d7e8 [0x123890ab]
 | STACK 0x4010d7ec [0x00008330]
 | STACK 0x4010d7f0 [0x00000008]
 | STACK 0x4010d7f4 [0x00000000]
 | STACK 0x4010d7f8 [0x00000002]
 | STACK 0x4010d7fc [0x00000004]
 | STACK 0x4010d800 [0x123890ab]
 | STACK 0x4010d804 [0x4010cff4]
 | STACK 0x4010d808 [0x123890ab]
 | STACK 0x4010d80c [0x00000001]
 | STACK 0x4010d810 [0x000007f8]
 | STACK 0x4010d814 [0x00000000]
 | STACK 0x4010d818 [0x00000000]
 | STACK 0x4010d81c [0x00000000]


Hier ist o.k.: Rücksprungadresse, Vordergundfarbe, Hintergrundfarbe
| STACK 0x4010d7d0 [0x00008365]
 | STACK 0x4010d7d4 [0x0000000f]
 | STACK 0x4010d7d8 [0x00000000]
 | STACK 0x4010d7dc [0x4010d7f4]
 | STACK 0x4010d7e0 [0x00008343]
 | STACK 0x4010d7e4 [0x00000004]
 | STACK 0x4010d7e8 [0x123890ab]
 | STACK 0x4010d7ec [0x00008330]
 | STACK 0x4010d7f0 [0x00000008]
 | STACK 0x4010d7f4 [0x00000000]
 | STACK 0x4010d7f8 [0x00000002]
 | STACK 0x4010d7fc [0x00000004]
 | STACK 0x4010d800 [0x123890ab]
 | STACK 0x4010d804 [0x4010cff4]
 | STACK 0x4010d808 [0x123890ab]
 | STACK 0x4010d80c [0x00000001]
Aber schon mal ein netter Anfang.

So sieht mein Log der Task-List aus:
id: 1 ebp: 00000000h esp: 00000000h eip: 00000000h PD: 00000000h k_stack: 40101800h next: 40101814h
id: 2 ebp: D00FC0DEh esp: 401057C0h eip: 00008299h PD: 40102000h k_stack: 40105800h next: 40101844h
id: 3 ebp: D00FC0DEh esp: 401097C0h eip: 00008299h PD: 40106000h k_stack: 40109800h next: 40101874h
id: 4 ebp: D00FC0DEh esp: 4010D7B8h eip: 00008299h PD: 4010A000h k_stack: 4010D800h next: 00000000h
Jede Task hat einen Kernel Stack und eine eigene Page Directory.

Den eip 8299h verstehe ich nicht.

                0x000082f6                _baa
                0x00008330                _surprise
                0x000082b8                _moo

Der "D00FC0DE" hat sich also auch irgendwo manifestiert.  :-D
Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 16. May 2009, 20:03
Bei call wird die Rücksprungadresse doch automatisch auf dem Stack abgelegt?
Ja, aber task_create macht keinen call. Wenn der Task das erste Mal aktiv wird, dann wird der in task_create erstellte Stack abgebaut, und im Laufe dessen landet der Kontrollfluss am Anfang der Funktion surprise.

Zu diesem Zeitpunkt (so die C-Aufrufkonvention) muss oben auf dem Stack die Rücksprungadresse liegen und darunter die Parameter.

In dem jetzigen Zustand wird allerdings arg1 von der Funktion für die Rücksprungadresse gehalten, a ist arg2, und b liegt in einem nicht definitierten Stackbereich (bzw. wäre "arg3"). Wenn du task_create2(surprise, Vordergrundfarbe, Hintergrundfarbe) aufrufst, dann landet in a also übergebene Hintergrund-Farbe.

Wo kommt jetzt also die 0x123890ab her: Als du mit malloc() den Stack angelegt hast, hat diese Funktion den Wert 0x123890ab an die Stelle geschrieben, wo deine surprise-Funktion den Parameter b erwartet hat.

Das hast du bereits früh gemerkt, aber nicht den korrekten Schluss gezogen:
dann schaltet die Hintergrundfarbe auf Türkis(5) um (wahrscheinlich vorne/hinten falsch übergeben?)
Türkis ist nicht 5 sondern 0x0b, und zwar genau das 0xb von 0x123890ab.

Direkt prüfen kannst du das, indem du einfach mal a und b ausgibst.

Deine beiden Stacks aus dem letzten Post sind übrigens derselbe zu unterschiedlichen Zeitpunkten.

Warum rufst du eigentlich nicht einfach settextcolor(a, b) auf, sondern bastelst am Stack rum und jonglierst mit Konstanten?
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 17. May 2009, 01:01
Zitat
Zu diesem Zeitpunkt (so die C-Aufrufkonvention) muss oben auf dem Stack die Rücksprungadresse liegen und darunter die Parameter.
"Oben und unten" habe ich verdreht. Dieses Bild muss man sich bezüglich des Codes in create_task auf dem Kopf vorstellen:
http://www.a-m-i.de/tips/stack/stackframe.jpg

Also es funktioniert jetzt so:
    *(--kernel_stack) = arg2; // arg2
    *(--kernel_stack) = arg1; // arg1
    *(--kernel_stack) = 0x0; // return address dummy

    *(--kernel_stack) = 0x0202; // eflags = interrupts aktiviert und iopl = 0
void surprise(ULONG a, ULONG b)
{
  asm volatile ("push %0" :/* no output registers */: "r" (b));
  asm volatile ("push %0" :/* no output registers */: "r" (a));
  while(TRUE)
  {
      asm volatile ("call 0x00008484");
      printformat("%d", getpid());
      settextcolor(15,0);
  }
}
Wenn ich es richtig verstanden habe, kommt "von oben gesehen" zuerst die Rücksprungadresse, dann die Argumente 1 ... n, richtig?

Warum muss ich diese dummy return address da in create_task2 ablegen, wogegen ich dies in create_task nicht machen muss? Diese Rücksprungadresse, die ich auf 0x0 gesetzt habe, wird ja auch nicht verwendet. So ganz habe ich das noch nicht verstanden. Liegt das daran, dass ich "call 0x00008484" anstelle settextcolor(...) verwende, so dass ich sowohl die Parameter als auch die Rücksprungadresse manuell anliefern muss? Ich hatte ja nur die Parameter geliefert.

Das kann man ja schnell ausprobieren:

Der einfache Fall (ohne selbst gepushte Rücksprungadresse und Argumente, das erledigt alles der Aufruf settextcolor):
    //TEST
    //*(--kernel_stack) = arg2; // arg2
    //*(--kernel_stack) = arg1; // arg1
    //*(--kernel_stack) = 0x0; // return address dummy

    *(--kernel_stack) = 0x0202; // eflags = interrupts aktiviert und iopl = 0
void surprise(ULONG a, ULONG b)
{
  //asm volatile ("push %0" :/* no output registers */: "r" (b));
  //asm volatile ("push %0" :/* no output registers */: "r" (a));
  while(TRUE)
  {
      //asm volatile ("call 0x00008484");
      settextcolor(4,2);//TEST
      printformat("%d", getpid());
      settextcolor(15,0);
  }
}
Der komplizierte Fall:
    *(--kernel_stack) = arg2; // arg2
    *(--kernel_stack) = arg1; // arg1
    *(--kernel_stack) = 0x0; // return address dummy

    *(--kernel_stack) = 0x0202; // eflags = interrupts aktiviert und iopl = 0
void surprise(ULONG a, ULONG b)
{
  asm volatile ("push %0" :/* no output registers */: "r" (b));
  asm volatile ("push %0" :/* no output registers */: "r" (a));
  while(TRUE)
  {
      asm volatile ("call 0x00008484");
      printformat("%d", getpid());
      settextcolor(15,0);
  }
}
Beide ergeben den gleichen Ablauf. Nun habe ich endlich ganz klar verstanden, was der Aufruf einer C-Funktion auf dem Stack bewirkt.
Bezüglich des Ablaufs von Programmen - aber auch Funktionen - frage ich mich gerade, wo genau die lokalen Variablen auf dem Stackframe hin gehören.

Zitat
Warum rufst du eigentlich nicht einfach settextcolor(a, b) auf, sondern bastelst am Stack rum und jonglierst mit Konstanten?
Gute Frage. Ich wollte den Mechanismus für das Ausführen von Code - sprich von Programmen - auf PrettyOS antesten. Dafür hatte ich mir settextcolor(...) ausgesucht, weil diese Funktion eine leicht sichtbare Funktion hat und darüber hinaus auch keine weiteren Funktionen in ihrem Inneren aufruft, so dass zwischen entry und ret nur wenige Schritte liegen, die man leicht per Debugger kontrollieren kann. Vielleicht bin ich da auf dem Holzweg.

Die nachstehende Aussage hat mich nach dem Erfolg mit "moo" und "baa" beim task switch dazu bewogen, auch das Ausführen von Code an allen möglichen Stellen zu testen:
Zitat
Bei .com-Dateien von DOS ist der Rest einfach: Das sind flache Binaries, sobald sie im Speicher liegen, muß man also nur noch einen Task erstellen, die Segmentregister für den Task richtig setzen und eip auf das erste Byte der Datei zeigen lassen. Das eigentliche Reinspringen in den Code sollte dann der Interrupthandler übernehmen, der das auch schon bei den Kerneltasks gemacht hat.
Wie auch immer, ich lerne momentan eine Menge über C-Funktionen. So "low level" schaue ich mir diese selten an.

Noch mal die Kernfrage: wie kann ich aus dem vorliegenden einfachen Modell möglichst einfach das Ausführen von Code - durch incbin auf eine RAM disk oder sonst etwas eingeschleust, noch kann ich nichts von Platte laden, oder bereits vorhanden - bewerkstelligen?  :?

Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 17. May 2009, 15:23
Kleine Ergänzung:
Anstelle der Konstanten kann man einfacher das Label verwenden, da sich die dahinter liegende Adresse bei Veränderungen im Code verschieben kann:
asm volatile ("call _settextcolor");
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 17. May 2009, 16:54
Ich teste gerade folgenden Ansatz:

Ein ASM-File wird assembliert:
nasmw -O32 -f bin file_data.asm -o file_data.datund mittels incbin in den kernel eingeschleust:
global _file_data_start
global _file_data_end
_file_data_start:
incbin "file_data.dat"
_file_data_end:
Dann wird es als Task 5 aufgerufen:
void test()
{
  while(TRUE)
  {
      asm volatile ("call _file_data_start");
      printformat("%d", getpid());
      settextcolor(15,0);
  }
}

task_t* task5 = create_task0(test);

Ich fahre hierbei zwei Varianten:
a) mit dummy return address auf stack (also wie bei task4)
b) ohne dummy return address auf stack (also wie bei task2 "baa" und task3 "moo")

Mit asm-File:
[BITS 32]
start:
   Mov [0x000b8000], byte 'T'
   Mov [0x000b8002], byte 'e'
   Mov [0x000b8004], byte 's'
   Mov [0x000b8006], byte 't'
   Ret
klappt es, allerdings bisher nur im Debugger sichtbar, weil ansonsten zu schnell. Dennoch super.  :-)

Da wollte ich genau hin! Nun kann ich von außen mit NASM erstellte Programme in einer Task ausführen. Ich habe das mal hier beschrieben:
http://www.henkessoft.de/OS_Dev/OS_Dev2.htm#mozTocId188270

Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 18. May 2009, 21:54
Frage: Wie führt man programme richtig aus? Die haben ja auch noch Datenbereiche, Ein- und Ausgaben, ... . Ich habe diesbezüglich noch kein Tutorial oder Wiki gefunden, eigentlich verwunderlich, wo das Ausführen von Programmen die Hauptaufgabe von OS ist.  :-D
Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 18. May 2009, 22:02
Frage: Wie führt man programme richtig aus?
Das fragen wir uns alle.

Angenommen du hast die Konzepte Prozess, Adressraum, Systemaufruf, usw. (halt das was Windows/Unix/tyndur... im Groben machen) bietet es sich an einfach das Programm an seine Wunschstelle in den Speicher zu laden, einen Task zu erstellen, und diesen in den Scheduler einzutragen. Der Prozess gibt dann dem Kernel über Systemaufrufe Anweisungen für I/O bzw. Interprozesskommunikation.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 18. May 2009, 22:09
Das verstehe ich alles - auch praktisch - soweit außer:

Adressraum - was meinst Du da ganz genau? Mein OS kann Paging, Heaps und RAM Disk erstellen und verwalten. VFS ebenfalls vorhanden. Syscalls/User Mode auch kein Problem.

Programm an seine Wunschstelle in den Speicher zu laden.
Passt das immer? Nehmen wir an, es sei ein xxx.com (alte DOS-Datei) mit ORG 0x100. Wo lade ich das hin? Man kann natürlich mittels Paging zaubern. Was sind denn typische Adressen von Programmen? Man liest da auch den Begriff "Relokation".

Prozess == Task?
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 18. May 2009, 22:16
Man kann nicht nur mit Paging zaubern, man macht das auch. Dafür ist es ja schließlich da. Die Programme liegen typischerweise alle an derselben Adresse (aber jeweils in ihrem eigenen Adreßraum, d.h. sie haben ein eigenes Page Directory, das für jedes Programm unterschiedliche Mappings enthält), die für dein OS spezifisch ist. Bei tyndur ist das 0x40000000 (also ab 1 GB, alles drunter ist Kernel).

Und Task = Prozeß könnte für dein OS im Moment noch gelten, aber sobald du Threads hast, hast du mehrere Tasks pro Prozeß.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 18. May 2009, 22:23
Zitat
aber jeweils in ihrem eigenen Adreßraum, d.h. sie haben ein eigenes Page Directory, das für jedes Programm unterschiedliche Mappings enthält
Das habe ich schon bei jeder Task so eingefädelt:

task_t* create_task(void* entry)
{
    cli();
    page_directory_t* directory = clone_directory(current_directory);
    task_t* new_task            = (task_t*)k_malloc_stack(sizeof(task_t),0,0);

    new_task->id  = next_pid++;
    new_task->page_directory = directory; // Das ist der "Adressraum"
    new_task->kernel_stack   = k_malloc_stack(KERNEL_STACK_SIZE,1,0)+KERNEL_STACK_SIZE; // Der Kernelstack
    new_task->next = 0;

    // Das hier ist die Task-Liste (momentan einfaches Round-Robin)
    task_t* tmp_task = (task_t*)ready_queue;
    while (tmp_task->next)
        tmp_task = tmp_task->next;
    tmp_task->next = new_task;
Momentan ist nur der Kernel-Stack unterschiedlich. Für jede Task ein eigener Bereich.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 18. May 2009, 23:10
Theoretisch ist mir das nun soweit klar. Ich würde mir das nur mal gerne praktisch anschauen. Gibt es denn dazu wirklich kein praktisches Tutorial mit Sourcecode. Irgendwie seltsam. Vielleicht könnt ihr mich bei tyndur zu der Stelle linken, an der das "Abspielen" eines Programmes läuft.
Titel: Re: Programm laden und ausführen
Beitrag von: bluecode am 19. May 2009, 11:44
modules.c (http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/kernel/src/modules.c;h=74215ea9d545e6f2ec3f47eaf2b47a17b090bdfb;hb=HEAD), die Funktion  load_multiboot_modules(struct multiboot_info * multiboot_info) wird von der main-Funktion aufgerufen und macht aus den Modulen die durch Grub geladen wurden Tasks. Alle davon aufgerufenen Funktionen findest du sicherlich selbst.

Das ganze in gelb kannst dir auch bei lightOS anschauen: kernel.cpp (http://repo.or.cz/w/lightOS.git?a=blob;f=kernel/kernel.cpp;h=da9a331567db5162db3ef29be337224b12936544;hb=HEAD) hier werden die Module geladen. Wieder gilt, alle davon aufgerufenen Klassen/Funktionen findest du sicherlich selbst. :-)
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 19. May 2009, 19:20
Zitat
und macht aus den Modulen die durch Grub geladen wurden Tasks
Wie sieht so ein "Modul" in tyndur aus? Ist das eine elf-Datei?
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 19. May 2009, 20:11
Ja, ein ganz normales tyndur-Programm, also eine ELF, die nach 0x40000000 gelinkt ist.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 19. May 2009, 23:38
Ich habe nun zunächst ein Virtuelles Filesystem und eine Initial RAM Disk eingebaut. Damit kann ich überhaupt Fileformate und FileNodes verwalten.

Bisher habe ich mein kleines Programm mit incbin eingelinkt, mit memcpy direkt hinter die Initial RAM Disk verschoben und dort mit
asm volatile ("call *%0"::"r"( ramdisk_start + (ULONG)&prog_start - (ULONG)&file_data_start ) ); innerhalb einer Task gestartet. In Assembler:
; data for ramdisk
global _file_data_start
global _file_data_end
global _prog_start
_file_data_start:
incbin "file_data.da1"; file_data
_prog_start:
incbin "file_data.dat"; program
_file_data_end:

Wenn ich nun mehrere Programme in die RAM Disk mittels Fileformat (proprietär oder ELF) einbinde, könnte ich doch mit einer Routine run(name) das entsprechende Programm starten. Der nächste Schritt wäre dann die Ressourcenbereitstellung für diese Programme (I/O, lokale Daten, Heap, ...).
Sehe ich das richtig?
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 20. May 2009, 19:51
Bisher habe ich mein kleines Programm mit incbin eingelinkt, mit memcpy direkt hinter die Initial RAM Disk verschoben und dort mit
asm volatile ("call *%0"::"r"( ramdisk_start + (ULONG)&prog_start - (ULONG)&file_data_start ) ); innerhalb einer Task gestartet.
Huch? Wieso startest du den Task nicht einfach gleich mit dem richtigen eip?

Zitat
Wenn ich nun mehrere Programme in die RAM Disk mittels Fileformat (proprietär oder ELF) einbinde, könnte ich doch mit einer Routine run(name) das entsprechende Programm starten. Der nächste Schritt wäre dann die Ressourcenbereitstellung für diese Programme (I/O, lokale Daten, Heap, ...).
Sehe ich das richtig?
Ist es möglicher Weg, ja. Wenn du ein Programm laufen hast, willst du vermutlich als erstes ein paar Syscalls implementieren, z.B. um eine Meldung auszugeben.

Ab dieser Stelle treibt dann eigentlich der Userspace deine Entwicklung - der Kernel bekommt immer diejenigen Syscalls neu dazu, die irgendein Programm gerade gebraucht hat. Das heißt, du solltest ungefähr wissen, was dein erstes Programm eigentlich machen soll. Eine Shell, eine Art init, das andere Programme nachlädt, was auch immer. Und spätestens hier solltest du eine Idee haben, wie du persönlich dein OS haben willst - ansonsten wird es nur noch ein langweiliges OS, womöglich der siebenhundertvierunddrölfzigste Unix-Klon...
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 20. May 2009, 19:56
Da ich noch keine shell habe, wäre dies ein sinnvolles Anwendungsprogramm.  :-) Wie man aus dem user mode syscalls aufruft, weiß ich, aber wie erstellt man dies in Programmen? Da benötigt man doch so etwas wie eine API. In C hat man da typsicherweise eine api.h/api.lib, die man dann einbindet. Wie geht das in Assembler?

Wie baut man die API von innen heraus auf?
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 12:21
Zitat
Wieso startest du den Task nicht einfach gleich mit dem richtigen eip?
Das Programm liegt in den mit incbin eingeschleusten Daten am Offset &prog_start minus &file_data_start. Nach dem Verschieben mittels memcpy muss ich dann noch die Basis ramdisk_start addieren, um die Startadresse zu finden. Vielleicht denke ich da auch zu kompliziert.
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 13:05
Deine Berechnung ist ja schön und gut - ich habe sie mir nicht näher angeschaut und gehe einfach davon aus, dass sie so stimmt.

Wenn ich es richtig verstanden habe, führst du als allererstes in dem neu erstellten Task ein call aus:
asm volatile ("call *%0"::"r"( ramdisk_start + (ULONG)&prog_start - (ULONG)&file_data_start ) );Was ich mich frage, ist, warum du nicht einfach direkt den Task an dieser Adresse startest:
create_task(ramdisk_start + (&prog_start - &file_data_start));
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 15:17
Problem 1: Eine aufgerufene Task muss bisher noch vom nächsten task_switch, also z.B. von IRQ0, umgeschaltet werden. Ein einfaches "Aufhören" durch ret geht noch nicht. Das knallt noch, vermutlich auf dem Stack mit den Tasks.  :-D Daher die Endlos-while-Schleife in der von der Task aufgerufenen Funktion.

Wie werde ich die while-Schleife los? Dann kann ich auch direkt mit create_task ohne Zwischenfunktion in einer Endlos-while-loop an den Start springen.

Problem 2: Ich möchte einen Task im User Mode (Ring 3) durchführen, was ich bisher nicht schaffe.


//in main():
void test1(ULONG addr, ULONG dummy)                 
{
  while(TRUE)
  {
      asm volatile ("call *%0"::"r"(addr));
  }
}

task_t* task6 = create_task2(test1,0,ramdisk_start+(ULONG)&prog_start-(ULONG)&file_data_start,0);

//in task.c:
task_t* create_task2(void* entry, UCHAR privilege, ULONG arg1, ULONG arg2){...} //siehe paralleler Thread
Das klappt leider nur mit Privileg 0, noch nicht mit 3. Bei 3 endet es mit einem GPF. Dabei verwende ich innerhalb der Funktion test1 doch gar nichts vom Kernel, oder etwa doch? Liegt das daran, dass test1 im Kernel definiert ist? 
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 15:54
Zum Beenden des Tasks willst du wahrscheinlich einen Syscall benutzen. Und bei Exceptions wirst du den Task wahrscheinlich auch beenden wollen.

Wegen Ring 3: Der einzige wirkliche Unterschied ist, dass das TSS benutzt wird. Hast du das korrekt eingerichtet und ss0/esp0 sinnvoll belegt? (Wobei letzteres eigentlich erst beim Rücksprung aus dem Task Probleme macht)
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 15:59
Ich habe nun folgendes probiert:
in main (extra die Konstante verwendet:
void test()                   
{
  while(TRUE)
  {
      asm volatile ("call *%0"::"r"( 0x40082372 ) ); // <- program from outside "loaded" by incbin ...
  }
}

task_t* task5 = create_task (test, 3);

in syscall.h/syscall.c
DECL_SYSCALL1(puts, UCHAR*)
DECL_SYSCALL0(test)
DECL_SYSCALL2(test1, ULONG, ULONG)

DEFN_SYSCALL1(puts,  0, UCHAR*);
DEFN_SYSCALL0(test,  1)
DEFN_SYSCALL2(test1, 2, ULONG, ULONG)

ULONG num_syscalls  = 3;
static void* syscalls[3] =
{
    &puts,
    &test,
    &test1
};

Dennoch klappt es nicht mit dem User Mode. Woran liegt das?
Im Kernel Mode übrigens kein Problem.
Die Konstante habe ich durch Screen-Ausdruck kontrolliert:
printformat("%x\n",ramdisk_start + (ULONG)&prog_start - (ULONG)&file_data_start);
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 16:12
Zitat
Zum Beenden des Tasks willst du wahrscheinlich einen Syscall benutzen.
Wie sieht die Funktion da aus? Normalerweise endet doch ein Programm, das man aufruft, mit ret. Wie führe ich da einen syscall aus bei create_task(entry)?
Bei create_task(moo) und moo(){...;syscall_BeendeTaskOrdentlich;} kann ich mir das vorstellen. BeendeTaskOrdentlich wäre dann z.B. ein task_switch generiert durch den Interrupt des syscalls.

Zitat
Und bei Exceptions wirst du den Task wahrscheinlich auch beenden wollen.
Das passiert jetzt schon weil ich den task_switch direkt im Interrupt analog tyndur durchführe.

Zitat
Wegen Ring 3: Der einzige wirkliche Unterschied ist, dass das TSS benutzt wird. Hast du das korrekt eingerichtet und ss0/esp0 sinnvoll belegt? (Wobei letzteres eigentlich erst beim Rücksprung aus dem Task Probleme macht)
Das mache ich nur in task_switch:
ULONG task_switch1(ULONG esp)
{
    if(!current_task) return esp;

    current_task->esp = esp;   

    current_task = current_task->next;
    if(!current_task) current_task = ready_queue;
 
    current_directory = current_task->page_directory;
    asm volatile("mov %0, %%cr3" : : "r" (current_directory->physicalAddr));
    tss_entry.esp0 = (current_task->kernel_stack)+KERNEL_STACK_SIZE;

    return current_task->esp; 
}
Ich setze also nur cr3 (nicht im TSS) und esp0 im TSS.

In create_task (siehe Threads) verwende ich TSS überhaupt nicht, sondern generiere nur einen adressraum (PD) und kmalloc'e den kernel stack. Alle Infos gehen in meine Task-Struktur.

Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 16:30
Folgendes führt leider auch noch zum GPF:
task_t* create_task(void* entry, UCHAR privilege)
{
    cli();
    page_directory_t* directory = clone_directory(current_directory);
    task_t* new_task            = (task_t*)k_malloc(sizeof(task_t),0,0);

    new_task->id  = next_pid++;
    new_task->page_directory = directory;
    new_task->kernel_stack   = k_malloc(KERNEL_STACK_SIZE,1,0)+KERNEL_STACK_SIZE;
    new_task->next = 0;

    task_t* tmp_task = (task_t*)ready_queue;
    while (tmp_task->next)
        tmp_task = tmp_task->next;
    tmp_task->next = new_task; // ... and extend it

    ULONG* kernel_stack = (ULONG*) new_task->kernel_stack;

    *(--kernel_stack) = 0x0202; // eflags = interrupts activated and iopl = 0

    ULONG code_segment=0x08, data_segment=0x10;
    if(privilege == 0) code_segment = 0x08;
    if(privilege == 3) code_segment = 0x1B; // 0x18|0x3=0x1B

    *(--kernel_stack) = code_segment; // cs

    *(--kernel_stack) = (ULONG)entry; // eip

    *(--kernel_stack) = 0; // error code
    *(--kernel_stack) = 0; // interrupt nummer

    // general purpose registers w/o esp
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;

    // data segment registers
    if(privilege == 0) data_segment = 0x10;
    if(privilege == 3) data_segment = 0x23; // 0x20|0x3=0x23

    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;

    //setup TSS
    tss_entry.ss0   = 0x10;
    tss_entry.esp0  = new_task->kernel_stack;
    tss_entry.ss    = data_segment;

    //setup task_t
    new_task->ebp = 0xd00fc0de; // test value
    new_task->esp = (ULONG)kernel_stack;
    new_task->eip = (ULONG)irq_tail;

    sti();
    return new_task;
}
Ich habe
    //setup TSS
    tss_entry.ss0   = 0x10;
    tss_entry.esp0  = new_task->kernel_stack;
    tss_entry.ss    = data_segment;
ergänzt.
Der GPF gibt mir folgende Daten aus meinem OS:
TSS log:
esp0: 40212000h ss0: 00000010h cr3: 00000000h eip: 00000000h eflags: 00000000h eax: 00000000h ecx: 00000000h edx: 00000000h ebx: 00000000h esp: 00000000h ebp: 00000000h esi: 00000000h edi: 00000000h es: 00000010h cs: 00000008h ss: 00000010h ds: 00000010h fs: 00000010h gs: 00000010h

err_code: 00000020h address(eip): 0000829Bh edi: 0000832Ch esi: 00002420h ebp: 4020D6C8h eax: 402117C0h ebx: 000001EDh ecx:
000B8DB3h edx: 00000020h cs: 8 ds: 16 es: 16 fs: 16 gs 16 ss 35
int_no 13 eflags 00010012h useresp 00000023h

General Protection Fault >>> Exception. System Halted!
Der GPF entsteht beim Wechsel auf den task5, wenn ich Privileg 3 verwende:
task_t* task5 = create_task (test, 3);
Bei 0 ist alles o.k.

Die Werte in der GPF sehen noch voll nach Kernel aus?  :?

Ist das richtig, dass ich z.B. das Datensegment auf 0x23 anstelle 0x20 setze, ebenso das Code-Segment 0x1B anstelle 0x18?
SS0 ist doch 0x10 (wie data segment) für den Kernel-Stack?
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 16:40
Zitat
Zum Beenden des Tasks willst du wahrscheinlich einen Syscall benutzen.
Wie sieht die Funktion da aus? Normalerweise endet doch ein Programm, das man aufruft, mit ret. Wie führe ich da einen syscall aus bei create_task(entry)?
Bei create_task(moo) und moo(){...;syscall_BeendeTaskOrdentlich;} kann ich mir das vorstellen. BeendeTaskOrdentlich wäre dann z.B. ein task_switch generiert durch den Interrupt des syscalls.
Genau so macht man es auch. Ein Programm endet nie mit ret. Was du vielleicht meinst, ist dass main() eine ganz normale Funktion ist, die entsprechend auch ein ret am Ende hat. Aber main ist normal nicht der Entrypoint, sondern es gibt noch ein _start, das die libc initialisiert und dann erst main aufruft (und hinterher eben den Syscall zum Beenden).

Zitat
In create_task (siehe Threads) verwende ich TSS überhaupt nicht, sondern generiere nur einen adressraum (PD) und kmalloc'e den kernel stack. Alle Infos gehen in meine Task-Struktur.
Ist auch nicht zwingend nötig. Wenn du im TSS einmal gültige Werte stehen hast, brauchst du die nicht mehr zu ändern.
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 16:52
TSS log:
esp0: 40212000h ss0: 00000010h cr3: 00000000h eip: 00000000h eflags: 00000000h eax: 00000000h ecx: 00000000h edx: 00000000h ebx: 00000000h esp: 00000000h ebp: 00000000h esi: 00000000h edi: 00000000h es: 00000010h cs: 00000008h ss: 00000010h ds: 00000010h fs: 00000010h gs: 00000010h

err_code: 00000020h address(eip): 0000829Bh edi: 0000832Ch esi: 00002420h ebp: 4020D6C8h eax: 402117C0h ebx: 000001EDh ecx:
000B8DB3h edx: 00000020h cs: 8 ds: 16 es: 16 fs: 16 gs 16 ss 35
int_no 13 eflags 00010012h useresp 00000023h

General Protection Fault >>> Exception. System Halted!
Der GPF entsteht beim Wechsel auf den task5, wenn ich Privileg 3 verwende:
task_t* task5 = create_task (test, 3);
Bei 0 ist alles o.k.

Die Werte in der GPF sehen noch voll nach Kernel aus?  :?
Okay, machen wir mal eine Runde Exception-Lesen. Deine Segmentregister sind alle Kernelspace, bis auf ss, das ist 0x23 (Hexzahlen als Ausgabe wären hier eigentlich sinnvoller). Der Errorcode ist 0x20 (Ringnummer wird rausmaskiert), also wirst du vermutlich versuchen, ds oder es zu setzen. Normal hätte ich jetzt an den GDT-Einträgen gezweifelt, aber ss ist ja anscheinend schon auf denselben Wert gesetzt. Sind vielleicht die Segmentregister für ds/es noch auf Ring 0 und beim Ringwechsel kracht es dann?

Zitat
Ist das richtig, dass ich z.B. das Datensegment auf 0x23 anstelle 0x20 setze, ebenso das Code-Segment 0x1B anstelle 0x18?
SS0 ist doch 0x10 (wie data segment) für den Kernel-Stack?
Ja, alles richtig.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 17:18
Zitat
Hexzahlen als Ausgabe wären hier eigentlich sinnvoller
ja, sofort erledigt.

Zitat
Normal hätte ich jetzt an den GDT-Einträgen gezweifelt
Zurecht! ich hatte da noch einen Murks drinnen, nämlich:
#ifdef USERMODE   // in os.h
    //gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // User mode code segment
    //gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // User mode data segment
    #endif
d.h. gate 3 und 4 waren überhaupt nicht gesetzt!

Nun habe ich das korrigiert:
void gdt_install()
{
    /* Setup the GDT pointer and limit */
    gdt_register.limit = (sizeof(struct gdt_entry) * NUMBER_GDT_GATES)-1;
    gdt_register.base  = (ULONG) &gdt;

    /* GDT GATES -  desriptors with pointers to the linear memory address */
    gdt_set_gate(0, 0, 0, 0, 0);                // NULL descriptor
    gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // CODE, privilege level 0 for kernel code
    gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // DATA, privilege level 0 for kernel code

    #ifdef KERNELMODE // in os.h
    gdt_set_gate(3, 0, 0xFFFFFFFF, 0x9A, 0xCF); // CODE, privilege level 0 for kernel code
    gdt_set_gate(4, 0, 0xFFFFFFFF, 0x92, 0xCF); // DATA, privilege level 0 for kernel code
    #endif

    #ifdef USERMODE   // in os.h
    gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // User mode code segment
    gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // User mode data segment
    #endif

    write_tss(5, 0x10, 0x0);                    // num, ss0, esp0

    gdt_flush((ULONG)&gdt_register); // inclusive gdt_load() in assembler code
    tss_flush();                     //in flush.asm: privilege level 3 for kernel mode 0x2B
}

Leider ist das noch nicht die Lösung. Nächste Fehlermeldung:
TSS log:
esp0: 40212000h ss0: 10h ... es: 10h cs: 08h ss: 10h ds: 10h fs: 10h gs: 10h

err_code: 00000FF4h address(eip): 000082ABh ... cs: 08h ds: 23h es: 23h fs: 23h gs 23h ss 1Bh int_no 13 eflags 00010012h useresp 0000836Ah
General Protection Fault >>> Exception. System Halted! <<<

Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 17:40
Zitat
Normal hätte ich jetzt an den GDT-Einträgen gezweifelt
Zurecht! ich hatte da noch einen Murks drinnen, nämlich:
#ifdef USERMODE   // in os.h
    //gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // User mode code segment
    //gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // User mode data segment
    #endif
d.h. gate 3 und 4 waren überhaupt nicht gesetzt!
Gut. Dann zweifle ich jetzt zusätzlich noch an deiner Exceptionmeldung, denn ss = 0x23 hätte dann eigentlich auch nicht sein dürfen.

Zitat
err_code: 00000FF4h address(eip): 000082ABh ... cs: 08h ds: 23h es: 23h fs: 23h gs 23h ss 1Bh int_no 13 eflags 00010012h useresp 0000836Ah
Kannst du eip mal auflösen, wo das genau ist? Ich tippe mal auf das iret. Anscheinend sind wir jetzt immerhin schonmal mit den Segmentregistern im Userspace. Ich würde vermuten, dass das jetzt was mit dem TSS zu tun hat. Vielleicht mal die Intel-Doku anschauen, was dieser Errorcode bedeuten könnte.


Wenn du willst, kannst du auch mal einen Minimalkernel (http://vs6503.vserver4free.de/~kevin/testkernel/) anschauen, den ich letztes Wochenende geschrieben habe, um ein bisschen herumzuexperimentieren. Das ist so ungefähr das minimale, was man für Multitasking mit Ring-3-Tasks braucht.

Was den Taskswitch betrifft, solltest du dich aber besser an das halten, was du schon hast. Das ist zwar eine interessante Variante, die ich da gebastelt habe (und verstehen versuchen schadet sicher nichts), aber sie ist grundlegend kaputt (Interrupts/Exceptions im Kernel gehen nicht).
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 18:05
Zitat
Kannst du eip mal auflösen, wo das genau ist? Ich tippe mal auf das iret.
Ja, stimmt genau: [0x000082ab] 0008:00000000000082ab (unk. ctxt): iretd

Den error code finde ich nicht im Intel Manual. Da steht aber folgendes in 3A:
Zitat
5.13 ERROR CODE
When an exception condition is related to a specific segment, the processor pushes an error code onto the stack of the exception handler (whether it is a procedure or task). The error code has the format shown in Figure 5-6. The error code resembles a segment selector; however, instead of a TI flag and RPL field, the error code contains 3 flags:

EXTExternal event (bit 0) — When set, indicates that an event external to the program, such as a hardware interrupt, caused the exception.

IDTDescriptor location (bit 1) — When set, indicates that the index portion of the error code refers to a gate descriptor in the IDT; when clear, indicates that the index refers to a descriptor in the GDT or the current LDT.

TIGDT/LDT (bit 2) — Only used when the IDT flag is clear. When set, the TI flag indicates that the index portion of the error code refers to a segment or gate descriptor in the LDT; when clear, it indicates that the index refers to a descriptor in the current GDT. The segment selector index field provides an index into the IDT, GDT, or current LDT to the segment or gate selector being referenced by the error code. In some cases the error code is null (that is, all bits in the lower word are clear). A null error code indicates that the error was not caused by a reference to a specific segment or that a null segment descriptor was referenced in an operation.

FF4h = 111111110100

EXT = 0 (bit0)
IDT = 0 (bit1) ==> indicates that the index refers to a descriptor in the GDT
TI = 1 (bit2) ==> indicates that the index portion of the error code refers to a segment

Schneidet man den Segment-Selektor raus:
111111110 = 1FEh   :?

Übrigens: ss 1Bh darf doch nicht sein? Das müsste doch entweder 0x10 (data kernelmode) oder 0x23 (data usermode) sein?

Ich setze aber brav:
    // data segment registers
    if(privilege == 0) data_segment = 0x10;
    if(privilege == 3) data_segment = 0x23; // 0x20|0x3=0x23

    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;

    //setup TSS
    tss_entry.ss0   = 0x10;
    tss_entry.esp0  = new_task->kernel_stack;
    tss_entry.ss    = data_segment;



Ich habe noch task-Strukur und Setzen von TSS-Werten etwas erweitert, bringt aber keine Besserung.  :-P


Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 19:18
Da ich den entscheidenden Fehler zur Zeit nicht finde (wahrscheinlich suche ich an der falschen Stelle), poste ich mal meinen Code:

http://www.henkessoft.de/OS_Dev/Downloads/36y_user.zip

... in der Hoffnung, dass dies den push zum user mode bringt.
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 20:16
Ich nehme an, ich soll einfach MyOS.bin als Floppy nehmen. Damit kriege ich keinen GPF, sondern er springt einfach irgendwann ins Nirvana. Neu durchbauen kann ich nicht, weil die Makefile offensichtlich für Windows gedacht ist und ich keine Ahnung habe, was die Binärprogramme im Verzeichnis anstellen sollen.

Aber wenn ich es richtig sehe, ist deine TSS-Definition falsch. Bei den 16-Bit-Feldern hast du das reserved und den Eintrag genau falschrum. Zumindest für ss solltest du das ändern, der Rest wird ja nicht benutzt. Das dürfte aber erst beim Rücksprung einen Fehler geben, insofern ist sicher auch noch irgendwas anderes nicht in Ordnung.

Edit: Also eine MyOS.bin habe ich jetzt gebaut bekommen, bootet aber nicht. Keine Ahnung, ob ich was falsch gemacht habe oder einfach der Bootloader nur zufällig bei dir tut. Ich habe jetzt auch keine großartige Lust, das zu debuggen.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 20:56
Da ich bei mir gerade nicht weiter komme, habe ich mir deinen Minimalkernel angeschaut.

Da ist mir aufgefallen, dass ich die Alignment Check Exception anders aufgestzt hatte als bei dir:

; 17: Alignment Check Exception (With Error Code!)
_isr17:
    cli
    ;push dword 0  ; correct?
    push dword 17
    jmp isr_common_stub

Ist da so wirklich o.k.?

Ansonsten habe ich eine Unsymmetrie bei mir, bezüglich der ich nicht weiß, ob es stört oder nicht:

Ich habe z.Z. einen fault_handler für die exceptions, über den ich auch den Software Interrupt (0x7F) für die Syscalls abwickle:

isr_common_stub:
    ;pusha
    push eax
    push ecx
    push edx
    push ebx
     ;push esp
    push ebp
    push esi
    push edi

    push ds
    push es
    push fs
    push gs

    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax
   
    mov eax, esp
    push eax
    mov eax, _fault_handler
    call eax
    pop eax

    pop gs
    pop fs
    pop es
    pop ds
   
;popa
    pop edi
    pop esi
    pop ebp
       ;pop esp
    pop ebx
    pop edx
    pop ecx
    pop eax

    add esp, 8
    iret
 

... auf der anderen Seite habe ich einen _irq_handler1 für IRQ0 bis IRQ15, den ich nach eurem Vorbild aufgebaut habe:
irq_common_stub:
    push eax
    push ecx
    push edx
    push ebx
    push ebp
    push esi
    push edi

    push ds
    push es
    push fs
    push gs

    mov ax, 0x10
    mov ds, ax
    mov es, ax
    mov fs, ax
    mov gs, ax

    push esp                  ; parameter of _irq_handler1
    call _irq_handler1    ; 
    global _irq_tail
    _irq_tail:
    mov esp, eax          ; return value: changed or unchanged esp

    pop gs
    pop fs
    pop es
    pop ds
   
    pop edi
    pop esi
    pop ebp
    pop ebx
    pop edx
    pop ecx
    pop eax

    add esp, 8
    iret

In irq_handler1 läuft momentan das direkte task_switching ab.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 21:23
Danke, dass Du es versucht hast. Vielleicht kann PorkChicken mir helfen. Zumindest hat er es das letzte Mal geschafft beim Multitasking. Er ist genial. :-)

Wahrscheinlich verwendest Du einen Linker, der kein aout-Format (wegen 16/32 bit Mischung in kernel.asm) kann.

Mal was anderes:
task_t* task5 = create_task (syscall_test, 0);

void test()                   
{
  while(TRUE)
  {
      syscall_puts("5u");
  }

}

Da gibt es bei mir das Problem, das ich mit dem Task_Switching nicht mehr ran komme, also Endlosschleife im fault_handler/syscall.

Nehme ich das while weg,
void test()
{
       syscall_puts("5u");
}
dann gibt es beim Rücksprung ein Problem auf dem Stack.
Zitat
44444444444444444444444444444444444444444444444444444444444444444444444444444444
5uPage Fault (page not present) at 123890ABh - EIP: 123890ABh
So etwas hatte ich schon mal, dass die magic number vom heap kommt.
Da war zu wenig auf dem Stack. Dadurch wurde diese Nummer geholt.
Sieht alles nicht gut aus.  :roll:
 
Zitat
Aber wenn ich es richtig sehe, ist deine TSS-Definition falsch. Bei den 16-Bit-Feldern hast du das reserved und den Eintrag genau falschrum.
Umdrehen hat nix gebracht. Aber ich war wirklich sicher, dass es stimmt (habe es gemäß Intel Manual aufgebaut, das höherwertige Byte nach dem niederwertigen). Von daher bin ich nicht sicher, ob Du da wirklich Recht hast. Leider habe ich das so bisher nirgends gesehen, so dass ich nicht vergleichen kann.
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 21:33
Da ich bei mir gerade nicht weiter komme, habe ich mir deinen Minimalkernel angeschaut.

Da ist mir aufgefallen, dass ich die Alignment Check Exception anders aufgestzt hatte als bei dir:

; 17: Alignment Check Exception (With Error Code!)
_isr17:
    cli
    ;push dword 0  ; correct?
    push dword 17
    jmp isr_common_stub

Ist da so wirklich o.k.?
Welche Interrupts einen Errorcode haben, habe ich aus dem qemu-Code abgeschrieben, die sollten also eigentlich passen. Das Intel-Manual sagt  für 17 auch "Exception Error Code: Yes (always zero)." Also hier nicht nochmal extra eine 0 draufpushen.

Zitat
Ansonsten habe ich eine Unsymmetrie bei mir, bezüglich der ich nicht weiß, ob es stört oder nicht:
Wozu ist das denn nötig? Der Code sieht sehr ähnlich aus, mit dem einzigen Unterschied, dass der fault_handler kein neues esp zurückgibt. Oder übersehe ich da noch was? Aber theoretisch dürfte das nicht stören, solange die Faulthandler keinen Taskswitch machen.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 21:40
Zitat
Aber theoretisch dürfte das nicht stören, solange die Faulthandler keinen Taskswitch machen.
Machen sie nicht.

Zum Compilieren/Linken nehme ich übrigens dies hier unter Windows: http://www.osdever.net/downloads/compilers/DJGPP-Installer-nocpp.exe
(wegen dem Linker, der auch aout kann)

Ich möchte das jetzt wegen eines Tutorials zunächst nicht ändern, weil ich GRUB erst später verwenden will. 

Diese User Mode Geschichte macht mir momentan echt Probleme.
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 21:46
Danke, dass Du es versucht hast. Vielleicht kann Port_Chicken mir helfen. Zumindest hat er es das letzte Mal geschafft beim Multitasking.  :-)

Wahrscheinlich verwendest Du einen Linker, der kein aout-Format (wegen 16/32 bit Mischung in kernel.asm) kann.
Falls du es nicht bemerkt hast, du bist immer noch beim Multitasking. ;)

Dass mein Linker kein a.out kann, würde ich mal ausschließen. Ansonsten hätte er nicht irgendwas produziert, sondern mit einem Fehler abgebrochen. Davon abgesehen ist a.out ja das alte native *nix-Format, das sollte mein Linux-ld also noch viel eher können als dein Windows-ld.

Er kommt ja auch bis zum "loading kernel ..." aber dann geht nichts mehr.


Zitat
Nehme ich das while weg,
void test()
{
       syscall_puts("5u");
}
dann gibt es beim Rücksprung ein Problem auf dem Stack.
Dann läuft er ja wieder ins ret anstatt durch einen Syscall beendet zu werden, und auf dem Stack liegt keine Rücksprungadresse. Kann also gar nicht gehen.

Zitat
Umdrehen hat nix gebracht. Aber ich war wirklich sicher, dass es stimmt (habe es gemäß Intel Manual aufgebaut, das höherwertige Byte nach dem niederwertigen). Von daher bin ich nicht sicher, ob Du da wirklich Recht hast. Leider habe ich das so bisher nirgends gesehen, so dass ich nicht vergleichen kann.
Hm, ich hatte die Grafik anders in Erinnerung...

Ich glaube du hast recht. Mein Minikernel tut ja und der setzt ganze 32-Bit-Words. Intel ist Little Endian, insofern setze ich da die ersten beiden Bytes. Ich nehme also alles zurück und behaupte das Gegenteil, tut mir leid.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 22:13
Zitat
Dann läuft er ja wieder ins ret anstatt durch einen Syscall beendet zu werden, und auf dem Stack liegt keine Rücksprungadresse. Kann also gar nicht gehen.

Ich rufe test als nicht als test, sondern als syscall_test auf. Das sollte z.B. im user-mode gehen. Die Frage ist, wie sollte ein syscall aussehen, der einen weiteren Task anstößt. Das müsste ein Interrupt sein wie z.B. IRQ0, das einen taskswitch auslöst. Ein syscall löst bei mir bisher keinen Taskswitch aus, nur die Exception-Schiene.
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 22:18
Syscalls sind üblicherweile (Software-)Interrupts.

Übrigens habe ich es mittlerweile hinbekommen, deinen Bootloader den Kernel laden zu lassen - im Linkerskript war rodata nicht berücksichtigt und deswegen ist die Reihenfolge durcheinandergekommen und der Kernel hat nicht mit Code angefangen. Habe ich schonmal erwähnt, dass flache Binaries doof sind?

Funktionieren tut der Kernel trotzdem nicht. Zwei Meldungen kommen am Anfang und dann marschiert eip munter durch den ganzen Speicher. Da ist wohl noch irgendwo ein falscher Sprung.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 22:24
Zitat
Übrigens habe ich es mittlerweile hinbekommen, deinen Bootloader den Kernel laden zu lassen - im Linkerskript war rodata nicht berücksichtigt und deswegen ist die Reihenfolge durcheinandergekommen und der Kernel hat nicht mit Code angefangen.
Toll! Kannst Du diese Verbesserung im Linker-Script bitte posten. Das kann ich ja bei mir einbauen und testen.

Zitat
Funktionieren tut der Kernel trotzdem nicht. Zwei Meldungen kommen am Anfang und dann marschiert eip munter durch den ganzen Speicher. Da ist wohl noch irgendwo ein falscher Sprung.
Das musst Du mir mal richtig erklären.  :?

Zitat
Syscalls sind üblicherweile (Software-)Interrupts.
Ja, bei mir auch (0x7F). Nur habe ich da den Taskswitch nicht mit verbunden.

Diese Verschachtelung von create_task(syscall_...) läuft daher so nicht. Momentan sehe ich aber auch nicht die Lösung.
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 22:42
Toll! Kannst Du diese Verbesserung im Linker-Script bitte posten. Das kann ich ja bei mir einbauen und testen.
Kein Problem, hier kommt der Patch:
--- kernel.ld.orig      2009-04-03 22:16:00.000000000 +0200
+++ kernel.ld   2009-05-21 22:10:14.000000000 +0200
@@ -9,6 +9,9 @@ SECTIONS
   .data  : {
     *(.data)
   }
+  .rodata  : {
+    *(.rodata)
+  }
   .bss  :  {
     *(.bss)
   }

Zitat
Zitat
Funktionieren tut der Kernel trotzdem nicht. Zwei Meldungen kommen am Anfang und dann marschiert eip munter durch den ganzen Speicher. Da ist wohl noch irgendwo ein falscher Sprung.
Das musst Du mir mal richtig erklären.  :?
Hm, viel erklären kann ich nicht, weil ich nicht weiß, was genau passiert. Ich sehe folgende Meldung:
Welcome to PrettyOS 0.08
HEAPhole 40081000h hole-size: 0017F0000h
Und dann passiert scheinbar gar nichts mehr. Um rauszukriegen, was er denn macht oder wo er hängt, habe ich dann ein paarmal info registers im qemu-Monitor genommen. Und das zeigt mir, dass er irgendwelchen Code ausführt, wo eigentlich gar nichts liegt.

Zitat
Zitat
Syscalls sind üblicherweile (Software-)Interrupts.
Ja, bei mir auch (0x7F). Nur habe ich da den Taskswitch nicht mit verbunden.

Diese Verschachtelung von create_task(syscall_...) läuft daher so nicht. Momentan sehe ich aber auch nicht die Lösung.
Zum Ausgleich verstehe ich das Problem nicht. ;)

An welcher Stelle willst du denn ein create_task(syscall_...) einbauen und was soll das überhaupt bringen? Willst du den Syscall in einem neuem Kernelthread ausführen oder sowas?
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 22:58
Ich möchte eine Task im User Mode ausführen. daher habe ich diesen Parameter privilege bei create_task(...) dabei. Bisher klappt das aber noch nicht im User Mode, nur im Kernel Mode.

Ich dachte, das liegt z.B. daran, dass ich eine Funktion im Kernel wie z.B. test(...) direkt aufrufe. Daher habe ich syscall_test in syscall.h/syscall.c deklariert/definiert. Nehme ich die Endlos-while-Schleife, dann bleibt das im syscall "hängen", d.h. der Task kommt nicht zum iret des irq_handlers zurück, weil der iret des syscalls (läuft über fault_handler) nicht ausgelöst wird. Ohne syscall funktioniert das problemlos, weil da kein neuer Interrupt ausgelöst wurde, dann kommt das brav zum iret des irq_handlers zurück.

Lässt man die while-Schleife weg, kommt der iret des syscalls zum Zug, aber dann ist offenbar alles verschoben bezüglich des iret des irq_handlers von create_task. Ich weiß nicht, wie ich das analysieren und vor allem lösen soll

Diese Schachtelung von creat_task(start) - syscall(start) - ... - syscall(ende) - create_task(ende) habe ich also nur gebaut, um endlich mal einen richtigen user mode in Ring 3 zu testen. Wie soll ich das denn sonst machen?
Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 23:01
Breakpoint z.B. vor das iret setzen und einfach mal den Stack an dieser Stelle anschauen?
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 23:06
Das mit den rodata habe ich eingebaut. Ich habe mal recherchiert und gefunden, dass es zwischen data und rodata (offenbar mingw-spezifisch) auch noch rdata gibt. Soll ich das zur Sicherheit auch noch einbauen?  :-D

Titel: Re: Programm laden und ausführen
Beitrag von: kevin am 21. May 2009, 23:10
Was auch immer das sein soll, aber wenn's hilft... Abgesehen davon benutze ich kein mingw, also brauchst du es wegen mir nicht. ;)
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 21. May 2009, 23:51
Zitat
Breakpoint
direkt vor iret nach irq_tail:

 | STACK 0x4020d7e8 [0x00008330] _surprise  - aufgerufene Fkt. von task 4
 | STACK 0x4020d7ec [0x00000008]
 | STACK 0x4020d7f0 [0x00000202]
 | STACK 0x4020d7f4 [0x00000000]
 | STACK 0x4020d7f8 [0x00000004]
 | STACK 0x4020d7fc [0x00000002]
 | STACK 0x4020d800 [0x123890ab]
 | STACK 0x4020d804 [0x4020cff4]
 | STACK 0x4020d808 [0x123890ab]
 | STACK 0x4020d80c [0x00000001]
 | STACK 0x4020d810 [0x000007ec]
 | STACK 0x4020d814 [0x00000000]
 | STACK 0x4020d818 [0x00000000]
 | STACK 0x4020d81c [0x00000000]
 | STACK 0x4020d820 [0x00000000]
 | STACK 0x4020d824 [0x00000000]

nach task 4 bei iret:

 | STACK 0x402117f4 [0x0000d672]  _syscall_test - aufgerufene Fkt. von task 5
 | STACK 0x402117f8 [0x00000008]
 | STACK 0x402117fc [0x00000202]
 | STACK 0x40211800 [0x123890ab]
 | STACK 0x40211804 [0x40210ff4]
 | STACK 0x40211808 [0x123890ab]
 | STACK 0x4021180c [0x00000001]
 | STACK 0x40211810 [0x000007ec]
 | STACK 0x40211814 [0x00000000]
 | STACK 0x40211818 [0x00000000]
 | STACK 0x4021181c [0x00000000]
 | STACK 0x40211820 [0x00000000]
 | STACK 0x40211824 [0x00000000]
 | STACK 0x40211828 [0x00000000]
 | STACK 0x4021182c [0x00000000]
 | STACK 0x40211830 [0x00000000]

vor nop in test (task 5):

 | STACK 0x40211710 [0x0000839b] ?? nicht in kernel.map  0x0000836e  test
 | STACK 0x40211714 [0x0000836a] ??
 | STACK 0x40211718 [0x00000000]
 | STACK 0x4021171c [0x00000000]
 | STACK 0x40211720 [0x00000000]
 | STACK 0x40211724 [0x00000000]
 | STACK 0x40211728 [0x00000000]
 | STACK 0x4021172c [0x40211770]
 | STACK 0x40211730 [0x0000d6cc]
 | STACK 0x40211734 [0x00000000]
 | STACK 0x40211738 [0x00000000]
 | STACK 0x4021173c [0x00000000]
 | STACK 0x40211740 [0x00000000]
 | STACK 0x40211744 [0x00000000]

vor dem ret von test:

 | STACK 0x40211730 [0x0000d6cc]  ??
 | STACK 0x40211734 [0x00000000]
 | STACK 0x40211738 [0x00000000]
 | STACK 0x4021173c [0x00000000]
 | STACK 0x40211740 [0x00000000]
 | STACK 0x40211744 [0x00000000]
 | STACK 0x40211748 [0x00000000]
 | STACK 0x4021174c [0x00000000]
 | STACK 0x40211750 [0x00000000]
 | STACK 0x40211754 [0x00000000]
 | STACK 0x40211758 [0x00000000]
 | STACK 0x4021175c [0x00000000]
 | STACK 0x40211760 [0x0000836e]
 | STACK 0x40211764 [0x00000000]
 | STACK 0x40211768 [0x402117bc]
 | STACK 0x4021176c [0x00000000]

vor noch einem ret ???

 | STACK 0x40211774 [0x00009668]  ??
 | STACK 0x40211778 [0x402117bc]
 | STACK 0x4021177c [0x00000000]
 | STACK 0x40211780 [0x00000000]
 | STACK 0x40211784 [0x00000000]
 | STACK 0x40211788 [0x00000000]
 | STACK 0x4021178c [0x00000000]
 | STACK 0x40211790 [0x00000000]
 | STACK 0x40211794 [0x00000000]
 | STACK 0x40211798 [0x00000000]
 | STACK 0x4021179c [0x00000000]
 | STACK 0x402117a0 [0x00000000]
 | STACK 0x402117a4 [0x00000000]
 | STACK 0x402117a8 [0x00000000]
 | STACK 0x402117ac [0x00000000]
 | STACK 0x402117b0 [0x402117fc]

und vor noch einem ret???

 | STACK 0x402117b4 [0x000081f6] ??
 | STACK 0x402117b8 [0x402117bc]
 | STACK 0x402117bc [0x00000010]
 | STACK 0x402117c0 [0x00000010]
 | STACK 0x402117c4 [0x00000010]
 | STACK 0x402117c8 [0x00000010]
 | STACK 0x402117cc [0x00000000]
 | STACK 0x402117d0 [0x00000000]
 | STACK 0x402117d4 [0x402117fc]
 | STACK 0x402117d8 [0x00000000]
 | STACK 0x402117dc [0x00000000]
 | STACK 0x402117e0 [0x00000000]
 | STACK 0x402117e4 [0x00000086]
 | STACK 0x402117e8 [0x0000007f]
 | STACK 0x402117ec [0x00000000]
 | STACK 0x402117f0 [0x0000d67c]
 | STACK 0x40211748 [0x00000000]
 | STACK 0x4021174c [0x00000000]

vor dem iret des syscalls:
(0) [0x00008207] 0008:0000000000008207 (unk. ctxt): iretd

 | STACK 0x402117f0 [0x0000d67c]  ??
 | STACK 0x402117f4 [0x00000008]
 | STACK 0x402117f8 [0x00000202]
 | STACK 0x402117fc [0x00000000]
 | STACK 0x40211800 [0x123890ab]
 | STACK 0x40211804 [0x40210ff4]
 | STACK 0x40211808 [0x123890ab]
 | STACK 0x4021180c [0x00000001]
 | STACK 0x40211810 [0x000007ec]
 | STACK 0x40211814 [0x00000000]
 | STACK 0x40211818 [0x00000000]
 | STACK 0x4021181c [0x00000000]
 | STACK 0x40211820 [0x00000000]
 | STACK 0x40211824 [0x00000000]
 | STACK 0x40211828 [0x00000000]
 | STACK 0x4021182c [0x00000000]

noch ein ret???

 | STACK 0x40211800 [0x123890ab] magic number of heap
 | STACK 0x40211804 [0x40210ff4]
 | STACK 0x40211808 [0x123890ab]
 | STACK 0x4021180c [0x00000001]
 | STACK 0x40211810 [0x000007ec]
 | STACK 0x40211814 [0x00000000]
 | STACK 0x40211818 [0x00000000]
 | STACK 0x4021181c [0x00000000]
 | STACK 0x40211820 [0x00000000]
 | STACK 0x40211824 [0x00000000]
 | STACK 0x40211828 [0x00000000]
 | STACK 0x4021182c [0x00000000]
 | STACK 0x40211830 [0x00000000]
 | STACK 0x40211834 [0x00000000]
 | STACK 0x40211838 [0x00000000]
 | STACK 0x4021183c [0x00000000]

(0).[41500949] ??? (physical address not available) // hier scheint schon alles zu spät

nun kommt noch der fault_handler ....


void test()
{
      syscall_puts("5u");syscall_puts("5u");syscall_puts("5u");
      nop();
}
task_t* task5 = create_task (syscall_test, 0);
Irgendwie noch nicht zielführend.   :|
Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 22. May 2009, 00:13
| STACK 0x40211710 [0x0000839b] ?? nicht in kernel.map
Da musst du die größte Adresse suchen, die kleiner ist als diese (oder gleich). Würdest du nicht ein flaches Binärformat nutzen, könntest du auch den Kernel disassemblieren und diese Adresse suchen. (Wenn du ihn jetzt einfach disassemblierst, dann musst du halt irgendwie so den Funktionsanfang finden und in kernel.map nachschauen.)
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 00:29
Zitat
0x0000839b - Da musst du die größte Adresse suchen, die kleiner ist als diese (oder gleich).
kernel.map:
Zitat
0x000083a0                _test1
0x0000836e                _test
0x00008330                _surprise

Das wäre dann also test?
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 00:43
@Porkchicken: Vielleicht kannst Du mir taktisch weiter helfen. Ich möchte echten User Mode (Ring 3) umsetzen. Das klappt bisher nicht. Ich habe es versucht, indem ich mittels create_task andere Funktionen als syscall_Funktion aufrufe, aber da beißen sich bisher zwei Stränge (syscall und create_task), die beide mit interrupts und iret arbeiten. Bei einer Verschachtelung hakt es dann (siehe oben). Rufe ich einen Syscall mit while(TRUE)-Schleife auf, dann bleibt er dabei hängen. kein Task-Switch mehr.

Eine prinzipielle Frage:
Ich habe Funktionen in create_task mit Deiner Hilfe wie folgt zum Laufen gebracht:
void moo()
{
  while(TRUE)
  {
      settextcolor(2,0);
      printformat("MOO %d", getpid()); // <-- cow
      settextcolor(15,0);
  }
}
Aber ich möchte nun auch das machen:
void moo()
{
      settextcolor(2,0);
      printformat("MOO %d", getpid()); // <-- cow
      settextcolor(15,0);
}
Als erstes passiert das:
Zitat
11111111111111111111111111111111111111111111111MOO 2
Page Fault (page not present) at 123890ABh - EIP: 123890ABh

err_code: 00000000h address(eip): 123890ABh
edi: 00000000h esi: 00000000h ebp: 00000000h eax: 0000000Fh ebx: 00000000h ecx: 000B8000h edx: 0000000Fh cs: 00000008h ds: 00000010h es: 00000010h fs: 00000010h gs 00000010h ss 123890ABh
int_no 14 eflags 00010206h useresp 40204FF4h
Das hat noch nichts mit User Mode zu schaffen, sondern immer noch Multitasking. Das Problem ist wohl das ret von moo(), das offensichtlich das Tasking stört.

Taljeth meint, ich solle mit einem Syscall zurück springen. Ein syscall ist bei mir der Aufruf einer Kernel-Fkt. aus Ring 3. Dieser syscall_... müsste doch das ret von moo() "aufhalten", indem er in eine Schleife (Ersatz für die while-Schleife) geht, bis der Dispatcher/Scheduler zuschlägt. Später noch informieren, dass zur Umschaltung bereit. Sehe ich das richtig?

Was macht ret?
Near Return: holt EIP vom Stack und springt dorthin (aber was ist das in diesem Fall? Was kommt nach einem einmaligen moo()in create_task? Irgendwie fehlt der Landeflughafen, bisher werden die Tasks im freien Flug geswitcht.  :-D ), CS unverändert.

Hier ist mein Stand: http://www.henkessoft.de/OS_Dev/Downloads/36y_user.zip

Dieses Experimentierfeld möchte ich nur nutzen, um den user mode zu testen.
Ich hänge leider
1) wegen eines Fehlers im User Mode (?)
2) zwischen syscall und create_task
übel fest.
Es wäre nett, wenn mir jemand einen Schubs in die richtige Richtung gegen könnte. 





Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 22. May 2009, 11:14
Das wäre dann also test?
Wenn du beachtet hast, dass die Liste nicht sortiert ist, ja.

Rufe ich einen Syscall mit while(TRUE)-Schleife auf, dann bleibt er dabei hängen. kein Task-Switch mehr.
Das ist nicht unerwartet: Im Kernel sind Interrupts deaktiviert -> kein präemptiver Task-Switch möglich. Lösung: Keine Endlosschleifen im Kernel. Du musst da auch nicht warten. Eine wartende Funktion sollte selbst den Task-Switch (Stack-Wechsel beim Verlassen des syscalls) initiieren können.

Zitat
Taljeth meint, ich solle mit einem Syscall zurück springen. Ein syscall ist bei mir der Aufruf einer Kernel-Fkt. aus Ring 3. Dieser syscall_... müsste doch das ret von moo() "aufhalten", indem er in eine Schleife (Ersatz für die while-Schleife) geht, bis der Dispatcher/Scheduler zuschlägt. Später noch informieren, dass zur Umschaltung bereit. Sehe ich das richtig?
Nein, der syscall kann den Task-Wechsel direkt vornehmen, und den esp-Wert vom neuen Task zurückgeben. Dann wird nach dem Syscall direkt zum nächsten Task gesprungen. Dazu braucht der Stub vom Syscall Handler die selbe Struktur wie der von den IRQs.

Der Syscall, der deinen Task beendet, sollte diesen Task aus der Liste der laufenden Tasks austragen, und zum nächsten Task in der Liste zurückkehren.

Zitat
Was macht ret?
Near Return: holt EIP vom Stack und springt dorthin (aber was ist das in diesem Fall? Was kommt nach einem einmaligen moo()in create_task?
Mit ret solltest du beim Taskwechsel nichts am Hut haben. Es sollte einfach kein ret ausgeführt werden, dass Probleme machen könnte.
Titel: Re: Programm laden und ausführen
Beitrag von: FreakyPenguin am 22. May 2009, 11:24
So, ich versuche mich jetzt auch mal daran, das zu erklären. Ich bin zwar nicht PorkChicken, aber vielleicht hilfts ja trotzdem. ;-)

Also gehen wir das Ganze mal Schritt für Schritt durch, ausgegangen vom folgenden einfachen Usermode-Programm:
int main() {
    puts("Hallo Welt!");
    return 0;
}

Nun wie schon von taljeth erwähnt ist die main()-Funktion nicht der direkte Einsprungspunkt in den Task. Dafür hast du in der libc eine Funktion _start:
void start() {
    int result;
    // Hier würde man normalerweise noch sowas machen wie Parameter vom Kernel holen und parsen, aber das lassen wir der Einfachheit halber mal weg.

    result = main();
    syscall_exit(result);
}

Dabei ist es wichtig, dass die Funktion syscall_exit() nie zurückkehrt von einem Aufruf. Es handelt sich dabei ja, wie der Name sagt, um einen Syscall, der als Interrupt umgesetzt wird.

Wenn nun der Kernel diesen Syscall erhält, entfernt er den aktuellen Task aus dem Scheduler, und zestört (Speicher des Tasks und interne Strukturen werden freigegeben) ihn. Danach muss er logischerweise einen neuen Task schedulen, da der bisherige nicht mehr existiert.

In tyndur geht das so, dass der Interrupt-Handler eh immer den nächsten zu benutzenden Stackpointer zurueckgibt, und auch cr3 für den neuen Task lädt, wenn er gewechselt hat. Damit muss der Syscall-Handler im Wesentlichen nur current_task neu setzen und der Interrupthandler übernimmt dann den Rest.

So, ich hoffe, dass ich dich damit nicht noch mehr verwirrt habe.

Edit: Bäh, der PorkChicken war schneller. Aber ich lasse das jetzt trotzdem mal stehen. ;-)
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 11:49
Zitat
Danke an euch beide! Der Stub vom Syscall Handler u. Exceptions braucht also die gleiche Struktur wie der von den IRQs.
Ich denke, dies ist der erste notwendige Schritt, ist mir inzwischen auch klar geworden, dass diese Unsymmetrie (Exception und SW-Interrupt über fault_handler und IRQ0...IRQ15 über irq_handler noch dazu mit unterschiedlichem stub - habe ich von Bran's Tutorial geerbt) Probleme macht, denn, wenn ich es richtig sehe, soll sowohl ein syscall als auch exceptions und die IRQ0...15 einen task_switch durchführen können. 

EDIT: Ich habe die beiden stubs und irq_handler (da läuft IRQ0 rein) und fault_handler (da läuft der syscall rein) angeglichen. irq_tail und fault_tail sind nun exakt gleich. create_task setzt den eip des neuen tasks auf irq_tail. syscall_... schaltet nun jeweils einen task weiter. Eine Runde 1->6->1->4 hat auch geklappt. Hier bricht es mit PF ab. Interessant.

Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 15:16
Ich habe mich jetzt mal vorgetastet an die kritische Stelle, an der es beim zweiten Durchgang schief geht:

0x00008294  _irq_tail   (IRQ0...15)
0x000081f2  _fault_tail (exceptions u. sw-interrupt 7fh für syscalls



(0) [0x000082a6]  iretd      <== wenn ich es richtig sehe, zweite Hälfte vom Software-Interrupt  (fault_handler)             

 | STACK 0x4020d7f0 [0x0000d4fc]
 | STACK 0x4020d7f4 [0x00000008]
 | STACK 0x4020d7f8 [0x00000202]
 | STACK 0x4020d7fc [0x00000000]
 | STACK 0x4020d800 [0x123890ab]
 | STACK 0x4020d804 [0x4020cff4]
 | STACK 0x4020d808 [0x123890ab]
 | STACK 0x4020d80c [0x00000001]
 | STACK 0x4020d810 [0x000007f8]

 (0) [0x0000d4fc]  pop ebp                   
 (0) [0x0000d4fd]  ret

 | STACK 0x4020d800 [0x123890ab]
 | STACK 0x4020d804 [0x4020cff4]
 | STACK 0x4020d808 [0x123890ab]
 | STACK 0x4020d80c [0x00000001]
 | STACK 0x4020d810 [0x000007f8]

(0).[41476341]  ???  (physical address not available)

1-2-3-5-1-2-3!5  (task 4 und 6 nicht aktiviert)

Dies ist die Stelle zwischen 3 und 5, an der es - beim zweiten Durchgang - schief geht.

void f3()
{
  while(TRUE)
  {
      settextcolor(4,0);
      printformat("%d", getpid());
      settextcolor(15,0);
  }
}

void test5()
{
}

    task_t* task2 = create_task (f2,0);           
    task_t* task3 = create_task (f3,0);           
    task_t* task5 = create_task (syscall_test5,0);


Das d4fc muss doch falsch sein!

0x0000d4f2   _syscall_test5  (kernel.map)

objdump -D syscall.o > syscall.txt

00000012 <_syscall_test5>:
  12: 55                    push   %ebp
  13: 89 e5                mov    %esp,%ebp
  15: b8 01 00 00 00        mov    $0x1,%eax
  1a: cd 7f                int    $0x7f
  1c: 5d                    pop    %ebp
  1d: c3                    ret   

Dieses int $0x7f ist mein syscall-Software-Interrupt.

Dieser dürfte doch aber keinesfalls dorthin zurück, oder sehe ich das falsch?

Vielleicht läuft in fault_handler etwas schief und der return-Value kommt nicht an? (Habe es separat mit einem nop() davor getestet, kommt an und kehrt im geordneten Fall zu _fault_tail zurück):
ULONG fault_handler(ULONG esp)
{
    ULONG retVal;
    struct regs* r = (struct regs*)esp;

    if(!pODA->ts_flag)
        retVal = esp;
    else
        retVal = task_switch1(esp); //new task's esp

    if (r->int_no < 32) //exception
    {
        settextcolor(4,0);

        if (r->int_no == 6 || r->int_no == 1) //Invalid Opcode
        {...}

        if (r->int_no == 14) //Page Fault
        {...}

        printformat("\n");
        printformat("%s >>> Exception. System Halted! <<<", exception_messages[r->int_no]);
        for (;;);
    }

    if (r->int_no == 127) //sw-interrupt syscall
    {
        syscall_handler(r);
    }
    return retVal;
}

Dieser syscall_handler leitet ja nach syscall_puts weiter, wenn ich das richtig sehe.  :?

So sieht dieser aus:
void syscall_handler(struct regs* r)
{
    // Firstly, check if the requested syscall number is valid. The syscall number is found in EAX.
    if( r->eax >= num_syscalls )
        return;

    void* addr = syscalls[r->eax]; // Get the required syscall location.

    // We don't know how many parameters the function wants, so we just push them all onto the stack in the correct order.
    // The function will use all the parameters it wants, and we can pop them all back off afterwards.
    int ret;
    asm volatile (" \
      push %1; \
      push %2; \
      push %3; \
      push %4; \
      push %5; \
      call *%6; \
      pop %%ebx; \
      pop %%ebx; \
      pop %%ebx; \
      pop %%ebx; \
      pop %%ebx; \
      " : "=a" (ret) : "r" (r->edi), "r" (r->esi), "r" (r->edx), "r" (r->ecx), "r" (r->ebx), "r" (addr));
    r->eax = ret;
}
Hier der objdump von _syscall_handler zur Sicherheit wegen inline-Assembler:
00000032 <_syscall_handler>:
  32: 55                    push   %ebp
  33: 89 e5                mov    %esp,%ebp
  35: 57                    push   %edi
  36: 56                    push   %esi
  37: 53                    push   %ebx
  38: 83 ec 1c              sub    $0x1c,%esp
  3b: 8b 55 08              mov    0x8(%ebp),%edx
  3e: 8b 42 28              mov    0x28(%edx),%eax
  41: 3b 05 90 00 00 00    cmp    0x90,%eax
  47: 73 31                jae    7a <_syscall_handler+0x48>
  49: 8b 04 85 94 00 00 00 mov    0x94(,%eax,4),%eax
  50: 89 45 f0              mov    %eax,0xfffffff0(%ebp)
  53: 8b 4a 10              mov    0x10(%edx),%ecx
  56: 8b 5a 14              mov    0x14(%edx),%ebx
  59: 8b 72 20              mov    0x20(%edx),%esi
  5c: 8b 7a 24              mov    0x24(%edx),%edi
  5f: 8b 42 1c              mov    0x1c(%edx),%eax
  62: 8b 55 f0              mov    0xfffffff0(%ebp),%edx
  65: 51                    push   %ecx
  66: 53                    push   %ebx
  67: 56                    push   %esi
  68: 57                    push   %edi
  69: 50                    push   %eax
  6a: ff d2                call   *%edx
  6c: 5b                    pop    %ebx

Ich denke, die Analyse ist ausreichend. Ich bin mir nur noch nicht sicher, wo der entscheidende Fehler liegt, denn beim ersten Durchgang läuft es ja durch.

Zitat
...
22222222222222222222222222222222222222222222222222222222222222222222222222222222
22222222222222222222222222222222222222222222222222222222222222222222222222222222
22222222222222222233333333333333333333333333333333333333333333333333333333333333
33333333333333333333333333333333333333333333333333333333333333333333333333333333
33333333333333333333333333333333333333333333333333333333333333333333333333333333
33333333333333333333333333333333333333333333333333333333333333333333333333333333
33333333333333333333333333333333333333333333333333333333333333333333333333333333
33333111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111111111111111111111111111111111111111111111111111111111111111111111
11111111111111122222222222222222222222222222222222222222222222222222222222222222
22222222222222222222222222222222222222222222222222222222222222222222222222222222
22222222222222222222222222222222222222222222222222222222222222222222222222222222
22222222222222222222222222222222222222222222222222222222222222222222222222222222
22222222222222222222222222222222222222222222222222222222222222222222222222222222
22333333333333333333333333333333333333333333333333333333333333333333333333333333
33333333333333333333333333333333333333333333333333333333333333333333333333333333
33333333333333333333333333333333333333333333333333333333333333333333333333333333
33333333333333333333333333333333333333333333333333333333333333333333333333333333
333333333333333333333333333333333333333333333333333333333333333333333Page Fault
(page not present) at 123890ABh - EIP: 123890ABh

Page Fault >>> Exception. System Halted! <<<
1 - 2 - 3 - [5] - 1 - 2 - 3 ! [5]
(5 zeigt nichts an)

Hier ist der gesamte Code: http://www.henkessoft.de/OS_Dev/Downloads/20090522_36z.zip
Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 22. May 2009, 16:31
Das d4fc muss doch falsch sein!

0x0000d4f2   _syscall_test5  (kernel.map)

objdump -D syscall.o > syscall.txt
Das ist die falsche Datei. Du musst den Kernel disassemblieren nicht die Objektdateien, wenn du Kerneladressen überprüfen willst.
Zitat
Dieser syscall_handler leitet ja nach syscall_puts weiter, wenn ich das richtig sehe.  :?
Nein.
ULONG num_syscalls  = 3;
static void* syscalls[3] =
{
    &puts, <---------- 0
    &test5, <----------- 1
    &test6 <-------------- 2
};
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 16:47
Da ich den fault_handler als "Übeltäter" im Visier habe:
ULONG fault_handler(ULONG esp)
{
    ULONG retVal;
   
    //...

    if (r->int_no == 127) //sw-interrupt syscall
    {
        syscall_handler(r);
    }
    nop();///TEST for debugger
    return retVal;
}

Mit einem Breakpoint auf nop(), dann muss ich jeweils noch einige Steps gehen bis zum übernächsten ret (eins gehört ja zu nop() ).

Nach dem ersten "Hello, user world": 0x000081f2  _fault_tail

beim zweiten Halt (in task 1 durch IRQ0): 0x000097a4  (0x00009784  _irq_handler, nächste kleinere Adresse in kernel.map)

beim dritten Halt (am Ende von task 2 durch IRQ0): 0x000097a4

beim vierten Halt (am Ende von task 3 durch IRQ0): 0x000097a4

(task4 wurde nicht aktiviert)

beim fünften Halt (Einstieg in task 5 durch syscall_test5): 0x000093ca (0x000093a6   _fault_handler, nächste kleinere Adresse in kernel.map)

beim sechsten Halt (am Ende von task 5 durch ??): 0x000081f2 _fault_tail in kernel.map

Umstieg 5 -> 1 klappt!

beim siebten Halt (am Ende von task 1 durch IRQ0): 0x000097a4

beim achten Halt (am Ende von task 2 durch IRQ0): 0x000097a4

beim neunten Halt (am Ende von task 3 durch IRQ0): 0x000097a4

beim zehnten Halt (Einstieg in task 5 durch syscall_test5): 0x000093ca (0x000093a6   _fault_handler, nächste kleinere Adresse in kernel.map)

-> Pagefault

Hier die beiden Stacks beim Einstieg in task 5 zum Vergleich:

 | STACK 0x4020d774 [0x000093ca]
 | STACK 0x4020d778 [0x4020d7bc]
 | STACK 0x4020d77c [0x00000000]
 | STACK 0x4020d780 [0x00000000]


 | STACK 0x4020d77c [0x000093ca]
 | STACK 0x4020d780 [0x4020d7c4]
 | STACK 0x4020d784 [0x00009563]
 | STACK 0x4020d788 [0x00000000]

Aus irgendwelchen düsteren Gründen ist der Stack nun zwei ULONG-Einheiten länger.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 17:03
Zitat
Du musst den Kernel disassemblieren nicht die Objektdateien, wenn du Kerneladressen überprüfen willst.
Die Adressen finde ich doch in kernel.map. Ich wollte nur in der Objektdatei nachschauen, wie wirklich in Assembler übersetzt wurde.

ld -T kernel.ld kernel.o isr.o ckernel.o video.o flush.o gdt.o idt.o isrs.o irq.o util.o math.o timer.o keyboard.o process.o ordered_array.o paging.o kheap.o descriptor_tables.o task.o fs.o initrd.o syscall.o  -o ckernel.bin -Map kernel.map

objdump -D ckernel.bin >ckernel.txt
File format not recognized

ndisasmw ckernel.bin >ckernel.txt (erfolgreich)
Darin finde ich aber nichts, alles am Stück.  :-D

Wie lautet der Hero Debugger Befehl?  :-)

Mit meiner Methode muss ich eben noch relativ selbst abzählen.

Ich sehe vor allem den entscheidenden Fehler noch nicht. Lasst ihr mich einfach hängen oder seht ihr ihn auch nicht? Macht hier bloß keinen auf Oberlehrer.  :-(
Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 22. May 2009, 17:23
Ach die eigentlich entscheidene Frage, wurde immer noch nicht beantwortet ... Also ss und esp werden auf dem Stack vor cs gepusht. Steht aber auch in den Intel Manuals Kapitel 5.12 oder so. Die sind bei deinem Stand dringend angebrachte Lektüre.

Ah, ja der Oberlehrer-Kommentar trifft zu. Ich bin hier der.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 17:40
Zitat
Also ss und esp werden auf dem Stack vor cs gepusht.
Ja, das liegt sogar ausgedruckt neben mir unter einem Stapel von Büchern:
eflags, cs, eip, error code ohne Privileg-Wechsel
ss, esp, eflags, cs, eip, error code mit Privileg-Wechsel
(Wer denkt sich nur so einen Mist aus?)

Den User Mode habe ich ja noch gar nicht aktiviert? Da knallt es sowieso immer, ist alles noch kernel privileg, also ring 0.

Wenn Du mir jetzt noch einen Tipp geben könntest, wo das zusätzlich rein muss. Nein, stopp! bei create_task(...), wenn der Parameter privileg  3 ist, richtig?


Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 22. May 2009, 17:50
Ja.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 17:52
Nur zur Sicherheit, weil das immer so lax gehandhabt wird:
sind das esp3 und ss3 oder esp0 und ss0?
Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 22. May 2009, 17:59
In ss muss ein User-Mode-Selektor und esp muss auf den User-Mode-Stack zeigen. Also esp3 und ss3. (Auch wenn glaub ich die Namen ungünstig sind, weil es nicht die Werte in der TSS sind.)
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 18:08
Bisher habe ich nur einen Kernel-Stack. Kann ich den hier auch für den User-Stack nehmen oder brauche ich auch noch einen zusätzlichen user-stack? SS wäre dann 0x23 (gate 4 mit Privileg 3). 

Aber der Fehler, den ich gefunden habe, lag nicht daran, denn die Tasks waren alle Privileg 0.

typedef struct task
{
    int id;                           // Process ID.
    ULONG esp, ebp;                   // Stack and base pointers.
    ULONG eip;                        // Instruction pointer.
    ULONG ss;
    page_directory_t* page_directory; // Page directory.
    ULONG kernel_stack;               // Kernel stack location.
    struct task* next;                // The next task in a linked list.
} task_t;


task_t* create_task(void* entry, UCHAR privilege)
{
    cli();
    page_directory_t* directory = clone_directory(current_directory);
    task_t* new_task            = (task_t*)k_malloc(sizeof(task_t),0,0);

    new_task->id  = next_pid++;
    new_task->page_directory = directory;
    new_task->kernel_stack   = k_malloc(KERNEL_STACK_SIZE,1,0)+KERNEL_STACK_SIZE;
    new_task->next = 0;

    task_t* tmp_task = (task_t*)ready_queue;
    while (tmp_task->next)
        tmp_task = tmp_task->next;
    tmp_task->next = new_task; // ... and extend it

    ULONG* kernel_stack = (ULONG*) new_task->kernel_stack;

    *(--kernel_stack) = 0x0202; // eflags = interrupts activated and iopl = 0

    ULONG code_segment=0x08, data_segment=0x10;
    if(privilege == 0) code_segment = 0x08;
    if(privilege == 3)
    {
        *(--kernel_stack) = new_task->ss = 0x23; // ss
        *(--kernel_stack) = new_task->esp = new_task->kernel_stack; // esp
        code_segment = 0x1B; // 0x18|0x3=0x1B
    }

    *(--kernel_stack) = code_segment; // cs

    *(--kernel_stack) = (ULONG)entry; // eip

    *(--kernel_stack) = 0; // error code
    *(--kernel_stack) = 0; // interrupt nummer

    // general purpose registers w/o esp
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;

    // data segment registers
    if(privilege == 0) data_segment = 0x10;
    if(privilege == 3) data_segment = 0x23; // 0x20|0x3=0x23

    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;

    //setup task_t
    new_task->ebp = 0xd00fc0de; // test value
    new_task->kernel_stack = (ULONG)kernel_stack;
    new_task->esp          = (ULONG)kernel_stack;
    new_task->eip = (ULONG)irq_tail;
    new_task->ss  = data_segment;

    //setup TSS
    tss_entry.ss0   = 0x10;
    tss_entry.esp0  = new_task->kernel_stack;
    tss_entry.ss    = data_segment;
    tss_entry.esp   = new_task->esp;
    tss_entry.cr3   = new_task->page_directory->physicalAddr;

    sti();
    return new_task;
}
So o.k.? oder noch ein extra user-stack mit kmalloc?

Damit:

Versuch 1:
task_t* task5 = create_task (syscall_test5,0);  =>
Beim zweiten Durchgang:
Zitat
333333333333333333333333333333333333333333333333333333333333333333333
Page Fault (page not present) at 123890ABh - EIP: 123890ABh
Warum sollte sich auch etwas ändern?
versuch2:
task_t* task5 = create_task (syscall_test5,3); =>
beim ersten Durchgang, also sofort bei task 5:
Zitat
33333333333333333333333333333333333333333333333333333333333333333333333333333333
33333
General Protection Fault >>> Exception. System Halted! <<<

Intel Kap. 5.12 war also nicht der Grund. Dennoch guter Hinweis bezüglich user mode. Hatte ich erst mal weg gelassen, weil ich das mit dem Parameter privilege noch nicht eingebaut hatte.

Brauche ich hier schon einen user stack oder ist der hier identisch mit dem kernel-stack? Muss das in TSS->esp3 TSS->ss3 eingetragen werden?

Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 22. May 2009, 19:17
Also ss und esp werden auf dem Stack vor cs gepusht.
Stimmt natürlich nicht. Vor eflags werden die auf den Stack gelegt.
Titel: Re: Programm laden und ausführen
Beitrag von: FreakyPenguin am 22. May 2009, 19:20
Brauche ich hier schon einen user stack oder ist der hier identisch mit dem kernel-stack? Muss das in TSS->esp3 TSS->ss3 eingetragen werden?
Sobald du Ring3-Prozesse hast, brauchst du einen zusätzlichen Stack. Dieser sollte dann auch mit User-Privilegien gemappt sein, sonst fliegen die Pagefaults frischfrölich. ;-)
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 19:28
Zitat
eflags, cs, eip, error code ohne Privileg-Wechsel
ss, esp, eflags, cs, eip, error code mit Privileg-Wechsel
Ich schreibe es richtig, und mache es dann doch so wie du es sagst.  :-D
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 19:46
Jetzt habe ich noch mehr Probleme, weil das totale Chaos zwischen esp0, esp3, esp(?) und kernel_stack ausgebrochen ist. Liegt an meiner beschissenen Task-Struktur, die ich von JM übernommen habe. Nur noch Reset.

Ich unterscheide jetzt zwischen:
esp0 = kernel_stack
esp3 = user_stack
ss0   = kernel_stack_segment
ss3   = user_stack_segment

Fangen wir mal am task_switch an, denn da haut es jetzt raus:
Was ist das für ein esp der da herum gereicht wird? esp0 oder esp3?

Wie sieht eure Struktur aus?
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 19:50
Zitat
Sobald du Ring3-Prozesse hast, brauchst du einen zusätzlichen Stack. Dieser sollte dann auch mit User-Privilegien gemappt sein
Danke für den Hinweis.

Das Problem ist, dass die grundlegenden Fehler (im MultiTasking und im UserMode) bei mir noch nicht gefunden sind. Das zeigt mir, dass meine Gesamtstruktur noch nicht tauglich ist. Aber was soll's, der Weg ist das Ziel.  :-P

Ich lese jetzt Intel Manual, vielleicht hilft das weiter:

Zitat
When the processor performs a call to the exception- or interrupt-handler procedure:

•If the handler procedure is going to be executed at a numerically lower privilege level, a stack switch occurs.

When the stack switch occurs:

a.The segment selector and stack pointer for the stack to be used by the handler are obtained from the TSS for the currently executing task. On this new stack, the processor pushes the stack segment selector and stack pointer of the interrupted procedure.

b.The processor then saves the current state of the EFLAGS, CS, and EIP registers on the new stack (see Figures 5-4).

c.If an exception causes an error code to be saved, it is pushed on the new stack after the EIP value.

•If the handler procedure is going to be executed at the same privilege level as the interrupted procedure:

a.The processor saves the current state of the EFLAGS, CS, and EIP registers on the current stack (see Figures 5-4).

b.If an exception causes an error code to be saved, it is pushed on the current stack after the EIP value.

Also beim Wechsel von 3 nach 0 benötigt man einen Kernel-Stack. Das lege ich in create_task an und baue es passend auf. Für einen unterbrochenen Prozess in Ring 3 legt man wohl ss3 und esp3 ("stack segment selector and stack pointer of the interrupted procedure") vor eflags auf den Kernel-Stack.   Woher kommen ss3 und esp3? "... are obtained from the TSS for the currently executing task". Hmmm?
Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 22. May 2009, 21:14
Aus dem TSS werden nur ss0 und esp0 ausgelesen.

Das auf den Stack Legen von ss(3) und esp(3) machst du für ein iret, nicht für den Aufruf des Interrupts.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 22. May 2009, 21:29
Ich bin zunächst zu einer funktionierenden Version mit Endlos-while-Schleife beim Aufruf der Funktion zurück gekehrt, um den user mode mit der neuen Erkenntnis mit ss und esp vor eflags zu testen, und es geht! Ich bin sogar etwas verblüfft.

void test5()
{
  while(TRUE)
  {
      syscall_puts("5");
      //puts("5");
  }
}

    task_t* task2 = create_task (f2,0);           // function moo in kernel
    task_t* task3 = create_task (f3,0);           // function baa in kernel
    task_t* task4 = create_task2(f4,0,4,2);       // function ... in kernel
    task_t* task5 = create_task (test5,3);    // <=== user mode !!!
    task_t* task6 = create_task2(test6,0,ramdisk_start+(ULONG)&prog_start-(ULONG)&file_data_start,0);

task_t* create_task(void* entry, UCHAR privilege)
{
    cli();
    page_directory_t* directory = clone_directory(current_directory);
    task_t* new_task            = (task_t*)k_malloc(sizeof(task_t),0,0);

    new_task->id  = next_pid++;
    new_task->page_directory = directory;
    new_task->kernel_stack   = k_malloc(KERNEL_STACK_SIZE,1,0)+KERNEL_STACK_SIZE;
    new_task->next = 0;

    task_t* tmp_task = (task_t*)ready_queue;
    while (tmp_task->next)
        tmp_task = tmp_task->next;
    tmp_task->next = new_task; // ... and extend it

    ULONG* kernel_stack = (ULONG*) new_task->kernel_stack;

    ULONG code_segment=0x08, data_segment=0x10;

    if(privilege == 3)
    {
        //Intel 3A Chapter 5.12
        *(--kernel_stack) = new_task->ss = 0x23;    // ss
        *(--kernel_stack) = new_task->kernel_stack; // esp0
        code_segment = 0x1B; // 0x18|0x3=0x1B
    }

    *(--kernel_stack) = 0x0202; // eflags = interrupts activated and iopl = 0
    *(--kernel_stack) = code_segment; // cs
    *(--kernel_stack) = (ULONG)entry; // eip
    *(--kernel_stack) = 0; // error code

    *(--kernel_stack) = 0; // interrupt nummer

    // general purpose registers w/o esp
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;
    *(--kernel_stack) = 0;

    if(privilege == 3) data_segment = 0x23; // 0x20|0x3=0x23

    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;
    *(--kernel_stack) = data_segment;

    //setup TSS
    tss_entry.ss0   = 0x10;
    tss_entry.esp0  = new_task->kernel_stack;
    tss_entry.ss    = data_segment;

    //setup task_t
    new_task->ebp = 0xd00fc0de; // test value
    new_task->esp = (ULONG)kernel_stack;
    new_task->eip = (ULONG)irq_tail;
    new_task->ss  = data_segment;

    sti();
    return new_task;
}

Das läuft, aber etwas seltsam. Die 5 wird immer nur einmal ausgegeben.
Offenbar passiert da etwas seltsames, muss ich im Debugger genau sehen.  Denn dieses einmalige Ausführen einer Funktion möchte ich ja gerade erreichen.

EDIT: Habe mir das im Debugger angeschaut. Als Rücksprungadresse des syscalls im Interrupt ist test6 enthalten, weil ich ja beim syscall z.Z. automatisch einen direkten Taskwechsel habe.  :-)


Streiche ich das while, dann klappt es mindestens ein Mal, dann kommt aber wieder PF mit dem magic code, wie bereits vorne mit dem Debugger intensiv untersucht, aber leider noch nicht gelöst.
Zitat
44444444444444444444444444444444444444444444444444444444444444444444444444444444
45  666666666666666666666666666666666666666666666666666666666666666666666666666666

Dass es sich wirklich um User Mode handelt, sieht man, wenn man nicht syscall_puts, sondern puts verwendet:
Zitat
44444444444444444444444444444444444444444444444444444444444444444444444444444444
4444444444444444444
Page Fault ( read-only - write operation user-mode) at 000B8C06h - EIP: 00008986h

Was mich verblüfft, ist, dass ich test5(...) im Kernel aufrufen kann. ich hatte extra syscall_test5 produziert.  Aber offensichtlich erfolgt die Umschaltung auf Ring 3 etwas später in create_task, so dass man test5 als Einsprung-Adresse verwenden kann.

Endlich user mode!  :-)
Code: http://www.henkessoft.de/OS_Dev/Downloads/20090522_43_user_mode.zip


Das Umschalten des Tasks durch syscall kann man natürlich leicht unterbinden, was ich auch inzwischen gemacht habe:
ULONG fault_handler(ULONG esp)
{
    ULONG retVal;
    struct regs* r = (struct regs*)esp;

    if(!pODA->ts_flag)
        retVal = esp;
    else
    {
        if(r->int_no == 127) //syscall
            retVal = esp;
        else
            retVal = task_switch1(esp); //new task's esp
    }
   
    //...
   
    if (r->int_no == 127) //sw-interrupt syscall
    {
        syscall_handler(r);
    }
    return retVal;
}
Zitat
4444444444444444444444444444444444444444444444444444444444444444444444444444444
5555555555555555555555555555555555555555555555555555555555555555555555555555555
555555555555555555555555555555555555555555555555
66666666666666666666666666666666666666666666666666666666666666666666666666666666
Titel: Re: Programm laden und ausführen
Beitrag von: ChristianF am 05. June 2009, 13:03
So.
Ich habe soeben das erste mal ein ELF-Programm ausgeführt und mein OS ist nicht abgestürzt. *g*
 
Allerdings habe ich noch ein kleines Problem.
Und zwar muss ich ja bei ELF-Dateien die ladbaren Abschnitte (.text und .data (momentan)) an die gegebene virtuelle Adresse laden, was momentan 0xa0000000 ist.
Dazu muss ich ja die Anzahl der benötigten Pages ermitteln und irgendwie stehe ich hier grad aufm Schlauch. Folgender Code wird momentan genutzt, da der andere nicht geht:
u32int_t num_needed_pages = ((program_header->p_memsz + (PAGE_SIZE - program_header->p_memsz)) / PAGE_SIZE);Allerdings kann ich dies nur benutzen, wenn die Größe kleiner als eine Page ist. Mein Ansatz, der nicht so will, wie ich will, sieht so aus:
u32int_t num_needed_pages = ((program_header->p_memsz + (program_header->p_memsz % PAGE_SIZE)) / PAGE_SIZE);Das Ergebnis was dann letzten endes raus kommt ist 0, warum auch immer...
 
Was ist hier bloß falsch, ich sehe es einfach nicht...
 
Gruß Christian
 
Anmerkung:
program_header->p_memsz beinhaltet 0x1b. Führe ich nur "program_header->p_memsz % PAGE_SIZE" aus und gebe das Ergebnis aus, bekomme ich 0x1b, was ja nicht korrekt ist.
Titel: Re: Programm laden und ausführen
Beitrag von: FreakyPenguin am 05. June 2009, 13:16
Anmerkung:
program_header->p_memsz beinhaltet 0x1b. Führe ich nur "program_header->p_memsz % PAGE_SIZE" aus und gebe das Ergebnis aus, bekomme ich 0x1b, was ja nicht korrekt ist.

Warum ist das falsch? Ich nehme mal an, PAGE_SIZE ist grösser als 0x1b, dann stimmt das doch genau?
Titel: Re: Programm laden und ausführen
Beitrag von: Jidder am 05. June 2009, 13:44
wenn X % PAGE_SIZE == 0
dann
    Gerundet := X                                             (* X ist bereits Vielfaches von PAGE_SIZE *)
sonst
    Gerundet := X + (PAGE_SIZE - X % PAGE_SIZE)               (* Den fehlenden Rest addieren*)

Anzahl Seiten := Gerundet / PAGE_SIZE;

Oder wenn du das ohne Verzweigung machen willst:
Gerundet := (X + PAGE_SIZE - 1) and not (PAGE_SIZE - 1)       (* PAGE_SIZE muss eine Potenz von 2 sein *)
Anzahl Seiten := Gerundet / PAGE_SIZE;
Die Addition von PAGE_SIZE - 1 sorgt dafür, dass wir genau dann in der nächsten Seite landen, wenn wir runden müssen, und die bitweise "and not"-Operation, löscht die unteren 12 Bits. Das kannst du zum besseren Verständnis mal mit ein paar Werten in der Nähe von PAGE_SIZE durchspielen, wie z.B. 4095, 4096, 4097.
Titel: Re: Programm laden und ausführen
Beitrag von: ChristianF am 05. June 2009, 14:01
Verdammt...
ich wusste doch, dass ich irgendwo einen Denkfehler habe. Jetzt funktioniert alles, vielen Dank.
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 07. June 2009, 13:14
Kann ich das mit dem Laden der elf-Datei bei dir nachlesen? Link?
Titel: Re: Programm laden und ausführen
Beitrag von: ChristianF am 08. June 2009, 10:10
Kann ich das mit dem Laden der elf-Datei bei dir nachlesen? Link?
Mmmmhhh...
Verwendet habe ich die Seite mit Informationen zum ELF-Format von der Lowlevel-Wiki: http://lowlevel.brainsware.org/wiki/index.php/ELF.
Dann hat mir noch der folgende Thread geholfen: http://lowlevel.brainsware.org/forum/index.php?topic=1017.msg11966#msg11966, zu dem aus einem anderen Thread verlinkt wurde...
Geladen werden die "Programme" von GRUB als Module.
Als letztes, was auch noch geholfen hat, da ich auch erst auf dem Schlauch gestanden habe, war der Code aus dem Tyndur Repository: http://git.tyndur.org/?p=tyndur.git;a=blob;f=src/lib/bin_loader/elf32.c.
 
Ich hoffe mal, das war, was du von mir hören wolltest.  :roll:
Wenn du in meinem Code lesen möchtest, müsste ich erst einmal den Admin des Servers fragen (auf dem das Repository liegt), da der Zugriff nur mit einem Benutzername und Passwort möglich ist. Oder ich poste ihn bei bedarf einfach hier...
Titel: Re: Programm laden und ausführen
Beitrag von: ehenkes am 09. June 2009, 00:07
Danke fuer die Links, komme leider erst naechste Woche zum Testen.