Assembler und Funktionsaufrufe



  • Hallo! 🙂

    Ich beschäftige mich im Moment etwas mit Assembler und habe gerade einige kleine Schwierigkeiten, den generellen Ablauf eines Funktionsaufrufes zu verstehen:

    Hier der C-Code:

    void test_function(int a, int b, int c, int d) {
    
    int flag;
    char buffer[10]
    
    flag = 31337;
    buffer[0] = 'A';
    
    }
    
    int main() {
    
        test_fuction(1,2,3,4);
    
    }
    

    Der Assembler sieht so aus:

    push ebp
    mov ebp, esp
    sub esp,0x18
    and esp,0xffffffff0
    mov eax,0x0
    sub esp,eax
    mov DWORD PTR [esp+12], 0x4
    mov DWORD PTR [esp+12], 0x3
    mov DWORD PTR [esp+12], 0x2
    mov DWORD PTR [esp+12], 0x1
    call <test_function>
    

    Dann der Code für den Funktionsaufruf:

    push ebp
    mov ebp, esp
    sub esp,0x28
    mov DWORD PTR [ebp-12], 0x7a69 ;das ist halt die 31337 in hexadezimal
    mov BYTE PTR [ebp-40], 0x41    ;ist das A in ASCII
    leave
    ret
    

    So, was ich nicht genau verstehe ist, warum macht man:

    and esp,0xffffffff0
    mov eax,0x0
    sub esp,eax
    

    Warum führt man auf dem esp ein logisches und mit dieser Zahl aus und warum schiebt man dann eine 0 ins eax und zieht diese von esp wieder ab?

    Zweitens:

    sub esp,0x28
    mov DWORD PTR [ebp-12], 0x7a69 ;das ist halt die 31337 in hexadezimal
    mov BYTE PTR [ebp-40], 0x41    ;ist das A in ASCII
    

    Hier werden ja durch

    sub esp,0x28
    

    40 Byte resaviert. Warum 40? Der Int und die 10 char's des Arrays sind doch insgesamt nur 14 Byte, oder?
    Und warum schiebe ich die 0x7a69 ab der Stelle [ebp-12] in den RAM und nicht ab Stelle [ebp-4]? Weil ich mache ja "mov ebp, esp", d.h. ich setze ebp auf den zu dem Zeitpunkt aktuellen ESP. Jetzt zeigte der ESP ja zu dem Zeitpunkt auch natürlich auf das Ende des Stacks was hier der Wert ist, nachdem ich

    push ebp
    

    ausgeführt habe. Das Heißt der ESP zeigt doch nach

    push ebp
    

    praktisch auf die Stelle hinter dem gesicherten EBP? Auf jeden Fall würde ich die 0x7a69 halt dann ab [ebp] schreiben, halt direkt nach dem gesicherten EBP.
    Und daher auch, warum schreibe ich das 'A' ab ebp-40?

    Kann mir das bitte jemand erklären? 🙂

    Danke!



  • CCat schrieb:

    Der Assembler sieht so aus:

    push ebp
    mov ebp, esp
    sub esp,0x18
    and esp,0xffffffff0
    mov eax,0x0
    sub esp,eax
    mov DWORD PTR [esp+12], 0x4
    mov DWORD PTR [esp+12], 0x3
    mov DWORD PTR [esp+12], 0x2
    mov DWORD PTR [esp+12], 0x1
    call <test_function>
    

    Ich nehme mal an, dass das kein copy&paste ist. Die Adressen der Funktionsargumente können so ja offensichtlich nicht stimmen.

    Mit welchem Compiler auf welchem System mit welchen Compiler-Einstellungen wurde dieser Code erzeugt?

    CCat schrieb:

    So, was ich nicht genau verstehe ist, warum macht man:

    and esp,0xffffffff0
    mov eax,0x0
    sub esp,eax
    

    Warum führt man auf dem esp ein logisches und mit dieser Zahl aus und warum schiebt man dann eine 0 ins eax und zieht diese von esp wieder ab?

    *Man* macht nicht, der Compiler hat sich in diesem speziellen Fall dazu entschieden, diesen Code zu erzeugen. Das and dürfte dazu dienen, den Stack auf 16 Byte auszurichten - gcc macht das z.B. einmalig in main, um dann ohne weiteren Aufwand mit automatischen SSE-Variablen umgehen zu können. Der Zweck des mov eax+sub erschließt sich daraus nicht; es könnte sich evtl. um Padding handeln, um Debugging zu erleichtern.

    CCat schrieb:

    Zweitens:

    sub esp,0x28
    mov DWORD PTR [ebp-12], 0x7a69 ;das ist halt die 31337 in hexadezimal
    mov BYTE PTR [ebp-40], 0x41    ;ist das A in ASCII
    

    Hier werden ja durch

    sub esp,0x28
    

    40 Byte resaviert. Warum 40? Der Int und die 10 char's des Arrays sind doch insgesamt nur 14 Byte, oder?
    Und warum schiebe ich die 0x7a69 ab der Stelle [ebp-12] in den RAM und nicht ab Stelle [ebp-4]?

    Der Compiler ist nicht gezwungen, Variablen direkt aufeinander folgend anzuordnen. Insbesondere um Arrays herum wird bei ausgelassener Optimierung gerne etwas Platz gelassen, um Bereichsüberschreitungen erkennen zu können.