Post

13. Stack and Procedures

13. Stack and Procedures

Welcome to the final chapter of our x86-64 assembly journey! Now we’ll learn how to write structured, maintainable code using the stack and procedures - the foundation of functions, local variables, and organized programming.

The Stack: A Fundamental Data Structure

The stack is a Last-In, First-Out (LIFO) memory region that grows downward in memory. It’s managed by two special registers:

  • RSP - Stack Pointer (points to the top of the stack)
  • RBP - Base Pointer (points to the base of the current stack frame)**
; Memory visualization:
; Higher addresses: [ older data   ]
;                   [ ...          ]
; RBP →             [ base frame   ]
;                   [ local vars   ]
; RSP →             [ top of stack ]
; Lower addresses:  [   ...        ]

Stack Operations

1. PUSH - Add to Stack

Syntax: PUSH operand

How it works:

  • Decrements RSP by 8 (in 64-bit mode)
  • Stores operand at new [RSP]
1
2
3
push rax            ; Decrement RSP by 8, store RAX at [RSP]
push 42             ; Push immediate value
push qword [var]    ; Push memory value

2. POP - Remove from Stack

Syntax: POP operand

How it works:

  • Loads value from [RSP] into operand
  • Increments RSP by 8
1
2
pop rbx             ; Load [RSP] into RBX, increment RSP by 8
pop qword [var]     ; Pop into memory location

Stack Frame Management

Each function call creates a stack frame containing:

  • Return address (where to return after function)
  • Previous RBP value
  • Local variables
  • Function parameters

Prologue (function start):

1
2
3
4
my_function:
    push rbp            ; Save old base pointer
    mov rbp, rsp        ; Set new base pointer
    sub rsp, 16         ; Allocate space for local variables

Epilogue (function end):

1
2
3
    mov rsp, rbp        ; Restore stack pointer
    pop rbp             ; Restore old base pointer
    ret                 ; Return to caller

Procedure Calls

1. CALL - Call a Procedure

Syntax: CALL target

How it works:

  • Pushes return address (next instruction) onto stack
  • Jumps to target address
1
2
call my_function    ; Push return address, jump to my_function
; After function returns, execution continues here

2. RET - Return from Procedure

Syntax: RET or RET imm16

How it works:

  • Pops return address from stack
  • Jumps to that address
  • Optional immediate value cleans stack parameters
1
2
ret                 ; Return to caller
ret 8               ; Return and remove 8 bytes of parameters

Calling Conventions

x86-64 uses a register-based calling convention for efficiency:

PurposeRegister
Return valueRAX
1st parameterRDI
2nd parameterRSI
3rd parameterRDX
4th parameterRCX
5th parameterR8
6th parameterR9
Additional parametersStack

Caller-saved registers: RAX, RCX, RDX, RSI, RDI, R8-R11
Callee-saved registers: RBX, RBP, R12-R15

Let’s write some code where we will call a function and return mathematical operation done on arguments passed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
section .data
    result dq 0

section .text
global _start

; Function: add_numbers(a, b)  returns a + b
add_numbers:
    ; Prologue
    push rbp
    mov rbp, rsp
    
    ; Function body - parameters are in RDI and RSI
    mov rax, rdi        ; RAX = first parameter
    add rax, rsi        ; RAX += second parameter
    ; Return value is in RAX
    
    ; Epilogue
    mov rsp, rbp
    pop rbp
    ret

; Function: multiply_numbers(a, b)  returns a × b
multiply_numbers:
    push rbp
    mov rbp, rsp
    
    mov rax, rdi        ; RAX = a
    imul rax, rsi       ; RAX = a × b
    
    mov rsp, rbp
    pop rbp
    ret

_start:
    ; Call add_numbers(10, 20)
    mov rdi, 10         ; First parameter
    mov rsi, 20         ; Second parameter
    call add_numbers
    mov [result], rax   ; Store result (30)
    
    ; Call multiply_numbers(5, 6)  
    mov rdi, 5
    mov rsi, 6
    call multiply_numbers
    ; RAX now contains 30
    
    ; Exit program
    mov rax, 60
    mov rdi, 0
    syscall
This post is licensed under CC BY 4.0 by the author.