当前位置: 首页 > 知识库问答 >
问题:

如何从GCC/clang程序集输出中消除“噪音”?

谭奕
2023-03-14

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

当我编译以下示例时(GCC 5.3使用g-O3-std=c 14-S),似乎编译器优化了所有内容并直接返回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);
}

然而,完整的程序集输出包含的内容远不止上面的摘录,在我看来,它似乎从未被调用过。有没有办法告诉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

共有3个答案

郜彬
2023-03-14

我喜欢插入标签,我可以很容易地grep出来的对象转储输出。

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

我还没有遇到这个问题,但是asm volatile对编译器的优化器来说可能非常困难,因为它倾向于保持这些代码不变。

夏侯朝斑
2023-03-14

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

您甚至可以告诉Objment将源代码与程序集混合,从而更容易找出哪些源代码行对应于哪些指令。示例会话:

$ 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    

解释Objment标志:

  • -d反汇编所有可执行部分
曹超
2023-03-14

去掉. cfi指令、未使用的标签和注释行是一个已解决的问题:马特·戈德博尔特编译器浏览器背后的脚本在其github项目上是开源的。它甚至可以做颜色高亮,以匹配源线到ASM线(使用调试信息)。

您可以在本地对其进行设置,以便可以使用所有\include路径等(使用-I/..)为项目中的it文件提供信息。因此,您可以在不希望通过Internet发送的私有源代码上使用它。

Matt Godbolt的CppCon2017演讲“最近我的编译器为我做了什么?打开编译器的盖子”展示了如何使用它(这很自然,但如果你阅读github上的文档,它有一些简洁的特性),以及如何阅读x86 asm,为初学者介绍了x86 asm本身,并介绍了如何查看编译器输出。他接着展示了一些简洁的编译器优化(例如,除以常数),以及哪种函数为查看优化的编译器输出提供了有用的asm输出(函数args,而不是int a=123;)。

对于普通gcc/clang(非g),-fno异步展开表避免了。cfi指令。也可能有用:-fno异常-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

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

对于C而不是C,使用-xc-std=gnu11或其他东西;编译器资源管理器站点只提供g/clang,而不是gcc/clang。(或者您可以在语言下拉列表中使用C模式,但它有不同的编译器选择,这些编译器大多更有限。它会重置您的源窗格,所以在C和C之间翻转更是一种考验。)

用于制作供人使用的asm的有用编译器选项:

>

我建议使用-O3-Wall-Wextra-fverbose asm-march=haswell)查看代码。(-fverbose asm只会使源代码看起来很嘈杂,但是,当您得到的只是作为操作数名称的临时数字。)当您摆弄源代码以查看它如何更改asm时,您肯定希望启用编译器警告。当解释是你做了一些值得在源代码中警告的事情时,你不想浪费时间在asm上挠头。

要了解调用约定是如何工作的,通常需要查看调用方和被调用方,而不使用内联。

您可以使用\uuuu属性(noipa))foo\utfoo(bar\utx){…} ,或使用gcc-O3-fno内联函数编译-fno内联函数调用once-fno内联小函数来禁用内联。(但这些命令行选项不会禁用克隆函数以进行持续传播。noipa=无过程间分析。它甚至比\uuuu属性(noinline,noclone))更强大。)从编译器的角度来看,数组的引用是如何处理的,为什么不允许按值传递(而不是衰减)?举个例子。

或者,如果您只是想了解函数如何传递/接收不同类型的参数,那么可以使用不同的名称,但使用相同的原型,这样编译器就没有内联的定义。这适用于任何编译器。如果没有定义,函数对于优化器来说只是一个黑盒子,只受调用约定/ABI的控制。

-ffast数学将获得许多libm函数内联,其中一些用于单个指令(特别是SSE4可用于roundsd)。有些将只使用-fno-math-errno-ffast-math-errno的其他更安全的部分,而不使用允许编译器以不同方式进行舍入的部分。如果你有FP代码,一定要看看它与/没有-ffast数学。如果您不能安全地在常规构建中启用任何-ffast-数学,也许您会想到可以在源代码中进行安全更改,以允许在没有-ffast-数学的情况下进行相同的优化。

-O3-fno-tree-vectorize将在没有自动矢量化的情况下进行优化,因此如果您想与-O2(在gcc上不启用自动矢量化,但在clang上启用)进行比较,则无需进行完全优化。

clang默认情况下会展开循环,所以-fno-unroll-loops在复杂函数中可能很有用。您可以了解编译器做了什么,而不必费力地通过展开的循环。(gcc启用-funrol-loops-ffile-use,但不是与-O3)。(这是对人类可读代码的建议,而不是运行速度更快的代码。)

一定要启用某种级别的优化,除非您特别想知道-O0做了什么。它的“可预测的调试行为”要求编译器在每个C语句之间存储/重新加载所有内容,因此您可以使用调试器修改C变量,甚至可以在同一函数中“跳转”到不同的源代码行,并像在C源代码中那样继续执行-O0存储/重新加载的输出非常嘈杂(而且速度非常慢),这不仅是因为缺乏优化,还因为强制取消优化以支持调试。(也相关)。

要混合使用源代码和asm,请使用gcc-Wa,-adhln-c-gfoo。c | less将额外选项传递给as。(在一篇博文和另一篇博文中对此进行了更多讨论。)。请注意,此输出不是有效的汇编程序输入,因为C源代码直接存在,而不是作为汇编程序注释。所以不要称之为。s。A。如果要将其保存到文件中,lst可能有意义。

戈德博尔特的颜色高亮也有类似的目的,它非常有助于帮助您查看多个非连续的ASM指令何时来自同一源线。我根本没有使用gcc列表命令,所以在这种情况下,IDK它做得有多好,眼睛看起来有多容易。

我喜欢戈德波特的ASM窗格的高代码密度,所以我不认为我喜欢混合源代码行。至少不是为了简单的功能。也许有一个函数太复杂了,无法掌握ASM的整体结构...

请记住,当您只需要查看的时候,请省略main()和编译时常量。您希望看到处理寄存器中函数arg的代码,而不是在常量传播将其转换为返回42后的代码,或者至少优化掉一些东西。

从函数中删除静态和/或内联将为它们生成一个独立的定义,以及为任何调用方生成的定义,所以您可以只看一下。

不要把你的代码放在一个叫做main()的函数中,gcc知道main是特殊的,并假设它只会被调用一次,所以它将其标记为“冷”,并对其进行更少的优化。

你可以做的另一件事是:如果你做了一个main(),你可以运行它并使用调试器<代码>步骤isi)按指令进行步骤。有关说明,请参见x86标记wiki的底部。但请记住,在使用编译时常量args内联到main之后,代码可能会不断优化。

\uuuuu属性(noinline))可能对您不希望内联的函数有所帮助。gcc还将为知道传递常数的调用站点制作函数的恒定传播克隆,即一个特殊版本,其中一个参数作为常数。符号名称将为。克隆福。constprop_1234或asm输出中的某些内容。您也可以使用\uuuu属性(noclone))来禁用它。)。

如果你想看看编译器是如何乘以两个整数的:我把下面的代码放在戈德博尔特编译器资源管理器上,以获得错误的方法和正确的方法来测试这个值。

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

(asm和C的混合是手工制作的,将godbolt的asm输出复制粘贴到正确的位置。我发现这是一个很好的方式,可以在SO答案/编译器错误报告/电子邮件中显示短函数是如何编译的。)

 类似资料:
  • 问题内容: 如何做到这一点? 如果我想分析某些东西是如何编译的,我将如何获取发出的汇编代码? 问题答案: 使用该选项来gcc(或g ++)。 这将在helloworld.c上运行预处理器(cpp),执行初始编译,然后在运行汇编器之前停止。 默认情况下,这将输出一个文件。仍可以使用该选项设置输出文件。 当然,这只有在您拥有原始来源的情况下才有效。如果仅具有结果对象文件,则可以通过设置选项(或-d缩写

  • 问题内容: 我正在Ubuntu 12.10上运行Clang 3.4(来自http://llvm.org/apt/)。我对一些代码运行了分析器(clang –analyze),发现了两个问题: 但是具体问题并不重要。我想知道得出该结论的步骤(代码很复杂,我无法在15分钟内看到它)。 我从Clang网站上看到了一个屏幕截图,其中显示了在Web浏览器中查看的工作步骤: 这可能是从Xcode获得的。 问题

  • 最近,我开始用MSVC编译一段代码,这段代码总是用GCC和Clang编译的。这段代码产生了一个有趣的编译错误(被截断): 这段代码的最小示例: 使用Clang 11成功编译:https://godbolt.org/z/o6Ge85rxG 使用GCC 12成功编译:https://godbolt.org/z/x1rhqhfGT MSVC 19出错:https://godbolt.org/z/4jMY

  • 我最近想出了如何在汇编中写入 stdout,但现在无法从 stdin 中读取,并将我读取的内容输出回 stdout。这是我到目前为止的代码: 我很确定我看错了。我很确定和的行为没有达到预期(如果我将 替换为 次),这是导致我的问题的原因(当我输入输入并点击return时,它什么也不显示)。 我已经摸索了一段时间了,希望能得到任何帮助。(我这么做只是为了了解这不是家庭作业)。 我的问题本质上是我做错

  • 如果(0)块,我如何防止GCC消除里面的代码? 当我使用Visual Studio时,我的调试技巧之一是在程序中放入如下代码: 然后,当遇到断点时,我单击do_some_debug_printing_and_checking()行,选择“设置下一个语句”并强制执行。 当我使用gcc/gdb作为后端时,“setnext语句”不再有效,因为gcc只是从if(0)语句中删除代码。 我当然使用-O0标志来

  • 我正在研究一些相干噪声的各种实现(我知道有库,但这主要是为了我自己的启发和好奇心)以及如何使用它,我对最初的Perlin噪声有一个问题。 根据这个经常链接的数学常见问题,输出范围将介于-1和1之间,但我不明白该值是如何在该范围内的。 据我所知,算法基本上是这样的:每个网格点都有一个相关的长度为1的随机梯度向量。然后,对于每个点,对于所有四个周围的网格点,计算随机梯度和从该网格点出发的向量的点积。然

  • 我有以下C代码: 我想查看此C代码编译为什么汇编代码,因此选择了以下选项: 此选项将输出每个C语句并直接在其下输出它对应的Assembly指令。但是有些C语句不对应于任何汇编指令(例如:)。所以我想确保在阅读生成的汇编代码时我的以下假设是正确的:

  • 我有一些脚本,产生与颜色输出,我需要删除ANSI代码。 输出为(在日志文件中): 我不知道如何把ESC字符放在这里,所以我把放在它的位置。 我把剧本改成: 但是现在它给我(在日志文件中): 我怎样才能删除这个'? 也许有一种方法可以完全禁用整个脚本的着色?