Skip to content

Stack Frames & Function Prologues Demystified: x86-64 & ARM64 Guide

Master stack frame prologue and epilogue patterns across x86-64 and ARM64 architectures. Learn how function calls work at the assembly level on Linux, macOS, and Windows.

Understanding stack frame prologue epilogue patterns is essential for anyone working with low-level code, debugging, or reverse engineering. In Part 2 of this series, we explored calling conventions — the contracts that govern how functions pass arguments and preserve registers. Now we’ll dive into stack frames themselves.

Every function call creates a stack frame — a dedicated workspace on the stack that holds local variables, saved registers, and the breadcrumb trail back to the caller. The function prologue sets up this frame at the start of a function, while the epilogue tears it down before returning. These are the bookends of every function call.

This comprehensive guide covers stack frame prologue and epilogue patterns across:

ArchitecturePlatformsABI
x86-64Linux, macOSSystem V AMD64
x86-64WindowsMicrosoft x64
ARM64Linux, macOS, WindowsAAPCS64

Stack frame prologue epilogue patterns for x86-64 and ARM64 - memory layout and register usage visualization

What Is a Stack Frame?

A stack frame (also called an activation record) is a contiguous block of memory on the stack allocated for a single function invocation. It contains:

  • Return address — Where to resume execution after the function returns
  • Saved frame pointer — The caller’s frame pointer (if frame pointers are used)
  • Saved registers — Callee-saved registers that this function modifies
  • Local variables — Function-local data
  • Spill slots — Temporary storage for register values
  • Outgoing arguments — Arguments for functions this function calls (stack-passed)

Interactive Lab: Stack Frame Animator

Understanding how the stack changes during function calls is essential for debugging and writing secure code. This tool animates the function prologue, showing how registers like RSP and RBP manage the stack frame.

Key Concepts

RSP (Stack Pointer)

Points to the very “top” of the stack (lowest address). It changes with every push, pop, and stack allocation.

RBP (Base Pointer)

Provides a fixed reference point within the current stack frame, making it easy to access local variables and parameters.

The Prologue

The sequence of instructions at the start of a function that saves the old RBP and allocates space for local variables.


x86-64 Stack Frame Prologue Epilogue (System V ABI)

The System V AMD64 ABI is used on Linux, macOS, FreeBSD, and most Unix-like systems. Here’s how a typical stack frame is constructed:

Standard Prologue

asm

push rbp          ; Save caller's frame pointer
mov rbp, rsp      ; Set up new frame pointer
sub rsp, N        ; Allocate N bytes for locals

This is the classic frame pointer-based prologue. After execution:

  • RBP points to the saved frame pointer
  • [RBP+8] contains the return address
  • [RBP-8] and below hold local variables

Standard Epilogue

asm

mov rsp, rbp      ; Deallocate locals
pop rbp           ; Restore caller's frame pointer
ret               ; Return to caller

Or using the leave instruction:

asm

leave             ; Equivalent to: mov rsp, rbp; pop rbp
ret

Saving Callee-Saved Registers

If a function uses callee-saved registers (RBX, R12-R15), they must be preserved:

asm

; Prologue
push rbp
mov rbp, rsp
push rbx          ; Save callee-saved registers
push r12
push r13
sub rsp, 32       ; Allocate locals (keep 16-byte aligned)

; ... function body ...

; Epilogue
add rsp, 32
pop r13
pop r12
pop rbx
pop rbp
ret

Red Zone Optimization

The System V ABI provides a 128-byte red zone below RSP that leaf functions can use without adjusting the stack pointer:

asm

; Leaf function using red zone - no prologue needed!
mov [rsp-8], rdi  ; Store in red zone
mov [rsp-16], rsi
; ... compute ...
ret

Important: The red zone is only safe for leaf functions. Signal handlers and interrupts will clobber it.


x86-64 Stack Frame Prologue Epilogue (Windows x64)

Windows uses a different ABI with several unique requirements:

Shadow Space Requirement

Every function must reserve 32 bytes of shadow space for the first four register arguments, even if not used:

asm

; Windows x64 Prologue
push rbp
mov rbp, rsp
sub rsp, 48       ; 32 bytes shadow + 16 bytes locals (aligned)

Callee-Saved Registers

Windows has more callee-saved registers: RBX, RBP, RDI, RSI, R12-R15, and XMM6-XMM15:

asm

; Saving XMM registers on Windows
sub rsp, 32
movaps [rsp], xmm6
movaps [rsp+16], xmm7

No Red Zone

Windows does not have a red zone. You must always adjust RSP before storing anything below it.


ARM64 Stack Frame Prologue Epilogue (AAPCS64)

ARM64 follows the AAPCS64 (Procedure Call Standard for ARM 64-bit Architecture). The prologue/epilogue patterns differ significantly from x86-64:

Standard Prologue

asm

stp x29, x30, [sp, #-16]!  ; Save FP and LR, pre-decrement SP
mov x29, sp                 ; Set up frame pointer
sub sp, sp, #N              ; Allocate N bytes for locals

Key differences from x86-64:

  • X29 is the frame pointer (equivalent to RBP)
  • X30 (LR) holds the return address (not pushed by call)
  • STP stores a pair of registers efficiently
  • Pre-indexed addressing [sp, #-16]! combines store and decrement

Standard Epilogue

asm

add sp, sp, #N             ; Deallocate locals
ldp x29, x30, [sp], #16    ; Restore FP and LR, post-increment SP
ret                         ; Return (uses X30)

Saving Callee-Saved Registers

ARM64 callee-saved registers are X19-X28 and D8-D15 (SIMD). They’re typically saved in pairs:

asm

; Prologue with callee-saved registers
stp x29, x30, [sp, #-48]!  ; Save FP, LR
mov x29, sp
stp x19, x20, [sp, #16]    ; Save callee-saved pairs
stp x21, x22, [sp, #32]

; ... function body ...

; Epilogue
ldp x21, x22, [sp, #32]
ldp x19, x20, [sp, #16]
ldp x29, x30, [sp], #48
ret

Apple ARM64 Variations

Apple’s ARM64 ABI has some specific requirements:

  • Pointer Authentication (PAC) — Return addresses may be signed
  • BTI — Branch Target Identification for control flow integrity
asm

; Apple ARM64 with PAC
pacibsp                    ; Sign return address
stp x29, x30, [sp, #-16]!
mov x29, sp

; ... function body ...

ldp x29, x30, [sp], #16
retab                      ; Authenticate and return

Frame Pointer Omission (FPO)

With optimizations enabled, compilers often omit the frame pointer to free up a register:

x86-64 Without Frame Pointer

asm

; No frame pointer - RBP available for general use
sub rsp, 40       ; Allocate stack space
mov [rsp+8], rbx  ; Save callee-saved if needed

; ... use RBP as general register ...

mov rbx, [rsp+8]
add rsp, 40
ret

This makes debugging harder but provides an extra register. Use -fno-omit-frame-pointer to preserve frame pointers.

ARM64 Without Frame Pointer

asm

; Leaf function - no frame setup needed
stp x19, x20, [sp, #-16]!  ; Save if using these registers
; ... function body using X29 freely ...
ldp x19, x20, [sp], #16
ret

Stack Alignment Requirements

Proper stack alignment is mandatory for correct execution:

PlatformAlignmentWhen Required
x86-64 System V16-byteBefore CALL instruction
x86-64 Windows16-byteBefore CALL instruction
ARM6416-byteAlways (SP must be aligned)

Misalignment causes crashes on ARM64 and performance penalties (or crashes with SSE) on x86-64.


Practical Debugging: Walking the Stack

Understanding stack frame prologue epilogue patterns is essential for debugging. Here’s how to walk a stack manually:

x86-64 Stack Walk

c

// Walk the stack using frame pointers
void **frame = (void **)__builtin_frame_address(0);
while (frame) {
  void *return_addr = frame[1];  // [RBP+8]
  printf("Return address: %p\n", return_addr);
  frame = (void **)frame[0];      // Follow saved RBP
}

ARM64 Stack Walk

c

// Walk the stack using frame pointers
void **frame = (void **)__builtin_frame_address(0);
while (frame) {
  void *return_addr = frame[1];  // Saved X30 (LR)
  printf("Return address: %p\n", return_addr);
  frame = (void **)frame[0];      // Follow saved X29 (FP)
}

Quick Reference Table

Featurex86-64 System Vx86-64 WindowsARM64
Frame PointerRBPRBPX29
Stack PointerRSPRSPSP
Return Address[RBP+8] (pushed by CALL)[RBP+8]X30/LR (saved manually)
Red Zone128 bytesNoneNone
Shadow SpaceNot required32 bytes requiredNot required
Alignment16-byte before CALL16-byte before CALL16-byte always

What’s Next

Now that you understand stack frame prologue epilogue patterns, you can:

  • Debug more effectively — Read crash dumps and walk stacks manually
  • Write correct assembly — Ensure proper register preservation and alignment
  • Reverse engineer binaries — Identify function boundaries and local variables
  • Understand compiler output — See how optimizations affect the generated code

In Part 4, we’ll explore program startup: what happens before main() runs, how the C runtime initializes, and where command-line arguments come from.

Experiment: Use Godbolt to compile the same function with different optimization levels (-O0, -O1, -O2, -O3) and watch how the stack frame prologue and epilogue evolve.


References

Coder Musings

A modern technical laboratory for systems programming. Master Assembly, Compilers, and Low-Level Engineering through curated paths and interactive visualizations.

© 2026 Coder Musings. All rights reserved. Built for the systems community.