The Stack

The stack is an area of memory that is reserved for stacking temporary items.

The operating system preallocates space for the stack and then puts the pointer to this memory in the stack pointer (%rsp).
While pointers generally refer to the beginning of a memory region at the beginning of a program %rsp points to the end of the memory region containing the stack.

You can add things to your stack using the push family of instructions

push does two things:

  1. decrements %rsp to point to the next location on the stack.
  2. copies the value to the location specified by %rsp

You can then get the values back using the pop family of instructions, which does the reverse

Note

push and pop aren’t the only way to touch the stack. Instead you can use %rsp as your starting location and use offsets movq -8(%rsp), %rax this loads the value from offset 8 from the current top of the stack. This is what compiled code does for locals.

Importance of the stack

Rather than having to memorize which variables are in use at which time, and making sure you don’t actually clobber something (like a register), you can store register values (and other values) on the stack before jumping out to another part of the program

Reserving Space on the Stack

All you have to do in order to do that is simply subtract 16 from the stack pointer using subq $16, %rsp. You just have to remember to add it back when you are done with it with addq $16, %rsp

Important

Everything done to the stack pointer has to be done in exactly the reverse order that it occurred when creating it.

Stack structure

Definition

stack frame: the section of the stack the belongs to a function. The stack frame consists of all of the local temporary storage needed for your function. Since each thread only has one stack, stack frames a way to break up the stack for each function to locally use.

Reminder of what memory looks like:

high  0x7fff...  ┌──────────────┐
                 │ stack        │  grows down ↓
                 ├──────────────┤
                 │ (mmap region)│  shared libs, big mallocs
                 ├──────────────┤
                 │ heap         │  grows up ↑ (brk)
                 ├──────────────┤
                 │ .bss / .data │
                 │ .text        │
low   0x400000   └──────────────┘

stack

Setting up the stack

  • starting a function save the value of %rbp (base of the stack frame)
  • after pushing the value of %rbp onto the stack, %rsp should be copied to %rbp. This makes %rbp point to the previous version of itself
# Save the pointer to the previous stack frame
pushq %rbp
# Copy the stack pointer to the base pointer for a fixed reference point
movq %rsp, %rbp
# Reserve however much memory on the stack I need
subq $NUMBYTES, %rsp
# Restore the stack pointer
movq %rbp, %rsp
# Restore the base pointer
popq %rbp

Question

What’s going on why do we need to perserve %rbp?

Answer

The caller function had it’s own stack frame and same as you the caller needs to know where the base is, so it can use it’s offsets to locate values on the stack. So be nice to them (not that it’s optional) and save %rbp.

These operations are common instructions were added to do both in a single instruction enter and leave. Note - the enter instruction is actually much slower than the equivalent set of instructions given before. Compilers will often opt for manual setup and use leave to clean up the stack.