Stacks and Stack FramesFrom lecture notes on low-level programming |
Date | ||
---|---|---|---|
Author | @asyncze |
The stack grows towards lower memory addresses. The stack pointer %esp
points to top of stack, which is the lowest valid address and last pushed to stack.
Here's the initial stack layout:
SP | Address | Value |
---|---|---|
0xbfff8000 |
a | |
0xbfff7ffc |
b | |
0xbfff7ff8 |
c | |
0xbfff7ff4 |
d | |
%esp |
0xbfff7ff0 |
e |
0xbfff7fec |
||
0xbfff7fe8 |
Here's stack layout after push f
to increment pointer, create space, and store f
at address.
SP | Address | Value |
---|---|---|
0xbfff8000 |
a | |
0xbfff7ffc |
b | |
0xbfff7ff8 |
c | |
0xbfff7ff4 |
d | |
0xbfff7ff0 |
e | |
%esp |
0xbfff7fec |
f |
0xbfff7fe8 |
And here's stack layout after pop %eax
to decrement pointer and store value in %eax
(note that f
is still at 0xbfff7fec
but will be overwritten on the next push
).
SP | Address | Value |
---|---|---|
0xbfff8000 |
a | |
0xbfff7ffc |
b | |
0xbfff7ff8 |
c | |
0xbfff7ff4 |
d | |
%esp |
0xbfff7ff0 |
e |
0xbfff7fec |
f | |
0xbfff7fe8 |
Register | Value |
---|---|
%eax |
f |
A stack is composed of frames that are pushed to the stack on function calls. The address to the current frame is stored in frame pointer %ebp
.
A frame contains function parameters, which are pushed to stack by caller, return address to jump to at the end, pointer to previous frame (save %ebp
to stack and set %ebp = %esp
, frame pointer is lowest valid address and part of prologue), and local variables, which are part of the prologue executed by caller (address location is subtracted to move towards lower addresses, typically 4 bytes).
Epilogue is executed by the callee to deallocate local variables, %esp = %ebp
, save result in some register, such as %eax
, restore frame pointer of caller function, and then resume execution from saved return address.
Here's an example program with function call written in the C programming language.
int convert(char *str) {
int result = atoi(str);
return result;
}
int main(int argc, char **argv) {
int sum, i;
for (i=0; i < argc; i++) {
sum += convert(argv[i]);
}
printf("sum=%d\n", sum);
return 0;
}
Here's the frame pushed by main caller.
Address | Value |
---|---|
0xbfff8000 |
argv |
0xbfff7ffc |
argc |
0xbfff7ff8 |
return address (from main) |
Here's the frame pushed by main
.
Address | Value |
---|---|
0xbfff7ff4 |
frame pointer (before main) |
0xbfff7ff0 |
sum |
0xbfff7fec |
i |
0xbfff7fe8 |
str |
0xbfff7fe4 |
return address (from convert to main) |
And here's the frame pushed by convert
.
Address | Value |
---|---|
0xbfff7fe0 |
frame pointer (before convert) |
0xbfff7fdc |
result |
0xbfff7fd8 |
paramater to atoi |
Here's a function call and the resulting assembly code. The assembly code is generated with Compiler Explorer using x86-64 gcc 4.1.2
and flag -m32
for 32-bit, AT&T syntax).
#include <stdio.h>
void func(int n) {
printf("argument: %d;\n", n);
}
int main(int argc, char **argv) {
func(10);
return 0;
}
Here's the generated assembly instructions for func
. Note that the mnemonic leave
is the same as instruction mov %ebp, %esp
followed by pop %ebp
(operand size suffix is omitted for clarity).
.LCO
.string "argument: %d;\n"
func:
; void func(int n) {
push %ebp
mov %esp, %ebp
sub $8, %esp
; printf("argument: %d;\n", n);
mov 8(%ebp), %eax
mov %eax, 4(%esp)
mov $.LCO, (%esp)
call printf
; }
leave
ret
And here's the generated assembly instructions for main
.
main:
; int main(int argc, char **argv) {
lea 4(%esp), %ecx
and $-16, %esp
push -4(%ecx)
push %ebp
mov %esp, %ebp
push %ecx
sub $4, %esp
; func(10);
mov $10, (%esp)
call func
; return 0;
mov $0, %eax
; }
add $4, %esp
pop %ecx
leave
lea -4(%ecx), %esp
ret
The call to func
is just below the ; func(10);
comment.