Stack cleanup on IA32 after call/ret

Viewed 110

I just started learning x86 and don't quite understand how the stack is cleaned up after a call/ret on IA32. I compiled the following code with gcc -m32 -no-pie:

#include <unistd.h>

int main()
{
    char text[108];
    int x = read(0, text, 108);
    return 0;
}

As far as I understand, the compiler makes space for 120 (116 + 4 bytes) on the stack. The first two words ebp-4 and ebp-8 remain unused (for memory alignment maybe?), and ebp-12 is reserved for storing x. ebp-16 to ebp-120 is reserved for the input text. Before calling read three values (4 bytes each) are pushed onto the stack, which are (after RET) later cleaned up with add esp, 16. But shouldn't it be add esp, 12 since we only pushed 3 values? Is it not overwriting the first/last character of "text"? I already drew the stack multiple times on paper, but I still don't see my mistake.

lea     ecx, [esp+4]
and     esp, -16
push    DWORD PTR [ecx-4]
push    ebp
mov     ebp, esp
push    ecx
sub     esp, 116
sub     esp, 4
push    108
lea     eax, [ebp-120]
push    eax
push    0
call    read
add     esp, 16
mov     DWORD PTR [ebp-12], eax
mov     eax, 0
mov     ecx, DWORD PTR [ebp-4]
leave
lea     esp, [ecx-4]
ret

Edit: Shouldn't also at the very end the stack and base pointers be restored, i.e., mov esp, ebp ; pop ebp so that the very last ret can access the stored instruction pointer and jump out of the main function?

0 Answers
Related