Mehrmaliges aufrufen von Funktionen verursacht Fehler
-
Grüße,
ich arbeite nun schon eine Weile an einem eigenen Kernel und bin auf einen Fehler gestoßen, dessen Ursache ich nicht finde. Sobald ich eine Funktion, z.B. zum Ausgeben eines Textes, mehrmals aufrufe, erhalte ich eine merkwürdige Ausgabe. Ich glaube besser kann ich das nicht beschreiben, deshalb lasse ich einfach Quellcode sprechen.Ich habe das Problem auf diese Zeilen eingegrenzt:
EXTERN void main(uint32 magic, MultibootInfo*) { clearScreen(); hideCursor(); print("Hello World!\r\n"); print("1234567890\r\n"); print("\tUmlaute:\n"); print("äöüÄÖÜß\r\n"); print((int)magic); print("\r\n"); printHex(magic); print("\r\n"); print("\r\n"); //print("\r\n"); } EXTERN void clearScreen() { byte* videomemory = (byte*)0xB8000; for(int i = 0; i < 2000; ++i) { videomemory[i*2] = ' '; videomemory[i*2+1] = 0x07; } cursorPosition = 0; } EXTERN void hideCursor() { //Position ist außerhalb des Bildschirms -> Cursor versteckt uint16 position = 25*80; outb(0x3D4, 0x0F); outb(0x3D5, static_cast<byte>(position&0xFF)); outb(0x3D4, 0x0E); outb(0x3D5, static_cast<byte>((position>>8)&0xFF)); } void print(const char* ptr, ConsoleColor color/* = CONSOLE_COLOR_LIGHT_GREY*/) { byte* videomemory = (byte*)0xB8000; const uint16 step = 4; for(; *ptr; ++ptr, ++cursorPosition) { if(cursorPosition > 4000) cursorPosition -= 4000; switch (*ptr) { default: videomemory[cursorPosition*2] = *ptr; videomemory[cursorPosition*2+1] = color; break; case '\r': cursorPosition -= cursorPosition % 80 + 1; continue; case '\n': cursorPosition += 80 - 1; continue; case '\t': cursorPosition += step; continue; case 'ü': videomemory[cursorPosition*2] = 0x81; videomemory[cursorPosition*2+1] = color; continue; case 'ä': videomemory[cursorPosition*2] = 0x84; videomemory[cursorPosition*2+1] = color; continue; case 'Ä': videomemory[cursorPosition*2] = 0x8E; videomemory[cursorPosition*2+1] = color; continue; case 'ö': videomemory[cursorPosition*2] = 0x94; videomemory[cursorPosition*2+1] = color; continue; case 'Ö': videomemory[cursorPosition*2] = 0x99; videomemory[cursorPosition*2+1] = color; continue; case 'Ü': videomemory[cursorPosition*2] = 0x9A; videomemory[cursorPosition*2+1] = color; continue; case 'ß': videomemory[cursorPosition*2] = 0xE1; videomemory[cursorPosition*2+1] = color; continue; } } } EXTERN void printHex(uint32 value, ConsoleColor color/* = CONSOLE_COLOR_LIGHT_GREY*/) { int n = value, i = 7; char output[] = "00000000"; do { auto tmp = n % 16; output[i--] = static_cast<char>(tmp < 10 ? tmp+0x30 : tmp+0x37); n = n >> 4; } while(i >= 0); print(output, color); }
Ausgabe (wie gewollt):
Hello World! 1234567890 Umlaute: äöüÄÖÜß 38144 00009500
Wenn ich nun jedoch den Kommentar aus Zeile 16 entferne, so dass dort ein drittes mal print("\r\n") steht, dann erhalte ich die folgende Ausgabe:
4
Wodurch lässt sich das erklären? Hat es mir irgendwo zwischendrinnen den Stack zerschossen? Ist das ein Bug des Emulators QEmu?
Falls ihr mehr Infos braucht (disassembly z.B.), dann lasst es mich wissen.Ich hoffe ihr könnt mir hier helfen!
Dennis
-
Schwer zu sagen, was schief läuft. Zumindest fällt mir nichts auf Anhieb ein. Der Bildschirm ist ja nach den paar Zeilen wohl kaum voll. Bemerkenswert ist insbesondere, dass \r\n ja nur den Cursor verschiebt, also garkeine Zugriffe auf den Bildspeicher passieren. Für mich sieht das so aus, als würde ein Fehler in einem anderen Teil deines Kernels (Multitasking, Paging, Heap-Verwaltung) hier einen Streich spielen. In der Anfangszeit von PrettyOS gab es öfter solche seltsamen Probleme.
-
Ich habe den gesamten Code auf diese Zeilen reduziert, es wird nichts anderes mehr ausgeführt, abgesehen vom Sprung des Multiboot Einstiegpunktes zur Main Funktion.
Das geschieht so:
NAKED void __multiboot_entry__() { __asm { multiboot_header: dd(0x1BADB002) ;magic dd(0x0010000) ;flags dd(-(0x1BADB002 + 0x0010000)) ;checksum dd(0x00101000) ;header_addr dd(0x00101000) ;load_addr dd(0x00102FFF) ;load_end_addr dd(0x0010583A) ;bss_end_addr dd(0x00101020) ;entry_addr //Wird nur gebraucht wenn flags & 3 /* dd(0x00000000) ;mode_type dd(0x00000000) ;width dd(0x00000000) ;height dd(0x00000000) ;depth */ kernel_entry: mov esp, 0x00105000 push eax push ebx call main stop: cli hlt jmp stop } }
Der Stack ist einfach ein 8 kByte großes Array in der .data Section und startet bei 0x00103000.
-
Hast den bug inzwischen genau investigiert und gefunden?
100%ig ist für mich daran noch nicht ersichtlich, was exakt schief läufft, dafür bräucht ich dissassembly+mapping. Aber irgenwie denk ich an bildschirmspeicher zerschossen.
Hier ein paar Hinweise:
Du solltest überall bedenken, dass cursorPosition deine Position am bildschirm ohne die farbe präsentiert, nicht das exakte offset (was %2 wäre)if(cursorPosition > 4000) cursorPosition -= 4000; // ->2000
Dieser check muss ausserdem auch unter das switch.
auserdem sollte das '\r', soweit ich das sehen kann, eher so lauten:
case '\r': cursorPosition -= cursorPosition % (80 - 1); continue;
funktionirt der code auch bei print("Hello World\r\n"); also ohne ausrufezeichen? (ungerade anzahl zeichen geht rein, nur so zum test).
wenn das 3. \r\n hinzukommt, überschreitest du überschlagen 50 byte, die in print reingegangen sind.
-
Gero_Programmierstil_de schrieb:
if(cursorPosition > 4000) cursorPosition -= 4000; // ->2000
Dieser check muss ausserdem auch unter das switch.
Oh, das ist mir gar nicht aufgefallen. Aber da muss natürlich 2000 stehen.
Und warum muss das unter das switch? Wenn ich so drüber nachdenke, dann fällt mir kein Grund ein, warum das unbedingt drunter muss. Das wäre hier sogar ein Fehler, da Steuerungszeichen wie z.B. \t den Cursor verschieben und danach wird die Schleife mit einem continue fortgesetzt. Sprich, der Teil würde nach dem Switch niemals aufgerufen werden und somit würde ein String wie "\tfoo" kurz vor Ende des Puffers des Puffers überlaufen.Gero_Programmierstil_de schrieb:
auserdem sollte das '\r', soweit ich das sehen kann, eher so lauten:
case '\r': cursorPosition -= cursorPosition % (80 - 1); continue;
Das siehst du leider falsch.
Gero_Programmierstil_de schrieb:
funktionirt der code auch bei print("Hello World\r\n"); also ohne ausrufezeichen? (ungerade anzahl zeichen geht rein, nur so zum test).
Ja.
Gero_Programmierstil_de schrieb:
wenn das 3. \r\n hinzukommt, überschreitest du überschlagen 50 byte, die in print reingegangen sind.
Könntest du das bitte Begründen? Ich habe den Code auserhalb des Kernels getestet und nach allen Funktionsaufrufen befindet sich der Cursor dort, wo er sein sollte. Ich sehe wohl den Wald vor lauter Bäumen nicht.
Aber da es scheinbar noch immer an Informationen und definitiv an Lösungsansätze mangelt, gebe ich Euch noch den Auszug aus dem Disassembler.
http://pastebin.com/JQMumBm5Das ist aber so viel, dass ich mich fast nicht traue, es hier zu posten, geschweige denn zu fragen, ob sich das jemand ansieht.
-
habs im debugger ma durchgespielt inem array statt bildschirmspeicher.
sieht alles gut aus. sehr seltsam, der bug.Dennis G. schrieb:
Und warum muss das unter das switch?
ich hab die "continue"s übersehen, dein code stimmt.
Gero_Programmierstil_de schrieb:
auserdem sollte das '\r', soweit ich das sehen kann, eher so lauten:
case '\r': cursorPosition -= cursorPosition % (80 - 1); continue;
Das siehst du leider falsch.
[/quote]
ok, wegen dem ++cursorPosition im for() konstrukt.aber funktioniert folgendes?
EXTERN void main(uint32 magic, MultibootInfo*) { clearScreen(); hideCursor(); print("\r\r\r\r\r\r\r"); print("funktioniert das?"); }
mit den 50 bytes war eher laut gedacht, weil 50*80==4000, usw...
hast mal nen bisschen rumprobiert mit anderen ähnlichen inputs?
wie is cursorPosition und magic eigentlich deklariert? wieso funktioniert eigentlich: print((int)magic);
-
Es liegt nicht an einem bestimmten Aufruf und auch nicht daran was ich der Funktion übergebe. Es liegt daran, dass ich nur x Funktionen aufrufen kann, danach spinnt es rum.
Ich kann genauso gut folgendes machen:clearScreen(); print("A"); clearScreen(); print("B"); clearScreen(); print("C"); clearScreen(); print("D"); clearScreen(); print("E"); clearScreen(); print("F"); clearScreen(); print("G"); clearScreen(); print("H");
Rufe ich jetzt nochmal print auf, dann bekomme ich wieder eine falsche Ausgabe.
hast mal nen bisschen rumprobiert mit anderen ähnlichen inputs?
Nein. Ich frage hier, weil ich noch kein bisschen rumprobiert habe und mir die Ursache des Problemes vollkommen klar ist.
wie is cursorPosition und magic eigentlich deklariert? wieso funktioniert eigentlich: print((int)magic);
uint16 cursorPosition = 0; //global
Magic legt mir der Bootloader ins eax register, bevor er den Kernel aufruft. eax wird vorm Aufrufen der Main Funktion gepusht.
print((int)magic) funktioniert, weil es - wie bereits gesagt - nichts mit den Parametern der Funktion zu tun hat.Ich habe noch etwas herumprobiert und das scheint mit der jump table des switches zusammenzuhängen.
möglichkeiten im switch aufrufbare funktionen 11 13 10 17 9 17 8 21 7 25 6 29 5 33
Das ist sehr merkwürdig.
Könntet ihr euch den Assembly Code der Funktion ansehen? Ich kann da keinen Fehler ausmachen.
print PROC var_18 = dword ptr -18h videomemory = dword ptr -14h bufferSize = word ptr -10h numRows = word ptr -0Ch numColumns = word ptr -8 step = word ptr -4 ptr = dword ptr 8 color = dword ptr 0Ch push ebp mov ebp, esp sub esp, 18h mov [ebp+videomemory], 0B8000h mov eax, 4 mov [ebp+step], ax mov ecx, 50h mov [ebp+numColumns], cx mov edx, 19h mov [ebp+numRows], dx mov eax, 7D0h mov [ebp+bufferSize], ax jmp for for: mov eax, [ebp+ptr] movsx ecx, byte ptr [eax] test ecx, ecx jz ende movzx edx, cursorPosition cmp edx, 2000 jl cursorValid movzx eax, cursorPosition sub eax, 2000 mov cursorPosition, ax cursorValid: mov ecx [ebp+ptr] movsx edx, byte ptr[ecx] mov [ebp+var_18], edx mov eax, [ebp+var_18] add eax, 3Ch mov [ebp+var_18], eax cmp [ebp+var_18], 50 ja caseDefault mov ecx, [ebp+var_18] movzx edx, ds:byte_101B18[ecx] jmp ds:off_10B00[edx*4] caseDefault: movzx eax, cursorPosition mov ecx, [ebp+videomemory] mov edx, [ebp+ptr] mov dl, [edx] mov [ecx+eax*2], dl movzx eax, cursorPosition mov ecx, [ebp+videomemory] mov dl, byte ptr [ebp+color] mov [ecx+eax*2+1], dl jmp loc_101AF2 ;case Ä movzx eax, cursorPosition mov ecx, [ebp+videomemory] mov byte ptr [ecx+eax*2], 8Eh movzx edx, cursorPosition mov eax, [ebp+videomemory] mov cl, byte ptr [ebp+color] mov [eax+edx*2+1], cl jmp jmptblEnde ;case ö movzx edx, cursorPosition mov eax, [ebp+videomemory] mov byte ptr [eax+edx*2], 94h movzx ecx, cursorPosition mov edx, [ebp+videomemory] mov al, byte ptr [ebp+color] mov [edx+ecx*2+1], al jmp jmptblEnde ;case Ö movzx ecx, cursorPosition mov edx, [ebp+videomemory] mov byte ptr [edx+ecx*2], 99h movzx eax, cursorPosition mov ecx, [ebp+videomemory] mov dl, byte ptr [ebp+color] mov [ecx+eax*2+1], dl jmp jmptblEnde ;gefolgt von zwei weiteren cases, die den selben code jedoch mit anderen zeichen beinhalten (Ü und ü) jmptblEnde: mov ecx, [ebp+ptr] add ecx, 1 mov [ebp+ptr], ecx mov dx, cursorPosition add dx, 1 mov cursorPosition jmp for ende: mov esp, ebp pop ebp retn 8 print ENDP
Ich hoffe jemand findet da was.