stringtranslate.com

Convenciones de llamadas x86

Este artículo describe las convenciones de llamada utilizadas al programar microprocesadores de arquitectura x86 .

Las convenciones de llamada describen la interfaz del código llamado:

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 se asignan los nombres de los símbolos en el código a los nombres de los símbolos utilizados por el enlazador. Las convenciones de llamada, las representaciones de tipos y la alteración de nombres forman parte de lo que se conoce como interfaz binaria de aplicación (ABI).

Existen diferencias sutiles en la forma en que los distintos compiladores implementan estas convenciones, por lo que a menudo resulta difícil interconectar código compilado por distintos compiladores. Por otro lado, las convenciones que se utilizan como estándar de API (como stdcall) se implementan de manera muy uniforme.

Antecedentes históricos

El estándar para los IBM PC compatibles 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 (salvo una convención de llamadas BIOS ) quedaron abiertos a la competencia en el mercado.

Numerosas 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, en función de diferentes requisitos, prácticas históricas y creatividad de los programadores.

Después de la reestructuración del mercado de IBM compatible, 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 GNU Compiler Collection (GCC), mantuvieron 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 este tipo de convenciones de llamada, el llamador limpia los argumentos de la pila (restablece el estado de la pila tal como estaba antes de que se llamara a la función llamada).

cdec

El cdecl(que significa declaración de C ) es una convención de llamada 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 en la pila . Si los valores de retorno son valores enteros o direcciones de memoria, el llamado los coloca en el registro EAX, mientras que los valores de punto flotante se colocan en el registro x87 ST0 . Los registros EAX, ECX y EDX son guardados por el llamador, y el resto son guardados por el llamado. Los registros de punto flotante x87 ST0 a ST7 deben estar vacíos (extraerse o liberarse) 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 C, los argumentos de función se colocan en la pila en orden de derecha a izquierda (RTL), es decir, el último argumento se coloca primero.

Considere el siguiente fragmento de código fuente en C:

int llamado ( int , int , int );   int llamador ( void ) { return llamador ( 1 , 2 , 3 ) + 5 ; }      

En x86 , podría producir el siguiente código ensamblador ( sintaxis Intel ):

llamador: ; crea un nuevo marco de llamada ; (algunos compiladores pueden producir una instrucción 'enter' en su lugar) push ebp ; guarda el antiguo marco de llamada mov ebp , esp ; inicializa el nuevo marco de llamada ; inserta los argumentos de la llamada, en sentido inverso ; (algunos compiladores pueden restar el espacio requerido del puntero de la pila, ; luego escribe cada argumento directamente, vea 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 push 3 push 2 push 1 call callee ; llama a la subrutina 'callee' add esp , 12 ; eliminar argumentos de llamada del marco agregar eax , 5 ; modificar resultado de subrutina ; (eax es el valor de retorno de nuestro llamado, ; por lo que no tenemos que moverlo a una variable local) ; restaurar marco de llamada anterior ; (algunos compiladores pueden producir una instrucción 'dejar' en su lugar) mov esp , ebp ; la mayoría de las convenciones de llamada dictan que ebp se guarde en el llamado, ; es decir, se conserva después de llamar al llamado. ; por lo tanto, todavía apunta al comienzo de nuestro marco de pila. ; debemos asegurarnos de ; que el llamado no modifique (o restaure) ebp, sin embargo, ; por lo que debemos asegurarnos de ; que use una convención de llamada que haga esto pop ebp ; restaurar marco de llamada anterior ret ; regresar                                                     

El llamador limpia la pila después de que la llamada a la función regresa.

La cdeclconvención de llamada suele ser la convención de llamada predeterminada para los compiladores C x86 , aunque muchos compiladores proporcionan opciones para cambiar automáticamente las convenciones de llamada utilizadas. Para definir manualmente una función como cdecl, algunos admiten la siguiente sintaxis:

tipo_de_retorno __cdecl nombre_función ();  

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 utilizan la convención "cdecl" y no llaman al entorno subyacente.

Algunos compiladores devuelven estructuras de datos simples con una longitud de 2 registros o menos en el par de registros EAX:EDX y las estructuras más grandes y los objetos de clase que requieren un tratamiento especial por parte del controlador de excepciones (por ejemplo, un constructor, destructor o asignación definidos) se devuelven en memoria. Para pasar "en memoria", el llamador asigna memoria y pasa un puntero a ella como primer parámetro oculto; el llamador 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 cdeclSystem V ABI para sistemas i386. [4]

llamada al sistema

Esto es similar a cdecl en el sentido de que los argumentos se insertan de derecha a izquierda. Sin embargo, EAX, ECX y EDX no se conservan, y el tamaño de la lista de parámetros en palabras dobles se pasa en AL.

Syscall es la convención de llamada estándar para la API de OS/2 de 32 bits .

enlace de opción

Los argumentos se introducen de derecha a izquierda. Los tres primeros argumentos (los que están más a la izquierda) se pasan en EAX, EDX y ECX y hasta cuatro argumentos de punto flotante se pasan en 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 IBM VisualAge utilizan Optlink .

Limpieza de calles

En estas convenciones, el receptor 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 retinstrucción x86 permite un parámetro opcional de 16 bits que especifica la cantidad de bytes de la pila que se liberarán después de regresar al receptor de la llamada. Este código se ve así:

ret 12 

Las convenciones nombradas fastcallo registerno se han estandarizado y se han implementado de manera diferente, dependiendo del proveedor del compilador. [1] Normalmente, las convenciones de llamada basadas en registros pasan uno o más argumentos en registros, lo que reduce la cantidad de accesos a la memoria necesarios para la llamada y, por lo tanto, generalmente las hace más rápidas.

Pascal

Según la convención de llamada del lenguaje Borland Turbo Pascal , los parámetros se colocan en la pila en orden de izquierda a derecha (LTR) (opuesto a cdecl), y el destinatario es responsable de eliminarlos de la pila.

La devolución del resultado funciona de la siguiente manera:

Esta convención de llamada era común en las siguientes API de 16 bits: OS/2 1.x, Microsoft Windows 3.x y Borland Delphi versión 1.x. Las versiones modernas de la API de Windows utilizan stdcall , que aún hace que el receptor de la llamada restaure la pila como en la convención de Pascal, pero los parámetros ahora se envían de derecha a izquierda.

llamada estándar

La convención de llamada stdcall [5] es una variación de la convención de llamada de Pascal en la que el receptor de la llamada es responsable de limpiar la pila, pero los parámetros se colocan 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 de retorno se almacenan en el registro EAX.

stdcall es la convención de llamada estándar para la API de Microsoft Win32 y para Open Watcom C++ .

Llamada rápida de Microsoft

La convención __fastcall de Microsoft (también conocida como __msfastcall ) pasa los dos primeros argumentos (evaluados de izquierda a derecha) que encajan en ECX y EDX. [6] Los argumentos restantes se colocan en la pila de derecha a izquierda. Cuando el compilador compila para IA64 o AMD64 , ignora la palabra clave __fastcall (o cualquier otra palabra clave de convención de llamada aparte de __vectorcall) y utiliza la convención de llamada de 64 bits predeterminada de Microsoft en su lugar.

Otros compiladores como GCC , [7] Clang , [8] e ICC [ cita requerida ] proporcionan convenciones de llamada "fastcall" similares, aunque no son necesariamente compatibles entre sí o con Microsoft Fastcall. [9]

Considere el siguiente fragmento de código C:

__attribute__ (( fastcall )) void printnums ( int num1 , int num2 , int num3 ){ printf ( "Los números que enviaste son: %d %d %d" , num1 , num2 , num3 ); }          int main (){ printnums ( 1 , 2 , 3 ); devuelve 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 tercer argumento se envía a la pila) mov edx , 0x2 ; inmediato 2 (segundo argumento) se copia al registro edx. mov ecx , 0x1 ; inmediato 1 (primer argumento) se copia al registro ecx. llamar printnums mov eax , 0 ; devolver 0 dejar retn               

Los dos primeros argumentos se pasan de izquierda a derecha y el tercer argumento se coloca en la pila. No hay limpieza de la pila, ya que la limpieza de la pila la realiza el receptor de la llamada. El desmontaje de la función receptor de la 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 push [ ebp + 0x08 ] ; arg3 se inserta en la pila. push [ ebp-0x08 ] ; arg2 se inserta push [ ebp-0x04 ] ; arg1 se inserta push 0x8065d67 ; "Los números que enviaste son %d %d %d" call printf ; limpieza de pila add esp , 0x10 nop leave retn 0x04                       

Como los dos argumentos pasaron a través de los registros y solo se introdujo un parámetro en la pila, el valor introducido se borra mediante la instrucción retn, ya que int tiene un tamaño de 4 bytes en los sistemas x86.

Llamada vectorial de Microsoft

En Visual Studio 2013, Microsoft introdujo la convención de llamada __vectorcall en respuesta a las preocupaciones de eficiencia de los desarrolladores de juegos, gráficos, video/audio y códecs. El esquema permite que los tipos de vectores más grandes ( float , double , __m128 , __m256 ) se pasen en registros en lugar de en la pila. [10]

Para el código IA-32 y x64, __vectorcall es similar a __fastcall y a las convenciones de llamada x64 originales respectivamente, pero las extiende para soportar el paso de argumentos vectoriales usando registros 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 causadas por, por ejemplo, un argumento int que aparece entre ellos. En x64, sin embargo, la regla de la convención x64 original todavía se aplica, de modo que XMM/YMM0-5 solo contienen argumentos de punto flotante, vector o HVA cuando resultan ser del primero al sexto. [11]

__vectorcall agrega soporte para pasar valores de agregados de vectores 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 los argumentos de tipo de vector, los registros no utilizados se asignan a los argumentos de HVA de izquierda a derecha. Las reglas de posicionamiento aún se aplican. Los valores de tipo de vector y HVA resultantes se devuelven utilizando los primeros cuatro registros XMM/YMM. [11]

El compilador Clang y el compilador Intel C++ también implementan vectorcall. [12] ICC tiene una convención anterior similar llamada __regcall ; [13] también es compatible con Clang. [14]

Registro Borland

Al evaluar los argumentos de izquierda a derecha, pasa tres argumentos a través de EAX, EDX, ECX. Los argumentos restantes se colocan en la pila, también de izquierda a derecha. [15] Es la convención de llamada predeterminada del compilador de 32 bits de Delphi , donde se conoce como register . Esta convención de llamada también la utiliza C++Builder de Embarcadero, donde se denomina __fastcall . [16] En este compilador, fastcall de Microsoft se puede utilizar como __msfastcall . [17]

Se puede hacer que GCC y Clang utilicen una convención de llamada similar utilizando __stdcallel regparmatributo function o el -mregparm=3conmutador (el orden de la pila se invierte). También es posible producir una variante de limpieza de llamadas utilizando cdeclo ampliar esto para utilizar también registros SSE. [18]cdecl El núcleo Linux utiliza una versión basada en en i386 desde la versión 2.6.20 (publicada en febrero de 2007). [19]

Registro de Watcom

Watcom no admite la palabra clave __fastcall, excepto para asignarle un alias de null. La convención de llamada de registro se puede seleccionar mediante un modificador de línea de comandos. (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 no se puede asignar un argumento a un registro (por ejemplo, porque es demasiado grande), este y todos los argumentos subsiguientes se asignan a la pila. Los argumentos asignados a la pila se colocan de derecha a izquierda. Los nombres se alteran agregando un guión bajo como sufijo.

Las funciones variádicas recurren a la convención de llamada basada en la pila Watcom.

El compilador C/C++ de Watcom también utiliza la directiva #pragma aux [20] que permite al usuario especificar su propia convención de llamada. Como dice su manual, "Es probable que muy pocos usuarios necesiten este método, pero si lo necesitan, puede ser un salvavidas".

Velocidad máxima, Clarion, JPI

Los primeros cuatro parámetros enteros se pasan en los registros eax, ebx, ecx y edx. Los parámetros de punto flotante se pasan en la pila de punto flotante: registros st0, st1, st2, st3, st4, st5 y st6. Los parámetros de estructura siempre se pasan en la pila. Los parámetros agregados se pasan en la pila después de que se agoten 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 llamada safecall encapsula el manejo de errores de COM ( modelo de objetos componentes ), por lo que las excepciones no se filtran al autor de la llamada, sino que se informan en el valor de retorno HRESULT , como lo requiere COM/OLE. Al llamar a una función safecall desde el código de Delphi, Delphi también verifica automáticamente el HRESULT devuelto y genera una excepción si es necesario.

La convención de llamada safecall es la misma que la convención de llamada stdcall, excepto que las excepciones se pasan de vuelta al llamador en EAX como un HResult (en lugar de en FS:[0]), mientras que el resultado de la función se pasa por referencia en la pila como si fuera un parámetro "de salida" final. Al llamar a una función de Delphi desde Delphi, esta convención de llamada aparecerá como cualquier otra convención de llamada, porque aunque las excepciones se pasan de vuelta en EAX, el llamador las convierte automáticamente en excepciones adecuadas. Al utilizar objetos COM creados en otros lenguajes, los HResults se generarán automáticamente como excepciones y el resultado de las funciones Get está en el resultado en lugar de ser un parámetro. Al crear objetos COM en Delphi con safecall, no hay necesidad de preocuparse por los HResults, ya que las excepciones se pueden generar de forma normal, pero se verán como HResults en otros lenguajes.

función nombre_función ( a : DWORD ) : DWORD ; safecall ;    

Devuelve un resultado y genera excepciones como una función Delphi normal, pero pasa valores y excepciones como si fuera:

función nombre_función ( a : DWORD ; salida Resultado : DWORD ) : HResult ; stdcall ;       

Limpieza tanto de quien llama como del destinatario de la llamada

Esta llamada

Esta convención de llamada se utiliza para llamar a funciones miembro no estáticas de C++. Existen dos versiones principales de la thiscallconvención, según el compilador y si la función utiliza o no una cantidad variable de argumentos.

Para el compilador GCC, thiscalles casi idéntico 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 thispuntero , que se inserta en la pila en último lugar, como si fuera el primer parámetro en el prototipo de la función.

En el compilador de Microsoft Visual C++, el thispuntero se pasa en ECX y es el llamador el que limpia la pila, reflejando la stdcallconvención utilizada en C para este compilador y en las funciones API de Windows. Cuando las funciones utilizan una cantidad variable de argumentos, es el llamador el que limpia la pila (cf. cdecl).

La thiscallconvención de llamada solo se puede especificar explícitamente en Microsoft Visual C++ 2005 y versiones posteriores. En cualquier otro compilador, thiscall no es una palabra clave. (Sin embargo, los desensambladores, como IDA , deben especificarla. Por lo tanto, IDA utiliza la palabra clave __thiscall para this).

Conservación de registros

Otra parte de una convención de llamada es qué registros tienen la garantía de conservar sus valores después de una llamada a una subrutina. Este comportamiento se conoce como conservación de registros.

Registros guardados por el llamante (volátiles)

De acuerdo con la ABI de Intel, a la que se ajusta la gran mayoría de compiladores, EAX, EDX y ECX deben ser libres para su uso dentro de un procedimiento o función, y no es necesario conservarlos. [ 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 colocar cada uno de estos registros en la pila, si desea restaurar sus valores después de una llamada de subrutina.

Registros guardados por el destinatario (no volátiles)

Los otros registros se utilizan para almacenar valores de larga duración (no volátiles), que deben conservarse entre llamadas.

En otras palabras, cuando el llamador realiza una llamada a un procedimiento, puede esperar que esos registros contengan el mismo valor después de que el llamado regrese.

Por lo tanto, es responsabilidad del destinatario de la llamada tanto guardarlos (insertarlos al principio) como restaurarlos (insertarlos en consecuencia) antes de devolverlos al emisor de la llamada. Como en el caso anterior, esta práctica solo debe realizarse en los registros que el destinatario de la llamada cambia.

Convenciones de llamadas x86-64

Las convenciones de llamada x86-64 aprovechan el espacio de registro adicional para pasar más argumentos en los registros. Además, se ha reducido la cantidad de convenciones de llamada incompatibles. Hay dos de uso común.

Convención de llamadas x64 de Microsoft

La convención de llamada x64 de Microsoft [21] [22] se sigue en Windows y UEFI de prearranque (para modo largo en x86-64 ). Los primeros cuatro argumentos se colocan en los registros. Eso significa RCX, RDX, R8, R9 (en ese orden) para argumentos enteros, de estructura o de puntero, y XMM0, XMM1, XMM2, XMM3 para argumentos de punto flotante. Los argumentos agregados se colocan en la pila (de derecha a izquierda). Los valores de retorno enteros (similares a x86) se devuelven en RAX si tienen 64 bits o menos. Los valores de retorno de punto flotante se devuelven en XMM0. Los parámetros de menos de 64 bits de longitud 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 provisto por el llamador como primer argumento, desplazando todos los demás argumentos a la derecha por un lugar. [23]

Al compilar para la arquitectura x64 en un contexto de Windows (ya sea usando herramientas de Microsoft o que no son de Microsoft), stdcall, thiscall, cdecl y fastcall se resuelven usando esta convención.

En la convención de llamadas de Microsoft x64, es responsabilidad del que llama asignar 32 bytes de "espacio de sombra" en la pila justo antes de llamar a la función (sin importar la cantidad real de parámetros utilizados) y vaciar la pila después de la llamada. El espacio de sombra se utiliza para volcar RCX, RDX, R8 y R9, [24] 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 el llamador). [25]

Los registros RBX, RBP, RDI, RSI, RSP, R12, R13, R14 y R15 se consideran no volátiles (guardados por el destinatario). [25]

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. Por lo tanto, cuando se ingresa a la función llamada, la pila estará compuesta (en orden ascendente) por la dirección de retorno, seguida por el espacio de sombra (32 bytes) seguido por el quinto parámetro.

En x86-64 , Visual Studio 2008 almacena números de punto flotante en XMM6 y XMM7 (así como en XMM8 a XMM15); en consecuencia, para x86-64 , las rutinas de lenguaje ensamblador escritas por el usuario deben preservar XMM6 y XMM7 (en comparación con x86 , donde las rutinas de lenguaje ensamblador escritas por el usuario no necesitaban preservar XMM6 y XMM7). En otras palabras, las rutinas de lenguaje ensamblador escritas por el usuario deben actualizarse para guardar/restaurar XMM6 y XMM7 antes/después de la función cuando se trasladan de x86 a x86-64 .

A partir de Visual Studio 2013, Microsoft introdujo la convención de llamada __vectorcall , que extiende la convención x64.

Sistema V AMD64 ABI

La convención de llamada de la ABI System V AMD64 se sigue en Solaris , Linux , FreeBSD , macOS , [26] y es el estándar de facto entre los sistemas operativos Unix y similares a Unix. El estándar de llamada OpenVMS en x86-64 se basa en la ABI System V con algunas extensiones necesarias para la compatibilidad con versiones anteriores. [27] Los primeros seis argumentos enteros o de puntero se pasan en los registros RDI, RSI, RDX, RCX, R8, R9 (R10 se usa como un puntero de cadena estático en el caso de funciones anidadas [28] : 21  ), mientras que XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 y XMM7 se usan para los primeros argumentos de punto flotante. [28] : 22  Al igual que en la convención de llamada x64 de Microsoft, los argumentos agregados se pasan en la pila. [28] : 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 punto flotante se almacenan de manera similar en XMM0 y XMM1. [28] : 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. [28] : 26, 55 

Los parámetros de estructura y unión con tamaños de dos punteros (ocho en el caso de campos SSE únicamente) o menos que están alineados en límites de 64 bits se descomponen en "ocho bytes" y cada uno se clasifica y se pasa como un parámetro separado. [28] : 24  De lo contrario, se reemplazan con un puntero cuando se usan como argumento. Los tipos de retorno de estructura y unión con tamaños de dos punteros o menos se devuelven en RAX y RDX (o XMM0 y XMM1). Cuando se necesita un retorno de estructura de gran tamaño, se antepone otro puntero a un espacio provisto por el llamador como primer argumento, desplazando todos los demás argumentos a la derecha un lugar, y el valor de este puntero se devuelve en RAX. [28] : 27 

Si el receptor de la llamada desea utilizar los registros RBX, RSP, RBP y R12–R15, debe restaurar sus valores originales antes de devolver el control al emisor de la llamada. El emisor de la llamada debe guardar todos los demás registros si desea conservar sus valores. [28] : 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á sobrescrita por ningún manejador de señal o interrupción. Por lo tanto, los compiladores pueden usar esta zona para guardar variables locales. Los compiladores pueden omitir algunas instrucciones al inicio de la función (ajuste de RSP, RBP) al usar esta zona. Sin embargo, otras funciones pueden sobrescribir esta zona. Por lo tanto, esta zona solo debe usarse para funciones de nodo hoja. gccy clangofrecer la -mno-red-zonebandera para deshabilitar las optimizaciones de la zona roja.

Si el llamado es una función variádica , entonces el número de argumentos de punto flotante pasados ​​a la función en registros vectoriales debe ser proporcionado por el llamador en el registro AL. [28] : 55 

A diferencia de la convención de llamada 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 llamada x86. [1] Estas son convenciones pensadas principalmente para compiladores C/C++ (especialmente la parte de 64 bits que se muestra a continuación) y, por lo tanto, son principalmente casos especiales. Otros lenguajes pueden utilizar otros formatos y convenciones en sus implementaciones.

Referencias

Notas al pie

  1. ^ abcde Fog, Agner (16 de febrero de 2010). Convenciones de llamada para diferentes compiladores de C++ y sistemas operativos (PDF) .
  2. ^ Pollard, Jonathan de Boyne (2010). "El gen sobre las convenciones de invocación de funciones". Respuestas dadas con frecuencia .
  3. ^ "GCC Bugzilla – Error 40838: gcc no debería asumir que la pila está alineada". 2009.
  4. ^ "Interfaz binaria de aplicación System V: Suplemento del procesador de arquitectura Intel 386" (PDF) (4.ª ed.).
  5. ^ "__stdcall (C++)". MSDN . Microsoft. Archivado desde el original el 10 de abril de 2008 . Consultado el 13 de febrero de 2019 .
  6. ^ "__fastcall". MSDN . Consultado el 26 de septiembre de 2013 .
  7. ^ Ohse, Uwe. «Descripción general de los atributos gcc: función fastcall». ohse.de. Consultado el 27 de septiembre de 2010 .
  8. ^ "Atributos en Clang: fastcall". Documentación de Clang . 2022 . Consultado el 15 de diciembre de 2022 .
  9. ^ Patocka, Mikulas (11 de agosto de 2009). "La convención de llamadas Fastcall es incompatible con Windows" . Consultado el 15 de diciembre de 2022 .
  10. ^ "Presentación de la 'Convención de llamadas vectoriales'". MSDN. 11 de julio de 2013. Consultado el 31 de diciembre de 2014 .
  11. ^ abcd "__vectorcall". MSDN . Consultado el 31 de diciembre de 2014 .
  12. ^ "Atributos en Clang: vectorcall". Documentación de Clang . 2022 . Consultado el 15 de diciembre de 2022 .
  13. ^ "Convenciones de llamada de C/C++". 16 de diciembre de 2022. Consultado el 15 de diciembre de 2022 .
  14. ^ "_vectorcall y __regcall desmitificados". software.intel.com . 7 de junio de 2017.
  15. ^ "Control de programas: Convención de registros". docwiki.embarcadero.com. 2010-06-01 . Consultado el 2010-09-27 .
  16. ^ "_llamada rápida, __llamada rápida". docwiki.embarcadero.com.
  17. ^ "__msfastcall". docwiki.embarcadero.com.
  18. ^ "Atributos de funciones x86". Uso de la colección de compiladores GNU (GCC) .
  19. ^ "i386: habilitar siempre regparm".
  20. ^ "Convenciones_de_llamada:_especificación_de_convenciones_de_llamada_al_estilo_Watcom". openwatcom.org. 2010-04-27. Archivado desde el original el 2021-03-08 . Consultado el 2018-08-31 .
  21. ^ ab "Convenciones de software x64: Convenciones de llamada". msdn.microsoft.com. 2010. Consultado el 27 de septiembre de 2010 .
  22. ^ "Arquitectura x64". msdn.microsoft.com. 6 de enero de 2023.
  23. ^ "Convención de llamada x64: valores de retorno". docs.microsoft.com . Consultado el 17 de enero de 2020 .
  24. ^ "Convenciones de software x64: asignación de pila". Microsoft . Consultado el 31 de marzo de 2010 .
  25. ^ ab "Registros guardados de llamada y destinatario". Microsoft Docs . Microsoft. 18 de mayo de 2022.
  26. ^ "Modelo de código x86-64". Biblioteca para 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 de OS X tiene un solo modelo de código para el código de espacio de usuario. Es muy similar al modelo PIC pequeño definido por la ABI x86-64 de System V.
  27. ^ "Estándar de llamadas VSI OpenVMS" (PDF) . vmssoftware.com . Mayo de 2020 . Consultado el 21 de diciembre de 2020 .
  28. ^ abcdefghij Lu, HJ; Matz, Michael; Girkar, Milind; Hubička, Jan; Jaeger, Andreas; Mitchell, Mark, eds. (23 de mayo de 2023). "Suplemento del procesador de la arquitectura AMD64 de la interfaz binaria de la aplicación System V (con modelos de programación LP64 e ILP32) versión 1.0" (PDF) . GitLab . 1.0.
  29. ^ Guía del usuario de Borland C/C++ versión 3.1 (PDF) . Borland. 1992. págs. 158, 189–191.
  30. ^ "Convenciones de llamadas de Linux de 32 bits" . Consultado el 22 de abril de 2024 .
  31. ^ "Uso de registro". Microsoft Docs . Microsoft. Archivado desde el original el 15 de septiembre de 2017 . Consultado el 15 de septiembre de 2017 .
  32. ^ "Convenciones de llamadas de Linux de 64 bits" . Consultado el 22 de abril de 2024 .

Otras fuentes

Lectura adicional