Understanding the Call and Ret Instructions in Assembly Language

Overview of Ret and Retf Instructions

In 8086 assembly language, <span>call</span> and <span>ret</span> instructions are crucial for implementing subroutine (procedure) calls. They achieve control flow transfer by modifying the IP or simultaneously modifying both CS and IP, utilizing the stack to save and restore the return address.

Principle of the Ret Instruction

<span>ret</span> instruction is used for near transfer, popping the return address from the stack into the IP register, thus returning from the subroutine to the calling point.

Steps for CPU Executing the Ret Instruction:

  1. (IP) = ((SS)*16 + (SP)); Pop the return address offset from the top of the stack into IP
  2. (SP) = (SP) + 2; Increment the stack pointer by 2

In assembly syntax, this is equivalent to:

pop IP

Principle of the Retf Instruction

<span>retf</span> instruction is used for far transfer, popping the return address into both IP and CS registers, enabling cross-segment subroutine returns.

Steps for CPU Executing the Retf Instruction:

  1. (IP) = ((SS)*16 + (SP)); Pop the return address offset from the top of the stack into IP
  2. (SP) = (SP) + 2; Increment the stack pointer by 2
  3. (CS) = ((SS)*16 + (SP)); Pop the return address segment from the top of the stack into CS
  4. (SP) = (SP) + 2; Increment the stack pointer by 2

In assembly syntax, this is equivalent to:

pop IP
pop CS

Code Examples

Example 1: Basic Usage of the Ret Instruction

; Example 1: Basic usage of the ret instruction
assume cs:code, ss:stack

stack segment
    db 16 dup(0)    ; 16 bytes of stack space
stack ends

code segment
start:
    mov ax, stack
    mov ss, ax      ; Set stack segment
    mov sp, 16      ; Set stack pointer

    mov ax, 1000h   ; Simulate some operations
    call my_proc    ; Call subroutine

    mov ax, 4C00h   ; End program
    int 21h

my_proc proc
    ; Subroutine begins
    mov bx, ax      ; Use passed parameter
    add bx, 100h    ; Perform some processing

    ret             ; Return to calling point
my_proc endp

code ends
end start

Example 2: Usage of the Retf Instruction

; Example 2: Usage of the retf instruction (cross-segment return)
assume cs:code, ss:stack

stack segment
    dw 2 dup(0)     ; Reserve space for return address
stack ends

code segment
start:
    mov ax, stack
    mov ss, ax      ; Set stack segment
    mov sp, 4       ; Set stack pointer

    ; Push return address onto stack (simulate far call)
    mov ax, cs      ; Current code segment
    push ax         ; Push CS first
    mov ax, offset after_call ; Offset of return address
    push ax         ; Then push IP

    jmp far_proc    ; Jump to far procedure (simulate far call)

after_call:
    mov ax, 4C00h   ; End program
    int 21h

far_proc:
    ; Far procedure begins
    mov ax, 0FFFFh  ; Perform some processing

    retf            ; Far return, popping IP and CS from stack
code ends
end start

Example 3: Using Call and Ret Together

; Example 3: Using call and ret to implement a subroutine
assume cs:code, ss:stack

stack segment
    db 64 dup(0)    ; 64 bytes of stack space
stack ends

code segment
start:
    mov ax, stack
    mov ss, ax      ; Set stack segment
    mov sp, 64      ; Set stack pointer

    mov ax, 10h     ; Set parameter
    mov bx, 20h     ; Set parameter

    call add_values ; Call addition subroutine

    ; Result is now stored in CX (10h + 20h = 30h)

    mov ax, 4C00h   ; End program
    int 21h

; Addition subroutine
; Input: AX, BX
; Output: CX = AX + BX
add_values proc
    push ax         ; Save registers
    push bx

    mov cx, ax      ; Store first parameter in CX
    add cx, bx      ; Add second parameter

    pop bx          ; Restore registers
    pop ax

    ret             ; Return to calling point
add_values endp

code ends
end start

Example 4: Nested Calls

; Example 4: Nested call example
assume cs:code, ss:stack

stack segment
    db 128 dup(0)   ; 128 bytes of stack space
stack ends

code segment
start:
    mov ax, stack
    mov ss, ax      ; Set stack segment
    mov sp, 128     ; Set stack pointer

    mov ax, 5       ; Set parameter
    call factorial  ; Call factorial function

    ; Result is now stored in AX (5!)

    mov ax, 4C00h   ; End program
    int 21h

; Factorial function
; Input: AX = n
; Output: AX = n!
factorial proc
    cmp ax, 1       ; Check base case
    jle base_case

    push ax         ; Save current n value
    dec ax          ; n-1
    call factorial  ; Recursive call

    pop bx          ; Restore n value
    mul bx          ; AX = AX * BX (factorial(n-1) * n)
    ret

base_case:
    mov ax, 1       ; 0! = 1, 1! = 1
    ret
factorial endp

code ends
end start

Principle of the Call Instruction

Although this chapter mainly discusses ret and retf, for better understanding, we will briefly introduce the principle of the call instruction.

Operations of the Call Instruction:

  1. Near Call:

  • Push the current IP onto the stack
  • Jump to the target address
  • Far Call:

    • Push the current CS and IP onto the stack
    • Jump to the target address (modifying both CS and IP)

    Cooperation of Call and Ret:

    ; Cooperation of call and ret
    assume cs:code, ss:stack
    
    stack segment
        db 32 dup(0)
    stack ends
    
    code segment
    start:
        mov ax, stack
        mov ss, ax
        mov sp, 32
    
        call my_procedure ; Call procedure
    
        ; Continue executing here after procedure returns
        mov ax, 4C00h
        int 21h
    
    my_procedure proc
        ; Procedure begins
        ; Some processing code...
    
        ret ; Return to calling point
    my_procedure endp
    
    code ends
    end start
    

    Detailed Stack Operations

    Understanding the call and ret instructions is key to understanding how they operate on the stack:

    1. Call Instruction:

    • Near call: push IP
    • Far call: push CS, push IP
  • Ret Instruction:

    • Near return: pop IP
    • Far return: pop IP, pop CS

    Considerations

    1. Stack Balance: Ensure each call has a corresponding ret to maintain stack balance.

    2. Register Preservation: In subroutines, if registers used by the caller need to be modified, they should be pushed to save, and popped to restore at the end.

    3. Parameter Passing: Parameters can be passed to subroutines via registers or the stack.

    4. Return Value Storage: Agree on the register for storing return values (usually AX).

    5. Recursive Calls: Be mindful of stack depth to avoid stack overflow.

    Practical Applications

    The call and ret instructions are fundamental for implementing structured programming, allowing code to be modular, enhancing code reusability and maintainability. By using these instructions appropriately, clear program structures can be created to implement complex algorithms and functionalities.

    Conclusion

    The ret and retf instructions are key for returning from subroutines, manipulating the stack to restore return addresses and achieve control flow transfer. Understanding how these instructions work is crucial for writing correct assembly programs, especially when implementing function calls, recursive algorithms, and modular code.

    By effectively using the cooperation of call and ret, clear and modular program structures can be created, improving code reusability and maintainability. Mastering these instructions is an important step towards becoming a proficient assembly language programmer.

    Leave a Comment