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 .
La sintaxis y la semántica de GCC Inline Assembly tiene las siguientes diferencias significativas:
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.
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");
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] :
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 ;Veamos cómo ocurre la sustitución.
Diseño:
asm ( "movl %0,%%eax" :: "i" ( 1 ));se convertirá en
movl $1 , %eaxLa 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.
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.