Ensamblaje en línea GCC

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 12 de octubre de 2019; las comprobaciones requieren 3 ediciones .

GCC Inline Assembly  : ensamblador en línea del compilador GCC , que es un lenguaje de descripción de macros para la interfaz del código compilado de alto nivel con inserción de ensamblaje .

Características

La sintaxis y la semántica de GCC Inline Assembly tiene las siguientes diferencias significativas:

Preliminares

Para comprender cómo funciona GCC Inline Assembly, debe tener una buena comprensión de los pasos involucrados en el proceso de compilación.

Al principio, gcc llama al preprocesador cpp, que incluye los archivos de encabezado , expande todas las directivas condicionales y realiza sustituciones de macros. Puede ver lo que sucedió después de la sustitución de macros con el comando gcc -E -o preprocessed.c some_file.c. El interruptor -E rara vez se usa, principalmente cuando está depurando macros.

Luego gcc analiza el código resultante, optimiza el código en la misma fase y eventualmente produce código ensamblador. Puede ver el código ensamblador generado con el comando gcc -S -o some_file.S some_file.c.

Luego, gcc llama al gas ensamblador para crear código objeto a partir del código ensamblador . Por lo general, el interruptor -c (solo compilar) se usa en proyectos que consisten en muchos archivos.

gcc luego llama al enlazador ld para construir el ejecutable a partir de los archivos de objeto resultantes .

Para ilustrar este proceso, creemos un archivo test.c con el siguiente contenido:

int principal () { asm ( "Bla-Bla-Bla" ); // inserta dicha instrucción return 0 ; }

Si se genera la advertencia -Wimplicit-function-declaration "Declaración de función asm implícita" durante la compilación, use:

__asm__ ( "Bla-Bla-Bla" );

Si decimos ejecutar gcc -S -o test.S test.c, entonces descubrimos un hecho importante: el compilador procesó la instrucción "incorrecta" y el archivo ensamblador resultante test.S contiene nuestra cadena "Bla-Bla-Bla". Sin embargo, si intentamos crear un código objeto o crear un archivo binario, gcc generará lo siguiente:

test.c: Mensajes del ensamblador: test.c:3: Error: no existe tal instrucción: 'Bla-Bla-Bla'

El mensaje proviene del ensamblador.

De esto se deriva una conclusión importante: GCC no interpreta el contenido de la inserción del ensamblador de ninguna manera, percibiéndolo como una sustitución de macro en tiempo de compilación.

Sintaxis

Estructura general

La estructura general del inserto del ensamblador es la siguiente:

asm [volátil] ("comandos y directivas del ensamblador": parámetros de salida: parámetros de entrada: parámetros mutables);

Sin embargo, también hay una forma más corta:

asm [volátil] ("instrucciones del ensamblador");

Sintaxis del comando

Una característica del ensamblador de gas y el compilador gcc es el hecho de que utilizan la sintaxis de AT&T , lo cual es inusual para x86 , que difiere significativamente de la sintaxis de Intel . Principales diferencias [1] :

  1. Orden de los operandos: Операция Источник,Приёмник.
  2. Los nombres de registro tienen un prefijo explícito %para indicar que se trata de un registro. Esto le permite trabajar con variables que tienen el mismo nombre que un registro, lo que no es posible en la sintaxis de Intel , que no utiliza prefijos de registro y sus nombres son palabras clave reservadas.
  3. Configuración explícita de tamaños de operandos en sufijos de instrucciones: b-byte, w-word, l-long, q-quadword. En comandos como movl %edx,%eax esto puede parecer redundante, pero es muy visual cuando se trata de incl (%esi) o xorw $0x7,mask
  4. Los nombres constantes comienzan con $ y pueden ser expresiones. Por ejemplomovl $1,%eax
  5. Un valor sin prefijo significa una dirección. Por ejemplo:
    movl $123,%eax - escribir el número 123 en %eax,
    movl 123,%eax - escribir el contenido de la celda de memoria con dirección 123
    movl var,%eax en %eax, - escribir el valor de la variable var en %eax,
    movl $var,%eax - cargar la dirección de la variable var
  6. Los paréntesis se deben utilizar para el direccionamiento indirecto. Por ejemplo movl (%ebx),%eax , cargue en %eax el valor de la variable en la dirección ubicada en el registro %ebx
  7. Dirección SIB: desplazamiento (base, índice, multiplicador)

El hecho generalmente ignorado de que dentro de la directiva asm puede haber no solo comandos de ensamblador, sino en general cualquier directiva reconocida por gas, puede servir bien. Por ejemplo, puede insertar el contenido de un archivo binario en el código objeto resultante:

asm ( "nuestro_archivo_de_datos: \n\t " ".incbin \" some_bin_file.txt \"\n\t " // usa la directiva .incbin "our_data_file_len: \n\t " ".long .-our_data_file \n\t " // inserta el valor .long con la longitud del archivo calculado );

Y luego dirija este archivo binario:

char externo nuestro_archivo_de_datos []; externo largo our_data_file_len ;

Cómo funciona la sustitución de macros

Veamos cómo ocurre la sustitución.

Diseño:

asm ( "movl %0,%%eax" :: "i" ( 1 ));

se convertirá en

movl $1 , %eax

Parámetros de entrada y salida

Modificadores

Momentos sutiles

La palabra clave volátil

La palabra clave volatile se usa para indicar al compilador que el código ensamblador insertado puede tener efectos secundarios, por lo que los intentos de optimización pueden conducir a errores lógicos.

Casos en los que la palabra clave volátil es obligatoria:

Supongamos que hay una inserción de ensamblador dentro del ciclo que verifica el empleo de una variable global y espera en el spinlock para su liberación. Cuando el compilador comienza a optimizar el ciclo, descarta todo lo que no se modificó explícitamente en el ciclo. Dado que en este caso el compilador optimizador no ve una relación explícita entre los parámetros de la inserción del ensamblador y las variables que cambian en el ciclo, la inserción del ensamblador puede ser expulsada del ciclo con todas las consecuencias resultantes.

SUGERENCIA: siempre especifique asm volatile en los casos en que su inserto de ensamblador deba "estar donde está". Esto es especialmente cierto cuando se trabaja con primitivas atómicas.

"memoria" en la lista de clobber

El siguiente "momento sutil" es la indicación explícita de "memoria" en la lista de golpes. Además de simplemente decirle al compilador que una inserción del ensamblador cambia el contenido de la memoria, también sirve como una directiva de barrera de memoria para el compilador. Esto significa que aquellas operaciones de acceso a la memoria que están más arriba en el código se ejecutarán en el código de máquina resultante antes que aquellas que están más abajo que la inserción del ensamblador. En el caso de un entorno multihilo, cuando el riesgo de una condición de carrera depende directamente de este , esta circunstancia es imprescindible.

CONSEJO #1:

Una forma rápida de hacer una barrera de la memoria

#define barrera() asm volátil ("":::"memoria")

CONSEJO #2: Especificar "memoria" en la lista de clobber no solo es una "buena práctica", sino que también es obligatorio en el caso de trabajar con operaciones atómicas diseñadas para resolver la condición de carrera.

Ejemplos de uso

int principal () { int suma = 0 , x = 1 , y = 2 ; asm ( "agregar %1, %0" : "=r" ( suma ) : "r" ( x ), "0" ( y ) ); // suma = x + y; printf ( "suma = %d, x = %d, y = %d" , suma , x , y ); // suma = 3, x = 1, y = 2 devuelve 0 ; }
  • código: agregue %1 a %0 y almacene el resultado en %0
  • parámetros de salida: registro universal guardado en la variable local después de la ejecución del código del ensamblador.
  • parámetros de entrada: registros universales inicializados a partir de variables locales x e y antes de ejecutar el código ensamblador.
  • parámetros mutables: nada más que registros de E/S.

Notas

  1. Wikilibros: Ensamblador en Linux para programadores de C. Consultado el 8 de mayo de 2022. Archivado desde el original el 26 de abril de 2022.

Enlaces

  • Documentación oficial (Uso de la colección de compiladores GNU (GCC) - 6 Extensiones a la familia del lenguaje C - 6.45 Cómo usar el lenguaje ensamblador en línea en el código C   )