Functions & Procedures in Assembly
Functions (also called procedures or subroutines) in assembly language allow you to organize code into reusable blocks. Proper function implementation requires understanding calling conventions, parameter passing, stack management, and register usage.
Basic Function Structure
A function in assembly typically has three parts:
- Prologue: Setup stack frame and save registers
- Function Body: Perform the actual work
- Epilogue: Clean up and return to caller
Simple Function Example
section .text
global _start
; Function definition
my_function:
; Prologue
push ebp ; Save old base pointer
mov ebp, esp ; Set new base pointer
; Function body
mov eax, 42 ; Do something
; Epilogue
mov esp, ebp ; Restore stack pointer
pop ebp ; Restore base pointer
ret ; Return to caller
_start:
call my_function ; Call the function
; ... rest of program ...
Calling Conventions
Calling conventions define how functions are called and how parameters are passed. Common conventions:
cdecl (C Declaration)
- Parameters pushed right-to-left
- Caller cleans up the stack
- Return value in EAX (or EDX:EAX for 64-bit)
stdcall
- Parameters pushed right-to-left
- Callee cleans up the stack
- Used by Windows API
fastcall
- First two parameters in ECX and EDX
- Remaining parameters pushed right-to-left
- Callee or caller cleans up depending on variation
Parameter Passing
Parameters can be passed via registers or the stack (or a combination).
Stack-based Parameter Passing
section .text
global _start
; Function to add two numbers
add_numbers:
push ebp
mov ebp, esp
mov eax, [ebp+8] ; First parameter
add eax, [ebp+12] ; Second parameter
mov esp, ebp
pop ebp
ret
_start:
push 20 ; Push second parameter
push 10 ; Push first parameter
call add_numbers
add esp, 8 ; Clean up parameters (cdecl)
; EAX now contains 30
Register-based Parameter Passing
section .text
global _start
; Function to add two numbers (fastcall style)
add_numbers:
push ebp
mov ebp, esp
mov eax, ecx ; First parameter in ECX
add eax, edx ; Second parameter in EDX
mov esp, ebp
pop ebp
ret
_start:
mov ecx, 10 ; First parameter
mov edx, 20 ; Second parameter
call add_numbers
; EAX now contains 30
Return Values
Functions typically return values in registers:
- EAX for 32-bit values
- EDX:EAX for 64-bit values
- XMM0 for floating-point values
- Memory locations for larger structures
Local Variables
Local variables are allocated on the stack by adjusting ESP:
my_function:
push ebp
mov ebp, esp
sub esp, 12 ; Allocate space for 3 integers
; Access local variables
mov dword [ebp-4], 10 ; First local
mov dword [ebp-8], 20 ; Second local
mov dword [ebp-12], 30 ; Third local
; ... use locals ...
mov esp, ebp ; Clean up locals
pop ebp
ret
Preserving Registers
According to calling conventions, some registers must be preserved across function calls:
- Caller-saved: EAX, ECX, EDX (can be modified by callee)
- Callee-saved: EBX, ESI, EDI, EBP (must be preserved by callee)
Register Preservation Example
my_function:
push ebp
mov ebp, esp
push ebx ; Save EBX (callee-saved)
push esi ; Save ESI (callee-saved)
; Function body that uses EBX and ESI
pop esi ; Restore ESI
pop ebx ; Restore EBX
mov esp, ebp
pop ebp
ret
Recursive Functions
Recursive functions follow the same rules but must carefully manage stack space:
section .text
global _start
; Recursive factorial function
factorial:
push ebp
mov ebp, esp
mov eax, [ebp+8] ; Get parameter n
cmp eax, 1
jle base_case ; if n <= 1, return 1
; Recursive case: n * factorial(n-1)
dec eax
push eax ; Push n-1 as argument
call factorial
add esp, 4 ; Clean up argument
imul dword [ebp+8] ; Multiply by original n
jmp end_factorial
base_case:
mov eax, 1 ; Return 1 for base case
end_factorial:
mov esp, ebp
pop ebp
ret
_start:
push 5 ; Compute factorial(5)
call factorial
add esp, 4
; EAX now contains 120 (5!)
Function Pointers
Functions can be called indirectly through pointers:
section .data
func_ptr dd my_function
section .text
global _start
my_function:
mov eax, 42
ret
_start:
call [func_ptr] ; Call through function pointer
; EAX now contains 42
Variadic Functions
Functions with variable number of arguments require special handling:
section .text
global _start
; Simple variadic sum function
variadic_sum:
push ebp
mov ebp, esp
mov ecx, [ebp+8] ; Get count of arguments
mov eax, 0 ; Initialize sum
lea edx, [ebp+12] ; Pointer to first argument
sum_loop:
add eax, [edx] ; Add argument to sum
add edx, 4 ; Move to next argument
loop sum_loop
mov esp, ebp
pop ebp
ret
_start:
push 3 ; Push third argument
push 2 ; Push second argument
push 1 ; Push first argument
push 3 ; Push argument count
call variadic_sum
add esp, 16 ; Clean up
; EAX now contains 6 (1+2+3)
Inline Assembly
Assembly functions can be called from high-level languages:
// C program calling assembly function
extern int assembly_function(int, int);
int main() {
int result = assembly_function(10, 20);
return 0;
}
; Corresponding assembly function
global assembly_function
assembly_function:
push ebp
mov ebp, esp
mov eax, [ebp+8] ; First parameter
add eax, [ebp+12] ; Second parameter
mov esp, ebp
pop ebp
ret
Tail Call Optimization
When a function's last action is calling another function, the call can be optimized to a jump:
; Regular recursive call
factorial:
; ...
call factorial
; ...
ret
; Tail call optimized version
factorial_tail:
; ...
jmp factorial_tail ; Instead of call/ret
Function Macros
Macros can simplify function definitions:
%macro FUNCTION 1
%1:
push ebp
mov ebp, esp
%endmacro
%macro ENDFUNCTION 0
mov esp, ebp
pop ebp
ret
%endmacro
FUNCTION my_function
mov eax, 42
ENDFUNCTION
Common Pitfalls
- Forgetting to clean up the stack after calls
- Not preserving callee-saved registers
- Mismatched push/pop operations
- Assuming registers retain values across calls
- Stack misalignment in mixed environments
Next Steps
Now that you understand functions, you can learn about: