c assembly x86 inline-assembly function-calls

¿Esta llamada a la función de ensamblaje es segura/completa?



assembly x86 (2)

Leí que son registros de propósito general, pero no pude encontrar si necesitan ser restaurados.

No soy el experto en el campo, pero a partir de mi lectura de la ABI x86-64 (Figura 3.4) los siguientes registros: %rdi , %rsi , %rdx y %rcx no se conservan entre las llamadas a funciones, por lo tanto, aparentemente no t requiere ser restaurado.

Como comentó David Wohlferd, debe tener cuidado, porque de cualquier forma, el compilador no tendrá conocimiento de la llamada a la función "personalizada" y, en consecuencia, puede interponerse en su camino, particularmente porque puede no estar al tanto de la modificación de los registros.

No tengo experiencia en ensamblaje, pero esto es en lo que he estado trabajando. Me gustaría ingresar si me falta algún aspecto fundamental para pasar los parámetros y llamar a una función mediante un puntero en el ensamblaje.

Por ejemplo, me pregunto si se supone que restauraré ecx , edx , esi , edi . Leí que son registros de propósito general, pero no pude encontrar si necesitan ser restaurados. ¿Hay algún tipo de limpieza que se supone que debo hacer después de una llamada?

Este es el código que tengo ahora, y funciona:

#include "stdio.h" void foo(int a, int b, int c, int d) { printf("values = %d and %d and %d and %d/r/n", a, b, c, d); } int main() { int a=3,b=6,c=9,d=12; __asm__( "mov %3, %%ecx;" "mov %2, %%edx;" "mov %1, %%esi;" "mov %0, %%edi;" "call %4;" : : "g"(a), "g"(b), "g"(c), "g"(d), "a"(foo) ); }


La pregunta original era Is this assembly function call safe/complete? . La respuesta a eso es no. Si bien puede parecer que funciona en este sencillo ejemplo (especialmente si las optimizaciones están desactivadas), está violando reglas que eventualmente llevarán a fallas (las que son realmente difíciles de rastrear).

Me gustaría abordar la (obvia) pregunta de seguimiento sobre cómo hacerla segura, pero sin comentarios del OP sobre el intento real, realmente no puedo hacer eso.

Por lo tanto, haré lo mejor que pueda con lo que tenemos y trataré de describir las cosas que lo hacen inseguro y algunas de las cosas que puede hacer al respecto.

Comencemos por simplificar esa asm:

__asm__( "mov %0, %%edi;" : : "g"(a) );

Incluso con esta única declaración, este código ya es inseguro. ¿Por qué? Porque estamos cambiando el valor de un registro (edi) sin dejar que el compilador sepa.

¿Cómo puede el compilador no saber que preguntas? ¡Después de todo, está ahí en el asm! La respuesta proviene de esta línea en los documentos de gcc :

GCC no analiza las instrucciones del ensamblador y no sabe lo que significan ni siquiera si son entradas de ensamblador válidas.

En ese caso, ¿cómo le haces saber a gcc lo que está pasando? La respuesta está en utilizar las restricciones (las cosas que siguen a los dos puntos) para describir el impacto del asm.

Quizás la forma más sencilla de corregir este código sería así:

__asm__( "mov %0, %%edi;" : : "g"(a) : edi );

Esto agrega edi a la lista de clobber . En resumen, esto le indica a gcc que el código va a cambiar el valor de edi, y que gcc no debe asumir ningún valor particular cuando salga el asm.

Ahora, aunque es más fácil, no es necesariamente la mejor manera. Considera este código:

__asm__( "" : : "D"(a) );

Esto usa una restricción de máquina para decirle a gcc que coloque el valor de la variable a en el registro edi por usted. Haciéndolo de esta manera, gcc cargará el registro para usted en un momento "conveniente", tal vez manteniendo siempre a edición editada.

Hay una advertencia (importante) para este código: al poner el parámetro después del 2do. Punto, estamos declarando que es una entrada. Los parámetros de entrada deben ser de solo lectura (es decir, deben tener el mismo valor al salir del asm).

En su caso, la declaración de call significa que no podremos garantizar que edi no se modifique, por lo que esto no funciona. Hay algunas formas de lidiar con esto. Lo más fácil es mover la restricción después de los primeros dos puntos, convirtiéndola en una salida, y especificar "+D" para indicar que el valor es de lectura + escritura. Pero luego los contenidos de a serán bastante indefinidos después del asm (printf podría configurarlo para cualquier cosa). Si destruir a es inaceptable, siempre hay algo como esto:

int junk; __asm__ volatile ( "" : "=D" (junk) : "0"(a) );

Esto le dice a gcc que al iniciar el asm, debe poner el valor de la variable a en el mismo lugar que la restricción de salida # 0 (es decir, edi). También dice que en la salida, edi ya no será a , contendrá la junk variable.

Editar: Debido a que la variable ''basura'' no se va a utilizar en realidad, debemos agregar el calificador volatile . Volátil estaba implícito cuando no había ningún parámetro de salida.

Otro punto en esa línea: lo terminas con un punto y coma. Esto es legal y funcionará como se espera. Sin embargo, si alguna vez desea usar la opción de línea de comando -S para ver exactamente qué código se generó (y si desea obtener buenos resultados con el asm en línea, lo hará), encontrará que produce código difícil de leer. Recomiendo usar /n/t lugar de un punto y coma.

Todo eso y todavía estamos en la primera línea ...

Obviamente, lo mismo se aplicaría a las otras dos declaraciones mov .

Lo que nos lleva a la declaración de call .

Tanto Michael como yo hemos enumerado una serie de razones por las que hacer call in inline asm es difícil.

  • Manejando todos los registros que pueden ser golpeados por la llamada de la función ABI.
  • Manejo de zona roja.
  • Manejo de alineación.
  • Clobber de memoria.

Si el objetivo aquí es ''aprender'', entonces siéntete libre de experimentar. Pero no sé si alguna vez me sentiría cómodo haciendo esto en el código de producción. Incluso cuando parece que funciona, nunca me sentí seguro de que no había algún caso extraño que me había perdido. Eso es aparte de mis preocupaciones normales sobre el uso de asm en línea en absoluto .

Lo sé, es mucha información. Probablemente más de lo que estabas buscando como introducción al comando asm de gcc, pero has escogido un lugar desafiante para comenzar.

Si aún no lo ha hecho, dedique un tiempo a revisar todos los documentos en la interfaz del lenguaje de ensamblaje de gcc. Hay mucha información buena allí junto con ejemplos para tratar de explicar cómo funciona todo.