Llamadas a función y ABI de x86-64 en la práctica
Argumentos, registros preservados, alineación de pila y unwinding en System V y Windows x64
Llamadas a función y ABI de x86-64 en la práctica
Estos artículos del blog se han creado con fines didácticos. Este artículo compara System V AMD64 y Microsoft x64. Kernel, syscalls, vectorcall y extensiones del toolchain requieren documentación específica del objetivo.
La instrucción call de la CPU solo guarda una dirección de retorno y transfiere el control. La ABI define dónde viven argumentos y resultados, quién conserva registros, cómo se alinea la pila y cómo se hace unwind de un marco.
ISA, convención de llamada y ABI
- La ISA define instrucciones y comportamiento de registros.
- La convención de llamada define argumentos, resultados, estado preservado y responsabilidad de pila.
- La ABI incluye además layout de tipos, formato de objeto, símbolos, relocations y reglas de unwind.
extern "C" controla el language linkage y name mangling de C++; no transforma una función Windows en System V.
Comparación
| Elemento | System V AMD64 | Microsoft x64 |
|---|---|---|
| Argumentos enteros | rdi,rsi,rdx,rcx,r8,r9 | posiciones 1–4: rcx,rdx,r8,r9 |
| Argumentos FP | principalmente xmm0–xmm7 | posiciones 1–4: xmm0–xmm3 |
| Retorno entero | rax | rax |
| Retorno FP | xmm0 | xmm0 |
| Zona del caller | sin shadow space | 32 bytes de shadow space |
| Red zone | 128 bytes para ciertas leaf de usuario | ninguna |
| GPR nonvolatile principales | rbx,rbp,r12–r15,rsp | además rdi,rsi |
| XMM nonvolatile | ninguno | 128 bits bajos de xmm6–xmm15 |
Aggregates, vectores, funciones variádicas y punteros de retorno ocultos necesitan clasificación adicional.
Caller-saved y callee-saved
Un registro volatile puede cambiar tras una llamada; el caller guarda un valor vivo si lo necesita. Si el callee usa un registro nonvolatile, debe restaurarlo.
push rbx
mov rbx, rcx
; use rbx
pop rbx
ret
Preservar no obliga a usar push. Un registro no usado no se guarda, y también puede emplearse un stack slot con mov.
Llamada System V y Windows
System V:
mov edi, 10
mov esi, 20
mov edx, 30
call combine
; resultado en rax
Windows x64:
sub rsp, 40
mov ecx, 10
mov edx, 20
mov r8d, 30
call combine
add rsp, 40
Los 32 bytes son el shadow space que aporta el caller. Los 8 restantes forman una disposición típica de alineación teniendo en cuenta el retorno insertado por call. Se reserva incluso con menos de cuatro argumentos.
El long de C suele medir 64 bits en System V LP64, pero 32 bits en Windows LLP64. x64 no impone una anchura idéntica a todos los tipos del lenguaje.
La alineación necesita un instante
Distinga:
- caller justo antes de
call; - entrada del callee tras apilar el retorno;
- después del prólogo;
- justo antes de un nested
call.
Aplicar la regla módulo de un instante a otro causa la habitual confusión de ocho bytes. Windows x64 exige rsp alineado a 16 bytes fuera del prólogo y epílogo.
Red zone y unwind
Ciertas funciones leaf de usuario System V pueden usar 128 bytes bajo rsp. La suposición no vale tras una llamada anidada, en Windows, en kernel ni con la red zone desactivada.
Una función non-leaf de Windows debe hacer coincidir su prólogo real con los metadatos de unwind.
Work PROC FRAME
push rbx
.pushreg rbx
sub rsp, 32
.allocstack 32
.endprolog
; body
add rsp, 32
pop rbx
ret
Work ENDP
Las directivas describen instrucciones, no las ejecutan. Una discrepancia puede superar pruebas de retorno normal y romper excepciones, profiler y stack walking.
Argumentos de pila y FP
Windows pasa en pila las posiciones desde la quinta. El offset cambia según se mida en la entrada o después del prólogo; [rsp+40] no significa siempre “quinto argumento” sin indicar el instante.
void f(int a, double b, int c, float d);
En Windows se obtiene aproximadamente a -> rcx, b -> xmm1, c -> r8, d -> xmm3. Se cuentan posiciones, no solo argumentos FP.
Variádicas, estructuras y parámetros ocultos
Las llamadas como printf añaden reglas:
- System V comunica en
alinformación relacionada con argumentos en registros vectoriales. - Microsoft x64 duplica un FP variádico en el registro entero de la misma posición.
- Las promociones por defecto de C convierten
floatendouble.
System V clasifica aggregates por eightbytes y puede dividirlos entre registros enteros y SSE. Windows pasa ciertos tamaños por valor y otros mediante un puntero a un temporal del caller. Una frontera ASM manual debe preferir escalares de anchura fija, funciones libres extern "C", assertions de sizeof, alignof, offsetof, e inspección de caller y callee.
Un resultado estructurado grande puede añadir un puntero de retorno oculto y desplazar aparentemente todos los argumentos visibles.
Tail call
wrapper:
; preparar argumentos de target
jmp target
Si las condiciones ABI lo permiten, el compilador cambia un call final seguido de ret por jmp, y target retorna directamente al caller original.
Orden de depuración
- ¿Caller y callee usan la misma arquitectura y ABI?
- ¿Coinciden símbolo y language linkage?
- ¿Están bits y tipos en el lugar correcto justo antes de
call? - ¿A qué instante de
rspse refiere cada offset? - ¿Son correctos alineación y shadow space de Windows?
- ¿Se restauran todos los nonvolatile modificados en cada camino?
- ¿Coinciden las directivas Windows con el prólogo real?
- ¿Se lee el resultado con la anchura correcta de
raxo desdexmm0?
Resumen
call y ret son solo el esqueleto. System V usa seis registros enteros principales y una red zone; Microsoft x64 usa cuatro slots por posición, 32 bytes de shadow space, XMM nonvolatile y reglas estrictas de unwind. Demuestre el contrato con desensamblado de caller y callee, pruebas de límites y stack walking.