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

  1. 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,

  2. 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

  1. Save Registers on the stack (Caller Saved)

    1. push/pop

  2. Pass Function Arguments (like syscalls)

    1. 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
  3. 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.

  1. Get Function's Return Value (in rax)

POV: CALLEE

  1. Saving Callee Saved registers (rbx and rbp)

  2. Get arguments from registers

  3. Align the Stack

  4. 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