Search code examples
c++cgccassemblyreverse-engineering

GCC generated assembly


Why printf function causes the change of prologue?

C code_1:

#include <cstdio>

int main(){
  int a = 11;
  printf("%d", a);
}

GCC -m32 generated one:

.LC0:
        .string "%d"
main:
        lea     ecx, [esp+4]           // What's purpose of this three
        and     esp, -16               // lines?
        push    DWORD PTR [ecx-4]      // 
        push    ebp
        mov     ebp, esp
        push    ecx
        sub     esp, 20                // why sub 20?
        mov     DWORD PTR [ebp-12], 11
        sub     esp, 8
        push    DWORD PTR [ebp-12]
        push    OFFSET FLAT:.LC0
        call    printf
        add     esp, 16
        mov     eax, 0
        mov     ecx, DWORD PTR [ebp-4]
        leave
        lea     esp, [ecx-4]
        ret

C code_2:

#include <cstdio>

int main(){
  int a = 11;
}

GCC -m32:

main:
        push    ebp
        mov     ebp, esp
        sub     esp, 16
        mov     DWORD PTR [ebp-4], 11
        mov     eax, 0
        leave
        ret

What is the purpose of first three lines added in first code? Please, explain first assembly code, if you can.

EDIT:

64-bit mode:

.LC0:
        .string "%d"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], 11
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, 0
        leave
        ret

Solution

  • The insight is that the compiler keep the stack aligned at function calls.
    The alignment is 16 byte.

    lea     ecx, [esp+4]           ;Save original ESP to ECX (ESP+4 actually)
    and     esp, -16               ;Align stack on 16 bytes (Lower esp)
    
    push    DWORD PTR [ecx-4]      ;Push main return address (Stack at 16B + 4)
                                   ;My guess is to aid debugging tools that expect the RA
                                   ;to be at [ebp+04h]
    push    ebp
    mov     ebp, esp               ;Prolog (Stack at 16B+8)
    
    push    ecx                    ;Save ECX (Original stack pointer) (Stack at 16B+12)
    
    sub     esp, 20                ;Reserve 20 bytes (Stack at 16B+0, ALIGNED AGAIN)
                                   ;4 for alignment + 1x16 for a variable (variable space is
                                   ;allocated in multiple of 16)
    
    mov     DWORD PTR [ebp-12], 11 ;a = 11
    
    sub     esp, 8                 ;Stack at 16B+8 for later alignment
    push    DWORD PTR [ebp-12]     ;a
    push    OFFSET FLAT:.LC0       ;"%d"     (Stack at 16B)
    call    printf
    add     esp, 16                ;Remove args+pad from the stack (Stack at 16B)
    
    mov     eax, 0                 ;Return 0
    
    mov     ecx, DWORD PTR [ebp-4] ;Restore ECX without the need to add to esp
    leave                          ;Restore EBP
    
    lea     esp, [ecx-4]           ;Restore original ESP
    ret
    

    I don't know why the compiler saves esp+4 in ecx instead of esp (esp+4 is the address of the first parameter of main).