C Calling Conventions und Parameterübergabe



  • Würde jemand mal über folgendes kleines Programm schauen und es verbessern? Es funktioniert nicht, aber es liegt an mir, da ich die Parameterübergaben nicht so recht verstehe. Es geht darum die jeweiligen Calling Conventions zu beachten. Es sollen jeweils die in den Kommentaren rechts neben den Assemblerbefehlen stehenden Werte in die gleichnamigen Parameter der jeweiligen Print-Funktion übergeben werden.

    // __fastcall:
    //   param #1: ecx,
    //   param #2: edx;
    //   param #3 and up: stack
    void __fastcall print_fastcall(char c, short s, int i, double f) {
        printf("%c, %d, %d, %f\n", c, s, i, f);
    }
    
    void asm_fastcall(void) {
        _asm {
            mov     ecx, 'x'        // c = 'x'
            mov     edx, 0x0c       // s = 0ch = 12d
            push    0x2000          // i = 2000h = 8192d
            push    0x0000          // f = 2.138d
            call    print_fastcall  // call print function
        }
    }
    
    // __cdecl:
    //   param #1 and up: stack
    void __cdecl print_cdecl(char c, short s, int i, double f) {
        printf("%c, %d, %d, %f\n", c, s, i, f);
    }
    
    void asm_cdecl(void) {
        _asm {
            push    'x'             // c = 'x'
            push    0x0c            // s = 0ch = 12d
            push    0x2000          // i = 2000h = 8192d
            push    0xabcd          // f = 2.138d
            call    print_cdecl     // call print function
        }
    }
    
    // __stdcall:
    //   param #1 and up: stack
    void __stdcall print_stdcall(char c, short s, int i, double f) {
        printf("%c, %d, %d, %f\n", c, s, i, f);
    }
    
    void asm_stdcall(void) {
        _asm {
            push    'x'             // c = 'x'
            push    0x0c            // s = 0ch = 12d
            push    0x2000          // i = 2000h = 8192d
            push    0xabcd          // f = 2.138d
            call    print_stdcall   // call print function
        }
    }
    
    int main(void) {
        asm_fastcall();
        asm_cdecl();
        asm_stdcall();
    
        return 0;
    }
    


  • Die Parameteruebergabe auf den Stack passiert von rechts nach links (siehe hier).
    Doubles musst Du als zwei separate dwords pushen (oder float nehmen):

    void __fastcall print_fastcall(char c, short s, int i, double f)
    {
       printf("%c, %d, %d, %f\n", c, s, i, f);
    }
    
    void asm_fastcall(void)
    {
       double f = 2.138;
       _asm {
             mov     ecx, 'x'        
             mov     edx, 0x0c       
             lea     edi, f
             push    dword ptr [edi+4]
             push    dword ptr [edi]
             push    0x2000          
             call    print_fastcall  
       }
    }
    

    Bei _cdecl musst Du die Parameter selber wieder vom Stack raeumen.



  • Hab mal ne Frage zu dem "push dword ptr [edi]", wie ist das zu interpretieren? Wieso gerade das Register EDI? verweißt EDI auf locale Variablen? Bzw. macht man das nicht eigentlich immer mit dem EBP?

    Gruß Tobi



  • Mit dem Befehl "lea" wird die Adresse der Variablen "f" in das Register "edi" explizit geladen und dient liedlich zur Verdeutlichung dass man bei groesseren Datentypen eher mit Referenzen/Pointer arbeiten wuerde. Das Register selbst ist hier vollkommen zufaellig gewaehlt. Aus historischen Gruenden bevorzugen viele esi/edi zur Adressierung.
    Da "f" hier (absolut willkuerlich) eine lokale Variable (Stack-Frame) ist, wuerde sich deren Adresse auch relativ zu "ebp" ergeben (erledigt der Compiler selbst).
    Man koennte genausogut schreiben:

    push    dword ptr [f+4]
    push    dword ptr [f]
    


  • hellihjb könntest du mir noch explizit schreiben wie das Poppen vom Stack nach dem Aufruf der Funktion mit der Convention __cdecl funktioniert. Also das Programm von oben noch mal nehmen? Ich hab es gestern geschafft, den Stack zu poppen und damit mein Programm-Thread unkillbar zu machen. Selbst der Taskmanager war nicht mehr in der Lage den Prozess zu killen. Vermutlich habe ich irgendwelche Rücksprungadresse vom Stack einfach mal weggepoppt. Wäre sehr nett.



  • Du pusht ja 5 Dwords auf den Stack (wobei der Stack-Pointer dekrementiert) und musst die hinterher wieder "runterpoppen" was effektiv nichts anderes ist als den Stack-Pointer wieder entsprechend zu erhoehen:

    void __cdecl print_cdecl(char c, short s, int i, double f)
    {
       printf("%c, %d, %d, %f\n", c, s, i, f);
    }
    
    void asm_cdecl(void)
    {
       double f = 2.138;
       _asm {
             push    dword ptr [f+4]
             push    dword ptr [f]
             push    0x2000          // i = 2000h = 8192d
             push    0x0c            // s = 0ch = 12d
             push    'x'             // c = 'x'
             call    print_cdecl     // call print function
             add     esp,4*5
       }
    }
    


  • Ok, danke. Noch was. Auch wenn das jetzt ne doofe Frage ist. Warum ist 'x', 0x0c, 0x2000 ein dword? 'x' ist ein Byte lang, 0x0c ebenfalls. Und 0x2000 ist ein Word lang. Oder pusht er automatisch immer ein Dword auch wenn der Inhalt eigentlich kein Dword ist?

    Edit: Und in der Definition ist c ja auch als Char deklariert also auch kein Dword!



  • Wie hier nachzulesen ist:

    All arguments are widened to 32 bits when they are passed



  • Ah ok. Wenn ich das jetzt also richtig verstehe, dann passiert folgendes:

    esp-offset    stack                             printf
    -0            hier wollen wir wieder zurück     ---
    -0  ... -4    dword ptr [f+4]                   castet das intern
    -4  ... -8    dword ptr [f]                     nach double
    -8  ... -12   0x2000                            castet das intern nach int
    -12 ... -16   0x0c                              castet das intern nach short
    -16 ... -20   'x'                               castet das intern nach char
    


  • Es ist nicht notwendig zu casten.
    Die Parameter stehen in gegebener Reihenfolge im Speicher.
    Die Variablen und deren Groessen sind dem Compiler durch die Parameterliste bekannt und er liesst diese beim Zugriff einfach an der entsprechenden Speicherstelle aus.



  • Ich hab mal noch eine Frage und zwar zur __thiscall convention.
    ECX bekommt die Referenz zum Klasseobject, und wie wird der rest also die Parameter mitgeteilt?

    typedef void (__thiscall *fpFunc)( int );
    fpFunc fpfunc  = (fpSetNewValue)(0x12345678);
    
    _asm mov ecx, dword ptr ds:[0x87654321]
    fpNew( 2 );
    

    So gehts nicht. Aber wie dann?

    Gruß Tobi



  • Hat sich erledigt.


Anmelden zum Antworten