如何从GCC /铿锵声组件输出中消除"噪音"?

m.s*_*.s. 56 c++ assembly gcc clang

我想检查boost::variant在我的代码中应用的程序集输出,以便查看哪些中间调用被优化掉了.

当我编译以下示例(使用GCC 5.3 g++ -O3 -std=c++14 -S)时,似乎编译器优化了所有内容并直接返回100:

(...)
main:
.LFB9320:
    .cfi_startproc
    movl    $100, %eax
    ret
    .cfi_endproc
(...)
Run Code Online (Sandbox Code Playgroud)
#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);
}
Run Code Online (Sandbox Code Playgroud)

但是,完整的程序集输出包含的内容远远超过上面的摘录,对我而言,它看起来永远不会被调用.有没有办法告诉GCC/clang删除所有"噪音"并输出程序运行时实际调用的内容?


完整装配输出:

    .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
Run Code Online (Sandbox Code Playgroud)

Pet*_*des 75

剥离.cfi指令,未使用的标签和注释行是一个已解决的问题:Matt Godbolt的编译器浏览器背后的脚本是其github项目的开源.它甚至可以进行颜色突出显示以将源行与asm行匹配​​(使用调试信息).

您可以在本地进行设置,这样您就可以使用所有#include路径(使用-I/...)来提供属于项目一部分的文件.因此,您可以将它用于您不希望通过Internet发送的私有源代码.

Matt Godbolt的CppCon2017演讲"我的编译器最近为我做了什么?Unbolting编译器的盖子" 展示了如何使用它(这是相当不言自明的,但如果你阅读GitHub上的文档有一些简洁的功能),以及如何阅读x86的汇编,用轻柔的介绍x86的ASM本身总的初学者,并查看编译器输出.他继续展示一些简洁的编译器优化(例如,用常数除法),以及哪种函数提供有用的asm输出以查看优化的编译器输出(函数args,而不是int a = 123;).


使用普通的gcc/clang(不是g ++),-fno-asynchronous-unwind-tables避免使用.cfi指令.可能也很有用:-fno-exceptions -fno-rtti -masm=intel.一定要省略-g.

将其复制/粘贴以供本地使用:

g++ -fno-asynchronous-unwind-tables -fno-exceptions -fno-rtti -fverbose-asm \
    -Wall -Wextra  foo.cpp   -O3 -masm=intel -S -o- | less
Run Code Online (Sandbox Code Playgroud)

但实际上,我建议直接使用Godbolt(在线或在本地设置)!你可以在gcc和clang的版本之间快速切换,看看旧的或新的编译器是否做了一些愚蠢的事情.(或者ICC做什么,甚至是MSVC做什么.)甚至还有ARM/ARM64 gcc 6.3,以及用于PowerPC,MIPS,AVR,MSP430的各种gcc.(看看在int比寄存器更宽的机器上发生了什么,或者不是32位.或者在RISC与x86上)会很有趣.

对于C而不是C++,使用-xc -std=gnu11或者其他东西; 编译器资源管理器站点只提供g ++/clang ++,而不是gcc/clang.


有用的编译器选项,用于为人类消费制作asm:

  • 请记住,您的代码只需编译,而不是链接:将指针传递给外部函数,这void ext(int*p)是阻止优化的好方法.你只需要一个原型,没有定义,所以编译器不能内联它或对它的作用做任何假设.

  • 我建议使用-O3 -Wall -Wextra -fverbose-asm -march=haswell)来查看代码.(-fverbose-asm但是,当你得到的所有内容都被编号为temporaries作为操作数的名称时,可以让源看起来很吵.)当你正在摆弄源代码以查看它如何更改asm时,你肯定希望启用编译器警告.你不想浪费时间在asm上搔头,因为你的解释是你在源头做了一些值得警告的事情.

  • 要查看调用约定的工作原理,通常需要查看调用者和被调用者而不进行内联.

    您可以__attribute__((noinline,noclone)) foo_t foo(bar_t x) { ... }在定义上使用,也可以编译gcc -O3 -fno-inline-functions -fno-inline-functions-called-once -fno-inline-small-functions以禁用内联.(但是这些命令行选项不会禁用克隆用于常量传播的函数.)请参阅从编译器的角度来看,如何处理数组的引用,以及为什么不允许通过值(不是衰减)进行传递?举个例子.

    或者,如果您只想查看函数如何传递/接收不同类型的args,您可以使用不同的名称但使用相同的原型,因此编译器没有内联定义.这适用于任何编译器.

  • -ffast-math将获得许多libm函数内联,一些到单个指令(特别是SSE4可用roundsd).有些内容将与仅仅-fno-math-errno或其他"更安全"的部分内联-ffast-math,而没有允许编译器以不同方式进行舍入的部分.如果您有FP代码,请务必查看/不使用-ffast-math.如果您无法安全地启用-ffast-math常规构建中的任何一个,那么您可能会想到可以在源代码中进行安全更改,以便在不进行相同优化的情况下进行相同的优化-ffast-math.

  • -O3 -fno-tree-vectorize将在没有自动矢量化的情况下进行优化,因此您无需进行比较即可获得完全优化-O2(不能在gcc上启用自动向量化,但在clang上启用自动向量化).
  • clang默认情况下展开循环,因此-funroll-loops在复杂函数中非常有用.您可以在不必浏览展开的循环的情况下了解"编译器做了什么".(GCC能够-funroll-loops-fprofile-use,但不是-O3).(这是对人类可读代码的建议,而不是对运行速度更快的代码的建议.)
  • 绝对能够优化一定程度上,除非您特别想知道什么-O0.它的"可预测的调试行为"要求使编译器在每个C语句之间存储/重新加载所有内容,因此您可以使用调试器修改C变量,甚至可以"跳转"到同一函数中的不同源代码行,并继续执行,就好像您一样在C源代码中做到了这一点. -O0存储/重载(以及如此慢)的输出是如此嘈杂,不仅仅是因为缺乏优化,而是强制去优化以支持调试.

要获得源和asm的混合,请使用gcc -Wa,-adhln -c -g foo.c | less传递额外选项as.(在博客文章另一篇博客中对此进行更多讨论.)请注意,此输出不是有效的汇编程序输入,因为C源是直接存在的,而不是汇编程序注释.所以不要称之为.s.一.lst,如果你想将它保存到一个文件可能是有意义的.

Godbolt的颜色突出显示有类似的用途,非常适合帮助您查看多个非连续的 asm指令来自同一源代码行.我根本没有使用过那个gcc列表命令,所以在这种情况下,IDK的表现如何,眼睛看起来有多容易.

我喜欢godbolt的asm窗格的高代码密度,所以我认为我不想让源代码行混合在一起.至少不是简单的函数.也许有一个太复杂的功能无法处理asm的整体结构......


请记住,当您只想查看asm时,请忽略main()编译时常量.您希望在寄存器中看到用于处理函数arg的代码,而不是在常量传播将其转换为代码之后的代码return 42,或者至少优化掉一些东西.

删除static和/或inline从函数中将为它们生成一个独立的定义,以及任何调用者的定义,因此您可以查看它.

不要将代码放在一个名为的函数中main().gcc知道这main是特殊的,并假设它只会被调用一次,因此它将其标记为"冷"并将其优化得更少.


你可以做的另一件事:如果你确实做了main(),你可以运行它并使用调试器. stepi(si)按指令逐步完成.有关说明,请参阅 标记wiki的底部.但请记住,在使用编译时常量args内联到main之后,代码可能会优化掉.

__attribute__((noinline))对于您不希望内联的函数可能有所帮助.gcc还将制作函数的常量传播克隆,即一个特殊版本,其中一个args作为常量,用于知道它们传递常数的调用点.符号名称将是.clone.foo.constprop_1234asm输出中的某个或某些内容.您也可以使用它__attribute__((noclone))来禁用它.)


例如

如果你想看看编译器如何乘以两个整数:我将以下代码放在Godbolt编译器资源管理器上以获取asm(from gcc -O3 -march=haswell -fverbose-asm)的错误方式和正确的方法来测试它.

// 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
Run Code Online (Sandbox Code Playgroud)

(的ASM和C这样的搭配是通过复制粘贴从godbolt输出的ASM手工制作到正确的地方.我觉得这是展示一个简短的函数在SO答案/编译器错误报告/邮件如何编译一个好办法.)

  • 您可以通过`-fno-asynchronous-unwind-tables`禁用CFI指令. (3认同)
  • `__attribute__((noipa))` 也可能值得一提;它包含“noinline”和“noclone”,基本上可以让您回到调用者将函数视为黑匣子的地方。它用于编译器调试,因此也非常适合实验。 (2认同)

Lea*_*ros 11

您始终可以从目标文件中查看生成的程序集,而不是使用编译器程序集输出.objdump浮现在脑海中.

您甚至可以告诉objdump使用汇编来混合源代码,从而更容易找出哪些源代码行与哪些指令相对应.示例会话:

$ 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    
Run Code Online (Sandbox Code Playgroud)

objdump标志说明:

  • -d 反汇编所有可执行部分
  • -S使用源混合汇编(-g编译时需要g++)
  • -M intel选择英特尔语法而不是丑陋的AT&T语法(可选)

  • 在反汇编`.o`文件时,我喜欢`objdump -Mintel -drw`,以显示重定位的符号名称(`-r`),而不是对多字节指令的机器代码进行换行.这有时更具可读性,但可读性更低,因为**您丢失了分支目标**上的标签,以及其他一些信息.(Agner Fog的objconv反汇编程序将为拆卸中的分支目标制作标签).这也意味着你无法从`gcc -fverbose-asm`中受益. (3认同)

Tim*_*Tim 8

我喜欢插入标签,我可以轻松地从objdump输出中grep.

int main() {
    asm volatile ("interesting_part_begin%=:":);
    do_something();
    asm volatile ("interesting_part_end%=:":);
}
Run Code Online (Sandbox Code Playgroud)

我还没有遇到过这个问题,但是asm volatile在编译器的优化器上可能会非常困难,因为它往往会使这些代码保持不变.

  • "没有输出操作数的`asm`语句,包括`asm goto`语句,是隐含的易失性." https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html#Volatile (4认同)
  • 您可以使用 asm 注释 (`asm("#start of something")`),但在这种情况下,发出甚至不组装的 asm 可能是一个好主意。它确保您不会意外地将它留在实际构建中并gimp 优化器。此外,我喜欢您使用扩展 asm 语法在语句的每个克隆上放置唯一 ID 的想法,这是帮助查找对函数的多次调用的好主意。不过,我不确定 `volatile` 是否会阻止优化器进行克隆。也许它可以被克隆,只要它仍然运行正确的次数。 (3认同)
  • 您实际上并不需要 volatile 因为 GCC 会假设它,因为 asm 语句没有输出操作数。 (2认同)

归档时间:

查看次数:

11157 次

最近记录:

6 年,2 月 前