Comment supprimer le «bruit» de la sortie d’assemblage GCC / Clang?

Je veux inspecter la sortie d’assemblage de l’application boost::variant dans mon code afin de voir quels appels intermédiaires sont optimisés.

Lorsque je comstack l’exemple suivant (avec GCC 5.3 utilisant g++ -O3 -std=c++14 -S ), il semble que le compilateur optimise tout et renvoie directement 100:

 (...) main: .LFB9320: .cfi_startproc movl $100, %eax ret .cfi_endproc (...) 

 #include  struct Foo { int get() { return 100; } }; struct Bar { int get() { return 999; } }; using Variant = boost::variant; int run(Variant v) { return boost::apply_visitor([](auto& x){return x.get();}, v); } int main() { Foo f; return run(f); } 

Cependant, la sortie de l’assemblage complet contient beaucoup plus que l’extrait ci-dessus, ce qui pour moi semble ne jamais être appelé. Existe-t-il un moyen de dire à GCC / clang de supprimer tout ce “bruit” et de ne sortir que ce qui est réellement appelé lorsque le programme est exécuté?


sortie d’assemblage complet:

  .file "main1.cpp" .section .rodata.str1.8,"aMS",@progbits,1 .align 8 .LC0: .ssortingng "/opt/boost/include/boost/variant/detail/forced_return.hpp" .section .rodata.str1.1,"aMS",@progbits,1 .LC1: .ssortingng "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__: .ssortingng "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__: .ssortingng "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 

.cfi directives .cfi , les étiquettes inutilisées et les lignes de commentaires est un problème résolu: les scripts derrière l’explorateur du compilateur de Matt Godbolt sont open source sur son projet github . Il peut même faire la mise en évidence des couleurs pour faire correspondre les lignes source aux lignes asm (en utilisant les informations de débogage).

Vous pouvez le configurer localement pour pouvoir alimenter les fichiers faisant partie de votre projet avec tous les chemins d’access #include , etc. (en utilisant -I/... ). Et vous pouvez donc l’utiliser sur du code source privé que vous ne souhaitez pas envoyer sur Internet.

CppCon2017 de Matt Godbolt: «Qu’est-ce que mon compilateur a fait pour moi récemment? Déboulonner le couvercle du compilateur » montre comment l’utiliser (c’est assez explicite, mais si vous lisez les docs sur github), et aussi comment lire x86 asm , avec une introduction en douceur au format x86 pour les débutants, et à regarder la sortie du compilateur. Il continue en montrant quelques optimisations du compilateur (par exemple pour la division par une constante), et quel genre de fonctions donne une sortie asm utile pour examiner la sortie optimisée du compilateur (args de fonctions, pas int a = 123; ).


Avec gcc / clang simple (pas g ++), -fno-asynchronous-unwind-tables .cfi -fno-asynchronous-unwind-tables évite les directives .cfi . Peut-être aussi utile: -fno-exceptions -fno-rtti -masm=intel . Assurez-vous d’omettre -g .

Copiez / collez ceci pour une utilisation locale :

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

Mais vraiment, je vous recommande d’utiliser directement Godbolt (en ligne ou de le configurer localement)! Vous pouvez rapidement basculer entre les versions de gcc et clang pour voir si les anciens ou nouveaux compilateurs font quelque chose de stupide. (Ou ce que fait ICC, ou même ce que fait MSVC.) Il y a même ARM / ARM64 gcc 6.3, et divers gcc pour PowerPC, MIPS, AVR, MSP430. (Il peut être intéressant de voir ce qui se passe sur une machine où int est plus large qu’un registre, ou 32 bits. Ou sur un RISC ou un x86).

Pour C au lieu de C ++, utilisez -xc -std=gnu11 ou quelque chose; le site explorateur du compilateur ne fournit que g ++ / clang ++, pas gcc / clang.


Options de compilateur utiles pour créer des asm pour la consommation humaine :

  • Rappelez-vous que votre code doit seulement comstackr, pas lier: passer un pointeur à une fonction externe comme void ext(int*p) est un bon moyen d’empêcher quelque chose d’optimiser . Vous n’avez besoin que d’un prototype, sans définition, afin que le compilateur ne puisse pas l’intégrer ou faire des hypothèses sur ce qu’il fait.

  • Je recommande d’utiliser -O3 -Wall -Wextra -fverbose-asm -march=haswell ) pour regarder le code. ( -fverbose-asm peut juste rendre la source plus bruyante quand tout ce que vous obtenez est un nom temporaire pour les opérandes.) Lorsque vous jouez avec la source pour voir comment elle modifie l’ASM, vous voudrez certainement activée. Vous ne voulez pas perdre votre temps à vous demander si l’explication est que vous avez fait quelque chose qui mérite un avertissement dans la source.

  • Pour voir comment fonctionne la convention d’appel, vous devez souvent regarder l’appelant et l’appelé sans l’inclure .

    Vous pouvez utiliser __atsortingbute__((noinline,noclone)) foo_t foo(bar_t x) { ... } sur une définition, ou comstackr avec gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions pour désactiver l’inline. (Mais ces options de ligne de commande ne désactivent pas le clonage d’une fonction pour la propagation constante.) Voir Du sharepoint vue du compilateur, comment la référence au tableau est-elle traitée? Pourquoi ne pas passer par la valeur? à titre d’exemple.

    Ou si vous voulez juste voir comment les fonctions passent / reçoivent des arguments de différents types, vous pouvez utiliser des noms différents mais le même prototype pour que le compilateur n’ait pas de définition à incorporer. Cela fonctionne avec n’importe quel compilateur.

  • -ffast-math va mettre en ligne de nombreuses fonctions de libm, certaines à une seule instruction (en particulier avec SSE4 disponible pour roundsd ). Certains vont simplement avec -fno-math-errno , ou d’autres parties “plus sûres” de -ffast-math , sans les parties qui permettent au compilateur de contourner différemment. Si vous avez un code FP, regardez-le avec / sans -ffast-math . Si vous ne pouvez pas activer en toute sécurité -ffast-math dans votre construction standard, vous aurez peut-être une idée de changement sûr que vous pouvez apporter à la source pour permettre la même optimisation sans -ffast-math .

  • -O3 -fno-tree-vectorize optimisera sans vectorisation automatique , vous pouvez donc obtenir une optimisation complète si vous voulez comparer avec -O2 (ce qui n’autorise pas l’autovectorisation sur gcc, mais sur clang).
  • clang déroule les boucles par défaut, donc -funroll-loops peut être utile dans les fonctions complexes . Vous pouvez avoir une idée de “ce que le compilateur a fait” sans avoir à parcourir les boucles déroulées. (gcc active -funroll-loops avec -fprofile-use , mais pas avec -O3 ). (Ceci est une suggestion pour un code lisible par l’homme, pas pour un code qui s’exécuterait plus rapidement.)
  • Activez définitivement un certain niveau d’optimisation, à moins que vous souhaitiez spécifiquement savoir ce -O0 fait -O0 . Son exigence de «comportement de débogage prévisible» oblige le compilateur à stocker / recharger tout ce qui se trouve entre chaque instruction C, vous pouvez donc modifier les variables C avec un débogueur et même passer à une autre ligne source dans la même fonction. a fait cela dans la source C. -O0 sortie -O0 est tellement bruyante avec les magasins / rechargements (et donc si lente) que non seulement le manque d’optimisation, mais la dé-optimisation forcée pour supporter le débogage .

Pour obtenir un mélange de source et asm , utilisez gcc -Wa,-adhln -c -g foo.c | less gcc -Wa,-adhln -c -g foo.c | less de passer des options supplémentaires à as . (Plus de discussion à ce sujet dans un article de blog et un autre blog .). Notez que la sortie de ceci n’est pas une entrée d’assembleur valide, car la source C est là directement, pas en tant que commentaire d’assembleur. Alors ne l’appelle pas un. Un .lst peut être .lst si vous souhaitez l’enregistrer dans un fichier.

La mise en surbrillance des couleurs de Godbolt a un objective similaire et est très utile pour vous aider à voir quand plusieurs instructions asm non contiguës proviennent de la même ligne source. Je n’ai pas utilisé cette commande de listage gcc, donc IDK, comment elle fonctionne, et à quel point il est facile à voir, dans ce cas.

J’aime la densité de code élevée du panneau asm de godbolt, donc je ne pense pas que je voudrais avoir des lignes sources mixtes. Du moins pas pour des fonctions simples. Peut-être avec une fonction trop complexe pour avoir une idée de la structure globale de l’asm …


Et rappelez-vous, lorsque vous voulez simplement regarder l’asm, ne renseignez pas les constantes main() et à la compilation . Vous voulez voir le code pour traiter une fonction arg dans un registre, pas pour le code après la propagation constante le return 42 , ou au moins optimise certaines choses.

Supprimer des fonctions static et / ou en inline des fonctions produira une définition autonome pour elles, ainsi qu’une définition pour tous les appelants.

Ne mettez pas votre code dans une fonction appelée main() . gcc sait que main est spécial et suppose qu’il ne sera appelé qu’une seule fois, donc il le marque comme “froid” et l’optimise moins.


L’autre chose que vous pouvez faire: si vous avez créé un main() , vous pouvez l’exécuter et utiliser un débogueur. stepi ( si ) étapes par instruction. Voir le bas du wiki de balise x86 pour les instructions. Mais rappelez-vous que le code peut être optimisé après avoir été inséré dans main avec des arguments constants à la compilation.

__atsortingbute__((noinline)) peut aider, sur une fonction que vous ne souhaitez pas incorporer. gcc créera également des clones de fonctions à propagation constante, c’est-à-dire une version spéciale avec l’un des arguments comme constante, pour les sites d’appel qui savent qu’ils transmettent une constante. Le nom du symbole sera .clone.foo.constprop_1234 ou quelque chose dans la sortie asm. Vous pouvez également utiliser __atsortingbute__((noclone)) pour le désactiver.)


Par exemple

Si vous voulez voir comment le compilateur multiplie deux entiers: je mets le code suivant sur l’explorateur du compilateur Godbolt pour obtenir l’asm (à partir de gcc -O3 -march=haswell -fverbose-asm ) pour la bonne façon de tester ce.

 // 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 # comstacks the same as return 200; not interesting // the right way: comstackr 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 

(Ce mélange d’asm et de c a été conçu à la main en collant la sortie asm de godbolt au bon endroit. Je trouve que c’est un bon moyen de montrer comment une fonction courte comstack dans des réponses SO / rapports de bogues / emails.)

Vous pouvez toujours regarder l’assembly généré à partir du fichier object, au lieu d’utiliser la sortie de l’assembly de compilateurs. objdump à objdump .

Vous pouvez même indiquer objdump à la source de mélange avec l’assemblage, ce qui permet de déterminer plus facilement quelle ligne source correspond à quelles instructions. Exemple de session:

 $ 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 

Explication des drapeaux objdump :

  • -d désassemble toutes les sections exécutables
  • -S assemblage de mélanges avec source ( -g requirejs lors de la compilation avec g++ )
  • -M intel choisit la syntaxe intel sur la syntaxe AT & T laide ( optionnel )

J’aime insérer des étiquettes que je peux facilement extraire de la sortie objdump.

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

Je n’ai pas encore eu de problème avec cela, mais asm volatile peut être très difficile pour l’optimiseur du compilateur car il a tendance à laisser ce code intact.