Assembly Language - Procedures (Subroutines)

A procedure is a mechanism in assembly language for implementing function/subroutine reuse, making code structured and reusable. Understanding the relationship between procedures and the stack is an important milestone in assembly programming.


What is a Procedure

A procedureis a block of code that can be called multiple times, similar to functions or subroutines in high-level languages.

In assembly, a procedure is called via theCALLCALL instruction, and returns via theRETRET instruction.CALLIt pushes the return address onto the stack,RETand it pops the return address from the stack and jumps back.

Example

; File path: simple_proc.asm
; Simplest example of a procedure call

section .data
    msg db 'Hello, example!', 0xA
    len equ $ - msg

section .text
    global _start

_start:
    call print_message           ; Call procedure (push the next instruction address onto the stack)
    call print_message           ; Call again

    mov eax, 1
    mov ebx, 0
    int 0x80

; Procedure: print message
print_message:
    push eax                     ; Save registers to be modified
    push ebx
    push ecx
    push edx

    mov eax, 4
    mov ebx, 1
    mov ecx, msg
    mov edx, len
    int 0x80

    pop edx                      ; Restore registers (in reverse order)
    pop ecx
    pop ebx
    pop eax
    ret                          ; Return to caller (pop return address from the stack)

Inside a procedure, you should save and restore all modified registers (except EAX when it holds the return value). This is basic etiquette in assembly programming. Not doing so will cause the caller's register values to be modified unexpectedly.


CALL and RET Principles

CALLandRETThey work in pairs and rely on the stack to manage return addresses:

InstructionActual operation performed
CALL labelpush eip(Save the next instruction address) +jmp label
RETpop eip(Pop the address from the stack and jump)

Passing Parameters via Registers

Before calling a procedure, place the parameters into agreed-upon registers:

Example

; File path: proc_params_reg.asm
; Passing parameters via registers

section .data
    newline db 0xA

section .text
    global _start

_start:
    ; Call the add_two procedure: calculate 10 + 20
    mov eax, 10                  ; Put the first parameter in eax
    mov ebx, 20                  ; Put the second parameter in ebx
    call add_two                 ; Call procedure
    ; Return value is in eax = 30

    mov ebx, eax                 ; Exit code = 30
    mov eax, 1
    int 0x80

; Procedure: calculate eax + ebx, return the result in eax
add_two:
    add eax, ebx                 ; eax = eax + ebx
    ret

Passing Parameters via the Stack

Passing parameters via the stack is more flexible and is the standard method for high-level languages such as C:

Example

; File path: proc_params_stack.asm
; Passing parameters via the stack (cdecl style)

section .data
    msg db 'Sum is: '
    msg_len equ $ - msg
    newline db 0xA

section .bss
    result_buf resb 4

section .text
    global _start

_start:
    ; Call the sum procedure: calculate 100 + 200
    push dword 200               ; Push the second parameter
    push dword 100               ; Push the first parameter
    call sum                     ; Call procedure
    add esp, 8                   ; ★ Caller cleans up the stack (cdecl convention)
    ; Return value is in eax = 300

    mov ebx, eax
    mov eax, 1
    int 0x80

; Procedure: sum(a, b) = a + b
sum:
    push ebp                     ; ★ Save the old base pointer
    mov ebp, esp                 ; ★ Set up the new stack frame base

    ; Stack layout (from high address to low address):
    ; [ebp+12] = parameter b (200)
    ; [ebp+8] = parameter a (100)
    ; [ebp+4] = return address
    ; [ebp] = old ebp
    ; [ebp-4] = local variable space

    mov eax, [ebp + 8]           ; Get the first parameter (a)
    add eax, [ebp + 12]          ; Add the second parameter (b)

    pop ebp                      ; ★ Restore the old base pointer
    ret                          ; Return

Stack frame structure diagram:

栈帧结构图

The following is the corresponding text illustration:

高地址
+------------------+
| 参数2 (200)      |  <-- [ebp + 12]
+------------------+
| 参数1 (100)      |  <-- [ebp + 8]
+------------------+
| 返回地址          |  <-- [ebp + 4]
+------------------+
| 旧的 EBP         |  <-- [ebp]  (当前 EBP 指向这里)
+------------------+
| 局部变量区        |  <-- [ebp - 4], [ebp - 8], ...
+------------------+  <-- ESP 指向这里
低地址

Function Prologuepush ebp; mov ebp, espand Epiloguepop ebp; ret(orleave; ret) is the standard stack frame management template; almost all assembly functions start and end with this structure.


Return Values of Procedures

Return values are usually stored in the EAX register (32-bit value) or EDX:EAX (64-bit value):

Example

; Return value example

    ; Return int
    call get_answer
    ; eax = 42

    ; Return a 64-bit value (e.g., long long)
    call get_big_value
    ; edx:eax = 64-bit result

get_answer:
    mov eax, 42                  ; Return value placed in eax
    ret

get_big_value:
    mov eax, 0x00000001          ; Low 32 bits
    mov edx, 0x00000000          ; High 32 bits
    ret

Local Variables

Local variables use stack space and are allocated by decreasing ESP in the procedure prologue:

Example

; File path: local_vars.asm
; Using local variables in a procedure

section .text
    global _start

_start:
    push dword 10
    push dword 20
    call multiply_add
    add esp, 8
    ; eax = 10 * 20 + 10 + 20 = 230

    mov ebx, eax
    mov eax, 1
    int 0x80

; Procedure: multiply_add(x, y) = x*y + x + y
multiply_add:
    push ebp
    mov ebp, esp
    sub esp, 8                   ; ★ Allocate 8 bytes of local variable space on the stack

    ; Now [ebp-4] and [ebp-8] are available local variables

    mov eax, [ebp + 8]           ; x
    mov ebx, [ebp + 12]          ; y

    ; [ebp-4] = x * y
    imul eax, ebx
    mov [ebp - 4], eax           ; Local variable 1 = x * y

    ; [ebp-8] = x + y
    mov eax, [ebp + 8]
    add eax, [ebp + 12]
    mov [ebp - 8], eax           ; Local variable 2 = x + y

    ; Return value = [ebp-4] + [ebp-8]
    mov eax, [ebp - 4]
    add eax, [ebp - 8]

    ; ★ Clean up local variable space and restore
    mov esp, ebp                  ; Equivalent to add esp, 8
    pop ebp
    ret

Local variables are allocated in the stack frame and automatically released when the procedure returns. Usingleavethe instruction can replacemov esp, ebp; pop ebp, with equivalent effect.


Comparison of Procedure Calling Conventions

ConventionParameter passingStack cleanupRegister preservationReturn value
cdeclStack (pushed right to left)CallerEAX, ECX, EDX are saved by the callerEAX
stdcallStack (pushed right to left)CalleeEAX, ECX, EDX are saved by the callerEAX
fastcallECX, EDX + stackCalleeEAX, ECX, EDX are saved by the callerEAX
Other extensions