¿Cómo eliminar el ruido de la salida del ensamblaje GCC/clang?

 C Programming >> Programación C >  >> Tags >> Clang
¿Cómo eliminar el ruido de la salida del ensamblaje GCC/clang?

Eliminando el .cfi directivas, etiquetas no utilizadas y líneas de comentarios es un problema resuelto:los scripts detrás del explorador del compilador de Matt Godbolt son de código abierto en su proyecto github. Incluso puede resaltar el color para hacer coincidir las líneas fuente con las líneas asm (usando la información de depuración).

Puede configurarlo localmente para que pueda alimentarlo con archivos que son parte de su proyecto con todos los #include rutas y así sucesivamente (usando -I/... ). Y puede usarlo en código fuente privado que no desea enviar a través de Internet.

Charla CppCon2017 de Matt Godbolt “¿Qué ha hecho mi compilador por mí últimamente? Desatornillar la tapa del compilador” muestra cómo usarlo (se explica por sí mismo, pero tiene algunas características interesantes si lee los documentos en github), y también cómo leer x86 asm , con una introducción suave a x86 asm para principiantes totales, y para ver la salida del compilador. Continúa mostrando algunas optimizaciones ordenadas del compilador (por ejemplo, para dividir por una constante), y qué tipo de funciones brindan una salida útil de ASM para ver la salida optimizada del compilador (argumentos de función, no int a = 123; ).

Con gcc/clang normal (no g++), -fno-asynchronous-unwind-tables evita .cfi directivas. Posiblemente también útil:-fno-exceptions -fno-rtti -masm=intel . Asegúrate de omitir -g .

Copia/pega esto para uso local :

g++ -fno-asynchronous-unwind-tables -fno-exceptions -fno-rtti -fverbose-asm \
    -Wall -Wextra  foo.cpp   -O3 -masm=intel -S -o- | less

¡Pero realmente, recomendaría simplemente usar Godbolt directamente (en línea o configurarlo localmente)! Puede alternar rápidamente entre las versiones de gcc y clang para ver si los compiladores antiguos o nuevos hacen algo tonto. (O lo que hace ICC, o incluso lo que hace MSVC). Incluso hay ARM / ARM64 gcc 6.3 y varios gcc para PowerPC, MIPS, AVR, MSP430. (Puede ser interesante ver qué sucede en una máquina donde int es más ancho que un registro, o no es de 32 bits. O en un RISC frente a x86).

Para C en lugar de C++, use -xc -std=gnu11 o algo; el sitio del explorador del compilador solo proporciona g ++ / clang ++, no gcc / clang. (O puede usar el modo C en el menú desplegable de idioma, pero eso tiene una selección diferente de compiladores que en su mayoría es más limitada. Y restablece su panel de código fuente, por lo que es más difícil alternar entre C y C ++).

Opciones de compilador útiles para hacer asm para consumo humano :

  • Recuerde, su código solo tiene que compilar, no vincular:pasar un puntero a una función externa como void ext(int*p) es una buena manera de evitar que algo se optimice . Solo necesita un prototipo para él, sin definición, por lo que el compilador no puede alinearlo ni hacer suposiciones sobre lo que hace.

  • Recomiendo usar -O3 -Wall -Wextra -fverbose-asm -march=haswell ) para mirar el código. (-fverbose-asm Sin embargo, puede hacer que la fuente parezca ruidosa cuando todo lo que obtiene son temporales numerados como nombres para los operandos). Cuando está jugando con la fuente para ver cómo cambia el asm, definitivamente desea que las advertencias del compilador estén habilitadas. No querrás perder el tiempo rascándote la cabeza cuando la explicación es que hiciste algo que merece una advertencia en la fuente.

  • Para ver cómo funciona la convención de llamadas, a menudo querrá mirar a la persona que llama y a la persona a la que llama sin incluirlos .

    Puedes usar __attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... } en una definición, o compilar con gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions para deshabilitar la inserción. (Pero esas opciones de línea de comando no deshabilitan la clonación de una función para la propagación constante). Consulte Desde la perspectiva del compilador, ¿cómo se trata la referencia para la matriz y por qué no se permite pasar por valor (no decaer)? para un ejemplo.

    O si solo desea ver cómo las funciones pasan / reciben argumentos de diferentes tipos, puede usar nombres diferentes pero el mismo prototipo para que el compilador no tenga una definición en línea. Esto funciona con cualquier compilador.

  • -ffast-math obtendrá muchas funciones libm en línea, algunas en una sola instrucción (especialmente con SSE4 disponible para roundsd ). Algunos estarán en línea con solo -fno-math-errno , u otras partes "más seguras" de -ffast-math , sin las partes que permiten al compilador redondear de manera diferente. Si tiene código FP, definitivamente mírelo con/sin -ffast-math . Si no puede habilitar de manera segura ninguno de los -ffast-math en su compilación regular, tal vez tenga una idea de un cambio seguro que puede hacer en la fuente para permitir la misma optimización sin -ffast-math .

  • -O3 -fno-tree-vectorize se optimizará sin vectorizar automáticamente , por lo que puede obtener una optimización completa sin si desea comparar con -O2 (que no habilita la autovectorización en gcc, pero sí en clang).

  • clang desenrolla bucles de forma predeterminada, por lo que -fno-unroll-loops puede ser útil en funciones complejas . Puede tener una idea de "lo que hizo el compilador" sin tener que pasar por los bucles desenrollados. (gcc habilita -funroll-loops con -fprofile-use , pero no con -O3 ). (Esta es una sugerencia para código legible por humanos, no para código que se ejecutaría más rápido).

  • Habilite definitivamente algún nivel de optimización, a menos que desee saber específicamente qué -O0 hizo . Su requisito de "comportamiento de depuración predecible" hace que el compilador almacene/recargue todo entre cada declaración de C, por lo que puede modificar las variables de C con un depurador e incluso "saltar" a una línea fuente diferente dentro de la misma función, y hacer que la ejecución continúe como si hizo eso en la fuente C. -O0 la salida es tan ruidosa con las tiendas/recargas (y tan lenta) no solo por la falta de optimización, sino también por la desoptimización forzada para admitir la depuración. (también relacionado).

Para obtener una combinación de source y asm , usa gcc -Wa,-adhln -c -g foo.c | less para pasar opciones adicionales a as . (Más discusión sobre esto en una publicación de blog y en otro blog). Tenga en cuenta que la salida de esto no es una entrada válida del ensamblador, porque la fuente C está allí directamente, no como un comentario del ensamblador. Así que no lo llames .s . Un .lst podría tener sentido si desea guardarlo en un archivo.

El resaltado de color de Godbolt tiene un propósito similar y es excelente para ayudarlo a ver cuando hay varios elementos no contiguos. Las instrucciones de asm provienen de la misma línea fuente. No he usado ese comando de listado de gcc en absoluto, así que no sé qué tan bien lo hace y qué tan fácil es verlo a simple vista, en ese caso.

Me gusta la alta densidad de código del panel asm de godbolt, así que no creo que me gustaría tener líneas fuente mezcladas. Al menos no para funciones simples. Tal vez con una función que era demasiado compleja para controlar la estructura general de lo que hace el asm...

Y recuerda, cuando solo quieras mirar el asm, omite el main() y las constantes de tiempo de compilación . Desea ver el código para tratar con una función arg en un registro, no para el código después de que la propagación constante lo convierta en return 42 , o al menos optimiza algunas cosas.

Eliminando static y/o inline from functions producirá una definición independiente para ellos, así como una definición para cualquier persona que llame, por lo que puede mirar eso.

No pongas tu código en una función llamada main() . gcc sabe que main es especial y asume que solo se llamará una vez, por lo que lo marca como "frío" y lo optimiza menos.

La otra cosa que puedes hacer:Si hiciste un main() , puede ejecutarlo y usar un depurador. stepi (si ) pasos por instrucción. Consulte la parte inferior de la wiki de etiquetas x86 para obtener instrucciones. Pero recuerde que el código podría optimizarse después de insertarlo en main con argumentos constantes en tiempo de compilación.

__attribute__((noinline)) puede ayudar, en una función que no desea que esté en línea. gcc también hará clones de funciones de propagación constante, es decir, una versión especial con uno de los argumentos como constante, para sitios de llamadas que saben que están pasando una constante. El nombre del símbolo será .clone.foo.constprop_1234 o algo en la salida de asm. Puedes usar __attribute__((noclone)) para deshabilitar eso también).

Por ejemplo

Si quieres ver cómo el compilador multiplica dos enteros:pongo el siguiente código en el explorador del compilador Godbolt para obtener el asm (de gcc -O3 -march=haswell -fverbose-asm ) para la forma incorrecta y la forma correcta de probar esto.

// the wrong way, which people often write when they're used to creating a runnable test-case with a main() and a printf
// or worse, people will actually look at the asm for such a main()
int constants() { int a = 10, b = 20; return a * b; }
    mov     eax, 200  #,
    ret                     # compiles the same as  return 200;  not interesting

// the right way: compiler doesn't know anything about the inputs
// so we get asm like what would happen when this inlines into a bigger function.
int variables(int a, int b) { return a * b; }
    mov     eax, edi  # D.2345, a
    imul    eax, esi        # D.2345, b
    ret

(Esta combinación de asm y C se elaboró ​​a mano copiando y pegando la salida de asm de godbolt en el lugar correcto. Considero que es una buena forma de mostrar cómo se compila una función corta en respuestas SO/informes de errores del compilador/correos electrónicos).


Siempre puede mirar el ensamblado generado desde el archivo de objeto, en lugar de usar la salida del ensamblado de los compiladores. objdump me viene a la mente.

Incluso puedes decirle a objdump para mezclar la fuente con el ensamblado, lo que facilita averiguar qué línea de fuente corresponde a qué instrucciones. Sesión de ejemplo:

$ cat test.cc
int foo(int arg)
{
    return arg * 42;
}

$ g++ -g -O3 -std=c++14 -c test.cc -o test.o && objdump -dS -M intel test.o

test.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <_Z3fooi>:
int foo(int arg)
{
    return arg + 1;
   0:   8d 47 01                lea    eax,[rdi+0x1]
}
   3:   c3                      ret    

Explicación de objdump banderas:

  • -d desensambla todas las secciones ejecutables
  • -S entremezcla ensamblado con código fuente (-g requerido al compilar con g++ )
  • -M intel elige la sintaxis de Intel sobre la fea sintaxis de AT&T (opcional )

Me gusta insertar etiquetas que puedo extraer fácilmente de la salida de objdump.

int main() {
    asm volatile ("interesting_part_begin%=:":);
    do_something();
    asm volatile ("interesting_part_end%=:":);
}

Todavía no he tenido ningún problema con esto, pero asm volatile puede ser muy difícil para el optimizador de un compilador porque tiende a dejar tal código intacto.