ESP sichern?



  • Hallo!
    Ich mache mich gerade daran, die Ackermann-Funktion in inline-assembler zu implementieren.

    int __stdcall ackermannASM(unsigned int x, unsigned int y)
    {
    	unsigned int result = 0;
    
    	__asm {
    
    		mov eax, x	//sollte schneller sein als immer im mem zu gucken
    		mov ebx, y	//hier ebenfalls
    
    	xzero:			//x auf null prüfen
    		cmp eax, 0	//x == 0?
    		jne xnzero	//x != 0!
    
    		add ebx, 1	//y++
    		mov result, ebx
    		jmp end
    
    	xnzero:
    		cmp ebx, 0	//y == 0?
    		jne xnzero_ynzero
    
    		sub eax, 1
    		push ebx
    		push eax
    		call ackermannASM
    
    		pop eax
    		mov result, eax
    		jmp end
    
    	xnzero_ynzero:
    		push eax	//altes x auf dem stack sichern
    
    		sub ebx, 1
    		push ebx
    		push eax
    
    		call ackermannASM
    		pop ecx
    		pop eax
    
    		push ecx
    		push eax
    
    		call ackermannASM
    		pop eax
    		mov result, eax
    		jmp end
    
    	end:
    	};
    
    	return result;
    }
    

    Wenn ich die Funktion nun mit z.B. x = 1 und y = 1 aufrufe, dann bekomme ich immer einen Fehler:
    "The value oof ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention."

    Muss ich vor jedem Funktionsaufruf ein "push esp" packen?

    Hier nochmal die Ackermannfunktion in C:

    int ackermannC(unsigned int x, unsigned int y)
    {
    	if(x == 0 && y  > 0) {
    		return y+1;
    	}
    
    	if(x > 0  && y == 0){
    		return ackermannC(x-1, 1);
    	}
    
    	if(x > 0 && y > 0) {
    		return ackermannC(x-1, ackermannC(x, y-1));
    	}
    }
    

    Danke schonmal für alle Antworten...


  • Mod

    das ergebnis wird direkt in eax und nicht über den stack zurückgegeben, insofern hast du glück, das es überhaupt einigermassen kontrolliert abschmiert. der c++ rahmen mit dem result ist daher auch überflüssig (und zeitraubend). das jmp am ende bringt auch nichts, da das label ja gleich darauf folgt. ausserdem darf ebx nicht verändert werden - benutze eax/ecx/edx. alle anderen pops sind auch fehlerhaft, __stdcall sorgt dafür, dass die aufgerufene funktion den stack aufräumt.



  • Danke! Hat geholfen!

    Kennt jemand eine gute Seite, auf der die verschiedenen Aufrufkonventionen erklärt werden?

    Hier die Lösung (wen es interessiert):

    int __stdcall ackermannASM(unsigned int x, unsigned int y)
    {
    	__asm {
    
    		mov ebx, x	//sollte schneller sein als immer im mem zu gucken
    		mov ecx, y	//hier ebenfalls
    
    	xzero:			//x auf null prüfen
    		cmp ebx, 0	//x == 0?
    		jne xnzero	//x != 0!
    
    		add ecx, 1	//y++
    		mov eax, ecx
    		jmp end
    
    	xnzero:
    		cmp ecx, 0	//y == 0?
    		jne xnzero_ynzero
    
    		sub ebx, 1  //x--
    		mov ecx, 1  //y = 1
    		push ecx
    		push ebx
    		call ackermannASM
    
    		//Rückgabewert ist nach Konvention in eax gespeichert
    		jmp end
    
    	xnzero_ynzero:
    		push ebx	//altes x auf dem stack sichern
    
    		sub ecx, 1
    		push ecx
    		push ebx
    
    		call ackermannASM
    
    		pop edx		//altes x wieder vom Stack holen
    
    		push eax
    		sub edx, 1
    		push edx
    
    		call ackermannASM
    
    		//Rückgabewert ist nach Konvention in eax gespeichert
    		jmp end
    
    	end:
    	};
    }
    

  • Mod

    ebx wird immer noch verändert. das ist nicht zulässig - auch wenn es manchmal trotzdem funktioniert 😉



  • Jo, ich werde mir da wohl noch was überlegen müssen.
    Irgendwie gerät man schnell in Registernot beim i386 kompatiblen Rechnern. Wieso gibt es eigentlich nur so wenig Allzweckregister? Bei anderen Architekturen (z.B. SPARC) hat man wesentlich mehr.

    Hast Du eine Idee, wo man mal nach Aufrufkonventionen und so einem Zeug nachschlagen (Buch) bzw. nachgucken (Internetseiten) kann?



  • Die Aufrufkonventionen kannst du im Handbuch deines Compilers nachschlagen.

    SPARC und andere RISC Architekturen haben so viele Allzweckregister, weil das einfach ein Bestandteil der RISC Architektur ist. RISC ist eben moderner, als dieser ekelige x86er Müll.

    btw. versteh ich den Sinn nicht davon, die Funktion in Assembler umzuwandeln. Wahrscheinlich bist du sogar noch langsamer als dein Compiler.


  • Mod

    sicherlich ist das langamer als das ergebnis des compilers - ich nehme eher an, dass es mehr um den lehreffekt ging. sonst könnte man gleich eine komplettlösung hinknallen:

    __declspec( naked ) int __fastcall ackermannASM(unsigned x, unsigned y)
    {
        __asm
        {
            test    ecx, ecx      // x == 0 ?
            jz      _x_is_zero
            test    edx, edx      // y == 0 ?
    __2:
            jz      _y_is_zero
    __1:
            push    ecx
            dec     edx
            call    __2
            pop     ecx
            mov     edx, eax
            dec     ecx
            jz      _x_is_zero    // tail recursion
            test    edx, edx
            jnz     __1
    _y_is_zero:
            dec     ecx
            mov     edx, 1
            jnz     __1           // tail recursion
    _x_is_zero:
            lea     eax, [ edx + 1 ]
            ret
        };
    }
    

    lerneffekt = 0; man könnte es genausogut iterativ machen 🙄


Anmelden zum Antworten