使用 GCC 生成可读的程序集?

Posted

技术标签:

【中文标题】使用 GCC 生成可读的程序集?【英文标题】:Using GCC to produce readable assembly? 【发布时间】:2010-11-20 08:46:41 【问题描述】:

我想知道如何在我的 C 源文件中使用 GCC 来转储机器代码的助记符版本,以便我可以看到我的代码被编译成什么。你可以使用 Java 来做到这一点,但我无法找到使用 GCC 的方法。

我正在尝试在汇编中重写一个 C 方法,看看 GCC 是如何做到的,这将是一个很大的帮助。

【问题讨论】:

请注意,“字节码”通常是指 VM 使用的代码,例如 JVM 或 .NET 的 CLR。 GCC 的输出最好称为“机器代码”、“机器语言”或“汇编语言” 我使用 Godbolt 添加了一个答案,因为它是一个非常强大的工具,可以快速试验不同选项对代码生成的影响。 ***.com/a/19083877/995714 有关使 asm 输出具有人类可读性的更多提示,另请参阅:How to remove “noise” from GCC/clang assembly output? 在这里回答:***.com/questions/137038/… 使用 gcc(或 g++)的 -S 选项。 【参考方案1】:

如果您使用调试符号进行编译(将 -g 添加到您的 GCC 命令行,即使您还使用 -O31), 您可以使用objdump -S 生成与 C 源代码交错的更具可读性的反汇编代码。

>objdump --help
[...]
-S, --source             Intermix source code with disassembly
-l, --line-numbers       Include line numbers and filenames in output

objdump -drwC -Mintel 不错:

-r 在重定位时显示符号名称(因此您会在下面的 call 指令中看到 puts-R 显示动态链接重定位/符号名称(对共享库有用) -C 对 C++ 符号名称进行解码 -w 是“宽”模式:它不对机器代码字节进行换行 -Mintel:使用 GAS/binutils 类似 MASM 的 .intel_syntax noprefix 语法而不是 AT&T -S:将源代码行与反汇编交错。

您可以在~/.bashrc 中添加类似alias disas="objdump -drwCS -Mintel" 的内容。如果不在 x86 上,或者如果您喜欢 AT&T 语法,请省略 -Mintel


例子:

> gcc -g -c test.c
> objdump -d -M intel -S test.o

test.o:     file format elf32-i386


Disassembly of section .text:

00000000 <main>:
#include <stdio.h>

int main(void)

   0:   55                      push   ebp
   1:   89 e5                   mov    ebp,esp
   3:   83 e4 f0                and    esp,0xfffffff0
   6:   83 ec 10                sub    esp,0x10
    puts("test");
   9:   c7 04 24 00 00 00 00    mov    DWORD PTR [esp],0x0
  10:   e8 fc ff ff ff          call   11 <main+0x11>

    return 0;
  15:   b8 00 00 00 00          mov    eax,0x0

  1a:   c9                      leave  
  1b:   c3                      ret

请注意,这不是使用-r,因此call rel32=-4 没有使用puts 符号名称进行注释。并且看起来像一个损坏的call,它跳到 main 中的调用指令的中间。请记住,调用编码中的rel32 位移只是一个占位符,直到链接器填充一个真正的偏移量(在这种情况下是一个 PLT 存根,除非您静态链接 libc)。


脚注 1:交错源代码可能很混乱,对优化构建没有多大帮助;为此,请考虑https://godbolt.org/ 或其他可视化哪些指令与哪些源代码行相关的方法。在优化的代码中有not always a single source line that accounts for an instruction,但调试信息会为每条asm指令选择一个源代码行。

【讨论】:

是否有只获取 Intel 指令的开关? 所有这些都是英特尔指令,因为它们在英特尔处理器上运行:D。 @toto 我认为他的意思是 Intel 语法而不是 AT&T 语法 可以通过使用开关序列-Wa,-adhln -g to gcc来放弃中间目标文件。这假设装配器是气体,但情况可能并非总是如此。 @James 是的,提供-Mintel【参考方案2】:

如果你给GCC 标志-fverbose-asm,它会

在生成的汇编代码中加入额外的注释信息,使其更具可读性。

[...] 添加的 cmets 包括:

有关编译器版本和命令行选项的信息, 与汇编指令相关的源代码行,格式为 FILENAME:LINENUMBER:CONTENT OF LINE, 提示哪些高级表达式对应于各种汇编指令操作数。

【讨论】:

但是,我会丢失所有用于objdump - objdump -drwCS -Mintel 的开关,那么我如何将verboseobjdump 一起使用?这样我就可以在 asm 代码中使用 cmets,就像 gcc 中的 -fverbose-asm 一样? @Herdsman:你不能。 -fverbose-asm 添加的额外内容是输出的 asm 语法中的 cmets 形式,而不是在 .o 文件中添加任何额外内容的指令。它在组装时全部丢弃。查看编译器 asm 输出 而不是 的反汇编,例如在godbolt.org 上,您可以通过鼠标悬停和相应源/asm 行的颜色突出显示轻松地将其与源行匹配。 How to remove "noise" from GCC/clang assembly output?【参考方案3】:

使用 -S(注意:大写的 S)开关切换到 GCC,它会将汇编代码发送到扩展名为 .s 的文件中。例如以下命令:

gcc -O2 -S foo.c

将生成的汇编代码留在文件 foo.s 中。

直接从http://www.delorie.com/djgpp/v2faq/faq8_20.html 撕下(但删除了错误的-c

【讨论】:

你不应该混合使用 -c 和 -S,只能使用其中之一。在这种情况下,一个会覆盖另一个,可能取决于它们的使用顺序。 @AdamRosenfield 有关“不应该混合 -c 和 -S”的任何参考?如果属实,我们可能应该提醒作者并对其进行编辑。 @Tony: gcc.gnu.org/onlinedocs/gcc/Overall-Options.html#Overall-Options "你可以使用...一个选项 -c、-S 或 -E 来表示gcc 将在哪里停止。” 如果你想要所有的中间输出,使用gcc -march=native -O3 -save-temps。您仍然可以使用-c 停止创建对象文件,而无需尝试链接或其他任何方式。 -save-temps 很有趣,因为它一次性转储了确切的代码生成代码,而使用-S 调用编译器的另一个选项意味着编译两次,并且可能使用不同的选项。 但是 -save-temps 将所有内容都转储到当前目录中,这有点乱。看起来它更像是 GCC 的调试选项,而不是检查代码的工具。【参考方案4】:

在基于 x86 的系统上使用-S 切换到 GCC 会生成 AT&T 语法转储,默认情况下,可以使用 -masm=att 开关指定,如下所示:

gcc -S -masm=att code.c

如果您想以 Intel 语法生成转储,您可以使用 -masm=intel 开关,如下所示:

gcc -S -masm=intel code.c

(两者都将code.c 转储到它们的各种语法中,分别转储到文件code.s 中)

为了使用 objdump 产生类似的效果,您需要使用 --disassembler-options= intel/att 开关,一个示例(使用代码转储来说明语法差异):

 $ objdump -d --disassembler-options=att code.c
 080483c4 <main>:
 80483c4:   8d 4c 24 04             lea    0x4(%esp),%ecx
 80483c8:   83 e4 f0                and    $0xfffffff0,%esp
 80483cb:   ff 71 fc                pushl  -0x4(%ecx)
 80483ce:   55                      push   %ebp
 80483cf:   89 e5                   mov    %esp,%ebp
 80483d1:   51                      push   %ecx
 80483d2:   83 ec 04                sub    $0x4,%esp
 80483d5:   c7 04 24 b0 84 04 08    movl   $0x80484b0,(%esp)
 80483dc:   e8 13 ff ff ff          call   80482f4 <puts@plt>
 80483e1:   b8 00 00 00 00          mov    $0x0,%eax
 80483e6:   83 c4 04                add    $0x4,%esp 
 80483e9:   59                      pop    %ecx
 80483ea:   5d                      pop    %ebp
 80483eb:   8d 61 fc                lea    -0x4(%ecx),%esp
 80483ee:   c3                      ret
 80483ef:   90                      nop

$ objdump -d --disassembler-options=intel code.c
 080483c4 <main>:
 80483c4:   8d 4c 24 04             lea    ecx,[esp+0x4]
 80483c8:   83 e4 f0                and    esp,0xfffffff0
 80483cb:   ff 71 fc                push   DWORD PTR [ecx-0x4]
 80483ce:   55                      push   ebp
 80483cf:   89 e5                   mov    ebp,esp
 80483d1:   51                      push   ecx
 80483d2:   83 ec 04                sub    esp,0x4
 80483d5:   c7 04 24 b0 84 04 08    mov    DWORD PTR [esp],0x80484b0
 80483dc:   e8 13 ff ff ff          call   80482f4 <puts@plt>
 80483e1:   b8 00 00 00 00          mov    eax,0x0
 80483e6:   83 c4 04                add    esp,0x4
 80483e9:   59                      pop    ecx
 80483ea:   5d                      pop    ebp
 80483eb:   8d 61 fc                lea    esp,[ecx-0x4]
 80483ee:   c3                      ret    
 80483ef:   90                      nop

【讨论】:

什么...gcc -S -masm=intel test.c 不适合我,我得到了一些 Intel 和 AT&T 语法的混合体,如下所示:mov %rax, QWORD PTR -24[%rbp],而不是:movq -24(%rbp), %rax。跨度> 不错的提示。应该注意,这在执行.o 和 ASM 文件的并行输出时也有效,即通过-Wa,-ahls -o yourfile.o yourfile.cpp&gt;yourfile.asm 可以使用-M选项,它与--disassembler-options相同但更短,例如objdump -d -M intel a.out | less -N【参考方案5】:

godbolt 是一个非常有用的工具,它们仅列出了 C++ 编译器,但您可以使用 -x c 标志将代码视为 C。然后它将并排为您的代码生成汇编列表并且您可以使用Colourise 选项生成彩色条,以直观地指示哪些源代码映射到生成的程序集。比如下面的代码:

#include <stdio.h>

void func()

  printf( "hello world\n" ) ;

使用以下命令行:

-x c -std=c99 -O3

Colourise 将生成以下内容:

【讨论】:

很高兴知道 Godbolt 过滤器是如何工作的:.LC0、.text、// 和 Intel。英特尔很容易-masm=intel,但其余的呢? 我猜这里解释了***.com/a/38552509/2542702 godbolt 确实支持 C(以及大量其他语言,如 Rust、D、Pascal...)。只是C编译器少了很多,所以还是用C++编译器搭配-x c 为什么源代码和程序集的字符串不同?换行符已在末尾被剥离【参考方案6】:

您是否尝试过gcc -S -fverbose-asm -O source.c 然后查看生成的source.s 汇编程序文件?

生成的汇编代码进入source.s(你可以用-o覆盖它assembler-filename); -fverbose-asm 选项要求编译器发出一些汇编程序 cmets “解释”生成的汇编程序代码。 -O 选项要求编译器进行一些优化(它可以使用-O2-O3 进行更多优化)。

如果您想了解 gcc 在做什么,请尝试传递 -fdump-tree-all,但要小心:您将获得数百个转储文件。

顺便说一句,GCC 可以通过plugins 或MELT(用于扩展 GCC 的高级域特定语言;我在 2017 年放弃)进行扩展

【讨论】:

可能会提到输出将在source.s,因为很多人都希望在控制台上打印输出。 @ecerulm: -S -o- 转储到标准输出。如果您想使用 NASM/YASM 语法,-masm=intel 会很有帮助。 (但它使用qword ptr [mem],而不仅仅是qword,所以它更像Intel/MASM而不是NASM/YASM)。 gcc.godbolt.org 在整理转储方面做得很好:可以选择剥离仅注释行、未使用的标签和汇编程序指令。 忘了提一下:如果您正在寻找“与源代码相似但没有在每个源代码行之后存储/重新加载的噪音”,那么-Og 甚至比-O1 更好。它的意思是“为调试而优化”并使 asm 没有太多棘手/难以遵循的优化,可以完成源所说的一切。从 gcc4.8 开始就可以使用了,但是 clang 3.7 还没有。 IDK,如果他们决定反对或什么。【参考方案7】:

你可以像 objdump 一样使用 gdb。

这段摘自http://sources.redhat.com/gdb/current/onlinedocs/gdb_9.html#SEC64


这是一个显示 Intel x86 混合源+程序集的示例:

(gdb) disas /m main 函数 main 的汇编代码转储: 5 0x08048330 : 推送 %ebp 0x08048331 : 移动 %esp,%ebp 0x08048333 : 低于 $0x8,%esp 0x08048336 : 和 $0xfffffff0,%esp 0x08048339 : 低于 $0x10,%esp 6 printf ("你好。\n"); 0x0804833c : 移动 $0x8048440,(%esp) 0x08048343 : 调用 0x8048284 7 返回 0; 8 0x08048348 : 移动 $0x0,%eax 0x0804834d : 离开 0x0804834e : 回复 汇编程序转储结束。

【讨论】:

存档链接:web.archive.org/web/20090412112833/http://sourceware.org:80/gdb/… 如果要将 GDB 的反汇编程序切换为 Intel 语法,请使用 set disassembly-flavor intel 命令。【参考方案8】:

使用 -S(注意:大写的 S)开关切换到 GCC,它会将汇编代码发送到扩展名为 .s 的文件中。例如以下命令:

gcc -O2 -S -c foo.c

【讨论】:

【参考方案9】:

我还没有给gcc 开枪,但如果是g++,下面的命令对我有用。

-g 用于调试构建 -Wa,-adhln 被传递给汇编器以列出源代码
g++ -g -Wa,-adhln src.cpp

【讨论】:

它也适用于 gcc! -Wa,... 用于汇编程序部分的命令行选项(在 C/++ 编译后在 gcc/g++ 中执行)。它在内部调用 as(在 Windows 中为 as.exe)。请参阅 >as --help 作为命令行以查看更多帮助【参考方案10】:

使用 -Wa,-adhln 作为 gcc 或 g++ 的选项,以生成到标准输出的列表输出。

-Wa,... 用于汇编程序部分的命令行选项(在 C/++ 编译后在 gcc/g++ 中执行)。它在内部调用 as(Windows 中的 as.exe)。 见

>作为 --help

作为命令行查看gcc内部汇编工具的更多帮助

【讨论】:

以上是关于使用 GCC 生成可读的程序集?的主要内容,如果未能解决你的问题,请参考以下文章

GCC 生成的程序集 - C 函数调用时的段错误

为啥 GCC 会为几乎相同的 C 代码生成如此完全不同的程序集?

使用指定初始化程序时的不同 gcc 程序集

C 内在函数、SSE2 点积和 gcc -O3 生成的程序集

一组可读的常数参数 C

有没有办法以可读的形式从 .NET 程序集中检索编译器生成的代码?