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?