Stacks and Stack Frames

From lecture notes on low-level programming
Date
Author @asyncze

Contents

The stack #

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

Stack frames #

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.

Function calls and stack layout #

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

Function calls in assembly #

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.