ChatGPT解决这个技术问题 Extra ChatGPT

如何从 gcc 中的 C/C++ 源代码获取汇编程序输出?

如何做到这一点?

如果我想分析某些东西是如何编译的,我将如何获得发出的汇编代码?

有关使 asm 输出易于阅读的提示,另请参阅:How to remove “noise” from GCC/clang assembly output?

P
Peter Cordes

使用 gcc(或 g++)的 -S 选项,可选地使用 -fverbose-asm,它在默认 -O0 下效果很好,将 C 名称作为注释附加到 asm 操作数。在任何优化级别上都不太好,您通常希望使用它来获得值得一看的 asm。

gcc -S helloworld.c

这将在 helloworld.c 上运行预处理器 (cpp),执行初始编译,然后在运行汇编器之前停止。有关在这种情况下使用的有用编译器选项,请参阅 How to remove "noise" from GCC/clang assembly output?(或者只是查看 Matt Godbolt's online Compiler Explorer 上的代码,它会过滤掉指令和内容,并突出显示以匹配源代码行与 asm 使用调试信息。)

默认情况下,这将输出一个文件 helloworld.s。仍然可以使用 -o 选项设置输出文件,包括将 -o - 写入标准输出以通过管道进入 less

gcc -S -o my_asm_output.s helloworld.c

当然,这仅在您拥有原始来源时才有效。如果您只有生成的目标文件,另一种方法是使用 objdump,方法是设置 --disassemble 选项(或 -d 的缩写形式)。

objdump -S --disassemble helloworld > helloworld.dump

-S 将源代码行与正常的反汇编输出交错,因此如果为目标文件启用了调试选项(在编译时为 -g)并且该文件尚未被剥离,则此选项效果最佳。

运行 file helloworld 将为您提供一些关于使用 objdump 将获得的详细程度的指示。

其他有用的 objdump 选项包括 -rwC(用于显示符号重定位、禁用长机器代码的换行以及对 C++ 名称进行分解)。如果您不喜欢 x86 的 AT&T 语法,-Mintel。请参阅the man page

例如,objdump -drwC -Mintel -S foo.o | less.
-r 对于只有 00 00 00 00 占位符用于符号引用的 .o 非常重要,而不是链接的可执行文件。


另外使用: objdump -M intel -S --disassemble helloworld > helloworld.dump 以与 Linux 上的 nasm 兼容的 intel 语法获取对象转储。
如果您有一个函数需要优化/检查,那么您可以尝试在线交互式 C++ 编译器,即 godbolt
@touchStone:GAS .intel_syntax与 NASM 兼容。它更像 MASM(例如 mov eax, symbol 是一个负载,不像在 NASM 中它是地址的 mov r32, imm32),但也不完全与 MASM 兼容。我强烈推荐它作为一种很好的阅读格式,特别是如果你喜欢用 NASM 语法编写。 objdump -drwC -Mintel | lessgcc foo.c -O1 -fverbose-asm -masm=intel -S -o- | less 很有用。 (另见How to remove “noise” from GCC/clang assembly output?)。 -masm=intel 也适用于 clang。
更好地使用gcc -O -fverbose-asm -S
a
applemonkey496

这将生成 C 代码 + 行号交织的汇编代码,以便更轻松地查看哪些行生成了哪些代码:

# create assembler code:
g++ -S -fverbose-asm -g -O2 test.cc -o test.s
# create asm interlaced with source lines:
as -alhnd test.s > test.lst

Algorithms for programmers,第 3 页(这是 PDF 的第 15 页)中找到。


遗憾的是,OS X 上的 as 不知道这些标志。但是,如果确实如此,您可能可以使用 -Wa 将选项传递给 as
g++ -g -O0 -c -fverbose-asm -Wa,-adhln test.cpp > test.lst 将是它的简写版本。
您也可以使用 gcc -c -g -Wa,-ahl=test.s test.cgcc -c -g -Wa,-a,-ad test.c > test.txt
一个 blog post 更详细地解释了这一点,包括传说和 Lu'u 发布的单命令版本。但为什么是 -O0?这充满了使跟踪值变得困难的加载/存储,并且没有告诉您优化代码的效率如何。
C
Christian Garbin

以下命令行来自 Christian Garbin's blog

g++ -g -O -Wa,-aslh horton_ex2_05.cpp >list.txt

我从 Win-XP 上的 DOS 窗口运行 G++,针对包含隐式转换的例程

c:\gpp_code>g++ -g -O -Wa,-aslh horton_ex2_05.cpp >list.txt
horton_ex2_05.cpp: In function `int main()':
horton_ex2_05.cpp:92: warning: assignment to `int' from `double'

输出是与原始 C++ 代码交叉的汇编生成代码(C++ 代码在生成的 asm 流中显示为注释)

  16:horton_ex2_05.cpp **** using std::setw;
  17:horton_ex2_05.cpp ****
  18:horton_ex2_05.cpp **** void disp_Time_Line (void);
  19:horton_ex2_05.cpp ****
  20:horton_ex2_05.cpp **** int main(void)
  21:horton_ex2_05.cpp **** {
 164                    %ebp
 165                            subl $128,%esp
?GAS LISTING C:\DOCUME~1\CRAIGM~1\LOCALS~1\Temp\ccx52rCc.s
166 0128 55                    call ___main
167 0129 89E5          .stabn 68,0,21,LM2-_main
168 012b 81EC8000      LM2:
168      0000
169 0131 E8000000      LBB2:
169      00
170                    .stabn 68,0,25,LM3-_main
171                    LM3:
172                            movl $0,-16(%ebp)

@Paladin - 不一定。 OP 是关于获得与 C/C++ 源代码等效的汇编器输出,这得到了清单,我同意这对于理解编译器和优化器正在做什么更有用。但这会导致汇编器本身出错,因为它不期望行号,并且在汇编指令的左侧编译字节。
如果您想了解 gcc 如何优化您的代码,请至少使用 -O2 或您在构建项目时实际使用的任何优化选项。 (或者,如果您使用 LTO,就像您应该的那样,那么您必须反汇编链接器输出以查看您真正得到的结果。)
@PeterCordes 有一种更简单的方法,请参阅 this question
D
Doug T.

使用 -S 开关

g++ -S main.cpp

或者也可以使用 gcc

gcc -S main.c

另见this


C
Ciro Santilli Путлер Капут 六四事

-save-temps

这在 https://stackoverflow.com/a/17083009/895245 中提到过,但让我进一步举例说明。

-S 相比,此选项的最大优势在于可以非常轻松地将其添加到任何构建脚本中,而不会过多地干扰构建本身。

当你这样做时:

gcc -save-temps -c -o main.o main.c

主程序

#define INC 1

int myfunc(int i) {
    return i + INC;
}

现在,除了正常的输出 main.o,当前工作目录还包含以下文件:

main.i 是额外的,包含预处理文件: # 1 "main.c" # 1 "" # 1 "" # 31 "" # 1 "/ usr/include/stdc-predef.h" 1 3 4 # 32 "" 2 # 1 "main.c" int myfunc(int i) { return i + 1; }

main.s 包含所需的生成程序集: .file "main.c" .text .globl myfunc .type myfunc, @function myfunc: .LFB0: .cfi_startproc pushq %rbp .cfi_def_cfa_offset 16 .cfi_offset 6, -16 movq %rsp, %rbp .cfi_def_cfa_register 6 movl %edi, -4(%rbp) movl -4(%rbp), %eax addl $1, %eax popq %rbp .cfi_def_cfa 7, 8 ret .cfi_endproc .LFE0: .size myfunc, .- myfunc .ident "GCC: (Ubuntu 8.3.0-6ubuntu1) 8.3.0" .section .note.GNU-stack,"",@progbits

如果要对大量文件执行此操作,请考虑改用:

 -save-temps=obj

它将中间文件保存到与 -o 对象输出而不是当前工作目录相同的目录中,从而避免潜在的基本名称冲突。

这个选项的另一个很酷的事情是如果您添加 -v

gcc -save-temps -c -o main.o -v main.c

它实际上显示了正在使用的显式文件,而不是 /tmp 下的丑陋临时文件,因此很容易确切地知道发生了什么,其中包括预处理/编译/组装步骤:

/usr/lib/gcc/x86_64-linux-gnu/8/cc1 -E -quiet -v -imultiarch x86_64-linux-gnu main.c -mtune=generic -march=x86-64 -fpch-preprocess -fstack-protector-strong -Wformat -Wformat-security -o main.i
/usr/lib/gcc/x86_64-linux-gnu/8/cc1 -fpreprocessed main.i -quiet -dumpbase main.c -mtune=generic -march=x86-64 -auxbase-strip main.o -version -fstack-protector-strong -Wformat -Wformat-security -o main.s
as -v --64 -o main.o main.s

在 Ubuntu 19.04 amd64、GCC 8.3.0 中测试。

CMake 预定义目标

CMake 自动为预处理文件提供一个目标:

make help

向我们展示了我们可以做到:

make main.s

并且该目标运行:

Compiling C source to assembly CMakeFiles/main.dir/main.c.s
/usr/bin/cc    -S /home/ciro/hello/main.c -o CMakeFiles/main.dir/main.c.s

因此可以在 CMakeFiles/main.dir/main.c.s 看到该文件

在 cmake 3.16.1 上测试。


D
Dark Shikari

如果您想看到的内容取决于输出的链接,那么除了前面提到的 gcc -S 之外,输出目标文件/可执行文件上的 objdump 也可能有用。这是 Loren Merritt 编写的一个非常有用的脚本,它将默认的 objdump 语法转换为更易读的 nasm 语法:

#!/usr/bin/perl -w
$ptr='(BYTE|WORD|DWORD|QWORD|XMMWORD) PTR ';
$reg='(?:[er]?(?:[abcd]x|[sd]i|[sb]p)|[abcd][hl]|r1?[0-589][dwb]?|mm[0-7]|xmm1?[0-9])';
open FH, '-|', '/usr/bin/objdump', '-w', '-M', 'intel', @ARGV or die;
$prev = "";
while(<FH>){
    if(/$ptr/o) {
        s/$ptr(\[[^\[\]]+\],$reg)/$2/o or
        s/($reg,)$ptr(\[[^\[\]]+\])/$1$3/o or
        s/$ptr/lc $1/oe;
    }
    if($prev =~ /\t(repz )?ret / and
       $_ =~ /\tnop |\txchg *ax,ax$/) {
       # drop this line
    } else {
       print $prev;
       $prev = $_;
    }
}
print $prev;
close FH;

我怀疑这也可以用于 gcc -S 的输出。


尽管如此,这个脚本是一个肮脏的黑客,它不能完全转换语法。例如 mov eax,ds:0x804b794 不是很 NASMish。此外,有时它只是去除了有用的信息:movzx eax,[edx+0x1] 让读者猜测内存操作数是 byte 还是 word
要首先在 NASM 语法中反汇编,请使用 Agner Fog's objconv。您可以使用输出文件 = /dev/stdout 将其反汇编为标准输出,因此您可以通过管道进入 less 进行查看。还有 ndisasm,但它只反汇编平面二进制文件,不知道目标文件(ELF / PE)。
M
METADATA

好吧,正如大家所说,使用 -S 选项。如果使用 -save-temps 选项,还可以获得预处理文件 (.i)、程序集文件 (.s) 和目标文件 (*.o)。 (使用 -E、-S 和 -c 获取它们中的每一个。)


D
Dan Lenski

正如每个人都指出的那样,使用 GCC 的 -S 选项。我还想补充一点,根据您是否添加优化选项(-O0 表示无,-O2 表示积极优化),结果可能会有所不同(非常大!)。

特别是在 RISC 架构上,编译器在进行优化时通常会将代码转换得几乎无法识别。看到结果令人印象深刻和迷人!


C
Chris Jefferson

如前所述,查看 -S 标志。

还值得一看 '-fdump-tree' 系列标志,特别是 '-fdump-tree-all',它可以让您看到 gcc 的一些中间形式。这些通常比汇编程序更具可读性(至少对我而言),并让您了解优化过程的执行情况。


m
mcandre

如果您正在寻找 LLVM 程序集:

llvm-gcc -emit-llvm -S hello.c

A
Antonin GAVREL

我在答案中看不到这种可能性,可能是因为这个问题来自 2008 年,但在 2018 年,您可以使用 Matt Goldbolt 的在线网站 https://godbolt.org

你也可以在本地 git clone 并运行他的项目https://github.com/mattgodbolt/compiler-explorer


P
Paige Ruten

使用 -S 选项:

gcc -S program.c

Y
Yun

这是使用 GCC 的 C 解决方案:

gcc -S program.c && gcc program.c -o output

这里的第一部分将程序的汇编输出存储在与 Program 相同的文件名中,但扩展名为 .s ,您可以将其作为任何普通文本文件打开。这里的第二部分编译您的程序以供实际使用,并为您的程序生成一个具有指定文件名的可执行文件。

上面使用的 program.c 是您的程序的名称,输出是您要生成的可执行文件的名称。


您也可以使用 gcc -O2 -save-temps foo.c 编译+组装+链接,但保存中间 .s.o 文件,而不是单独运行仅编译为 asm 的构建。 (还有一个 .i 预处理的 C 文件)。所以它的步骤更少,但会产生你不想要的文件。
A
Anonymous

来自:http://www.delorie.com/djgpp/v2faq/faq8_20.html

gcc -c -g -Wa,-a,-ad [其他 GCC 选项] foo.c > foo.lst

代替 PhirePhly 的回答或者就像大家说的那样使用 -S 。


A
Ashutosh K Singh

https://i.stack.imgur.com/KOXCa.png

以下是在 Windows 上查看/打印任何 C 程序的汇编代码的步骤

控制台/终端/命令提示符:

在代码块之类的 C 代码编辑器中编写 C 程序并以扩展名 .c 保存它 编译并运行它。运行成功后,进入安装 gcc 编译器的文件夹,输入以下命令获取 '.c' 文件的 '.s' 文件 C:\ gcc> gcc -S C 文件的完整路径 ENTER一个示例命令(在我的例子中) C:\gcc> gcc -SD:\Aa_C_Certified\alternate_letters.c 这将输出原始“.c”文件的“.s”文件

4.在此之后,键入以下命令

C;\gcc> cpp 文件名.s ENTER

示例命令(如我的情况)

C;\gcc> cpp 备用字母.s

这将打印/输出 C 程序的整个汇编语言代码。


P
Pizearke

使用“-S”作为选项。它在终端中显示程序集输出。


要在终端中显示,请使用 gcc foo.c -masm=intel -fverbose-asm -O3 -S -o- |less-S 自己创建 foo.s
Y
Yun

最近想知道a中各个函数的汇编。我是这样做的:

$ gcc main.c                      // main.c source file
$ gdb a.exe                       // gdb a.out in linux
  (gdb) disass main               // note here main is a function
                                  // similary it can be done for other functions