Uso de call y ret para llamadas y retornos en ensamblador x86

La instrucción ret extrae una palabra de la pila y la coloca en ip, produciendo un retorno dentro del mismo segmento de código. Internamente equivale a pop ip.

assume cs:codigo, ss:pila

pila segment
    dw 20 dup (0)
pila ends

codigo segment
inicio:
    mov ax, pila
    mov ss, ax
    mov sp, 40

    mov ax, offset continuar
    push ax
    ret

continuar:
    mov ax, 4c00h
    int 21h
codigo ends
end inicio

retf: retorno lejano

Para regresar de una subrutina intersegmento se emplea retf, que restaura primero ip y luego cs desde la pila. Equivale a pop ip seguido de pop cs.

assume cs:codigo, ss:pila

pila segment
    dw 20 dup (0)
pila ends

codigo segment
principal:
    mov ax, pila
    mov ss, ax
    mov sp, 40

    push cs
    mov ax, offset siguiente
    push ax
    retf

siguiente:
    mov ax, 4c00h
    int 21h
codigo ends
end principal

call: llamada a subrutina

call almacena en la pila la dirección de retorno y transfiere el control al destino. En su forma cercana guarda solo ip; en la lejana guarda cs:ip. A diferencia de jmp, no admite salto corto.

assume cs:codigo

codigo segment
principal:
    mov ax, 2
    call duplicar
    ; ax ahora vale 4

    mov ax, 4c00h
    int 21h

duplicar:
    add ax, ax
    ret
codigo ends
end principal

También es posible llamar a través de un registro o de una dirección de memoria:

lea bx, duplicar
call bx

; o bien
call word ptr [tabla_funciones]

call far ptr y retf

Una llamada lejana con call far ptr empuja cs e ip. La subrutina correspondiente debe finalizar con retf para restaurar ambos registros.

call far ptr tarea_lejana
...
tarea_lejana:
    mov bx, ax
    retf

call y ret para implementar subrutinas

El par call/ret permite crear procedimientos reutilizables. El siguiente ejemplo calcula una potencia entera medainte multiplicaciones sucesivas.

assume cs:codigo

codigo segment
principal:
    mov ax, 1          ; resultado inicial
    mov cx, 4          ; exponente
    call potencia

    mov ax, 4c00h
    int 21h

potencia:
    mov bx, 3          ; base
multiplicar:
    mul bx
    loop multiplicar
    ret
codigo ends
end principal

Multiplicación con mul

mul multiplica operandos sin signo. Si los operandos son de 8 bits, el resultado se aloja en ax; si son de 16 bits, el resultado de 32 bits se guarda en dx:ax.

; 8 bits
mov al, 50
mov bl, 20
mul bl              ; ax = 1000

; 16 bits
mov ax, 2500
mov bx, 400
mul bx              ; dx:ax = 1 000 000

Diseño modular

Dividir un programa en subrutinas pequeñas mejora la organización y la reutilización. Cada rutina debe realizar una única tarea clara y documentarse con comentarios que indiquen los registros de entrada y el valor devuelto.

Paso de parámetros y resultados

Los registros son el medio más directo. El siguiente procedimiento recibe dos sumendos en ax y bx y deja el resultado en ax.

sumar:
    add ax, bx
    ret

; uso
mov ax, 7
mov bx, 13
call sumar          ; ax = 20

Transferencia de bloques de datos

Para procesar muchos elementos se pasa un puntero al inicio del bloque y un contador. El siguiente ejemplo convierte una cadena a mayúsculas.

assume cs:codigo, ds:datos

datos segment
    mensaje db 'EjemploXyZ'
datos ends

codigo segment
principal:
    mov ax, datos
    mov ds, ax
    mov si, offset mensaje
    mov cx, 10
    call mayusculas

    mov ax, 4c00h
    int 21h

mayusculas:
    and byte ptr [si], 11011111b
    inc si
    loop mayusculas
    ret
codigo ends
end principal

Como alternativa general, los parámetros también pueden apilarse antes de llamar a la subrutina, aunque entonces la rutina debe conocer el convenio de pila acordado.

Etiquetas: x86 Ensamblador call ret retf

Publicado el 7-3 09:17