使用 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 命令行,即使您还使用 -O3
1),
您可以使用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
的开关,那么我如何将verbose
与objdump
一起使用?这样我就可以在 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>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 代码生成如此完全不同的程序集?