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
; 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:
| Instruction | Actual operation performed |
|---|---|
CALL label | push eip(Save the next instruction address) +jmp label |
RET | pop 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
; 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
; 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 Prologue
push 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 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
; 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. Using
leavethe instruction can replacemov esp, ebp; pop ebp, with equivalent effect.
Comparison of Procedure Calling Conventions
| Convention | Parameter passing | Stack cleanup | Register preservation | Return value |
|---|---|---|---|---|
| cdecl | Stack (pushed right to left) | Caller | EAX, ECX, EDX are saved by the caller | EAX |
| stdcall | Stack (pushed right to left) | Callee | EAX, ECX, EDX are saved by the caller | EAX |
| fastcall | ECX, EDX + stack | Callee | EAX, ECX, EDX are saved by the caller | EAX |