JMP vs CALL en ensamblador x86: La pila marca toda la diferencia

En ensamblador x86, tanto JMP como CALL transfieren el control a otra ubicación de código, pero su comportamiento con la pila crea semántica de flujo de control fundamentalmente diferente. Entender esta distinción es esencial para escribir subrutinas, implementar recursividad y depurar flujo de control.

Diferencia central: Interacción con la pila

AspectoJMP destinoCALL destino
Efecto en pilaNinguno — pila sin cambiosApila dirección de retorno (EIP/RIP)
Mecanismo de retornoNinguno — transferencia unidireccionalRET desapila dirección, reanuda después de CALL
Uso típicoBucles, condicionales, saltos incondicionalesSubrutinas, funciones, procedimientos
Soporte de anidamientoNo — no se puede anidar limpiamenteSí — la pila permite anidamiento arbitrario
RecursividadImposible sin manejo manual de pilaSoporte nativo mediante marcos de pila

Codificación de instrucciones (x86-64)

 ; Variantes JMP EB cb JMP rel8 ; Salto corto (-128 a +127 bytes) E9 cw JMP rel16/32 ; Salto cercano (relativo) FF /4 JMP r/m16/32/64 ; Salto indirecto (registro/memoria) EA cd JMP ptr16:16/32 ; Salto lejano (segmento:desplazamiento) - legado ; Variantes CALL E8 cw CALL rel16/32 ; Llamada cercana (relativa, apila RIP) FF /2 CALL r/m16/32/64 ; Llamada indirecta 9A cd CALL ptr16:16/32 ; Llamada lejana (legado) 

Mecánica de marcos de pila

Flujo de ejecución de CALL

 ; Antes de CALL RIP = dirección de la siguiente instrucción RSP = puntero de pila actual ; CALL objetivo ejecuta: 1. RSP ← RSP - 8 ; Decrementar puntero de pila (64-bit) 2. [RSP] ← RIP ; Apilar dirección de retorno (siguiente instrucción) 3. RIP ← objetivo ; Saltar a subrutina ; Dentro de la subrutina (prólogo típico): PUSH RBP ; Guardar base pointer del llamador MOV RBP, RSP ; Establecer nuevo frame pointer SUB RSP, N ; Asignar espacio de pila local ; Ejecución de RET: 1. RIP ← [RSP] ; Desapilar dirección de retorno 2. RSP ← RSP + 8 ; Restaurar puntero de pila 3. La ejecución reanuda después de CALL 

Flujo de ejecución de JMP

 ; JMP objetivo ejecuta: 1. RIP ← objetivo ; Salto incondicional ; El puntero de pila (RSP) no cambia ; No se almacena dirección de retorno ; No hay forma de regresar automáticamente 

Implicaciones prácticas

Patrón de subrutina (CALL/RET)

 ; Llamador CALL calculate_sum ; Apila dirección de retorno, salta ; ... se reanuda aquí después de RET ; Subrutina calculate_sum: PUSH RBP MOV RBP, RSP ; ... cálculo ... MOV RAX, resultado POP RBP RET ; Desapila dirección de retorno, salta de vuelta 

Patrón de bucle/máquina de estados (JMP)

 state_loop: CMP AL, 'A' JE handle_A CMP AL, 'B' JE handle_B JMP default_handler handle_A: ; ... procesar A ... JMP next_char handle_B: ; ... procesar B ... JMP next_char default_handler: ; ... error ... JMP next_char next_char: ; ... obtener siguiente ... JMP state_loop ; Volver al bucle 

Avanzado: Optimización de llamada en cola

Los compiladores a veces reemplazan la secuencia CALL ... RET con JMP cuando la última acción de una función es llamar a otra función:

 ; Sin optimización foo: CALL bar RET ; Con optimización de llamada en cola (TCO) foo: JMP bar ; Reutiliza el marco de pila del llamador 

Beneficios: Ahorro de espacio de pila, retorno más rápido. Requiere: sin limpieza después de la llamada, misma convención de llamada.

Consideraciones de seguridad

  • Return-Oriented Programming (ROP): Explotaciones de cadenas RET — CALL crea direcciones de retorno predecibles
  • Control Flow Integrity (CFI): Hardware (Intel CET, ARM PAC) valida objetivos de CALL/RET
  • Shadow Stacks: Pila separada protegida para direcciones de retorno (CET)

Resumen

  • JMP = "Ve allí" (sin retorno, sin cambio de pila)
  • CALL = "Ve allí, regresa aquí" (apila dirección de retorno, habilita RET)
  • CALL/RET = Base de la programación procedural, recursividad, código modular
  • JMP = Base de bucles, máquinas de estado, flujo condicional

Cada llamada a función de alto nivel (func() en C, def en Python, method() en Java) se compila a la maquinaria CALL/RET. Cada while, for, if, goto se compila a variantes de JMP. La pila es lo que hace poderoso a CALL—y lo que hace peligrosos los desbordamientos de búfer.