我想知道如何在我的 C 源文件上使用 GCC 来转储机器代码的助记符版本,以便我可以看到我的代码被编译成什么。你可以用 Java 做到这一点,但我无法找到 GCC 的方法。
我正在尝试在汇编中重写一个 C 方法,看看 GCC 是如何做到的,这将是一个很大的帮助。
如果您使用调试符号进行编译(将 -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 在重定位时显示符号名称(因此您会在下面的调用指令中看到 puts)
-R 显示动态链接重定位/符号名称(对共享库有用)
-C 对 C++ 符号名称进行解码
-w 是“宽”模式:它不换行机器代码字节
-Mintel:使用 GAS/binutils 类似 MASM 的 .intel_syntax 无前缀语法而不是 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 指令选择一个源代码行。
如果您给 GCC 标志 -fverbose-asm
,它将
在生成的汇编代码中加入额外的注释信息,使其更具可读性。 [...] 添加的注释包括:编译器版本和命令行选项的信息、与汇编指令相关的源代码行,格式为 FILENAME:LINENUMBER:CONTENT OF LINE,高级表达式对应的提示到各种汇编指令操作数。
objdump
- objdump -drwCS -Mintel
的所有开关,那么如何将 verbose
与 objdump
一起使用?这样我就可以在 asm 代码中添加注释,就像 gcc 中的 -fverbose-asm
一样?
-fverbose-asm
添加的额外内容是以输出的 asm 语法中的注释形式,而不是在 .o
文件中添加任何额外内容的指令。它在组装时全部丢弃。查看编译器 asm 输出而不是的反汇编,例如在 godbolt.org 上,您可以通过鼠标悬停和相应源/asm 行的颜色突出显示轻松地将其与源行匹配。 How to remove "noise" from GCC/clang assembly output?
使用 -S(注意:大写的 S)切换到 GCC,它会将汇编代码发送到扩展名为 .s 的文件中。例如,以下命令:gcc -O2 -S foo.c 会将生成的汇编代码留在文件 foo.s 中。
直接从 http://www.delorie.com/djgpp/v2faq/faq8_20.html 撕下(但删除了错误的 -c
)
gcc -march=native -O3 -save-temps
。您仍然可以使用 -c
停止对象文件的创建,而无需尝试链接或其他方式。
-save-temps
很有趣,因为它一次性转储了确切的代码生成代码,而使用 -S
调用编译器的另一个选项意味着编译两次,并且可能使用不同的选项。 但是 -save-temps
将所有内容都转储到当前目录中,这有点乱。看起来它更像是 GCC 的调试选项,而不是检查代码的工具。
在基于 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
godbolt 是一个非常有用的工具,它们仅列出 C++ 编译器,但您可以使用 -x c
标志使其将代码视为 C。然后它将并排为您的代码生成汇编列表,您可以使用 Colourise
选项生成彩色条以直观地指示哪些源代码映射到生成的程序集。例如下面的代码:
#include <stdio.h>
void func()
{
printf( "hello world\n" ) ;
}
使用以下命令行:
-x c -std=c99 -O3
Colourise
将生成以下内容:
https://i.stack.imgur.com/VnbGz.png
-masm=intel
,但其余的呢?
-x c
比较好
您是否尝试过 gcc -S -fverbose-asm -O source.c
然后查看生成的 source.s
汇编文件?
生成的汇编代码进入 source.s
(您可以用 -o
assembler-filename 覆盖它); -fverbose-asm
选项要求编译器发出一些汇编注释“解释”生成的汇编代码。 -O
选项要求编译器进行一些优化(它可以使用 -O2
或 -O3
进行更多优化)。
如果您想了解 gcc
在做什么,请尝试传递 -fdump-tree-all
,但要小心:您将获得数百个转储文件。
顺便说一句,GCC 可通过 plugins 或使用 MELT 进行扩展(用于扩展 GCC 的高级域特定语言;我在 2017 年放弃了)
source.s
中,因为很多人都希望在控制台上打印输出。
-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,如果他们决定反对它或什么。
您可以像 objdump 一样使用 gdb。
这段摘自 http://sources.redhat.com/gdb/current/onlinedocs/gdb_9.html#SEC64
这是一个显示 Intel x86 的混合源+程序集的示例:
(gdb) disas /m main Dump of assembler code for function main: 5 { 0x08048330 : push %ebp 0x08048331 : mov %esp,%ebp 0x08048333 : sub $0x8,%esp 0x08048336 : and $0xfffffff0,%esp 0x08048339 : sub $0x10,%esp 6 printf ("Hello.\n"); 0x0804833c : movl $0x8048440,(%esp) 0x08048343 : call 0x8048284 7 return 0; 8 } 0x08048348 : mov $0x0,%eax 0x0804834d : leave 0x0804834e : ret End of assembler dump.
set disassembly-flavor intel
命令。
使用 -S(注意:大写的 S)切换到 GCC,它会将汇编代码发送到扩展名为 .s 的文件中。例如,以下命令:
gcc -O2 -S -c foo.c
我还没有尝试过 gcc
,但如果是 g++
,下面的命令对我有用。
-g 用于调试构建
-Wa,-adhln 被传递给汇编器以便与源代码一起列出
g++ -g -Wa,-adhln src.cpp
使用 -Wa,-adhln 作为 gcc 或 g++ 上的选项以生成到标准输出的列表输出。
-Wa,... 用于汇编程序部分的命令行选项(在 C/++ 编译后在 gcc/g++ 中执行)。它在内部调用 as(在 Windows 中为 as.exe)。看
>作为--帮助
作为命令行查看 gcc 中汇编器工具的更多帮助
-Wa,-adhln -g to gcc
放弃中间目标文件。这假设组装者是气体,但情况可能并非总是如此。-Mintel
。