AsAssembly · Lesson 6 of 10

The Stack & Functions

The stack is how functions store local variables and return addresses. `push` and `pop` move data on and off it. Understanding the stack is the key to understanding function calls, recursion, and most security vulnerabilities.

x86-64 ASM
; Stack mechanics
; rsp = stack pointer, always points to the last pushed value
; Stack grows DOWNWARD (rsp decreases as you push)

push rax        ; rsp -= 8, [rsp] = rax
pop  rbx        ; rbx = [rsp], rsp += 8

; Manually allocate stack space for local variables
sub rsp, 32     ; reserve 32 bytes for locals
; ... use [rsp], [rsp+8], [rsp+16], [rsp+24] ...
add rsp, 32     ; free the space (must match the sub!)

; call and ret — the function call mechanism
; call label:
;   push the return address (rip + instruction size) onto the stack
;   jmp label
; ret:
;   pop the return address from the stack
;   jmp to it

call my_function
; execution resumes here after ret

my_function:
    ; Standard function prologue
    push rbp            ; save caller's base pointer
    mov  rbp, rsp       ; establish new stack frame
    sub  rsp, 16        ; reserve 16 bytes for locals (keep rsp 16-byte aligned!)

    ; Local variables live at [rbp-8], [rbp-16], etc.
    mov qword [rbp-8], 42

    ; ... function body ...

    ; Standard function epilogue
    mov rsp, rbp        ; restore rsp (discard locals)
    pop rbp             ; restore caller's rbp
    ret                 ; pop return address, jump to it
x86-64 ASM
; x86-64 System V ABI — the calling convention used on Linux/macOS
; Integer/pointer arguments passed in: rdi, rsi, rdx, rcx, r8, r9
; Further arguments go on the stack
; Return value in rax (and rdx for 128-bit returns)
; Caller-saved: rax, rcx, rdx, rsi, rdi, r8-r11 (callee may trash these)
; Callee-saved: rbx, rbp, r12-r15 (callee must restore these)
; rsp must be 16-byte aligned before a call instruction

; Example: add(a, b) → rdi=a, rsi=b, return rax
add_two:
    push rbp
    mov  rbp, rsp

    mov  rax, rdi       ; rax = a
    add  rax, rsi       ; rax = a + b
    ; return value is in rax — caller reads it from there

    pop  rbp
    ret

; Calling it:
;   mov rdi, 10    ; first arg
;   mov rsi, 32    ; second arg
;   call add_two
;   ; rax = 42

; Recursive factorial
; factorial(n): if n <= 1 return 1; else return n * factorial(n-1)
factorial:
    push rbp
    mov  rbp, rsp
    push rbx            ; save rbx (callee-saved)

    mov  rbx, rdi       ; save n in rbx
    cmp  rdi, 1
    jle  .base

    dec  rdi            ; n - 1
    call factorial      ; recursive call — result in rax
    imul rax, rbx       ; rax = n * factorial(n-1)
    jmp  .done

.base:
    mov rax, 1          ; base case: return 1

.done:
    pop  rbx
    pop  rbp
    ret
⚠ Warning
The stack must be 16-byte aligned before any `call` instruction — the ABI requires it for SSE/AVX instructions. A `call` pushes 8 bytes (return address), so rsp becomes misaligned by 8 inside a function. The standard prologue (`push rbp; mov rbp, rsp`) restores alignment because push is another 8 bytes (8+8=16). If you skip the prologue, align manually: `sub rsp, 8` before calling external functions.