Lesson 33 of 48 intermediate

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

The stack is like a spring-loaded plate dispenser in a cafeteria. You push plates (data) onto the top, and you can only take (pop) the top plate — last in, first out (LIFO). The spring (SP) automatically moves down when you add a plate and up when you remove one. You cannot reach into the middle. A stack frame is like putting a divider between one customer's plates and the next — BP marks where your plates begin.

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

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 MAIN

Line-by-line walkthrough

  1. 1. PUSH AX / PUSH BX — pushes two values. SP decreases by 2 each time. Stack now has 1111h (bottom) and 2222h (top)
  2. 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. 3. PUSH [val2] / PUSH [val1] — push function parameters right-to-left (C calling convention). val1 is on top
  4. 4. CALL add_words — pushes the return address (IP of next instruction) onto the stack, then jumps to add_words
  5. 5. PUSH BP / MOV BP, SP — standard prologue: save the caller's BP and establish the new frame. BP is now our anchor
  6. 6. [BP+4] and [BP+6] — access parameters. +4 skips saved BP (2 bytes) and return IP (2 bytes) to reach first param
  7. 7. RET 4 — pops the return address AND adds 4 to SP, removing both pushed parameters. Called 'callee cleanup'
  8. 8. In print_ax: SUB SP, 6 allocates 6 bytes of local space (not used in this version but demonstrates the pattern)
  9. 9. The digit extraction loop divides AX by 10 repeatedly, pushing each remainder (digit) onto the stack
  10. 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 ENDP
Need a hint?
What is at [BP+2]? Draw out the stack frame: what sits between BP and the parameters?
Show answer
Bug: [BP+2] is the return address, not the first parameter! The stack layout is: [BP+0]=saved BP, [BP+2]=return IP, [BP+4]=first param, [BP+6]=second param. Fix: use [BP+4] for the first param and [BP+6] for the second. Also, should use RET 4 if the procedure is responsible for cleaning up the 4 bytes of parameters.

Explain like I'm 5

Imagine a stack of Lego bricks. You can only put a new brick on top (PUSH) and take a brick from the top (POP). If you put on a red brick, then a blue one, then a green one — when you take one off, you get the green one first (the last one you added). The stack pointer is your finger pointing at the top brick. A stack frame is like wrapping a rubber band around 'your' bricks so you know which ones belong to you when multiple people are stacking bricks.

Fun fact

The famous 'stack smashing' security attack was first formally described by Aleph One in 1996 in the paper 'Smashing the Stack for Fun and Profit.' The attack overwrites the return address on the stack to redirect execution to attacker-controlled code. Modern protections like stack canaries, ASLR, and DEP all exist because the stack frame layout — the same layout you are learning now — is fundamentally exploitable.

Hands-on challenge

Write a recursive Fibonacci procedure that uses proper stack frames. The procedure should accept n in AX and return fib(n) in AX. Use the stack for saving registers and the recursive state. Test with n=10 (result should be 55). Monitor SP in emu8086 to see the stack grow and shrink.

More resources

Open interactive version (quiz + challenge) ← Back to course: Microprocessor A–Z