CALL
The call
instruction is used to transfer program control to a procedure or function. Before jumping to the target address, call
automatically pushes the return address (the instruction pointer rip
of the next instruction) onto the stack. This ensures that once the procedure finishes, the program knows where to resume execution. Essentially, call
combines two steps—saving the return location and jumping to the procedure—making it a key instruction for structured programming in assembly.
OPERATIONS OF THE CALL INSTRUCTION
it pushes the return address onto the
stack
so that the execution of the program can be continued after the function has successfully fulfilled its goal,it changes the
instruction pointer
(EIP
) to the call destination and starting execution there.
64-BIT
student@nix-bow:~$ gdb -q bow64
Reading symbols from bow64...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
0x00000000000006bc <+0>: push rbp # <---- 1. Stores previous EBP (caller’s base pointer)
0x00000000000006bd <+1>: mov rbp,rsp # <---- 2. Creates new stack frame (EBP = current SP)
# Local variables are allocated by subtracting from rsp in 64-bit, or esp in 32-bit.
0x00000000000006c0 <+4>: sub rsp,0x10 # <---- 3. Moves ESP to the top - # <---- 3. Allocates space for local variables (ESP moves down)
0x00000000000006c4 <+8>: mov DWORD PTR [rbp-0x4],edi
0x00000000000006c7 <+11>: mov QWORD PTR [rbp-0x10],rsi
0x00000000000006cb <+15>: mov rax,QWORD PTR [rbp-0x10]
0x00000000000006cf <+19>: add rax,0x8
0x00000000000006d3 <+23>: mov rax,QWORD PTR [rax]
0x00000000000006d6 <+26>: mov rdi,rax
0x00000000000006d9 <+29>: call 0x68a <bowfunc>
0x00000000000006de <+34>: lea rdi,[rip+0x9f]
0x00000000000006e5 <+41>: call 0x560 <puts@plt>
0x00000000000006ea <+46>: mov eax,0x1
0x00000000000006ef <+51>: leave # <---- Restores old EBP and moves ESP back to base of frame
0x00000000000006f0 <+52>: ret # <---- Return from function
End of assembler dump.
* leave is equivalent to...which restores the caller’s frame and prepares for return.
mov rsp, rbp
pop rbp
32-BIT
student@nix-bow:~$ gdb ./bow32 -q
Reading symbols from bow...(no debugging symbols found)...done.
(gdb) disassemble main
Dump of assembler code for function main:
0x00000582 <+0>: lea ecx,[esp+0x4]
0x00000586 <+4>: and esp,0xfffffff0
0x00000589 <+7>: push DWORD PTR [ecx-0x4]
0x0000058c <+10>: push ebp # <---- 1. Stores previous EBP
0x0000058d <+11>: mov ebp,esp # <---- 2. Creates new Stack Frame
0x0000058f <+13>: push ebx # <---- Save callee-saved registers (common in x86)
0x00000590 <+14>: push ecx
0x00000591 <+15>: call 0x450 <__x86.get_pc_thunk.bx>
0x00000596 <+20>: add ebx,0x1a3e
0x0000059c <+26>: mov eax,ecx
0x0000059e <+28>: mov eax,DWORD PTR [eax+0x4]
0x000005a1 <+31>: add eax,0x4
0x000005a4 <+34>: mov eax,DWORD PTR [eax]
0x000005a6 <+36>: sub esp,0xc
0x000005a9 <+39>: push eax
0x000005aa <+40>: call 0x54d <bowfunc> # <--- CALL function
<SNIP>
PROCEDURE
SOURCE
section .data
message db "Fibonacci Sequence:", 0x0a
msg_len equ $ - message ; $ is the current address, so $ - message = length
section .text
global _start
_start:
;The call instruction pushes the return address—the address of the instruction
;immediately following the call—onto the stack and then jumps directly to the
;target procedure to begin execution. When the procedure completes, the ret
;instruction is executed, which pops the saved return address from the stack into
;rip, restoring program execution to the point immediately after the original call.
call printMessage ; print intro message
call initFib ; set initial Fib values
call loopFib ; calculate Fib numbers
call Exit ; Exit the program
printMessage:
mov rax, 1 ; rax: syscall number 1
mov rdi, 1 ; rdi: fd 1 for stdout
mov rsi,message ; rsi: pointer to message
mov rdx, msg_len ; length calculated dynamically with equ
;mov rdx, 20 ; rdx: print length of 20 bytes
syscall ; call write syscall to the intro message
ret
initFib:
xor rax, rax ; initialize rax to 0
xor rbx, rbx ; initialize rbx to 0
inc rbx ; increment rbx to 1
ret
loopFib:
add rax, rbx ; get the next number
xchg rax, rbx ; swap values
cmp rbx, 10 ; do rbx - 10
js loopFib ; jump if result is <0
ret
; The ret instruction is not used in the Exit procedure because the goal is to
; terminate the program rather than return to the calling point. Using ret would pop
; a return address from the stack and continue execution, but since the program is
; finished, we instead use the exit syscall (e.g., rax = 60 on Linux x86-64) to end
;the program cleanly and hand control back to the operating system.
Exit:
mov rax, 60
mov rdi, 0
syscall
* The call instruction saves the address of the next instruction (the current value of
rip) onto the stack and then jumps to the specified procedure. Once the procedure
completes, the ret instruction pops the saved address from the top of the stack back
into rip, returning execution to the point immediately following the original call.
Together, call and ret provide a structured mechanism for function and procedure
calls in assembly, enabling nested calls, recursion, and proper program flow control.
VISUALIZATION
Program start: _start
Stack (top at bottom of diagram):
Empty
_start calls printMessage
Push return address of _start+next_instruction onto stack
+--------------------+ <- rsp
| return to _start |
+--------------------+
Inside printMessage, syscall executes
ret pops return address from stack
Stack after ret from printMessage
(empty)
_start calls initFib
Push return address of _start+next_instruction onto stack
+--------------------+
| return to _start |
+--------------------+
Inside initFib
ret pops return address from stack
Stack after ret from initFib
(empty)
_start calls loopFib
Push return address of _start+next_instruction onto stack
+--------------------+
| return to _start |
+--------------------+
Inside loopFib
(ret inside loop after finishing calculation)
ret pops return address from stack
Stack after ret from loopFib
(empty)
_start calls Exit
Push return address of _start+next_instruction onto stack
Inside Exit, syscall 60 terminates program
Stack is no longer used
FUNCTION
Things to consider prior to calling a function. This is similar to calling a syscall. The only difference with syscalls is that we have to store the syscall number in rax
, while we can call functions directly with call function
. Furthermore, with syscall we don't have to worry about Stack Alignment
.
PROCESS
POV: CALLER
Save Registers
on the stack (Caller Saved
)push/pop
Pass
Function Arguments
(like syscalls)identify the arguments required by printf
root@dev:~$ man -s 3 printf int printf(const char *format, ...); * -s 3 for library functions manual - the function takes a pointer to the print format (shown with a *), and then the string(s) to be printed
Fix
Stack Alignment
Before calling a function, the stack pointer (rsp
) must be aligned to a 16-byte boundary, starting from the _start
function. This alignment ensures efficient processor performance and prevents crashes in certain functions (e.g., in libc) that rely on this alignment. To maintain it, you may need to push 16 bytes (or a multiple) onto the stack before making a function call.
Get Function's
Return Value
(inrax
)
POV: CALLEE
Saving
Callee Saved
registers (rbx
andrbp
)Get arguments from registers
Align the Stack
Return value in
rax
The caller sets up necessary data before calling a function, and the callee (receiver) retrieves and uses that data. This setup and cleanup happen at the start and end of the function, known as the prologue and epilogue, which ensure the function can run without affecting the current stack or register state.
SOURCE
extern printf ;import printf function from libc
section .data
message db "Fibonacci Sequence:", 0x0a
;create a variable that contains the output format to pass it as the first argument
;0x00 is a null character which is the string terminator in printf
;this is required to terminate any string.
outFormat db "%d", 0x0a, 0x00
section .text
global _start
_start:
call printMessage ; print intro message
call initFib ; set initial Fib values
call loopFib ; calculate Fib numbers
call Exit ; Exit the program
printMessage:
mov rax, 1 ; rax: syscall number 1
mov rdi, 1 ; rdi: fd 1 for stdout
mov rsi, message ; rsi: pointer to message
mov rdx, 20 ; rdx: print length of 20 bytes
syscall ; call write syscall to the intro message
ret
initFib:
xor rax, rax ; initialize rax to 0
xor rbx, rbx ; initialize rbx to 0
inc rbx ; increment rbx to 1
ret
;STEP 1: SAVING ANY USED REGISTERS (RAX & RBX) ONTO THE STACK
;printFib holds function call instructions
printFib:
push rax ; push registers to stack
push rbx
mov rdi, outFormat ; set 1st argument (Print Format)
mov rsi, rbx ; set 2nd argument (Fib Number)
call printf ; printf(outFormat, rbx) - function call
pop rbx ; restore registers from stack
pop rax
ret
loopFib:
call printFib ; print current Fib number
add rax, rbx ; get the next number
xchg rax, rbx ; swap values
cmp rbx, 10 ; do rbx - 10
js loopFib ; jump if result is <0
ret
Exit:
mov rax, 60
mov rdi, 0
syscall
OUTPUT
1
1
2
3
5
8
Last updated