Stack, PUSH/POP, and Stack Frames
Master the hidden workhorse of every program — the stack that powers calls, returns, and local storage
Open interactive version (quiz + challenge)Real-world analogy
What is it?
The stack is a Last-In-First-Out (LIFO) memory structure located in the Stack Segment (SS), managed by the Stack Pointer (SP). PUSH decrements SP and stores a word; POP loads a word and increments SP. The stack powers procedure calls (saving return addresses), register preservation, parameter passing, and local variable storage. Stack frames, anchored by BP, give procedures structured access to parameters and locals.
Real-world relevance
Every function call in every programming language uses a stack frame. When you see a 'stack trace' in an error message, that is the chain of BP-linked stack frames from nested calls. Buffer overflow attacks exploit stack layout to overwrite return addresses. Stack size limits in embedded systems (often just 256-512 bytes) force careful design. Understanding the stack is essential for debugging, security, and systems programming.
Key points
- Stack Segment and SS:SP — The stack lives in the Stack Segment (SS). SP (Stack Pointer) always points to the top of the stack (the last item pushed). SS:SP together form the 20-bit physical address of the stack top. The linker initializes SS and SP based on the .STACK directive.
- Stack Grows Downward — On the 8086, the stack grows from high addresses to low addresses. PUSH decreases SP by 2 (word size), then writes the value. POP reads the value at SP, then increases SP by 2. This means SP decreases as the stack fills up.
- PUSH Instruction — PUSH decrements SP by 2, then stores a 16-bit value at SS:SP. You can push general registers (AX-DX, SI, DI, BP), segment registers (CS, DS, ES, SS), flags (PUSHF), and memory/immediate operands. On the 8086, PUSH always operates on words.
- POP Instruction — POP reads the 16-bit value at SS:SP, stores it in the destination, then increments SP by 2. POP restores values in reverse order of PUSH. You can POP into general registers, segment registers (except CS), and memory operands.
- LIFO Order Matters — Since the stack is Last-In-First-Out, POP must be in reverse order of PUSH to get the correct values back. PUSH AX / PUSH BX requires POP BX / POP AX. Getting the order wrong swaps the register values — a subtle and common bug.
- PUSHF and POPF — PUSHF pushes the entire 16-bit FLAGS register onto the stack. POPF pops it back. This saves and restores all arithmetic flags (CF, ZF, SF, OF, PF, AF) plus control flags (DF, IF, TF). Essential for preserving flag state across procedure calls.
- Stack Frames with BP — A stack frame is the stack region owned by one procedure call. BP (Base Pointer) anchors the frame. The standard prologue is PUSH BP / MOV BP, SP. Now [BP+4] is the first parameter, [BP+2] is the return address, and [BP-N] are local variables.
- Allocating Local Variables on Stack — SUB SP, n after the prologue reserves n bytes for local variables. These are accessed at negative offsets from BP: [BP-2], [BP-4], etc. The epilogue (MOV SP, BP / POP BP) deallocates them. This is how C local variables work.
- Nested Calls and Stack Growth — When procedure A calls procedure B which calls procedure C, three frames stack on top of each other. Each CALL pushes a return address, each prologue pushes BP and allocates locals. The stack can grow quite deep — stack overflow occurs if SP decreases past the stack segment.
- Stack Overflow and Protection — If the stack grows beyond its allocated size, it overwrites other data or code — a stack overflow. The .STACK directive determines the size. For deep recursion or many local variables, increase the stack size. Always monitor SP during debugging.
Code example
; Program: Demonstrate stack operations and stack frames
.MODEL SMALL
.STACK 200h
.DATA
val1 DW 10
val2 DW 20
msg DB 'Result: $'
.CODE
MAIN PROC
MOV AX, @DATA
MOV DS, AX
; --- Show PUSH/POP ---
MOV AX, 1111h
MOV BX, 2222h
PUSH AX ; stack: 1111
PUSH BX ; stack: 2222, 1111
; Swap via stack
POP AX ; AX = 2222, stack: 1111
POP BX ; BX = 1111, stack: empty
; Now AX=2222h, BX=1111h (swapped!)
; --- Call procedure with stack params ---
PUSH [val2] ; push 20
PUSH [val1] ; push 10
CALL add_words ; result in AX
; AX = 30
; --- Display result ---
MOV AH, 09h
LEA DX, msg
INT 21h
CALL print_ax ; display 30
MOV AH, 4Ch
INT 21h
MAIN ENDP
; Procedure: add two words passed on stack
; Returns sum in AX
add_words PROC NEAR
PUSH BP
MOV BP, SP
; [BP+0] = saved BP
; [BP+2] = return IP
; [BP+4] = first param (val1 = 10)
; [BP+6] = second param (val2 = 20)
MOV AX, [BP+4] ; AX = 10
ADD AX, [BP+6] ; AX = 10 + 20 = 30
POP BP
RET 4 ; return and clean 4 bytes of params
add_words ENDP
; Procedure: print AX as unsigned decimal
print_ax PROC NEAR
PUSH BP
MOV BP, SP
SUB SP, 6 ; local buffer for digits
PUSH BX
PUSH CX
PUSH DX
MOV BX, 10
XOR CX, CX ; digit count
extract:
XOR DX, DX
DIV BX ; AX/10, remainder in DX
PUSH DX ; save digit
INC CX
CMP AX, 0
JNE extract
display:
POP DX ; get digit (reversed order)
ADD DL, 30h
MOV AH, 02h
INT 21h
LOOP display
POP DX
POP CX
POP BX
MOV SP, BP
POP BP
RET
print_ax ENDP
END MAINLine-by-line walkthrough
- 1. PUSH AX / PUSH BX — pushes two values. SP decreases by 2 each time. Stack now has 1111h (bottom) and 2222h (top)
- 2. POP AX / POP BX — pops in LIFO order. AX gets 2222h (top), BX gets 1111h. This swaps the register values using the stack
- 3. PUSH [val2] / PUSH [val1] — push function parameters right-to-left (C calling convention). val1 is on top
- 4. CALL add_words — pushes the return address (IP of next instruction) onto the stack, then jumps to add_words
- 5. PUSH BP / MOV BP, SP — standard prologue: save the caller's BP and establish the new frame. BP is now our anchor
- 6. [BP+4] and [BP+6] — access parameters. +4 skips saved BP (2 bytes) and return IP (2 bytes) to reach first param
- 7. RET 4 — pops the return address AND adds 4 to SP, removing both pushed parameters. Called 'callee cleanup'
- 8. In print_ax: SUB SP, 6 allocates 6 bytes of local space (not used in this version but demonstrates the pattern)
- 9. The digit extraction loop divides AX by 10 repeatedly, pushing each remainder (digit) onto the stack
- 10. The display loop pops digits in reverse order (most significant first) and converts each to ASCII for printing
Spot the bug
my_proc PROC NEAR
PUSH BP
MOV BP, SP
MOV AX, [BP+2] ; read first parameter
ADD AX, [BP+4] ; add second parameter
POP BP
RET
my_proc ENDPNeed a hint?
Show answer
Explain like I'm 5
Fun fact
Hands-on challenge
More resources
- Stack in 8086 Microprocessor (GeeksforGeeks)
- x86 Stack Frame Layout (Wikipedia)
- PUSH, POP, and Stack Frames Explained (YouTube)
- Smashing the Stack for Fun and Profit (Phrack Magazine)