Stack frame visualizer
Every function call gets a stack frame: the return address, the caller’s frame pointer, local variables and padding. Enter a C function’s parameters and locals to see exactly where each one lives on x86-64 or 32-bit x86, with real addresses, and step through the call, prologue and return.
| Address | Offset | Bytes | Contents |
|---|---|---|---|
| … caller’s frame | |||
| 0x7fffffffe0b8 | rbp+8 | 8 | return addresspushed by the call instruction |
| 0x7fffffffe0b0 | rbp | 8 | saved frame pointerthe caller’s rbp, pushed by push rbp |
| 0x7fffffffe0ac | rbp−4 | 4 | xint x |
| 0x7fffffffe0a2 | rbp−14 | 10 | buf[10]char buf[10] |
| 0x7fffffffe0a0 | rbp−16 | 2 | (padding)2 unused bytes so y is 8-byte aligned |
| 0x7fffffffe098 | rbp−24 | 8 | ylong y |
| 0x7fffffffe090 | rbp−32 | 8 | (padding)locals rounded to 16 bytes |
| 0x7fffffffe08c | rbp−36 | 4 | aint a: copy of the register argument (rdi) |
| 0x7fffffffe088 | rbp−40 | 4 | bint b: copy of the register argument (rsi) |
| 0x7fffffffe080 | rbp−48 | 8 | (padding)keeps the stack pointer 16-byte aligned for calls |
| 0x7fffffffe080 | rbp−48 | rsp after the prologuethe next call pushes its return address below here | |
rbp = 0x7fffffffe0b0. Layout of locals and argument copies as GCC 13 places them at -O0 on Linux.
| a | rdi |
| b | rsi |
- call frsp = 0x7fffffffe0b8pushes the return address and jumps to f
- push rbprsp = 0x7fffffffe0b0saves the caller’s frame pointer
- mov rbp, rsprsp = 0x7fffffffe0b0rbp now marks this frame: arguments above it, locals below
- sub rsp, 48rsp = 0x7fffffffe080reserves 48 bytes for locals, copies of register arguments and padding
- …body…rsp = 0x7fffffffe080locals are addressed as negative offsets from rbp
- leaversp = 0x7fffffffe0b8same as mov rsp, rbp then pop rbp: frees the frame and restores the caller’s rbp
- retrsp = 0x7fffffffe0c0pops the return address and jumps back to the caller
How to use the visualizer
Type the function’s parameters as in a C prototype and its local variables separated by semicolons, and pick the calling convention. The table draws the stack with higher addresses at the top, as most textbooks do: arguments the caller pushed, the return address, the saved frame pointer, then the function’s own locals, argument copies and padding. Each row gives its absolute address, its offset from the frame pointer and its size. Change the stack pointer before the call to match your exercise.
- The right-hand panel lists which register carries each argument on x86-64, and walks through the call, prologue and return with the stack pointer after each instruction.
- Copy answer gives the frame as a table you can paste.
What is a stack frame?
A program keeps one call stack per thread. Each function call adds a stack frame to it, holding what that call needs and nothing else: where to return to, the caller’s frame pointer, its local variables and any values it must keep across calls. Returning removes the frame, so local variables disappear when the function ends, and recursion works because every active call has its own frame.
On x86 the stack grows toward lower addresses. The stack pointer (rsp, or esp in 32-bit code) points at the lowest byte in use, and the frame pointer (rbp, or ebp) stays fixed for the life of the call, so locals can be addressed at fixed negative offsets from it and arguments at positive ones.
The call, prologue and epilogue
A call happens in a fixed order. The caller puts the arguments where the convention says, in registers or on the stack. The call instruction pushes the return address and jumps. The function’s prologue pushes the caller’s frame pointer, copies the stack pointer into the frame pointer, and subtracts the size of the locals area from the stack pointer. At the end, the epilogue (leave, then ret) undoes it: the stack pointer returns to the frame pointer, the old frame pointer is popped, and ret pops the return address into the instruction pointer.
For a function f(int a, int b) with locals int x; char buf[10]; long y; on x86-64, the prologue reserves 48 bytes. x lands at rbp-4, buf at rbp-14 (ten bytes up to rbp−5), y at rbp-24, and the copies of a and b, which arrived in edi and esi, at rbp-36 and rbp-40.
Calling conventions
A calling convention fixes where arguments and return values go, which registers a function must preserve, and who removes the arguments afterwards. Caller and callee must agree on it, or each will look for the other’s values in the wrong place.
x86-64 System V, used on Linux, macOS and the BSDs, passes the first six integer or pointer arguments in rdi, rsi, rdx, rcx, r8 and r9, and the first eight floating-point arguments in xmm0 to xmm7. The rest go on the stack, the first of them at rbp+16 once the frame is set up: a function with eight int arguments finds the seventh at rbp+16 and the eighth at rbp+24. Integers are returned in rax and floating-point values in xmm0. The stack must be 16-byte aligned at each call, and the 128 bytes below rsp form a red zone that a function may use without moving rsp.
32-bit cdecl, the traditional convention for C on 32-bit x86, passes every argument on the stack, pushed right to left so the first ends up lowest. With the frame pointer set up, [ebp] holds the saved ebp, [ebp+4] the return address, and the arguments start at [ebp+8]: for f(int a, int b, int c), a is at ebp+8, b at ebp+12 and c at ebp+16. The result comes back in eax, and the caller removes the arguments after the call returns.
Where the locals go
The C standard says nothing about the order or position of local variables; the compiler chooses. Without optimisation, GCC 13 on x86-64 Linux places them in declaration order going down from rbp, each at a multiple of its own size, then rounds that area to 16 bytes and stores copies of the register arguments below it, filling earlier alignment gaps where they fit. The calculator follows that rule, and on 1,400 randomly generated functions it matched the offsets in GCC’s output exactly.
Alignment explains the gaps. A long must start at a multiple of 8, so after a 10-byte array some bytes go unused. The x86-64 ABI also requires a local array of 16 bytes or more to be 16-byte aligned: with locals char a[20]; char c[15]; char f;, a starts at rbp-32 rather than rbp−20, while the 15-byte c needs no special alignment and starts at rbp-47.
Optimised code
With optimisation on, most of this changes. Values live in registers and may never touch the stack, variables whose lifetimes don’t overlap share slots, and GCC omits the frame pointer from -O1 upward, addressing the stack relative to rsp instead. A leaf function may keep everything in the red zone and never adjust rsp. The unoptimised layout shown here is the one exercises and debuggers usually describe, and the fixed parts (argument registers, the return address, alignment) are the same either way, because they are set by the ABI rather than the compiler.
Stack frame questions and answers
What is in a stack frame?
The return address pushed by the call instruction, usually the caller’s saved frame pointer, the function’s local variables, copies of arguments or saved registers it needs to keep, and padding to keep the stack aligned. Arguments that do not fit in registers sit just above it, in the caller’s frame.
Where is the first argument on the stack?
In 32-bit x86 cdecl code with a frame pointer, the first argument is at [ebp+8], the second at [ebp+12], and so on: [ebp] holds the saved ebp and [ebp+4] the return address. On x86-64 the first six integer arguments arrive in rdi, rsi, rdx, rcx, r8 and r9, and only the seventh and later are on the stack, from [rbp+16].
Why must the stack be 16-byte aligned?
The x86-64 ABI requires the stack pointer to be a multiple of 16 at every call, so that the callee can use aligned SSE instructions on its locals. Because call pushes an 8-byte return address, rsp is 8 off alignment on entry, and push rbp brings it back; frame sizes are then rounded to 16.
What is the red zone?
On x86-64 System V, the 128 bytes below the stack pointer are reserved for the current function and are not touched by signal or interrupt handlers. A leaf function, one that calls nothing, can keep its locals there without moving rsp at all.
Does every function have a frame pointer?
No. GCC omits it by default from -O1 upward, addressing locals relative to rsp and freeing rbp as a general register. Debuggers and profilers then rely on separate unwind information. This page shows the unoptimised layout, with rbp, which is what most exercises assume.