Autor Thema: Multitasking (qemu: fatal: Trying to execute code outside RAM or ROM)  (Gelesen 6426 mal)

nnosdev

  • Beiträge: 61
    • Profil anzeigen
Gespeichert
Ein besserer Betreff ist mir nicht eingefallen ^^

Habe das Multitasking implementiert. Das eigenartige.. mal funktioniert es und mal nicht..
Also manchmal startet alles normal und der Output ist wie erwartet (A bzw. B)

Aber manches mal bekomme ich im Terminal folgendes zu lesen:

qemu: fatal: Trying to execute code outside RAM or ROM at 0xd08ec08e

EAX=000000fe EBX=666f5365 ECX=2e532074 EDX=0000b800
ESI=00000000 EDI=00000000 EBP=d88e0000 ESP=fffffeec
EIP=d08ec08e EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
DS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
FS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
GS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]
LDT=0000 00000000 0000ffff 00008200 DPL=0 LDT
TR =0000 00000000 0000ffff 00008b00 DPL=0 TSS32-busy
GDT=     00108820 00000027
IDT=     00104000 000007ff
CR0=00000011 CR2=00000000 CR3=00000000 CR4=00000000
DR0=00000000 DR1=00000000 DR2=00000000 DR3=00000000
DR6=ffff0ff0 DR7=00000400
CCS=00000008 CCD=fffffedc CCO=SUBL   
EFER=0000000000000000
FCW=037f FSW=0000 [ST=0] FTW=00 MXCSR=00001f80
FPR0=0000000000000000 0000 FPR1=0000000000000000 0000
FPR2=0000000000000000 0000 FPR3=0000000000000000 0000
FPR4=0000000000000000 0000 FPR5=0000000000000000 0000
FPR6=0000000000000000 0000 FPR7=0000000000000000 0000
XMM00=00000000000000000000000000000000 XMM01=00000000000000000000000000000000
XMM02=00000000000000000000000000000000 XMM03=00000000000000000000000000000000
XMM04=00000000000000000000000000000000 XMM05=00000000000000000000000000000000
XMM06=00000000000000000000000000000000 XMM07=00000000000000000000000000000000

ich hab keine Ahnung wie/wo er auf 0xd08ec08e kommt. Hat jemand eine Idee wie ich an das Problem herangehen kann?

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
Herausfinden, wo du den Sprung hast, der an diese komische Adresse springt. Erster Hauptverdächtiger ist immer der Stack beim iret, der gern mal falsche Werte enthält oder leicht verschoben ist.

Ansonsten kann es unter Umständen auch noch helfen, qemu mit -d in-asm laufen zu lassen, dann wird jede Assemblerinstruktion mitgeloggt, die qemu übersetzt (Achtung: Nicht Instruktion, die er ausführt! Mit etwas Glück ist das aber dasselbe.) Das macht natürlich die Logdatei groß und die VM ziemlich langsam.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

nnosdev

  • Beiträge: 61
    • Profil anzeigen
Gespeichert
Hi taljeth :)

Also ich hab das mal versucht. Muss gestehen, dass ich noch immer nicht ganz nachvollziehen kann was da passiert (vor allem versteh ich nicht warum das nur manchmal passiert und manchmal eben nicht).

Kannst du mir vielleicht vorweg erklären wo die Register EBP und EIP ihre Werte bekommen?
EBP ist der Basepointer vom Stack und EIP hat das Offset zur nächsten Instruktion soweit ich das richtig verstanden habe.

Beim Suchen sind eigentlich nur mehr Fragen entstanden ^^
Ich hab mir den Output geben lassen wenn das System läuft und problemlos zwischen Task A und B wechselt. Als erstes fiel mir auf, dass anscheinend immer ein INT 32 ausgelöst wird. Woher kommt der? Ich muss gestehen, dass mir das hier: http://www.ctyme.com/intr/int-32.htm nicht wirklich weiter hilft :D

Also bei meinem Problem hier seh ich im qemu.log (mit dem parameter -d in_asm) folgendes..

Ich kann sehen, dass Idt_InstallEntried() noch die Interrupt-Table erstellt. Ich gehe davon aus das IN: Func_Name() dafür steht, aus welcher Funktion die Instruktionen kommen. Liege ich da richtig?
Danach kommt noch der Befehl sti für das enablen der Ints.

Jetzt wirds spannend.. Es kommt ein INT 20, der Isr_Handler() wird gerufen, dieser ruft Scheduler_Schedule(), der kommt zurück in Isr_Handler().. wo anscheinend nocht outb(0x20, 0x20) end of interrupt ausgeführt wird. Danach springt der Code zurück in isr_common_stub (also der Interrupt-Main sozusagen).

Hier kann ich noch sehen, dass alles bis zum iret läuft. Da wirds etwas komisch.. Plötzlich befindet sich die CPU hier:

00101a29 <isr13>:
  101a29: fa                    cli   
  101a2a: 6a 0d                push   0xd
  101a2c: eb 97                jmp    1019c5 <isr_common_stub>

Laut qemu.log werden
----------------
IN:
0x00101a2a:  push   $0xd
0x00101a2c:  jmp    0x1019c5

ausgeführt. Sollte sich der PC da nicht wenn schon in der isr20 befinden das es ja ein INT20 ist?
Unmittelbar danach befindet sich der PC hier:

(IN: Isr_Handler)
dprint("[ISR] Exception captured. Kernel stopped.");
  1016f9: c7 04 24 2c 34 10 00 mov    DWORD PTR [esp],0x10342c
  101700: e8 cd e9 ff ff        call   1000d2 <dprint>

OBWOHL der vorige Befehl jmp    0x1019c5 war ..

Das ist dann die Stelle wo dann eben die Meldung von qemu kommt
qemu: fatal: Trying to execute code outside RAM or ROM at 0xd08ec08e
Ich weiß, das ist schon recht viel verlangt hier um Hilfe zu bitten aber ich kann nicht wirklich nachvollziehen wie das passiert.

Nur ne kleine Frage am Rande: Kann ich mir das qemu log auch in intel-Syntax ausgeben lassen? ^^


MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
Also Int 32 = Int 0x20, somit vermutlich dein IRQ 0 (Timer).

Wenn du nach einem iret plötzlich in einem isr13 landest tritt vermutlich ein #GP auf. Wenn das Problem wie du sagst nur manchmal auftritt, dann stellst du vermutlich nach einem Interrupt den CPU zustand/Stack nicht vollständig wieder her. Wenn ein Timer-Interrupt dann an der falschen stelle auftaucht, verhaut der dir ein paar register oder den stack und es passieren unerklärbare Dinge.

Was macht bzw. soll dein Kernel nach einer #GP-Exception machen (außer dprint)? asm volatile ('cli;hlt'); ?

Nur ne kleine Frage am Rande: Kann ich mir das qemu log auch in intel-Syntax ausgeben lassen? ^^
1016f9: c7 04 24 2c 34 10 00 mov    DWORD PTR [esp],0x10342c
ist Intel-Syntax.
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

nnosdev

  • Beiträge: 61
    • Profil anzeigen
Gespeichert
Also GP höre ich zum ersten Mal :D
Mein Kernel macht in diesem Falle also noch nichts ^^

Aber es ist mir ja schon ein Rätsel wieso der PC auf
0x001016f9
landet obwohl davor eindeutig
0x0010193c:  jmp    0x1018d5
steht..
----------------
IN:
0x0010193a:  push   $0xd
0x0010193c:  jmp    0x1018d5

----------------
IN: Isr_Handler
0x001016f9:  movl   $0x10342c,(%esp)
0x00101700:  call   0x1000d2


Also ich hab hier jetzt schon ewig versucht dem Grund auf die Schliche zu kommen. Leider hab ich bemerkt, dass das nicht nur manchmal auftritt sondern fast immer. Das Problem fängt eigentlich immer bei einem Interrupt an.

Die main sieht im Moment so aus:
Gdt_Init();
Idt_Init();

Thread init_threads[2];
cpu_state *init_states[2];
init_states[0] = Thread_Init(&init_threads[0], (uint32_t*)&task_a, stack_a);
init_states[1] = Thread_Init(&init_threads[1], (uint32_t*)&task_b, stack_b);

Scheduler_Init(scheduler, init_states, 2);

dprint("lets go ..");
while(1);

Davor und danach passieren keine wesentlichen Dinge. Das Problem wird tatsächlich von einem Interrupt verursacht.
Sobald die IDT installiert ist und die Interrupts enabled werden kommt natürlich sofort INT 20 vom PIC..
Danach geht er wie geplant in die Interrupt-Routine aber scheitert da irgendwo nach iret

----------------
IN:
0x001018f0:  iret   

----------------
IN:
0x00101944:  cli   

----------------
IN:
0x00101945:  push   $0xd
0x00101947:  jmp    0x1018d5

----------------
IN: Isr_Handler
0x001016f9:  movl   $0x10342c,(%esp)
0x00101700:  call   0x1000d2

qemu: fatal: Trying to execute code outside RAM or ROM at 0xd08ec08e

EAX=000000fe EBX=666f5365 ECX=2e532074 EDX=0000b800
ESI=00000000 EDI=00000000 EBP=d88e0000 ESP=fffffeec
EIP=d08ec08e EFL=00000002 [-------] CPL=0 II=0 A20=1 SMM=0 HLT=0
ES =0000 00000000 00000000 00000000
CS =0008 00000000 ffffffff 00cf9a00 DPL=0 CS32 [-R-]
SS =0010 00000000 ffffffff 00cf9300 DPL=0 DS   [-WA]

Hier die allgemeine Interrupt Service Routine:
isr_common_stub:
pusha ; Pushes edi,esi,ebp,esp,ebx,edx,ecx,eax (inverted order)
push eax

push esp ; pointer to cpu_state struct
call Isr_Handler
mov esp, eax

pop eax
popa

add esp, 8
sti
iret

Obigen stub hab ich von einem anderen Tutorial. Es müsste aber quasi dasselbe machen wie der vom lowlevel Tutorial. 

Nur ne kleine Frage am Rande: Kann ich mir das qemu log auch in intel-Syntax ausgeben lassen? ^^
1016f9: c7 04 24 2c 34 10 00 mov    DWORD PTR [esp],0x10342c
ist Intel-Syntax.
Ja, das ist auf dem dump-file. Die verwendet die intel-Syntax aber das qemu.log leider nicht.

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
Dieses in_asm ist kein Trace des kompletten Programmablaufs, sondern gibt an, welche Blöcke der TCG von qemu übersetzt. Der Block zu dem der Sprungbefehl springt, wurde früher schon mal ausgeführt, und deswegen eher übersetzt. Das heißt, du müsstest ihn weiter vorne in der Datei finden.

Das Problem ist allerdings nicht, dass die CPU nach 10193a springt. Das ist ja der GPF-Handler. Der wird von der CPU ausgeführt, wenn ein Fehler auftritt. Das heißt du solltest dir den Befehl davor anschauen.

Allerdings kann es wieder sein, dass dich das log täuscht, und der Block, der gerade ausgeführt wird, gar nicht an dieser Stelle steht im Log steht, sondern ebenfalls weiter vorne. Deswegen solltest du mal versuchen einen Breakpoint auf deinen GPF-Handler zu setzen und dann den Stack zu inspizieren.
« Letzte Änderung: 12. July 2012, 20:56 von Jidder »
Dieser Text wird unter jedem Beitrag angezeigt.

nnosdev

  • Beiträge: 61
    • Profil anzeigen
Gespeichert
Du sprichst von Debugging :D

Also mein Setup sieht im Moment noch so aus:

Ich verwende Eclipse zum coden und benütze ein Makefile um den Kernel zu starten.
Leider hab ich nicht den Hauch einer Ahnung wie ich am einfachsten Debuggen kann bzw wie ich das entsprechend einrichten kann.

Kannst du mir da was empfehlen?


Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
Du brauchst dazu GDB.

Ein paar Anleitungen:

http://www.lowlevel.eu/wiki/Debugging#bochs_.2F_QEMU
http://wiki.osdev.org/How_Do_I_Use_A_Debugger_With_My_OS#Use_gdb_with_Qemu

Im Prinzip musst du nur qemu mit -s -S starten, gdb mit dem Kernel als Parameter starten und dann target remote :1234 eingeben.
Dieser Text wird unter jedem Beitrag angezeigt.

Martin Erhardt

  • Beiträge: 165
    • Profil anzeigen
Gespeichert
Apropos Qemu Debuging und "Trying to execute code outside RAM or ROM"Absturz könnte dir vielleicht dieser Link helfen:
http://forum.lowlevel.eu/index.php?topic=3038.msg35433#msg35433

kevin

  • Administrator
  • Beiträge: 2 767
    • Profil anzeigen
Gespeichert
Dieses in_asm ist kein Trace des kompletten Programmablaufs, sondern gibt an, welche Blöcke der TCG von qemu übersetzt. Der Block zu dem der Sprungbefehl springt, wurde früher schon mal ausgeführt, und deswegen eher übersetzt. Das heißt, du müsstest ihn weiter vorne in der Datei finden.
Ich bin mir gerade nicht hundertprozentig sicher, aber dagegen könnte auch -singlestep helfen. Ich glaube, da übersetzt er jedesmal neu.
Thou shalt not follow the NULL pointer, for chaos and madness await thee at its end.

nnosdev

  • Beiträge: 61
    • Profil anzeigen
Gespeichert
Du brauchst dazu GDB.

Ein paar Anleitungen:

http://www.lowlevel.eu/wiki/Debugging#bochs_.2F_QEMU
http://wiki.osdev.org/How_Do_I_Use_A_Debugger_With_My_OS#Use_gdb_with_Qemu

Im Prinzip musst du nur qemu mit -s -S starten, gdb mit dem Kernel als Parameter starten und dann target remote :1234 eingeben.

Okay, ich hab das zum Laufen gebracht. Werd mir das jetzt mal anschaun :)
« Letzte Änderung: 14. July 2012, 15:49 von nnosdev »

nnosdev

  • Beiträge: 61
    • Profil anzeigen
Gespeichert
So das sieht jetzt etwas hässlich aus:


Das ist outb(). Die wird am Ende für das End Of Interrupt Signal gecallt mit outb(0x20, 0x20);

static inline void outb(uint16_t port, uint8_t val)
{
  1016c0: 55                    push   ebp
  1016c1: 89 e5                mov    ebp,esp
  1016c3: 83 ec 08              sub    esp,0x8
  1016c6: 8b 55 08              mov    edx,DWORD PTR [ebp+0x8]
  1016c9: 8b 45 0c              mov    eax,DWORD PTR [ebp+0xc]
  1016cc: 66 89 55 fc          mov    WORD PTR [ebp-0x4],dx
  1016d0: 88 45 f8              mov    BYTE PTR [ebp-0x8],al
   __asm__ __volatile__ (
  1016d3: 0f b6 45 f8          movzx  eax,BYTE PTR [ebp-0x8]
  1016d7: 0f b7 55 fc          movzx  edx,WORD PTR [ebp-0x4]
  1016db: ee                    out    dx,al
   "outb %b0, %w1"
   :
   : "a"(val), "d"(port));
}
  1016dc: c9                    leave 
  1016dd: c3                    ret

Direkt nach "ret" springt die CPU an eine Stelle an der sie nichts verloren hat. Es muss also am Stack liegen. Aber wie bekomme ich heraus wo der Fehler passiert? Ich kann mir den Stack nicht ausgeben lassen da das System vorher crasht.

Hier die letzten drei Schritte aufgezeichnet mit gdb. Man sieht wie nach "ret" etwas Falsches im EIP Register steht.

(gdb) stepi
27    __asm__ __volatile__ (

(gdb) stepi
0x001016d7 27    __asm__ __volatile__ (

(gdb) stepi
0x001016db 27    __asm__ __volatile__ (

(gdb) info registers
eax            0xb7 183
ecx            0x0 0
edx            0x6667 26215
ebx            0x0 0
esp            0xffffffb0 0xffffffb0
ebp            0xffffffb8 0xffffffb8
esi            0x0 0
edi            0x0 0
eip            0x1016db 0x1016db <outb+27>
eflags         0x82 [ SF ]
cs             0x8 8
ss             0x10 16
ds             0x10 16
es             0x10 16
fs             0x10 16
gs             0x10 16

(gdb) stepi
31 }

(gdb) info registers
eax            0xb7 183
ecx            0x0 0
edx            0x6667 26215
ebx            0x0 0
esp            0xffffffb0 0xffffffb0
ebp            0xffffffb8 0xffffffb8
esi            0x0 0
edi            0x0 0
eip            0x1016dc 0x1016dc <outb+28>
eflags         0x82 [ SF ]
cs             0x8 8
ss             0x10 16
ds             0x10 16
es             0x10 16
fs             0x10 16
gs             0x10 16

(gdb) stepi
0x001016dd in outb (port=26215, val=183 '\267')
    at arch/x86/common/source/../../../../debug/include/../../arch/x86/ports/include/Ports.h:31
31 }

(gdb) info registers
eax            0xb7 183
ecx            0x0 0
edx            0x6667 26215
ebx            0x0 0
esp            0xffffffbc 0xffffffbc
ebp            0x66042454 0x66042454
esi            0x0 0
edi            0x0 0
eip            0x1016dd 0x1016dd <outb+29>
eflags         0x82 [ SF ]
cs             0x8 8
ss             0x10 16
ds             0x10 16
es             0x10 16
fs             0x10 16
gs             0x10 16

(gdb) stepi
0x67c2af0f in ?? ()

(gdb) info registers
eax            0xb7 183
ecx            0x0 0
edx            0x6667 26215
ebx            0x0 0
esp            0xffffffc0 0xffffffc0
ebp            0x66042454 0x66042454
esi            0x0 0
edi            0x0 0
eip            0x67c2af0f 0x67c2af0f
eflags         0x82 [ SF ]
cs             0x8 8
ss             0x10 16
ds             0x10 16
es             0x10 16
fs             0x10 16
gs             0x10 16

MNemo

  • Beiträge: 547
    • Profil anzeigen
Gespeichert
erst wenn ich den common_stub CALLe schmeißt es alles über den Haufen.
Wenn du das tatsächlich CALLst dann dann ist das der/ein Fehler.

Auf ein CALL muss ein RET folgen und auf ein INT ein IRET. So wie auf ein PUSH ein POP folgen muss.

Zitat
Liegt das daran, dass erst im common stub ein "iret" erfolgt? oder warum werden die isr() nacheinander alle aufgerufen?
Ich kenne denn relevanten Code nicht. Aber das hört sich an wie:
int0: /* nichts tun */
int1: /* nichts tun */
int2: call isr_common_stub
Das kann so natürlich nicht funktionieren. int0, int1 und int2 sind Namen für die gleiche Adresse.

Also dieser teil von deinem Code scheint mir hier relevant zu sein. Da du scheinbar nicht wirklich weist was du da machst.
„Wichtig ist nicht, besser zu sein als alle anderen. Wichtig ist, besser zu sein als du gestern warst!“

Jidder

  • Administrator
  • Beiträge: 1 625
    • Profil anzeigen
Gespeichert
Der Wert 0xffffffb0 in ESP scheint das Problem zu sein. Du solltest mal gucken, wie der da reinkommen kann. Zum Beispiel könnte ESP irgendwo mit 0 geladen werden. Nach ein paar PUSH/CALL ist dann der Wert, den wir hier sehen, in ESP.
Dieser Text wird unter jedem Beitrag angezeigt.

nnosdev

  • Beiträge: 61
    • Profil anzeigen
Gespeichert
Danke für den Tipp Jidder! Zwar durch Umwege aber vor allem durch den Tipp mit dem Stack-Pointer bin ich - ziemlich umständlich - dann auf den Punkt gekommen, der den Fehler verursacht hat.

Es hat zwar echt gedauert den durchaus offensichtlichen Fehler zu finden, aber ich hab dafür immerhin ne Menge gelernt. ^^

Der Fehler war, dass ich "Dieser Teil muss aufgerufen werden, bevor Hardwareinterrupts aktiviert werden." gekonnt ignoriert habe und die Interrupts enabled habe bevor der Scheduler korrekt initialisiert wurde. Ich hätte das eigentlich sofort sehen müssen und es roch eigentlich schon die ganze Zeit nach einem "Timing-Problem".

Danke Leute für eure Hilfe!

Gdt_Init();
Idt_Init();

Thread init_threads[2];
cpu_state *init_states[2];
init_states[0] = Thread_Init(&init_threads[0], (uint32_t*)&task_a, stack_a);
init_states[1] = Thread_Init(&init_threads[1], (uint32_t*)&task_b, stack_b);

Scheduler_Init(scheduler, init_states, 2);

Idt_EnableHardwareInterrupts();

 

Einloggen