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


Answers:


420

使用该-S选项来gcc(或g ++)。

gcc -S helloworld.c

这将在helloworld.c上运行预处理器(cpp),执行初始编译,然后在运行汇编器之前停止。

默认情况下,这将输出一个文件helloworld.s。仍可以使用该-o选项设置输出文件。

gcc -S -o my_asm_output.s helloworld.c

当然,这只有在您拥有原始来源的情况下才有效。如果仅具有结果对象文件,则可以objdump通过设置--disassemble选项(或-d缩写形式)来使用。

objdump -S --disassemble helloworld > helloworld.dump

如果为目标文件启用了调试选项(-g在编译时)并且未剥离该文件,则此选项最有效。

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


3
尽管这是正确的,但我发现麦克唐纳(Cr McDonough)的答案的结果更为有用。
Rhys Ulerich

3
附加使用:objdump -M intel -S --disassemble helloworld> helloworld.dump以与Linux上的nasm兼容的intel语法获取对象转储。
touchStone

2
如果您有一个要优化/检查的功能,那么您可以尝试在线进行交互式C ++编译器,即Godbolt
佛罗伦萨,

1
@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有用。(另请参见如何从GCC / c装配件输出中消除“噪音”?)。 -masm=intel也适用于clang。
彼得·科德斯

3
更好地利用gcc -O -fverbose-asm -S
巴西莱Starynkevitch

172

这将生成带有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

《程序员算法》(第3页)中找到(这是PDF的第15页)。


3
(实际上位于第3页(已编号PDF)第15页)
Grumdrig

1
可悲的是,as在OS X上不知道这些标志。但是,如果这样做的话,您可以使用-Wa来将其传递给as
Grumdrig

23
g++ -g -O0 -c -fverbose-asm -Wa,-adhln test.cpp > test.lst将是这个的简写版本。
legends2k 2013年

4
您也可以使用gcc -c -g -Wa,-ahl=test.s test.cgcc -c -g -Wa,-a,-ad test.c > test.txt
phuclv 2014年

1
一篇博客文章对此进行了更详细的解释,其中包括传说和Lu'u之类的单命令版本。但是为什么-O0呢?这充满了加载/存储,这使得跟踪值变得很困难,并且没有告诉您优化代码的效率。
彼得·科德斯

51

以下命令行来自Christian Garbin的博客

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 ++源代码等效的汇编程序输出,因此获得了Listing,我同意该清单对于理解编译器和优化器正在执行的操作更为有用。但这会导致汇编程序本身发声,因为它不期望行号,并且汇编字节位于汇编指令的左边。
杰西·奇斯霍尔姆

-O2如果要查看gcc如何优化代码,请至少使用或您在构建项目时实际使用的任何优化选项。(或者,如果您像应该那样使用LTO,则必须分解链接器输出才能看到真正的结果。)
Peter Cordes,2017年


13

如果要查看的内容取决于输出的链接,则除了上述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的输出上使用。


2
尽管如此,该脚本还是一个肮脏的技巧,无法完全转换语法。例如mov eax,ds:0x804b794,不是非常NASMish。另外,有时它只是剥离有用的信息:movzx eax,[edx+0x1]让读者猜测内存操作数是byte还是word
Ruslan

首先要使用NASM语法进行反汇编,请使用Agner Fog'sobjconv。您可以使用输出文件=将其分解为stdout /dev/stdout,以便通过管道less进行查看。也有ndisasm,但它仅反汇编平面二进制文件,并且不了解目标文件(ELF / PE)。
彼得·科德斯

9

就像每个人都指出的那样,-S对GCC 使用该选项。我还想补充一点,结果可能会有所不同(疯狂!),具体取决于您是否添加了优化选项(-O0对于无优化选项,-O2对于激进式优化而言)。

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


9

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


8

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

还值得研究“ -fdump-tree”标志家族,尤其是“ -fdump-tree-all”标志,它使您可以看到gcc的某些中间形式。这些通常比汇编器更具可读性(至少对我而言),并让您了解优化过程的执行方式。


8

如果您正在寻找LLVM组装:

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


8

-save-temps

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

该选项的最大优点-S是,很容易将其添加到任何构建脚本中,而不会干扰构建本身。

当您这样做时:

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

main.c

#define INC 1

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

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

  • main.i 是奖金,其中包含预先处理的文件:

    # 1 "main.c"
    # 1 "<built-in>"
    # 1 "<command-line>"
    # 31 "<command-line>"
    # 1 "/usr/include/stdc-predef.h" 1 3 4
    # 32 "<command-line>" 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中进行了测试。




3

这些命令的输出

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

控制台/ terminal /命令提示符:

  1. 在C代码编辑器(如代码块)中编写C程序,并使用扩展名.c保存

  2. 编译并运行它。

  3. 成功运行后,转到已安装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 filename.s ENTER

命令示例(以我的情况为例)

C; \ gcc> cpp Alternative_letters.s

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


2

使用“ -S”作为选项。它在终端中显示装配体输出。


要在终端中显示,请使用gcc foo.c -masm=intel -fverbose-asm -O3 -S -o- |less-S靠自己创造foo.s
彼得·科德斯

2

最近,我想知道程序中每个功能的汇编,
这就是我的方法。

$ 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

2

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

gcc -S program.c && gcc program.c -o output
  1. 在这里,第一部分以与Program相同的文件名存储程序的程序集输出,但扩展名更改为.s,您可以将其作为任何常规文本文件打开。

  2. 这里的第二部分将编译程序以供实际使用,并使用指定的文件名为您的程序生成可执行文件。

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

顺便说一句,这是我在StackOverFlow上的第一篇文章:-}

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.