c++ - ¿Cómo eliminar el "ruido" de la salida del conjunto GCC/clang?
assembly (3)
Me gusta insertar etiquetas que pueda 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 ese código intacto.
Quiero inspeccionar el resultado del ensamblaje de la aplicación
boost::variant
en mi código para ver qué llamadas intermedias están optimizadas.
Cuando compilo el siguiente ejemplo (con GCC 5.3 usando
g++ -O3 -std=c++14 -S
), parece que el compilador optimiza todo y devuelve directamente 100:
(...)
main:
.LFB9320:
.cfi_startproc
movl $100, %eax
ret
.cfi_endproc
(...)
#include <boost/variant.hpp>
struct Foo
{
int get() { return 100; }
};
struct Bar
{
int get() { return 999; }
};
using Variant = boost::variant<Foo, Bar>;
int run(Variant v)
{
return boost::apply_visitor([](auto& x){return x.get();}, v);
}
int main()
{
Foo f;
return run(f);
}
Sin embargo, el resultado del ensamblaje completo contiene mucho más que el extracto anterior, que para mí parece que nunca se llama. ¿Hay alguna manera de decirle a GCC / clang que elimine todo ese "ruido" y simplemente genere lo que realmente se llama cuando se ejecuta el programa?
salida de montaje completo:
.file "main1.cpp"
.section .rodata.str1.8,"aMS",@progbits,1
.align 8
.LC0:
.string "/opt/boost/include/boost/variant/detail/forced_return.hpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC1:
.string "false"
.section .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LCOLDB2:
.section .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LHOTB2:
.p2align 4,,15
.weak _ZN5boost6detail7variant13forced_returnIvEET_v
.type _ZN5boost6detail7variant13forced_returnIvEET_v, @function
_ZN5boost6detail7variant13forced_returnIvEET_v:
.LFB1197:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $_ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, %ecx
movl $49, %edx
movl $.LC0, %esi
movl $.LC1, %edi
call __assert_fail
.cfi_endproc
.LFE1197:
.size _ZN5boost6detail7variant13forced_returnIvEET_v, .-_ZN5boost6detail7variant13forced_returnIvEET_v
.section .text.unlikely._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LCOLDE2:
.section .text._ZN5boost6detail7variant13forced_returnIvEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIvEET_v,comdat
.LHOTE2:
.section .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LCOLDB3:
.section .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LHOTB3:
.p2align 4,,15
.weak _ZN5boost6detail7variant13forced_returnIiEET_v
.type _ZN5boost6detail7variant13forced_returnIiEET_v, @function
_ZN5boost6detail7variant13forced_returnIiEET_v:
.LFB9757:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $_ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, %ecx
movl $39, %edx
movl $.LC0, %esi
movl $.LC1, %edi
call __assert_fail
.cfi_endproc
.LFE9757:
.size _ZN5boost6detail7variant13forced_returnIiEET_v, .-_ZN5boost6detail7variant13forced_returnIiEET_v
.section .text.unlikely._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LCOLDE3:
.section .text._ZN5boost6detail7variant13forced_returnIiEET_v,"axG",@progbits,_ZN5boost6detail7variant13forced_returnIiEET_v,comdat
.LHOTE3:
.section .text.unlikely,"ax",@progbits
.LCOLDB4:
.text
.LHOTB4:
.p2align 4,,15
.globl _Z3runN5boost7variantI3FooJ3BarEEE
.type _Z3runN5boost7variantI3FooJ3BarEEE, @function
_Z3runN5boost7variantI3FooJ3BarEEE:
.LFB9310:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl (%rdi), %eax
cltd
xorl %edx, %eax
cmpl $19, %eax
ja .L7
jmp *.L9(,%rax,8)
.section .rodata
.align 8
.align 4
.L9:
.quad .L30
.quad .L10
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.quad .L7
.text
.p2align 4,,10
.p2align 3
.L7:
call _ZN5boost6detail7variant13forced_returnIiEET_v
.p2align 4,,10
.p2align 3
.L30:
movl $100, %eax
.L8:
addq $8, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 8
ret
.p2align 4,,10
.p2align 3
.L10:
.cfi_restore_state
movl $999, %eax
jmp .L8
.cfi_endproc
.LFE9310:
.size _Z3runN5boost7variantI3FooJ3BarEEE, .-_Z3runN5boost7variantI3FooJ3BarEEE
.section .text.unlikely
.LCOLDE4:
.text
.LHOTE4:
.globl _Z3runN5boost7variantI3FooI3BarEEE
.set _Z3runN5boost7variantI3FooI3BarEEE,_Z3runN5boost7variantI3FooJ3BarEEE
.section .text.unlikely
.LCOLDB5:
.section .text.startup,"ax",@progbits
.LHOTB5:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB9320:
.cfi_startproc
movl $100, %eax
ret
.cfi_endproc
.LFE9320:
.size main, .-main
.section .text.unlikely
.LCOLDE5:
.section .text.startup
.LHOTE5:
.section .rodata
.align 32
.type _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, @object
.size _ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__, 58
_ZZN5boost6detail7variant13forced_returnIvEET_vE19__PRETTY_FUNCTION__:
.string "T boost::detail::variant::forced_return() [with T = void]"
.align 32
.type _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, @object
.size _ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__, 57
_ZZN5boost6detail7variant13forced_returnIiEET_vE19__PRETTY_FUNCTION__:
.string "T boost::detail::variant::forced_return() [with T = int]"
.ident "GCC: (Ubuntu 5.3.0-3ubuntu1~14.04) 5.3.0 20151204"
.section .note.GNU-stack,"",@progbits
Siempre puede mirar el ensamblado generado desde el archivo de objeto, en lugar de usar la salida del ensamblador del compilador.
objdump
viene a la mente.
Incluso puede decirle a
objdump
que mezcle la fuente con el ensamblaje, lo que hace que sea más fácil averiguar qué línea 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 las banderas
objdump
:
-
-d
desmonta todas las secciones ejecutables -
-S
entremezcla el ensamblaje con la fuente (se requiere-g
al compilar cong++
) -
-M intel
elige la sintaxis de Intel sobre la fea sintaxis de AT&T ( opcional )
.cfi
directivas
.cfi
, las etiquetas no utilizadas y las 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
gcc
.
Incluso puede resaltar el color para hacer coincidir las líneas de origen con las líneas asm (utilizando la información de depuración).
Puede configurarlo localmente para poder alimentar los archivos que forman parte de su proyecto con todas las rutas
#include
, etc. (usando
-I/...
).
Y así puede usarlo en el 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?
Desatornillando la tapa del compilador "
muestra cómo usarlo
(es bastante claro, 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 mirar la salida del compilador.
Continúa mostrando algunas optimizaciones ordenadas del compilador (por ejemplo, para dividir por una constante), y qué tipo de funciones dan una salida asm útil para ver la salida optimizada del compilador (argumentos de función, no
int a = 123;
).
Con gcc / clang simple (no g ++),
-fno-asynchronous-unwind-tables
evita
.cfi
directivas
.cfi
.
Posiblemente también útil:
-fno-exceptions -fno-rtti
-masm=intel
.
Asegúrese de omitir
-g
.
Copie / pegue 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 usar Godbolt directamente (en línea o configurarlo localmente)!
Puede cambiar 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 vs.x86).
Para C en lugar de C ++, use
-xc -std=gnu11
o algo así;
el sitio del explorador del compilador solo proporciona g ++ / clang ++, no gcc / clang.
(O puede usar el modo C en el menú desplegable de lenguaje, pero tiene una selección diferente de compiladores que es en su mayoría más limitada. Y restablece su panel de origen, por lo que es más una experiencia difícil cambiar entre C y C ++).
Opciones útiles de compilación 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
puede hacer que la fuente parezca ruidosa, sin embargo, cuando todo lo que obtienes son números temporales como nombres para los operandos). Cuando estás jugando con la fuente para ver cómo cambia la asm, definitivamente quieres advertencias del compilador habilitado No querrá perder el tiempo rascándose la cabeza por encima del asm cuando la explicación es que hizo algo que merece una advertencia en la fuente. -
Para ver cómo funciona la convención de llamadas, a menudo desea mirar a la persona que llama y a la persona que llama sin incluir la línea .
Puede usar
__attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... }
en una definición, o compilar congcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions
para deshabilitar la alineación. (Pero esas opciones de línea de comandos no deshabilitan la clonación de una función para 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 decaimiento)? para un ejemplo.O si solo desea ver cómo las funciones pasan / reciben argumentos de diferentes tipos, puede usar diferentes nombres 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 de libm en línea, algunas en una sola instrucción (especialmente con SSE4 disponible pararoundsd
). Algunos se-fno-math-errno
con solo-fno-math-errno
u otras partes "más seguras" de-ffast-math
, sin las partes que permiten que el compilador se redondee de manera diferente. Si tienes código FP, definitivamente míralo con / sin-ffast-math
. Si no puede habilitar de forma segura ninguno de-ffast-math
en su construcción regular, tal vez tenga una idea para 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 auto-vectorización , 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 los bucles de forma predeterminada, por lo que
-funroll-loops
puede ser útil en funciones complejas . Puede tener una idea de "lo que hizo el compilador" sin tener que atravesar 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 ejecute más rápido). -
Definitivamente habilite algún nivel de optimización, a menos que específicamente desee saber qué hizo
-O0
. Su requisito de "comportamiento de depuración predecible" hace que el compilador almacene / vuelva a cargar todo entre cada instrucción C, por lo que puede modificar las variables C con un depurador e incluso "saltar" a una línea de origen diferente dentro de la misma función, y hacer que la ejecución continúe como si usted hizo eso en la fuente C.-O0
salida de-O0
es tan ruidosa con las tiendas / recargas (y tan lenta) no solo por falta de optimización, sino también por la des-optimización forzada para soportar la depuración .
Para obtener una mezcla de fuente y asm
, use
gcc -Wa,-adhln -c -g foo.c | less
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 el resultado de esto no es una entrada de ensamblador válida, porque la fuente C está allí directamente, no como un comentario de ensamblador.
Así que no lo llames un
.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 cuándo varias instrucciones asm no contiguas provienen de la misma línea de origen. No he usado ese comando de listado gcc en absoluto, así que IDK lo bien que lo hace, y lo fácil que es para la 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 mezclar las líneas de origen. Al menos no para funciones simples. Quizás con una función que era demasiado compleja para manejar la estructura general de lo que hace el asm ...
Y recuerde, cuando solo quiera mirar el asm, omita las
constantes
main()
y 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 convierte en el
return 42
, o al menos optimiza algunas cosas.
Eliminar las funciones
static
y / o en
inline
de las funciones producirá una definición independiente para ellas, así como una definición para las personas que llaman, por lo que puede ver eso.
No ponga su 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.
Otra cosa que puedes hacer: si hiciste un
main()
, puedes ejecutarlo y usar un depurador.
stepi
(
si
) pasos por instrucción.
Consulte la parte inferior del
wiki
de la
etiqueta
x86
para obtener instrucciones.
Pero recuerde que el código podría optimizarse después de integrarse en main con args de tiempo de compilación constante.
__attribute__((noinline))
puede ayudar, en una función que no desea que esté en línea.
gcc también creará clones de funciones de propagación constante, es decir, una versión especial con uno de los argumentos como constante, para los sitios de llamada que saben que están pasando una constante.
El nombre del símbolo será
.clone.foo.constprop_1234
o algo en la salida asm.
Puede usar
__attribute__((noclone))
para deshabilitar eso también).
Por ejemplo
Si desea ver cómo el compilador multiplica dos enteros: puse el siguiente código
en el explorador del compilador Godbolt
para obtener el asm (de
gcc -O3 -march=haswell -fverbose-asm
) por la forma incorrecta y la forma correcta de probar esta.
// 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 mezcla de asm y C fue hecha a mano copiando y pegando la salida de asm de godbolt en el lugar correcto. Creo que es una buena manera de mostrar cómo se compila una función corta en respuestas SO / informes de errores del compilador / correos electrónicos).