Skip to content

Assembly Procedures — Function Calls and Calling Conventions

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Assembly Procedures. We cover key concepts, practical examples, and best practices to help you master this topic.

Assembly procedures use call/ret instructions with the stack to implement function calls following the System V AMD64 ABI calling convention for parameter passing and register preservation.

What You'll Learn

  • The call stack and stack frame
  • The System V AMD64 calling convention
  • Caller vs callee-saved registers
  • Passing arguments and return values

Why It Matters

Procedures are fundamental to all structured programming. Understanding the calling convention is essential for debugging, reverse engineering, and writing assembly that interfaces with C libraries.

Real-World Use

Function calls in compiled code, C library interop, operating system kernel calls, and reverse engineering malware to understand its subroutine structure.

flowchart LR
    A["Caller"] --> B["call func"]
    B --> C["Stack Frame"]
    C --> D["Function Body"]
    D --> E["ret"]
    E --> F["Caller Resumes"]
    G["Arguments"] --> C
    H["Return Value"] --> F
    style A fill:#2563eb,stroke:#2563eb,color:#fff
    style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
    style E fill:#dbeafe,stroke:#2563eb,color:#1e40af

Basic Procedure

section .text
    global _start

_start:
    call my_proc        ; push return address, jump to my_proc
    mov rax, 60         ; exit
    xor rdi, rdi
    syscall

my_proc:
    ; procedure body
    mov rax, 42
    ret                 ; pop return address, jump back

Arguments in Registers

System V AMD64 calling convention:

Register Purpose
RDI 1st argument
RSI 2nd argument
RDX 3rd argument
RCX 4th argument
R8 5th argument
R9 6th argument
RAX Return value
; add_two(a, b) -> a + b
add_two:
    mov rax, rdi        ; rax = first arg
    add rax, rsi        ; rax += second arg
    ret

_start:
    mov rdi, 10         ; first arg
    mov rsi, 20         ; second arg
    call add_two
    ; rax = 30

Caller vs Callee-Saved Registers

; Callee-saved: RBX, RBP, R12-R15
;   Must preserve these values
; Caller-saved: RAX, RCX, RDX, RSI, RDI, R8-R11
;   Can freely use (caller saves if needed)

my_proc:
    push rbx            ; save rbx
    push rbp            ; save rbp

    mov rbx, rdi        ; use rbx
    mov rbp, rsi        ; use rbp
    ; ... procedure body ...

    pop rbp             ; restore rbp
    pop rbx             ; restore rbx
    ret

Stack Frame

my_function:
    ; Prologue
    push rbp            ; save old base pointer
    mov rbp, rsp        ; set new base pointer

    ; Allocate local variables
    sub rsp, 32         ; reserve 32 bytes for locals

    ; Body
    mov [rbp - 8], rdi  ; local var at rbp-8
    mov [rbp - 16], rsi ; local var at rbp-16

    ; Epilogue
    mov rsp, rbp        ; restore stack pointer
    pop rbp             ; restore base pointer
    ret

Accessing Stack Parameters

; For functions with more than 6 arguments
; Arguments 7+ are passed on the stack

func_with_many_args:
    push rbp
    mov rbp, rsp

    ; arg1 = rdi
    ; arg2 = rsi
    ; arg7 = [rbp + 16]   (above return address and saved rbp)
    ; arg8 = [rbp + 24]

    mov rax, [rbp + 16]  ; 7th argument

    pop rbp
    ret

Calling C Library Functions

extern printf
extern exit

section .data
    fmt db "Result: %d", 10, 0

section .text
    global _start

_start:
    mov rdi, 42          ; first arg to square
    call square
    mov rsi, rax         ; result as second arg to printf
    mov rdi, fmt         ; format string
    call printf

    mov rdi, 0
    call exit

square:
    mov rax, rdi
    imul rax, rax
    ret

Assemble with: nasm -f elf64 file.asm && gcc -no-pie file.o -o file

Recursive Functions

; factorial(n) -> n!
factorial:
    cmp rdi, 1
    jle base_case

    push rdi             ; save n
    dec rdi
    call factorial        ; factorial(n-1)
    pop rdi              ; restore n
    imul rax, rdi        ; n * factorial(n-1)
    ret

base_case:
    mov rax, 1
    ret

_start:
    mov rdi, 5
    call factorial
    ; rax = 120

Common Mistakes

1. Mismatched push/pop

Every push needs a matching pop. Unbalanced stack causes ret to jump to the wrong address.

2. Forgetting to preserve callee-saved registers

If a function modifies RBX without saving it, the caller crashes when using its own RBX value.

3. Stack alignment

The System V ABI requires 16-byte stack alignment at the point of a call. Violations cause crashes in SSE/AVX instructions.

4. Not zeroing RBP in leaf functions

If a leaf function doesn't touch the stack, it can skip the prologue but must not use RBP-based addressing.

5. Using 32-bit registers for return values

int functions return in EAX (32-bit). long functions return in RAX (64-bit). The upper 32 bits of RAX are undefined for 32-bit returns.

Practice Questions

1. What registers hold the first 6 integer arguments in System V AMD64?

RDI, RSI, RDX, RCX, R8, R9.

2. What is the purpose of the prologue push rbp; mov rbp, rsp?

It saves the caller's base pointer and sets up a fixed reference point for local variables and parameters.

3. Why must callee-saved registers be preserved?

The caller expects them to have the same value after the call returns. Violations cause subtle bugs.

4. What is the stack alignment requirement for calls?

The stack must be 16-byte aligned before the call instruction, meaning RSP mod 16 == 0 at the function entry point.

Challenge: Write a recursive assembly function that computes the nth Fibonacci number.

Solution
fibonacci:
    cmp rdi, 0
    je return_0
    cmp rdi, 1
    je return_1

    push rdi
    dec rdi
    call fibonacci        ; fib(n-1)
    pop rdi
    push rax              ; save fib(n-1)
    sub rdi, 2
    call fibonacci        ; fib(n-2)
    pop rdi               ; restore fib(n-1)
    add rax, rdi          ; fib(n-1) + fib(n-2)
    ret

return_0:
    xor rax, rax
    ret

return_1:
    mov rax, 1
    ret

FAQ

{{< faq question="What happens if I don't follow the calling convention?" >}} The code still runs but may crash when calling external libraries or when the caller optimizes based on ABI guarantees. {{< /faq >}}

{{< faq question="Can I use any register for arguments?" >}} Yes, but if you call C functions, you must follow the System V ABI. For internal procedures, you can use any convention you define. {{< /faq >}}

{{< faq question="How many arguments can be passed in registers?" >}} Six integer arguments in registers. Additional arguments go on the stack. {{< /faq >}}

{{< faq question="What is a leaf function?" >}} A function that calls no other functions. Leaf functions can omit the prologue/epilogue for efficiency. {{< /faq >}}

{{< faq question="How do I return a struct from a function?" >}} The caller passes a hidden pointer argument in RDI for the return struct. The function writes to that pointer and returns it in RAX. {{< /faq >}}

Mini Project

Write a program that uses procedures to compute the sum of squares from 1 to n.

section .data
    result_msg db "Sum of squares: ", 0

section .text
    global _start

_start:
    mov rdi, 10
    call sum_of_squares
    ; rax now has sum of 1^2 + 2^2 + ... + 10^2

    mov rdi, rax
    mov rax, 60
    syscall

sum_of_squares:
    xor rax, rax
    xor rcx, rcx

.loop:
    inc rcx
    mov rdx, rcx
    imul rdx, rdx
    add rax, rdx
    cmp rcx, rdi
    jl .loop
    ret

What's Next

Now that you understand procedures, proceed to system calls.

Topic Description Link
Syscalls Linux system call interface {{< ref "13-syscalls" >}}
Stack Stack operations {{< ref "05-stack" >}}
Instructions CPU instruction set {{< ref "06-instructions" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro