wandres.dev
MAESTRO · ABI y calling conventions

ABI y convenciones de llamada

La interfaz binaria que permite que el código compilado encaje: cómo se pasan los argumentos por registros, la pila, y por qué la estabilidad de ABI sostiene el software.

⏱ 15 min

La ABI es el contrato invisible que hace que dos trozos de código máquina —quizá compilados por compiladores distintos— se entiendan. Es lo que permite que tu programa llame a una función de una .so, o que enlaces objetos de fuentes diversas. Entenderla es ver cómo la máquina ejecuta una llamada de verdad.

🎯 Al terminar esta lección sabrás
  • Qué es una ABI y qué garantiza.
  • La convención de llamada System V (x86-64).
  • Registros de argumentos, retorno y la pila.
  • Estabilidad de ABI y por qué importa.

¿Qué es una ABI?

Una API es un contrato a nivel de código fuente (qué funciones existen y sus tipos). Una ABI (Application Binary Interface) es el contrato a nivel binario: cómo se pasan los argumentos en registros y pila, cómo se devuelven los valores, cómo se representa una struct en memoria, cómo se decoran los símbolos.

La ABI es lo que hace posible enlazar mundos separados

Piensa en lo que ocurre cuando tu programa llama a printf de una .so: tu código y el de la libc se compilaron por separado, quizá con compiladores y en años distintos, y aun así encajan a la perfección. ¿Cómo? Porque ambos respetan la misma ABI: los dos saben que el primer argumento va en el registro rdi, que el retorno vuelve en rax, que la pila está alineada a 16 bytes. La ABI es el estándar compartido que permite que piezas compiladas independientemente cooperen. Sin ABIs estables, cada actualización de compilador rompería todos los binarios existentes. Es una de las infraestructuras más importantes y menos visibles de la informática.

La convención de llamada System V (x86-64)

En Linux/macOS de 64 bits, la ABI dominante es System V AMD64. Sus reglas para llamar a una función:

📥

Argumentos enteros

Los 6 primeros van en registros: rdi, rsi, rdx, rcx, r8, r9. Del séptimo en adelante, en la pila.

📤

Valor de retorno

Vuelve en rax (y rdx para valores de 128 bits). Los flotantes usan los registros xmm.

🧱

La pila

Alineada a 16 bytes en la llamada. Guarda argumentos extra, la dirección de retorno y variables locales.

💾

Registros preservados

Algunos registros los debe preservar la función llamada (callee-saved: rbx, rbp, r12-r15); otros puede pisarlos.

Puedes verlo compilando a ensamblador (nivel 3):

gcc -std=c23 -S -O2 -masm=intel prog.c -o prog.s
# observa cómo los argumentos entran en rdi, rsi... y el retorno sale en rax

Structs y casos especiales

La ABI también dicta cómo se pasan las structs: las pequeñas pueden ir en registros (divididas por campos), las grandes se pasan por referencia oculta en la pila. Estas reglas explican por qué el layout de un struct (nivel 11) afecta al rendimiento de las llamadas, y por qué cambiar un campo puede romper compatibilidad binaria.

C no decora nombres (y por qué importa)

Un detalle clave de la ABI de C: el símbolo de una función sumar es, simplemente, sumar. C no hace “name mangling”. C++ sí (codifica los tipos en el nombre para permitir sobrecarga), por eso para llamar C desde C++ se usa extern "C".

💡
La ABI de C es la lingua franca

Precisamente porque la ABI de C es simple y estable —sin decoración de nombres, con reglas de paso claras—, C es el lenguaje puente entre ecosistemas. Python, Rust, Go, Swift… todos hablan con librerías nativas a través de la ABI de C (su “FFI”). Cuando un lenguaje quiere interoperar con otro, el terreno común casi siempre es la ABI de C. Dominar esto te explica por qué C sigue en el centro del ecosistema aunque escribas en otro lenguaje: es el idioma binario que todos entienden.

Estabilidad de ABI

Una biblioteca mantiene estabilidad de ABI cuando puedes actualizarla sin recompilar los programas que la usan. Romperla (cambiar el layout de una struct pública, quitar una función, cambiar una firma) obliga a recompilar todo. Por eso las librerías serias usan tipos opacos (nivel 12), versionado de símbolos (nivel 20) y sonames: para poder evolucionar sin romper binarios ajenos.

⚔️ Mira la ABI en acción
  1. Compila una función de varios argumentos a ensamblador con -S -masm=intel y localiza rdi, rsi, rdx…
  2. Encuentra dónde se coloca el valor de retorno (rax).
  3. Prueba a pasar 8 argumentos y observa cómo los últimos van a la pila.
  4. Investiga por qué llamar C desde C++ necesita extern "C" (name mangling).