Intel x86 Assembly Language & Microarchitecture
Convenciones de llamadas
Buscar..
Observaciones
Recursos
Resúmenes / comparaciones: la guía de convenciones de llamadas agradables de Agner Fog . Además, x86 ABIs (wikipedia) : convenciones de llamada para funciones, incluidas x86-64 Windows y System V (Linux).
SystemV x86-64 ABI (estándar oficial) . Utilizado por todos los sistemas operativos excepto Windows. ( Esta página wiki de github , actualizada por HJ Lu, tiene enlaces a 32 bits, 64 bits y x32. También contiene enlaces al foro oficial para mantenedores / contribuyentes de ABI). También tenga en cuenta que clang / gcc sign / zero extienden argumentos estrechos para 32 bits , aunque el ABI como está escrito no lo requiere. El código generado por el sonido depende de ello.
SystemV 32bit (i386) ABI (estándar oficial) , utilizado por Linux y Unix. ( versión antigua ).
OS X 32bit x86 convención de llamadas, con enlaces a los demás . La convención de llamadas de 64 bits es el Sistema V. El sitio de Apple solo se enlaza con un pdf de FreeBSD para eso.
Windows x86-64
__fastcallllamando a la convenciónWindows
__vectorcall: documenta las versiones de 32 bits y 64 bitsWindows de 32 bits
__stdcall: se utiliza para llamar a las funciones de la API de Win32. Esa página enlaza con los otros documentos de la convención de llamada (por ejemplo,__cdecl).¿Por qué Windows64 utiliza una convención de llamada diferente de todos los demás sistemas operativos en x86-64? : alguna historia interesante, esp. para el SysV ABI donde los archivos de listas de correo son públicos y se remontan antes del lanzamiento del primer silicio de AMD.
Cdecl de 32 bits
cdecl es una convención de llamada de función de Windows de 32 bits que es muy similar a la convención de llamada utilizada en muchos sistemas operativos POSIX (documentado en el i386 System V ABI ). Una de las diferencias está en devolver pequeñas estructuras.
Parámetros
Los parámetros se pasan a la pila, con el primer argumento en la dirección más baja de la pila en el momento de la llamada (empujado en último lugar, por lo que está justo arriba de la dirección de retorno al ingresar a la función). La persona que llama es responsable de quitar los parámetros de la pila después de la llamada.
Valor de retorno
Para los tipos de retorno escalar, el valor de retorno se coloca en EAX o EDX: EAX para enteros de 64 bits. Los tipos de punto flotante se devuelven en st0 (x87). La devolución de tipos más grandes como las estructuras se realiza por referencia, con un puntero pasado como primer parámetro implícito. (Este puntero se devuelve en EAX, por lo que la persona que llama no tiene que recordar lo que pasó).
Registros guardados y aplastados
EBee, EDI, ESI, EBP y ESP (y la configuración del modo de redondeo de FP / SSE) deben ser conservados por la persona que llama, de modo que la persona que llama pueda confiar en que esos registros no hayan sido modificados por una llamada.
Todos los demás registros (EAX, ECX, EDX, FLAGS (que no sean DF), x87 y registros vectoriales) pueden ser modificados libremente por el destinatario; Si una persona que llama desea conservar un valor antes y después de la llamada a la función, debe guardar el valor en otro lugar (como en uno de los registros guardados o en la pila).
Sistema V de 64 bits
Esta es la convención de llamada predeterminada para aplicaciones de 64 bits en muchos sistemas operativos POSIX.
Parámetros
Los primeros ocho parámetros escalares se pasan en (en orden) RDI, RSI, RDX, RCX, R8, R9, R10, R11. Los parámetros pasados los primeros ocho se colocan en la pila, con los parámetros anteriores más cerca de la parte superior de la pila. La persona que llama es responsable de quitar estos valores de la pila después de la llamada si ya no es necesario.
Valor de retorno
Para los tipos de retorno escalar, el valor de retorno se coloca en RAX. La devolución de tipos más grandes, como las estructuras, se realiza cambiando conceptualmente la firma de la función para agregar un parámetro al principio de la lista de parámetros que es un puntero a una ubicación en la que colocar el valor de retorno.
Registros guardados y aplastados
El destinatario de la llamada conserva RBP, RBX y R12 – R15. Todos los demás registros pueden ser modificados por la persona que llama, y la persona que llama debe conservar el valor de un registro (por ejemplo, en la pila) si desea usar ese valor más adelante.
Llamada de 32 bits
stdcall se utiliza para llamadas de API de Windows de 32 bits.
Parámetros
Los parámetros se pasan a la pila, con el primer parámetro más cercano a la parte superior de la pila. La persona que llama sacará estos valores de la pila antes de regresar.
Valor de retorno
Los valores de retorno escalares se colocan en EAX.
Registros guardados y aplastados
EAX, ECX y EDX pueden ser modificados libremente por la persona que llama, y deben ser guardados por la persona que llama si lo desea. EBee, ESI, EDI y EBP deben ser guardados por el destinatario si se modifican y se restauran a sus valores originales en la devolución.
32 bits, cdecl - Tratar con enteros
Como parámetros (8, 16, 32 bits)
Los enteros de 8, 16, 32 bits siempre se pasan, en la pila, como valores de ancho completo de 32 bits 1 .
Ninguna extensión, firmada o puesta a cero, es necesaria.
La persona que llama solo usará la parte inferior de los valores de ancho completo.
//C prototype of the callee
void __attribute__((cdecl)) foo(char a, short b, int c, long d);
foo(-1, 2, -3, 4);
;Call to foo in assembly
push DWORD 4 ;d, long is 32 bits, nothing special here
push DWORD 0fffffffdh ;c, int is 32 bits, nothing special here
push DWORD 0badb0002h ;b, short is 16 bits, higher WORD can be any value
push DWORD 0badbadffh ;a, char is 8 bits, higher three bytes can be any value
call foo
add esp, 10h ;Clean up the stack
Como parámetros (64 bits)
Los valores de 64 bits se pasan a la pila utilizando dos pulsaciones, respetando la convención littel endian 2 , empujando primero los 32 bits más altos y luego los más bajos.
//C prototype of the callee
void __attribute__((cdecl)) foo(char a, short b, int c, long d);
foo(0x0123456789abcdefLL);
;Call to foo in assembly
push DWORD 89abcdefh ;Higher DWORD of 0123456789abcdef
push DWORD 01234567h ;Lower DWORD of 0123456789abcdef
call foo
add esp, 08h
Como valor de retorno
Los enteros de 8 bits se devuelven en AL , y finalmente se destruye todo el eax .
Los números enteros de 16 bits se devuelven en AX , eventualmente obstruyendo todo el eax .
Los enteros de 32 bits se devuelven en EAX .
Los números enteros de 64 bits se devuelven en EDX:EAX , donde EAX contiene los 32 bits inferiores y EDX los superiores.
//C
char foo() { return -1; }
;Assembly
mov al, 0ffh
ret
//C
unsigned short foo() { return 2; }
;Assembly
mov ax, 2
ret
//C
int foo() { return -3; }
;Assembly
mov eax, 0fffffffdh
ret
//C
int foo() { return 4; }
;Assembly
xor edx, edx ;EDX = 0
mov eax, 4 ;EAX = 4
ret
1 Esto mantiene la pila alineada en 4 bytes, el tamaño natural de la palabra. Además, una CPU x86 solo puede enviar 2 o 4 bytes cuando no está en modo largo.
2 DWORD inferior en la dirección inferior
32 bits, cdecl - Tratar con punto flotante
Como parámetros (float, doble)
Los flotadores tienen un tamaño de 32 bits, se pasan naturalmente en la pila.
Los dobles tienen un tamaño de 64 bits, se pasan, en la pila, respetando la convención Little Endian 1 , empujando primero los 32 bits superiores y los inferiores.
//C prototype of callee
double foo(double a, float b);
foo(3.1457, 0.241);
;Assembly call
;3.1457 is 0x40092A64C2F837B5ULL
;0.241 is 0x3e76c8b4
push DWORD 3e76c8b4h ;b, is 32 bits, nothing special here
push DWORD 0c2f837b5h ;a, is 64 bits, Higher part of 3.1457
push DWORD 40092a64h ;a, is 64 bits, Lower part of 3.1457
call foo
add esp, 0ch
;Call, using the FPU
;ST(0) = a, ST(1) = b
sub esp, 0ch
fstp QWORD PTR [esp] ;Storing a as a QWORD on the stack
fstp DWORD PTR [esp+08h] ;Storing b as a DWORD on the stack
call foo
add esp, 0ch
Como parámetros (doble largo)
Los dobles largos son 80 bits 2 de ancho, mientras que en la pila se puede almacenar un TBYTE con dos empujes de 32 bits y un empuje de 16 bits (para 4 + 4 + 2 = 10), para mantener la pila alineada en 4 bytes, termina ocupando 12 bytes, utilizando así tres empujes de 32 bits.
Respetando la convención de Little Endian, los bits 79-64 son empujados primero 3 , luego los bits 63-32 seguidos por los bits 31-0.
//C prototype of the callee
void __attribute__((cdecl)) foo(long double a);
foo(3.1457);
;Call to foo in assembly
;3.1457 is 0x4000c9532617c1bda800
push DWORD 4000h ;Bits 79-64, as 32 bits push
push DWORD 0c9532617h ;Bits 63-32
push DWORD 0c1bda800h ;Bits 31-0
call foo
add esp, 0ch
;Call to foo, using the FPU
;ST(0) = a
sub esp, 0ch
fstp TBYTE PTR [esp] ;Store a as ten byte on the stack
call foo
add esp, 0ch
Como valor de retorno
Los valores de punto flotante, cualquiera que sea su tamaño, se devuelven en ST(0) 4 .
//C
float one() { return 1; }
;Assembly
fld1 ;ST(0) = 1
ret
//C
double zero() { return 0; }
;Assembly
fldz ;ST(0) = 0
ret
//C
long double pi() { return PI; }
;Assembly
fldpi ;ST(0) = PI
ret
1 DWORD inferior en la dirección inferior.
2 Conocido como TBYTE, de Diez Bytes.
3 Usando un empuje de ancho completo con cualquier extensión, no se usa la PALABRA más alta.
4 Que es ancho TBYE, tenga en cuenta que, a diferencia de los enteros, los PF siempre se devuelven con la mayor precisión que se requiere.
Windows de 64 bits
Parámetros
Los primeros 4 parámetros se pasan en (en orden) RCX, RDX, R8 y R9. XMM0 a XMM3 se utilizan para pasar parámetros de punto flotante.
Cualquier otro parámetro se pasa en la pila.
Los parámetros mayores de 64 bits se pasan por dirección.
Espacio de derrame
Incluso si la función utiliza menos de 4 parámetros, la persona que llama siempre proporciona espacio para 4 parámetros de tamaño QWORD en la pila. El destinatario de la llamada es libre de usarlos para cualquier propósito, es común copiar los parámetros allí si otra llamada los derrama.
Valor de retorno
Para los tipos de retorno escalar, el valor de retorno se coloca en RAX. Si el tipo de retorno es mayor que 64 bits (por ejemplo, para estructuras), RAX es un puntero a eso.
Registros guardados y aplastados
Todos los registros utilizados en el paso de parámetros (RCX, RDX, R8, R9 y XMM0 a XMM3), RAX, R10, R11, XMM4 y XMM5 pueden ser derramados por el destinatario. Todos los demás registros deben ser conservados por la persona que llama (por ejemplo, en la pila).
Alineación de la pila
La pila debe mantenerse alineada de 16 bytes. Dado que la instrucción "llamada" empuja una dirección de retorno de 8 bytes, esto significa que cada función no hoja va a ajustar la pila en un valor de la forma 16n + 8 para restaurar la alineación de 16 bytes.
Es el trabajo de las personas que llaman limpiar la pila después de una llamada.
Fuente: La historia de las convenciones de llamadas, parte 5: amd64 Raymond Chen
32 bits, cdecl - Manejo de estructuras
Relleno
Recuerde, los miembros de una estructura generalmente se rellenan para asegurarse de que están alineados en su límite natural:
struct t
{
int a, b, c, d; // a is at offset 0, b at 4, c at 8, d at 0ch
char e; // e is at 10h
short f; // f is at 12h (naturally aligned)
long g; // g is at 14h
char h; // h is at 18h
long i; // i is at 1ch (naturally aligned)
};
Como parámetros (pasar por referencia)
Cuando se pasa por referencia, un puntero a la estructura en la memoria se pasa como el primer argumento en la pila. Esto es equivalente a pasar un valor entero de tamaño natural (32 bits); ver cdecl de 32 bits para más detalles.
Como parámetros (pasar por valor)
Cuando se pasa por valor, las estructuras se copian por completo en la pila, respetando el diseño de la memoria original ( es decir , el primer miembro estará en la dirección inferior).
int __attribute__((cdecl)) foo(struct t a);
struct t s = {0, -1, 2, -3, -4, 5, -6, 7, -8};
foo(s);
; Assembly call
push DWORD 0fffffff8h ; i (-8)
push DWORD 0badbad07h ; h (7), pushed as DWORD to naturally align i, upper bytes can be garbage
push DWORD 0fffffffah ; g (-6)
push WORD 5 ; f (5)
push WORD 033fch ; e (-4), pushed as WORD to naturally align f, upper byte can be garbage
push DWORD 0fffffffdh ; d (-3)
push DWORD 2 ; c (2)
push DWORD 0ffffffffh ; b (-1)
push DWORD 0 ; a (0)
call foo
add esp, 20h
Como valor de retorno
A menos que sean triviales 1 , las estructuras se copian en un búfer proporcionado por la persona que llama antes de regresar. Esto es equivalente a tener un primer parámetro oculto struct S *retval (donde struct S es el tipo de la estructura).
La función debe devolver con este puntero al valor de retorno en eax ; Se permite que la persona que llama dependa de que eax mantenga el puntero en el valor de retorno, que presionó justo antes de la call .
struct S
{
unsigned char a, b, c;
};
struct S foo(); // compiled as struct S* foo(struct S* _out)
El parámetro oculto no se agrega al conteo de parámetros para la limpieza de la pila, ya que debe ser manejado por el destinatario.
sub esp, 04h ; allocate space for the struct
; call to foo
push esp ; pointer to the output buffer
call foo
add esp, 00h ; still as no parameters have been passed
En el ejemplo anterior, la estructura se guardará en la parte superior de la pila.
struct S foo()
{
struct S s;
s.a = 1; s.b = -2; s.c = 3;
return s;
}
; Assembly code
push ebx
mov eax, DWORD PTR [esp+08h] ; access hidden parameter, it is a pointer to a buffer
mov ebx, 03fe01h ; struct value, can be held in a register
mov DWORD [eax], ebx ; copy the structure into the output buffer
pop ebx
ret 04h ; remove the hidden parameter from the stack
; EAX = pointer to the output buffer
1 Una estructura "trivial" es aquella que contiene solo un miembro de un tipo sin estructura, sin matriz (de hasta 32 bits de tamaño). Para tales estructuras, el valor de ese miembro simplemente se devuelve en el registro eax . (Este comportamiento se ha observado con GCC que apunta a Linux)
La versión de cdecl para Windows es diferente de la convención de llamada ABI de System V: se permite que una estructura "trivial" contenga hasta dos miembros de un tipo sin estructura, sin matriz (de hasta 32 bits de tamaño). Estos valores se devuelven en eax y edx , como lo sería un entero de 64 bits. (Este comportamiento se ha observado para MSVC y Clang para Win32).