Page 2 of 2
Popping data with the stack pointer
Similarly, the pop instruction takes a value off the top of stack and places it in its operand, increasing the stack pointer afterwards. In other words, this:
is equivalent to this:
mov eax, [esp]
add esp, 4
So, again, taking the previous diagram (after the push) as a starting point, pop eax will do the following:
and the value 0xDEADBEEF will be written into eax. Note that 0xDEADBEEF also stays at address 0x9080ABC8, since we did nothing to overwrite it yet.
Stack frames and calling conventions
When looking at assembly code generated from C, you will find a lot of interesting patterns. Perhaps the most recognizable pattern is the way parameters are passed into functions using the stack, and the way local variables are allocated on the stack. Note that this only applies to some calling conventions and architectures, of course. In others, some parameters are passed in registers.
I’ll demonstrate this with a simple C program:
int foobar(int a, int b, int c)
int xx = a + 2;
int yy = b + 3;
int zz = c + 4;
int sum = xx + yy + zz;
return xx * yy * zz + sum;
return foobar(77, 88, 99);
Both the arguments passed into foobar and the local variables of that function, along with some other data, are going to be stored on the stack when foobar is called. This set of data on the stack is called a frame for this function. Right before the return statement, the stack frame for foobar looks like this:
The green data were pushed onto the stack by the calling function, and the blue ones by foobar itself.
Compiled with gcc into assembly as follows:
gcc -masm=intel -S z.c -o z.s
The following assembly listing is generated for foobar.
I commented it heavily for easy understanding:
; ebp must be preserved across calls.
; Sincethis function modifies it,
; it must be saved.
; From now on, ebp points to the
; current stack frame of the function
mov ebp, esp
; Make space on the stack for local
; sub esp, 16
; eax <-- a. eax += 2. then store eax in xx
mov eax, DWORD PTR [ebp+8]
add eax, 2
mov DWORD PTR [ebp-4], eax
; eax <-- b. eax += 3. then store eax in yy
mov eax, DWORD PTR [ebp+12]
add eax, 3
mov DWORD PTR [ebp-8], eax
; eax <-- c. eax += 4. then store eax in zz
mov eax, DWORD PTR [ebp+16]
add eax, 4
mov DWORD PTR [ebp-12], eax
; add xx + yy + zz and store it in sum
mov eax, DWORD PTR [ebp-8]
mov edx, DWORD PTR [ebp-4]
lea eax, [edx+eax]
add eax, DWORD PTR [ebp-12]
mov DWORD PTR [ebp-16], eax
; Compute final result into eax, which
; stays there until return
mov eax, DWORD PTR [ebp-4]
imul eax, DWORD PTR [ebp-8]
imul eax, DWORD PTR [ebp-12]
add eax, DWORD PTR [ebp-16]
; The leave instruction here is equivalent to:
; mov esp, ebp
; pop ebp
; Which cleans the allocated locals
; and restores ebp.
Since esp keeps moving as the function executes, ebp (base pointer, also known as frame pointer in other architectures) is used as a convenient anchor relatively to which all function arguments and locals can be found. Arguments are above ebp in the stack (hence the positive offset when accessing them), while locals are below ebp in the stack.
This article was originally published as Where the top of the stack is on x86on Eli Bendersky's website and has been used here with kind permission of the author.