Este artículo describe las convenciones de llamada utilizados en la programación x86 arquitectura microprocesadores .
Las convenciones de llamada describen la interfaz del código llamado:
- El orden en el que se asignan los parámetros atómicos (escalares) o las partes individuales de un parámetro complejo.
- Cómo se pasan los parámetros (insertados en la pila, colocados en registros o una combinación de ambos)
- Qué registros debe preservar la función llamada para la persona que llama (también conocidos como: registros guardados de llamadas o registros no volátiles)
- Cómo se divide la tarea de preparar la pila para, y restaurar después, una llamada de función entre la persona que llama y la persona que llama
Esto está íntimamente relacionado con la asignación de tamaños y formatos a los tipos de lenguajes de programación. Otro tema estrechamente relacionado es la alteración de nombres , que determina cómo los nombres de los símbolos en el código se asignan a los nombres de los símbolos utilizados por el vinculador. Las convenciones de llamadas, las representaciones de tipos y la manipulación de nombres son parte de lo que se conoce como interfaz binaria de aplicación (ABI).
A menudo existen diferencias sutiles en la forma en que varios compiladores implementan estas convenciones, por lo que a menudo es difícil interconectar el código compilado por diferentes compiladores. Por otro lado, las convenciones que se utilizan como estándar API (como stdcall) se implementan de manera muy uniforme.
Antecedentes históricos
Antes de las microcomputadoras , el fabricante de la máquina generalmente proporcionaba un sistema operativo y compiladores para varios lenguajes de programación . La (s) convención (es) de llamada para cada plataforma fueron las definidas por las herramientas de programación del fabricante.
Las primeras microcomputadoras antes de Commodore Pet y Apple II generalmente venían sin un sistema operativo o compiladores. El IBM PC vino con el precursor de Windows de Microsoft, el Sistema Operativo de Disco ( DOS ), pero no vino con un compilador. El único estándar de hardware para máquinas IBM compatibles con PC fue definido por los procesadores Intel (8086, 80386) y el hardware literal que IBM envió. Las extensiones de hardware y todos los estándares de software (a excepción de una convención de llamadas de BIOS ) se abrieron a la competencia del mercado.
Una multitud de empresas de software independientes ofrecían sistemas operativos, compiladores para muchos lenguajes de programación y aplicaciones. Las empresas implementaron muchos esquemas de llamadas diferentes, a menudo mutuamente excluyentes, basados en diferentes requisitos, prácticas históricas y creatividad del programador.
Después de la sacudida del mercado compatible con IBM , predominaron los sistemas operativos y las herramientas de programación de Microsoft (con diferentes convenciones), mientras que empresas de segundo nivel como Borland y Novell , y proyectos de código abierto como GCC , aún mantenían sus propios estándares. Finalmente, se adoptaron disposiciones para la interoperabilidad entre proveedores y productos, lo que simplificó el problema de elegir una convención viable. [1]
Limpieza de llamadas
En estas convenciones, el llamador limpia los argumentos de la pila.
cdecl
El cdecl (que significa declaración de C ) es una convención de llamada que se origina en el compilador de Microsoft para el lenguaje de programación C y es utilizado por muchos compiladores de C para la arquitectura x86 . [1] En cdecl, los argumentos de subrutina se pasan a la pila . Los valores enteros y las direcciones de memoria se devuelven en el registro EAX , los valores de punto flotante en el registro ST0 x87 . Los registros EAX, ECX y EDX se guardan para la persona que llama y el resto se guarda para la persona que llama. Los registros de coma flotante x87 ST0 a ST7 deben estar vacíos (desplegados o liberados) cuando se llama a una nueva función, y ST1 a ST7 deben estar vacíos al salir de una función. ST0 también debe estar vacío cuando no se usa para devolver un valor.
En el contexto del lenguaje de programación C, los argumentos de función se insertan en la pila en orden de derecha a izquierda, es decir, el último argumento se inserta primero.
Considere el siguiente fragmento de código fuente de C:
int callee ( int , int , int );int caller ( void ) { return callee ( 1 , 2 , 3 ) + 5 ; }
En x86 , podría producir el siguiente código ensamblador ( sintaxis Intel ):
persona que llama: ; hacer un nuevo marco de llamada ; (algunos compiladores pueden producir una instrucción 'enter' en su lugar) push ebp ; guardar el antiguo marco de llamada mov ebp , esp ; inicializar nueva trama de llamada ; empujar argumentos de llamada, a la inversa ; (algunos compiladores pueden restar el espacio requerido del puntero de la pila ; luego escribir cada argumento directamente, ver más abajo. La instrucción 'enter' también puede hacer algo similar) ; sub esp, 12: la instrucción 'enter' podría hacer esto por nosotros ; mov [ebp-4], 3: o mov [esp + 8], 3 ; mov [ebp-8], 2: o mov [esp + 4], 2 ; mov [ebp-12], 1: o mov [esp], 1 presionar 3 presionar 2 presionar 1 llamar al destinatario ; llamar a la subrutina 'callee' agregar esp , 12 ; eliminar argumentos de llamada del marco agregar eax , 5 ; modificar el resultado de la subrutina ; (eax es el valor de retorno de nuestro destinatario, por lo que no tenemos que moverlo a una variable local) ; restaurar el marco de llamada antiguo ; (algunos compiladores pueden producir una instrucción 'dejar' en su lugar) mov esp , ebp ; la mayoría de las convenciones de llamadas dictan que ebp se guarde la llamada ; es decir, se conserva después de llamar al destinatario. ; por lo tanto, todavía apunta al inicio de nuestro marco de pila. ; necesitamos asegurarnos ; sin embargo, callee no modifica (ni restaura) ebp ; así que debemos asegurarnos ; usa una convención de llamada que hace este pop ebp ; restaurar antiguo marco de llamada ret ; regreso
La persona que llama limpia la pila después de que regresa la llamada a la función.
La convención de llamada cdecl suele ser la convención de llamada predeterminada para los compiladores x86 C , aunque muchos compiladores ofrecen opciones para cambiar automáticamente las convenciones de llamada utilizadas. Para definir manualmente una función para que sea cdecl, algunos admiten la siguiente sintaxis:
return_type __cdecl FUNC_NAME ();
Variaciones
Existen algunas variaciones en la interpretación de cdecl. Como resultado, los programas x86 compilados para diferentes plataformas de sistemas operativos y / o por diferentes compiladores pueden ser incompatibles, incluso si ambos usan la convención "cdecl" y no llaman al entorno subyacente.
Con respecto a cómo devolver valores, algunos compiladores devuelven estructuras de datos simples con una longitud de 2 registros o menos en el par de registros EAX: EDX, y estructuras más grandes y objetos de clase que requieren un tratamiento especial por parte del manejador de excepciones (por ejemplo, un constructor definido, destructor o asignación) se devuelven en la memoria. Para pasar "en memoria", la persona que llama asigna memoria y le pasa un puntero como primer parámetro oculto; el destinatario de la llamada llena la memoria y devuelve el puntero, haciendo estallar el puntero oculto al regresar. [2]
En Linux , GCC establece el estándar de facto para las convenciones de llamadas. Desde la versión 4.5 de GCC, la pila debe estar alineada con un límite de 16 bytes cuando se llama a una función (las versiones anteriores solo requerían una alineación de 4 bytes). [1] [3]
Se describe una versión de cdecl en System V ABI para sistemas i386. [4]
syscall
Esto es similar a cdecl en que los argumentos se empujan de derecha a izquierda. EAX, ECX y EDX no se conservan. El tamaño de la lista de parámetros en palabras dobles se pasa en AL.
Syscall es la convención de llamadas estándar para la API OS / 2 de 32 bits .
optlink
Los argumentos se desplazan de derecha a izquierda. Los tres primeros argumentos (más a la izquierda) se pasan en EAX, EDX y ECX y hasta cuatro argumentos de punto flotante se pasan de ST0 a ST3, aunque se reserva espacio para ellos en la lista de argumentos de la pila. Los resultados se devuelven en EAX o ST0. Se conservan los registros EBP, EBX, ESI y EDI.
Los compiladores de IBM VisualAge utilizan Optlink .
Limpieza de llamadas
En estas convenciones, el destinatario de la llamada limpia los argumentos de la pila. Las funciones que utilizan estas convenciones son fáciles de reconocer en el código ASM porque desenrollarán la pila después de regresar. La instrucción ret x86 permite un parámetro opcional de 16 bits que especifica el número de bytes de pila que se liberarán después de regresar al llamador. Dicho código se ve así:
ret 12
Las convenciones tituladas llamada rápida o registro no se han estandarizado y se han implementado de manera diferente, según el proveedor del compilador. [1] Normalmente, las convenciones de llamadas basadas en registros pasan uno o más argumentos en los registros, lo que reduce el número de accesos a la memoria necesarios para la llamada y, por lo tanto, los hace generalmente más rápidos.
pascal
Según la convención de llamadas del lenguaje de programación Borland Pascal , los parámetros se insertan en la pila en orden de izquierda a derecha (opuesto a cdecl), y el destinatario de la llamada es responsable de eliminarlos de la pila.
Devolver el resultado funciona de la siguiente manera:
- Los valores ordinales se devuelven en AL (valores de 8 bits), AX (valores de 16 bits), EAX (valores de 32 bits) o DX: AX (valores de 32 bits en sistemas de 16 bits).
- Los valores reales se devuelven en DX: BX: AX.
- Los valores de coma flotante (8087) se devuelven en ST0.
- Los punteros se devuelven en EAX en sistemas de 32 bits y en AX en sistemas de 16 bits.
- Las cadenas se devuelven en una ubicación temporal señalada por el símbolo @Result.
Esta convención de llamada era común en las siguientes API de 16 bits: OS / 2 1.x, Microsoft Windows 3.xy Borland Delphi versión 1.x. Las versiones modernas de la API de Windows usan stdcall , que todavía tiene al destinatario restaurando la pila como en la convención de Pascal, pero los parámetros ahora se empujan de derecha a izquierda.
stdcall
La convención de llamada stdcall [5] es una variación de la convención de llamada de Pascal en la que el destinatario de la llamada es responsable de limpiar la pila, pero los parámetros se insertan en la pila en orden de derecha a izquierda, como en la convención de llamada _cdecl. Los registros EAX, ECX y EDX están designados para su uso dentro de la función. Los valores devueltos se almacenan en el registro EAX.
stdcall es la convención de llamadas estándar para la API de Microsoft Win32 y para Open Watcom C ++ .
Llamada rápida de Microsoft
Microsoft __fastcall convention (también conocido como __msfastcall ) pasa los dos primeros argumentos (evaluados de izquierda a derecha) que encajan en ECX y EDX. [6] Los argumentos restantes se insertan en la pila de derecha a izquierda. Cuando el compilador compila para IA64 o AMD64 , ignora la __fastcall palabra clave y utiliza la única convención de llamada de 64 bits en su lugar.
Como una convención de llamadas muy común, otros compiladores como GCC, Clang e ICC también admiten fastcall. [7]
Considere el siguiente fragmento de c:
__attribute__ (( llamada rápida )) void printnums ( int num1 , int num2 , int num3 ) { printf ( "Los números que enviaste son:% d% d% d" , num1 , num2 , num3 ); }int main () { printnums ( 3 , 2 , 1 ); return 0 ; }
La descompilación x86 de la función principal se verá así (en sintaxis Intel):
principal :; configuración de pila push ebp mov ebp , esp push 3 ; inmediato 3 (el primer argumento se envía a la pila) mov edx , 0x2 ; El inmediato 2 (segundo argumento) se copia al registro edx. mov ecx , 0x1 ; El 1 inmediato (tercer argumento) se copia en el registro ecx. llamar printnums mov eax , 0 ; volver 0 dejar retn
Los dos primeros argumentos se pasan en orden de izquierda a derecha y el tercer argumento se inserta en la pila. No hay limpieza de la pila, ya que la persona que llama la realiza. El desmontaje de la función de llamada es:
printnums: ; configuración de pila push ebp mov ebp , esp sub esp , 0x08 mov [ ebp-0x04 ], ecx ; en x86, ecx = primer argumento. mov [ ebp-0x08 ], edx ; arg2 empujar [ ebp + 0x08 ] ; arg3 se empuja a la pila. empujar [ ebp-0x08 ] ; se presiona arg2 presionar [ ebp-0x04 ] ; se presiona arg1 push 0x8065d67 ; "Los números que envió son% d% d% d" llame a printf ; limpieza de pila agregar esp , 0x10 nop dejar retn 0x04
Como los dos argumentos se pasaron a través de los registros y solo se insertó un parámetro en la pila, la instrucción retn borra el valor insertado, ya que int tiene un tamaño de 4 bytes en los sistemas x86.
Microsoft vectorcall
En Visual Studio 2013, Microsoft presentó el __convención de llamadas de vectores en respuesta a preocupaciones de eficiencia de los desarrolladores de juegos, gráficos, video / audio y códecs. El esquema permite tipos de vectores más grandes ( flotar , doble , __m128 , __m256 ) para pasar a los registros en lugar de a la pila. [8]
Para código IA-32 y x64, __vectorcall es similar a __fastcall y lasconvenciones de llamada x64 originales respectivamente, pero las amplía para admitir el paso de argumentos vectoriales medianteregistros SIMD . En IA-32, los valores enteros se pasan como de costumbre, y los primeros seis registros SIMD ( XMM / YMM 0-5) contienen hasta seis valores de punto flotante, vector o HVA secuencialmente de izquierda a derecha, independientemente de las posiciones reales. causado por, por ejemplo, un argumento int que aparece entre ellos. En x64, sin embargo, la regla de la convención x64 original aún se aplica, de modo que XMM / YMM0-5 solo contienen argumentos de punto flotante, vectoriales o HVA cuando son del primero al sexto. [9]
__vectorcall agrega soporte para pasar valores de agregados vectoriales homogéneos (HVA), que son tipos compuestos (estructuras) que constan únicamente de hasta cuatro tipos de vectores idénticos, utilizando los mismos seis registros. Una vez que se han asignado los registros para argumentos de tipo vectorial, los registros no utilizados se asignan a los argumentos HVA de izquierda a derecha. Las reglas de posicionamiento aún se aplican. El tipo de vector resultante y los valores de HVA se devuelven utilizando los primeros cuatro registros XMM / YMM. [9]
El compilador clang y el compilador Intel C ++ también implementan vectorcall. [10] El compilador Intel C ++ tenía una convención anterior similar llamada __regcall ; también es compatible con clang. [11]
Registro Borland
Al evaluar los argumentos de izquierda a derecha, pasa tres argumentos a través de EAX, EDX, ECX. Los argumentos restantes se insertan en la pila, también de izquierda a derecha. [12] Es la convención de llamada predeterminada del compilador de 32 bits de Delphi , donde se conoce como registro . Esta convención de llamada también es utilizada por C ++ Builder de Embarcadero, donde se llama __fastcall . [13] En este compilador, la llamada rápida de Microsoft se puede utilizar como __msfastcall . [14]
Se puede hacer que GCC y Clang utilicen una convención de llamada similar utilizando __stdcall
el regparm
atributo de función o el -mregparm=3
conmutador. (El orden de la pila está invertido). También es posible producir una variante de limpieza de llamadas usando cdecl
o extender esto para usar también registros SSE. [15] El cdecl
kernel de Linux utiliza una versión basada en A en i386 desde la versión 2.6.20 (publicada en febrero de 2007). [dieciséis]
Registro Watcom
Watcom no admite la palabra clave __fastcall excepto para asignarle un alias a null. La convención de llamada de registro puede seleccionarse mediante el cambio de línea de comando. (Sin embargo, IDA usa __fastcall de todos modos para uniformidad).
Se asignan hasta 4 registros a los argumentos en el orden EAX, EDX, EBX, ECX. Los argumentos se asignan a los registros de izquierda a derecha. Si algún argumento no puede asignarse a un registro (digamos que es demasiado grande), éste y todos los argumentos posteriores se asignan a la pila. Los argumentos asignados a la pila se empujan de derecha a izquierda. Los nombres se alteran agregando un subrayado con sufijo.
Las funciones variables recurren a la convención de llamadas basada en la pila de Watcom.
El compilador Watcom C / C ++ también usa el Directiva #pragma aux [17] que permite al usuario especificar su propia convención de llamadas. Como dice su manual, "Es probable que muy pocos usuarios necesiten este método, pero si es necesario, puede ser un salvavidas".
TopSpeed / Clarion / JPI
Los primeros cuatro parámetros enteros se pasan en los registros eax, ebx, ecx y edx. Los parámetros de coma flotante se pasan a la pila de coma flotante: registros st0, st1, st2, st3, st4, st5 y st6. Los parámetros de estructura siempre se pasan a la pila. Los parámetros adicionales se pasan a la pila después de que se agotan los registros. Los valores enteros se devuelven en eax, los punteros en edx y los tipos de punto flotante en st0.
llamada segura
En Delphi y Free Pascal en Microsoft Windows , la convención de llamadas seguras encapsula el manejo de errores COM ( Modelo de objetos componentes ), por lo que las excepciones no se filtran a la persona que llama, sino que se informan en el valor de retorno HRESULT , como lo requiere COM / OLE. Al llamar a una función de llamada segura desde el código Delphi, Delphi también verifica automáticamente el HRESULT devuelto y genera una excepción si es necesario.
La convención de llamada segura es la misma que la convención de llamada stdcall, excepto que las excepciones se devuelven a la persona que llama en EAX como un resultado de HR (en lugar de en FS: [0]), mientras que el resultado de la función se pasa por referencia en la pila como aunque era un parámetro final de "salida". Cuando se llama a una función Delphi desde Delphi, esta convención de llamada aparecerá como cualquier otra convención de llamada, porque aunque las excepciones se devuelven en EAX, el llamador las convierte automáticamente en excepciones adecuadas. Cuando se utilizan objetos COM creados en otros idiomas, los HResults se generarán automáticamente como excepciones, y el resultado de las funciones Get está en el resultado en lugar de en un parámetro. Al crear objetos COM en Delphi con safecall, no hay necesidad de preocuparse por HResults, ya que las excepciones se pueden generar como normales pero se verán como HResults en otros lenguajes.
función nombre_función ( a : DWORD ) : DWORD ; llamada segura ;
Devuelve un resultado y genera excepciones como una función normal de Delphi, pero pasa valores y excepciones como si fuera:
función function_name ( un : DWORD ; a cabo Resultado : DWORD ) : HResult ; stdcall ;
Limpiar la persona que llama o la persona que llama
esta llamada
Esta convención de llamada se utiliza para llamar a funciones miembro no estáticas de C ++. Hay dos versiones principales de esta llamada que se utilizan según el compilador y si la función utiliza o no un número variable de argumentos.
Para el compilador GCC, esta llamada es casi idéntica a cdecl : el llamador limpia la pila y los parámetros se pasan en orden de derecha a izquierda. La diferencia es la adición del puntero this , que se inserta en la pila en último lugar, como si fuera el primer parámetro en el prototipo de función.
En el compilador de Microsoft Visual C ++, este puntero se pasa en ECX y es el destinatario de la llamada el que limpia la pila, reflejando la convención stdcall utilizada en C para este compilador y en las funciones de la API de Windows. Cuando las funciones usan un número variable de argumentos, es el llamador el que limpia la pila (cf. cdecl ).
La convención de llamada thiscall solo se puede especificar explícitamente en Microsoft Visual C ++ 2005 y versiones posteriores. En cualquier otro compilador, esta llamada no es una palabra clave. (Sin embargo, los desensambladores, como IDA , deben especificarlo. Por lo tanto, IDA usa la palabra clave __thiscall para esto).
Conservación del registro
Otra parte de una convención de llamadas es qué registros están garantizados para retener sus valores después de una llamada de subrutina.
Registros (volátiles) guardados por la persona que llama
Según la ABI de Intel a la que se ajusta la gran mayoría de los compiladores, EAX, EDX y ECX deben ser de uso gratuito dentro de un procedimiento o función, y no es necesario conservarlas [ cita requerida ] .
Como su nombre lo indica, estos registros de propósito general generalmente contienen información temporal (volátil), que puede ser sobrescrita por cualquier subrutina.
Por lo tanto, es responsabilidad del llamador insertar cada uno de estos registros en la pila, si desea restaurar sus valores después de una llamada de subrutina.
Registros de llamadas guardadas (no volátiles)
Los otros registros se utilizan para mantener valores de larga duración (no volátiles), que deben conservarse en todas las llamadas.
En otras palabras, cuando la persona que llama realiza una llamada a procedimiento, puede esperar que esos registros mantengan el mismo valor después de que la persona que llama regrese.
Por lo tanto, es responsabilidad de la persona que llama guardarlos (presionar al principio) y restaurarlos (hacer estallar en consecuencia) antes de regresar a la persona que llama. Como en el caso anterior, esta práctica solo debe realizarse en los registros que cambia el destinatario.
Convenciones de llamadas x86-64
Las convenciones de llamadas x86-64 aprovechan el espacio de registro adicional para pasar más argumentos en los registros. Además, se ha reducido el número de convenciones de llamadas incompatibles. Hay dos de uso común.
Convención de llamadas de Microsoft x64
La convención de llamadas de Microsoft x64 [18] [19] se sigue en Windows y UEFI previo al arranque (para el modo largo en x86-64 ). Los primeros cuatro argumentos se colocan en los registros. Eso significa RCX, RDX, R8, R9 para argumentos de tipo entero, estructura o puntero (en ese orden), y XMM0, XMM1, XMM2, XMM3 para argumentos de punto flotante. Los argumentos adicionales se insertan en la pila (de derecha a izquierda). Los valores devueltos enteros (similares a x86) se devuelven en RAX si son de 64 bits o menos. Los valores de retorno de coma flotante se devuelven en XMM0. Los parámetros de menos de 64 bits no se extienden a cero; los bits altos no se ponen a cero.
Las estructuras y uniones con tamaños que coinciden con números enteros se pasan y se devuelven como si fueran números enteros. De lo contrario, se reemplazan con un puntero cuando se usan como argumento. Cuando se necesita un retorno de estructura de gran tamaño, se antepone otro puntero a un espacio proporcionado por la persona que llama como primer argumento, desplazando todos los demás argumentos a la derecha en un lugar. [20]
Al compilar para la arquitectura x64 en un contexto de Windows (ya sea con herramientas de Microsoft o que no sean de Microsoft), stdcall, thiscall, cdecl y fastcall se resuelven para usar esta convención.
En la convención de llamadas de Microsoft x64, es responsabilidad de la persona que llama asignar 32 bytes de "espacio de sombra" en la pila justo antes de llamar a la función (independientemente del número real de parámetros utilizados) y hacer estallar la pila después de la llamada. El espacio de sombra se usa para derramar RCX, RDX, R8 y R9, [21] pero debe estar disponible para todas las funciones, incluso aquellas con menos de cuatro parámetros.
Los registros RAX, RCX, RDX, R8, R9, R10, R11 se consideran volátiles (guardados por la persona que llama). [22]
Los registros RBX, RBP, RDI, RSI, RSP, R12, R13, R14 y R15 se consideran no volátiles (llamadas guardadas). [22]
Por ejemplo, una función que toma 5 argumentos enteros tomará del primero al cuarto en los registros, y el quinto se colocará en la parte superior del espacio de sombra. Entonces, cuando se ingresa la función llamada, la pila estará compuesta por (en orden ascendente) la dirección de retorno, seguida del espacio de sombra (32 bytes) seguido del quinto parámetro.
En x86-64 , Visual Studio 2008 almacena números de punto flotante en XMM6 y XMM7 (así como de XMM8 a XMM15); en consecuencia, para x86-64 , las rutinas en lenguaje ensamblador escritas por el usuario deben preservar XMM6 y XMM7 (en comparación con x86, donde las rutinas en lenguaje ensamblador escritas por el usuario no necesitaban preservar XMM6 y XMM7). En otras palabras, las rutinas en lenguaje ensamblador escritas por el usuario deben actualizarse para guardar / restaurar XMM6 y XMM7 antes / después de la función cuando se transfieren de x86 a x86-64 .
A partir de Visual Studio 2013, Microsoft introdujo el __vectorcall convención de llamada que extiende la convención x64.
Sistema V AMD64 ABI
La convención de llamada de System V AMD64 ABI se sigue en Solaris , Linux , FreeBSD , macOS , [23] y es el estándar de facto entre los sistemas operativos Unix y similares a Unix. El estándar de llamadas OpenVMS en x86-64 se basa en System V ABI con algunas extensiones necesarias para la compatibilidad con versiones anteriores. [24] Los primeros seis argumentos enteros o punteros se pasan en los registros RDI, RSI, RDX, RCX, R8, R9 (R10 se utiliza como puntero de cadena estática en el caso de funciones anidadas [25] : 21 ), mientras que XMM0, XMM1 , XMM2, XMM3, XMM4, XMM5, XMM6 y XMM7 se utilizan para los primeros argumentos de coma flotante. [25] : 22 Como en la convención de llamadas de Microsoft x64, se pasan argumentos adicionales en la pila. [25] : 22 Los valores de retorno enteros de hasta 64 bits de tamaño se almacenan en RAX, mientras que los valores de hasta 128 bits se almacenan en RAX y RDX. Los valores de retorno de coma flotante se almacenan de manera similar en XMM0 y XMM1. [25] : 25 Los registros YMM y ZMM más amplios se utilizan para pasar y devolver valores más amplios en lugar de XMM cuando existen. [25] : 26,55
Si la persona que llama desea utilizar los registros RBX, RSP, RBP y R12 – R15, debe restaurar sus valores originales antes de devolver el control a la persona que llama. Todos los demás registros deben ser guardados por la persona que llama si desea conservar sus valores. [25] : 16
Para las funciones de nodo hoja (funciones que no llaman a ninguna otra función), se almacena un espacio de 128 bytes justo debajo del puntero de pila de la función. El espacio se llama zona roja . Esta zona no será golpeada por ningún manejador de señales o interrupciones. Por tanto, los compiladores pueden utilizar esta zona para guardar variables locales. Los compiladores pueden omitir algunas instrucciones al comienzo de la función (ajuste de RSP, RBP) utilizando esta zona. Sin embargo, otras funciones pueden bloquear esta zona. Por lo tanto, esta zona solo debe usarse para funciones de nodo hoja. gcc
y clang
ofrecer la -mno-red-zone
bandera para deshabilitar las optimizaciones de la zona roja.
Si la persona que llama es una función variada , entonces la persona que llama en el registro AL debe proporcionar el número de argumentos de punto flotante pasados a la función en los registros vectoriales. [25] : 55
A diferencia de la convención de llamadas de Microsoft, no se proporciona un espacio de sombra; en la entrada de la función, la dirección de retorno es adyacente al séptimo argumento entero en la pila.
Lista de convenciones de llamadas x86
Esta es una lista de convenciones de llamadas x86. [1] Se trata de convenciones destinadas principalmente a compiladores de C / C ++ (especialmente la parte de 64 bits a continuación) y, por tanto, a casos en gran medida especiales. Otros lenguajes pueden utilizar otros formatos y convenciones en sus implementaciones.
Arquitectura | Nombre | Sistema operativo, compilador | Parámetros | Limpieza de pila | Notas | |
---|---|---|---|---|---|---|
Registros | Orden de pila | |||||
8086 | cdecl | RTL (C) | Llamador | |||
Pascal | LTR (Pascal) | Llamado | ||||
fastcall (no miembro) | Microsoft | AX, DX, BX | LTR (Pascal) | Llamado | Devuelve el puntero en BX. | |
fastcall (función miembro) | Microsoft | AX, DX | LTR (Pascal) | Llamado | this en la pila de direcciones bajas. Regresa el puntero en AX. | |
llamada rápida | Turbo C [26] | AX, DX, BX | LTR (Pascal) | Llamado | this en la pila de direcciones bajas. Devuelve el puntero en la dirección alta de la pila. | |
Watcom | AX, DX, BX, CX | RTL (C) | Llamado | Devuelve el puntero en SI. | ||
IA-32 | cdecl | Similar a Unix ( GCC ) | RTL (C) | Llamador | Al devolver la estructura / clase, el código de llamada asigna espacio y pasa un puntero a este espacio a través de un parámetro oculto en la pila. La función llamada escribe el valor de retorno en esta dirección. Pila alineada en el límite de 16 bytes debido a un error. | |
cdecl | Microsoft | RTL (C) | Llamador | Al devolver la estructura / clase,
Pila alineada en un límite de 4 bytes. | ||
stdcall | Microsoft | RTL (C) | Llamado | También es compatible con GCC. | ||
llamada rápida | Microsoft | ECX, EDX | RTL (C) | Llamado | Devuelve el puntero en la pila si no es una función miembro. También es compatible con GCC. | |
Registrarse | Delphi y Free Pascal | EAX, EDX, ECX | LTR (Pascal) | Llamado | ||
esta llamada | Windows ( Microsoft Visual C ++ ) | ECX | RTL (C) | Llamado | Predeterminado para funciones miembro. | |
vector llamada | Windows ( Microsoft Visual C ++ ) | ECX, EDX, [XY] MM0–5 | RTL (C) | Llamado | Extendido de llamada rápida. También es compatible con ICC y Clang. [9] | |
Compilador Watcom | EAX, EDX, EBX, ECX | RTL (C) | Llamado | Retorno del puntero en ESI. | ||
x86-64 | Convención de llamadas de Microsoft x64 [18] | De Windows ( Microsoft Visual C ++ , GCC , Intel C ++ Compiler , Delphi ), UEFI | RCX / XMM0, RDX / XMM1, R8 / XMM2, R9 / XMM3 | RTL (C) | Llamador | Pila alineada en 16 bytes. 32 bytes de espacio de sombra en la pila. Los 8 registros especificados solo se pueden usar para los parámetros del 1 al 4. Para las clases de C ++, el this parámetro oculto es el primer parámetro y se pasa en RCX. [27] |
vector llamada | Windows ( Microsoft Visual C ++ , Clang, ICC) | RCX / [XY] MM0, RDX / [XY] MM1, R8 / [XY] MM2, R9 / [XY] MM3 + [XY] MM4–5 | RTL (C) | Llamador | Extendido desde MS x64. [9] | |
Sistema V AMD64 ABI [25] | Solaris , Linux , BSD , macOS , OpenVMS ( GCC , compilador Intel C ++ , Clang , Delphi ) | RDI, RSI, RDX, RCX, R8, R9, [XYZ] MM0–7 | RTL (C) | Llamador | Pila alineada en el límite de 16 bytes. Zona roja de 128 bytes debajo de la pila. La interfaz del kernel utiliza RDI, RSI, RDX, R10, R8 y R9. En C ++, this es el primer parámetro. |
Referencias
Notas al pie
- ↑ a b c d e Agner Fog (16 de febrero de 2010 ). Convenciones de llamada para diferentes compiladores y sistemas operativos de C ++ (PDF) .
- ^ de Boyne Pollard, Jonathan (2010). "El gen en convenciones de llamadas a funciones" . Respuestas dadas con frecuencia .
- ^ "GCC Bugzilla - Error 40838 - gcc no debería asumir que la pila está alineada" . 2009.
- ^ "INTERFAZ BINARIA DE LA APLICACIÓN DEL SISTEMA V Suplemento del procesador de arquitectura Intel 386 cuarta edición" (PDF) .
- ^ "__stdcall (C ++)" . MSDN . Microsoft. Archivado desde el original el 10 de abril de 2008 . Consultado el 13 de febrero de 2019 .
- ^ "__fastcall" . MSDN . Consultado el 26 de septiembre de 2013 .
- ^ Oh, Uwe. "Descripción general del atributo gcc: función de llamada rápida" . ohse.de . Consultado el 27 de septiembre de 2010 .
- ^ "Presentación de la 'Convención de llamadas de vectores ' " . MSDN. 11 de julio de 2013 . Consultado el 31 de diciembre de 2014 .
- ^ a b c d "__vectorcall" . MSDN . Consultado el 31 de diciembre de 2014 .
- ^ "Atributos en Clang: convenciones de llamadas" . Documentación de Clang . Consultado el 8 de octubre de 2019 .
- ^ "_vectorcall y __regcall desmitificados" . software.intel.com . 7 de junio de 2017.
- ^ "Control de programa: Convención de registro" . docwiki.embarcadero.com. 2010-06-01 . Consultado el 27 de septiembre de 2010 .
- ^ "_fastcall, __fastcall" . docwiki.embarcadero.com.
- ^ "__msfastcall" . docwiki.embarcadero.com.
- ^ "Atributos de función x86" . Usando la colección de compiladores GNU (GCC) .
- ^ "i386: habilite siempre regparm" .
- ^ "Convenciones_de_la_llamada: Especificación de las convenciones_de_la_llamada_el_Watcom_Way" . openwatcom.org. 2010-04-27 . Consultado el 31 de agosto de 2018 .
- ^ a b "Convenciones de software x64: convenciones de llamada" . msdn.microsoft.com. 2010 . Consultado el 27 de septiembre de 2010 .
- ^ "Arquitectura x64" . msdn.microsoft.com.
- ^ "Convención de llamadas x64: valores de retorno" . docs.microsoft.com . Consultado el 17 de enero de 2020 .
- ^ "Convenciones de software x64 - Asignación de pila" . Microsoft . Consultado el 31 de marzo de 2010 .
- ^ a b "Registros guardados de llamantes / destinatarios de llamadas" . Microsoft Docs . Microsoft.
- ^ "Modelo de código x86-64" . Biblioteca de desarrolladores de Mac . Apple Inc. Archivado desde el original el 10 de marzo de 2016 . Consultado el 6 de abril de 2016 .
El entorno x86-64 en OS X tiene solo un modelo de código para el código de espacio de usuario. Es muy similar al modelo PIC pequeño definido por el sistema V ABI x86-64.
- ^ "Estándar de llamadas VSI OpenVMS" (PDF) . vmssoftware.com . Mayo de 2020 . Consultado el 21 de diciembre de 2020 .
- ^ a b c d e f g h Michael Matz; Jan Hubička; Andreas Jaeger; et al., eds. (28 de enero de 2018). "Interfaz binaria de aplicación System V: Suplemento de procesador de arquitectura AMD64 (con modelos de programación LP64 e ILP32) Versión 1.0" (PDF) . 1.0.
- ^ Borland C / C ++ versión 3.1 Guía del usuario (PDF) . Borland. 1992. págs. 158, 189-191.
- ^ "Registro de uso" . Microsoft Docs . Microsoft . Consultado el 15 de septiembre de 2017 .
Otras fuentes
- "INTERFAZ BINARIA DE LA APLICACIÓN DEL SISTEMA V Suplemento del procesador de arquitectura Intel386" (PDF) (4ª ed.). La Operación Santa Cruz, Inc. 1997-03-19. Cite journal requiere
|journal=
( ayuda ) - Nemanja Trifunovic (22 de julio de 2001). Sean Ewington (ed.). "Convenciones de llamadas desmitificadas" . El proyecto de código .
- Stephen J. Friedl. "Convenciones de llamadas de función Intel x86 - Vista de ensamblaje" . Consejos técnicos de Unixwiz.net de Steve Friedl .
- "Visual Studio 2010 - Convención de llamadas de Visual C ++" . Biblioteca de MSDN . Microsoft. 2010.
- Andreas Jonsson (13 de febrero de 2005). "Convenciones de llamadas en la plataforma x86" .
- Raymond Chen (2 de enero de 2004). "La historia de las convenciones de llamadas, parte 1" . Lo viejo y nuevo .
- Raymond Chen (7 de enero de 2004). "La historia de las convenciones de llamadas, parte 2" . Lo viejo y nuevo .
- Raymond Chen (8 de enero de 2004). "La historia de las convenciones de llamadas, parte 3" . Lo viejo y nuevo .
- Raymond Chen (13 de enero de 2004). "La historia de las convenciones de llamadas, parte 4: ia64" . Lo viejo y nuevo .
- Raymond Chen (14 de enero de 2004). "La historia de las convenciones de llamadas, parte 5; amd64" . Lo viejo y nuevo .
Otras lecturas
- Jonathan de Boyne Pollard (2010). "El gen en convenciones de llamadas a funciones" . Respuestas dadas con frecuencia .
- Kip R. Irvine (2011). "Procedimientos avanzados (Capítulo 8)". Lenguaje ensamblador para procesadores x86 (6.a ed.). Prentice Hall. ISBN 978-0-13-602212-1.
- Borland C / C ++ versión 3.1 Guía del usuario (PDF) . Borland. 1992. págs. 158, 189-191.
- Thomas Lauer (1995). "La nueva secuencia de llamada __stdcall". Portar a Win32: una guía para preparar sus aplicaciones para el futuro de 32 bits de Windows . Saltador. ISBN 978-0-387-94572-9.