ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

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

P
Peter Cordes

如果您使用调试符号进行编译(将 -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 在重定位时显示符号名称(因此您会在下面的调用指令中看到 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 指令选择一个源代码行。


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

如果您给 GCC 标志 -fverbose-asm,它将

在生成的汇编代码中加入额外的注释信息,使其更具可读性。 [...] 添加的注释包括:编译器版本和命令行选项的信息、与汇编指令相关的源代码行,格式为 FILENAME:LINENUMBER:CONTENT OF LINE,高级表达式对应的提示到各种汇编指令操作数。


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

使用 -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 的调试选项,而不是检查代码的工具。
T
Toby Speight

在基于 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
A
Arnie97

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


很高兴知道 Godbolt 过滤器是如何工作的:.LC0、.text、// 和 Intel。英特尔很容易-masm=intel,但其余的呢?
Godbolt 确实支持 C(以及大量其他语言,如 Rust、D、Pascal ......)。只是C编译器少了很多,所以还是用C++编译器搭配-x c比较好
为什么源代码和程序集之间的字符串不同?换行符在最后被剥离
B
Basile Starynkevitch

您是否尝试过 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 中,因为很多人都希望在控制台上打印输出。
@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,如果他们决定反对它或什么。
a
agf

您可以像 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.

要将 GDB 的反汇编程序切换为 Intel 语法,请使用 set disassembly-flavor intel 命令。
c
codymanix

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

gcc -O2 -S -c foo.c


A
Alexey Vazhnov

我还没有尝试过 gcc,但如果是 g++,下面的命令对我有用。

-g 用于调试构建

-Wa,-adhln 被传递给汇编器以便与源代码一起列出

g++ -g -Wa,-adhln src.cpp

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

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

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

>作为--帮助

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