web-dev-qa-db-fra.com

Comment supprimer le «bruit» de la sortie de l'assemblage GCC / clang?

Je souhaite inspecter la sortie Assembly de l'application de boost::variant dans mon code afin de voir quels appels intermédiaires sont optimisés.

Lorsque je compile l'exemple suivant (avec GCC 5.3 en 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 <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);
}

Cependant, la sortie complète de l'assemblage contient beaucoup plus que l'extrait ci-dessus, qui, à mes yeux, ne semble jamais être appelé. Existe-t-il un moyen de dire à GCC/clang de supprimer tout ce "bruit" et de simplement afficher ce qui est réellement appelé lorsque le programme est exécuté?


sortie complète de l'assemblage:

    .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
52
m.s.

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

Vous pouvez le configurer localement afin de lui donner des fichiers qui font partie de votre projet avec tous les chemins #include Et ainsi de suite (en utilisant -I/...). Et vous pouvez donc l'utiliser sur du code source privé que vous ne voulez pas envoyer sur Internet.

Conférence 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 a quelques fonctionnalités intéressantes si vous lisez les documents sur github), et aussi comment lire x86 asm , avec une introduction en douceur à x86 asm lui-même pour les débutants, et regarder la sortie du compilateur. Il continue en montrant des optimisations de compilateur soignées (par exemple pour la division par une constante), et quel type de fonctions donnent une sortie asm utile pour regarder la sortie de compilateur optimisée (args de fonction, pas int a = 123;).


Avec gcc/clang simple (pas g ++), -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 recommanderais d'utiliser Godbolt directement (en ligne ou de le configurer localement)! Vous pouvez rapidement basculer entre les versions de gcc et de clang pour voir si les anciens ou les 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 que se produit sur une machine où int est plus large qu'un registre, ou n'est pas 32 bits. Ou sur un RISC vs x86).

Pour C au lieu de C++, utilisez -xc -std=gnu11 Ou quelque chose; le site de l'explorateur du compilateur fournit uniquement g ++/clang ++, pas gcc/clang. (Ou vous pouvez utiliser le mode C dans la liste déroulante du langage, mais cela a une sélection différente de compilateurs qui est généralement plus limitée. Et cela réinitialise votre volet source, il est donc plus difficile de basculer entre C et C++.)


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

  • Rappelez-vous, votre code n'a qu'à compiler, pas à lier: passer un pointeur vers une fonction externe comme void ext(int*p) est un bon moyen d'empêcher quelque chose de s'optimiser . Vous n'avez besoin que d'un prototype pour cela, sans définition, de sorte que le compilateur ne peut pas l'inclure ou faire des hypothèses sur ce qu'il fait.

  • Je recommanderais d'utiliser -O3 -Wall -Wextra -fverbose-asm -march=haswell) Pour consulter le code. (-fverbose-asm Peut simplement rendre la source bruyante, cependant, lorsque tout ce que vous obtenez est numéroté en tant que noms pour les opérandes.) Lorsque vous tripotez la source pour voir comment elle modifie l'asm, vous definitely souhaite que les avertissements du compilateur soient activés. Vous ne voulez pas perdre de temps à vous gratter la tête lorsque vous expliquez que vous avez fait quelque chose qui mérite un avertissement dans la source.

  • Pour voir comment fonctionne la convention d'appel, , vous voulez souvent regarder l'appelant et l'appelé sans les insérer .

    Vous pouvez utiliser __attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... } sur une définition, ou compiler 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 une propagation constante.) Voir Du point de vue du compilateur, comment la référence pour le tableau est-elle traitée, et pourquoi le passage par la valeur (pas la décroissance) n'est pas autorisé? pour un exemple.

    Ou si vous voulez simplement 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 afin que le compilateur n'ait pas de définition à aligner. Cela fonctionne avec n'importe quel compilateur.

  • -ffast-math Obtiendra de nombreuses fonctions libm en ligne, certaines en une seule instruction (en particulier avec SSE4 disponible pour roundsd). Certains s'aligneront avec juste -fno-math-errno, Ou d'autres parties "plus sûres" de -ffast-math, Sans les parties qui permettent au compilateur d'arrondir différemment. Si vous avez du code FP, regardez-le avec/sans -ffast-math. Si vous ne pouvez pas activer en toute sécurité -ffast-math Dans votre build normal, peut-être vous aurez une idée d'un changement sûr que vous pouvez faire dans 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 sans si vous voulez comparer avec -O2 (qui n'active pas l'autovectorisation sur gcc, mais le fait 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). (Il s'agit d'une suggestion de code lisible par l'homme, pas de code qui s'exécuterait plus rapidement.)
  • Activez certainement un certain niveau d'optimisation, sauf si vous voulez spécifiquement savoir ce que -O0 A fait . Son exigence de "comportement de débogage prévisible" fait que le compilateur stocke/recharge tout entre chaque instruction C, vous pouvez donc modifier les variables C avec un débogueur et même "sauter" vers une ligne source différente dans la même fonction, et faire continuer l'exécution comme si vous a fait cela dans la source C. La sortie de -O0 Est si bruyante avec les magasins/rechargements (et si lente) non seulement par manque d'optimisation, mais désoptimisation forcée pour prendre en charge le débogage .

Pour obtenir un mélange de source et d'asm , utilisez gcc -Wa,-adhln -c -g foo.c | less pour passer des options supplémentaires à as. (Plus de discussion à ce sujet dans n article de blog , et n 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'appelez pas un .s. Un .lst Peut être utile si vous souhaitez l'enregistrer dans un fichier.

Le surlignage des couleurs de Godbolt sert un objectif similaire et est excellent pour vous aider à voir lorsque plusieurs instructions non contiguës asm proviennent de la même ligne source. Je n'ai pas du tout utilisé cette commande de listage gcc, donc IDK à quel point elle fonctionne bien et à quel point il est facile à voir pour les yeux, dans ce cas.

J'aime la haute densité de code du volet asm de godbolt, donc je ne pense pas que j'aimerais avoir des lignes sources mélangées. Du moins pas pour des fonctions simples. Peut-être avec une fonction qui était trop complexe pour avoir une idée de la structure globale de ce que fait l'asm ...


Et rappelez-vous, lorsque vous souhaitez simplement regarder l'asm, omettez la main() et les constantes de compilation . Vous voulez voir le code pour traiter une fonction arg dans un registre, pas pour le code après que la propagation constante le transforme en return 42, Ou au moins optimise certaines choses.

La suppression de static et/ou inline des fonctions produira une définition autonome pour elles, ainsi qu'une définition pour tous les appelants, vous pouvez donc simplement regarder cela.

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éé une main(), vous pouvez l'exécuter et utiliser un débogueur. stepi (si) étapes par instruction. Voir le bas du x86tag wiki pour les instructions. Mais rappelez-vous que le code peut s'optimiser après s'être inséré dans main avec des arguments constants au moment de la compilation.

__attribute__((noinline)) peut aider, sur une fonction que vous ne souhaitez pas inclure. 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 passent une constante. Le nom du symbole sera .clone.foo.constprop_1234 Ou quelque chose dans la sortie asm. Vous pouvez également utiliser __attribute__((noclone)) pour désactiver cela.).


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 (de gcc -O3 -march=haswell -fverbose-asm) Pour la mauvaise et la bonne façon pour tester cela.

// 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

(Ce mélange d'asm et de C a été conçu à la main en copiant-collant la sortie asm de godbolt au bon endroit. Je trouve que c'est un bon moyen de montrer comment une fonction courte se compile dans SO réponses/rapports de bogue/e-mails du compilateur.)

69
Peter Cordes

Vous pouvez toujours regarder l'assembly généré à partir du fichier objet, au lieu d'utiliser la sortie d'assembly des compilateurs. objdump me vient à l'esprit.

Vous pouvez même dire à objdump de mélanger la source avec Assembly, 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émonte toutes les sections exécutables
  • -S mélange l'assemblage avec la source (-g requis lors de la compilation avec g++)
  • -M intel choisit la syntaxe Intel plutôt que la laide syntaxe AT&T (facultatif)
11
Leandros

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 ça, mais asm volatile peut être très difficile pour l'optimiseur d'un compilateur car il a tendance à laisser ce code intact.

8
Tim