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/JQMumBm5

    Das 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.


Anmelden zum Antworten