Assembly Language - Procedures (Subroutines)
Assembly Language - Procedures (Subroutines)
A Procedure is a mechanism in assembly language for implementing reusable functions/subroutines, enabling structured and reusable code. Understanding the relationship between procedures and the stack is a significant milestone in assembly programming.
What Is a Procedure?
A procedure is a block of code that can be called multiple times, similar to a function or subroutine in high-level languages.
In assembly, procedures are invoked using the CALL instruction and returned from using the RET instruction. CALL pushes the return address onto the stack, and RET pops the return address from the stack and jumps back to it.
Example
; File path: simple_proc.asm
; Simplest procedure call example
section .data
msg db 'Hello, tutorial!', 0xA
len equ $ - msg
section .text
global _start
_start:
call print_message ; Call procedure (pushes address of next instruction onto 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 stack)
In a procedure, you must save and restore all registers that are modified (except EAX when it is used as a return value). This is basic etiquette in assembly programming. Failing to do so may unintentionally modify the callerβs register values.
How CALL and RET Work
CALL and RET work in pairs and rely on the stack to manage return addresses:
| Instruction | Actual Operation |
|---|---|
CALL label |
push eip (save address of next instruction) + jmp label |
RET |
pop eip (pop address from stack and jump) |
Passing Parameters via Registers
Place parameters into predefined registers before calling the procedure:
Example
; File path: proc_params_reg.asm
; Pass parameters via registers
section .data
newline db 0xA
section .text
global _start
_start:
; Call add_two procedure: compute 10 + 20
mov eax, 10 ; First parameter in eax
mov ebx, 20 ; Second parameter in ebx
call add_two ; Call procedure
; Return value in eax = 30
mov ebx, eax ; Exit code = 30
mov eax, 1
int 0x80
; Procedure: compute eax + ebx, result returned in eax
add_two:
add eax, ebx ; eax = eax + ebx
ret
Passing Parameters via Stack
Stack-based parameter passing is more flexible and is the standard approach used by high-level languages like C:
Example
; File path: proc_params_stack.asm
; Pass parameters via 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 sum procedure: compute 100 + 200
push dword 200 ; Push 2nd parameter
push dword 100 ; Push 1st parameter
call sum ; Call procedure
add esp, 8 ; β
Caller cleans up stack (cdecl convention)
; Return value in eax = 300
mov ebx, eax
mov eax, 1
int 0x80
; Procedure: sum(a, b) = a + b
sum:
push ebp ; β
Save old base pointer
mov ebp, esp ; β
Set new stack frame base
; Stack layout (high to low addresses):
; [ebp+12] = parameter b (200)
; [ebp+8] = parameter a (100)
; [ebp+4] = return address
; = old ebp
; = local variable space
mov eax, [ebp+8] ; Get 1st parameter (a)
add eax, [ebp+12] ; Add 2nd parameter (b)
pop ebp ; β
Restore old base pointer
ret ; Return
Stack frame structure diagram:
Textual illustration:
High Address
+------------------+
| Parameter2 (200) | <-- [ebp + 12]
+------------------+
| Parameter1 (100) | <-- [ebp + 8]
+------------------+
| Return Address | <-- [ebp + 4]
+------------------+
| Old EBP | <-- (current EBP points here)
+------------------+
| Local Variables | <-- , , ...
+------------------+
Low Address <-- ESP points here
The prologue (push ebp; mov ebp, esp) and epilogue (pop ebp; retorleave; ret) form the standard stack frame management template. Almost all assembly functions begin and end with this structure.
Procedure Return Values
Return values are typically stored in the EAX register (for 32-bit values) or EDX:EAX (for 64-bit values):
Example
; Return value examples
; Return int
call get_answer
; eax = 42
; Return 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 in the procedure prologue by decreasing ESP:
Example
; File path: local_vars.asm
; Use 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-byte local variable space on stack
; Now and are available as local variables
mov eax, [ebp+8] ; x
mov ebx, [ebp+12] ; y
; = x * y
imul eax, ebx
mov , eax ; Local variable 1 = x * y
; = x + y
mov eax, [ebp+8]
add eax, [ebp+12]
mov , eax ; Local variable 2 = x + y
; Return value = +
mov eax,
add eax,
; β
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 upon procedure return. Theleaveinstruction can be used instead ofmov esp, ebp; pop ebp, with equivalent effect.
Comparison of Calling Conventions
| Convention | Parameter Passing | Stack Cleaner | Register Preservation | Return Value |
|---|---|---|---|---|
| cdecl | Stack (pushed right-to-left) | Caller | EAX, ECX, EDX preserved by caller | EAX |
| stdcall | Stack (pushed right-to-left) | Callee | EAX, ECX, EDX preserved by caller | EAX |
| fastcall | ECX, EDX + stack | Callee | EAX, ECX, EDX preserved by caller | EAX |
YouTip