【问题标题】:Decoding equivalent assembly code of C code解码C代码的等效汇编代码
【发布时间】:2010-06-07 10:08:38
【问题描述】:

想要查看编译器的输出(在汇编中)对于一些 C 代码,我用 C 编写了一个简单的程序并使用 gcc 生成了它的汇编文件。

代码是这样的:

#include <stdio.h>  

int main()  
{  
    int i = 0;

    if ( i == 0 )
    {
        printf("testing\n");
    }

    return 0;  
}  

为其生成的程序集在这里(仅主要功能):

_main:  
pushl   %ebpz  
movl    %esp, %ebp  
subl    $24, %esp  
andl    $-16, %esp  
movl    $0, %eax  
addl    $15, %eax  
addl    $15, %eax  
shrl    $4, %eax  
sall    $4, %eax  
movl    %eax, -8(%ebp)  
movl    -8(%ebp), %eax  
call    __alloca  
call    ___main  
movl    $0, -4(%ebp)  
cmpl    $0, -4(%ebp)  
jne L2  
movl    $LC0, (%esp)  
call    _printf  
L2:  
movl    $0, %eax  
leave  
ret  

我完全无法关联 C 代码和汇编代码。代码所要做的就是将 0 存储在寄存器中并将其与常量 0 进行比较并采取适当的措施。但是大会中发生了什么?

【问题讨论】:

  • 您应该将其设置为输出 intel 语法以使其可读 (-MintelIIRC)。
  • AT&T 语法完美无瑕,如果您使用的是 UNIX,建议您具备足够的双语能力来阅读它。
  • 投票结束,因为范围太广。

标签: c gcc assembly


【解决方案1】:

由于main 是特殊的,您通常可以通过在另一个函数中执行此类操作来获得更好的结果(最好在没有main 的自己的文件中)。例如:

void foo(int x) {
    if (x == 0) {
       printf("testing\n");
    }
}

作为汇编可能会更清晰。这样做还可以让您进行优化编译并仍然观察条件行为。如果您要编译具有高于 0 的任何优化级别的原始程序,它可能会取消比较,因为编译器可以继续计算结果。使用此代码部分比较对编译器隐藏(在参数x 中),因此编译器无法进行此优化。

额外的东西实际上是什么

_main:  
pushl   %ebpz  
movl    %esp, %ebp  
subl    $24, %esp  
andl    $-16, %esp

这是为当前函数设置堆栈帧。在 x86 中,堆栈帧是堆栈指针的值(SP、ESP 或 16、32 或 64 位的 RSP)和基指针的值(BP、EBP 或 RBP)之间的区域。这应该是局部变量存在的地方,但实际上并非如此,并且在大多数情况下,显式堆栈帧是可选的。不过,alloca 和/或可变长度数组的使用需要使用它们。

这种特殊的堆栈帧结构与非main 函数不同,因为它还确保堆栈是 16 字节对齐的。 ESP 的减法增加了堆栈大小,足以容纳局部变量,andl 有效地从中减去 0 到 15,使其 16 字节对齐。这种对齐方式似乎过度,除了它会强制堆栈也开始缓存对齐以及字对齐。

movl    $0, %eax  
addl    $15, %eax  
addl    $15, %eax  
shrl    $4, %eax  
sall    $4, %eax  
movl    %eax, -8(%ebp)  
movl    -8(%ebp), %eax  
call    __alloca  
call    ___main 

我不知道这一切有什么用。 alloca 通过改变堆栈指针的值来增加堆栈帧的大小。

movl    $0, -4(%ebp)  
cmpl    $0, -4(%ebp)  
jne L2  
movl    $LC0, (%esp)  
call    _printf  
L2:  
movl    $0, %eax  

我想你知道这是做什么的。如果没有,movl 就在call 之前,它会将字符串的地址移动到堆栈的顶部位置,以便它可以被 printf 检索。它必须在堆栈上传递,以便 printf 可以使用它的地址来推断 printf 的其他参数的地址(如果有,在这种情况下不存在)。

leave  

这条指令移除了前面提到的栈帧。它本质上是movl %ebp, %esp,后跟popl %ebp。还有一个enter 指令可以用来构造栈帧,但是gcc 没有使用它。当没有显式使用堆栈帧时,EBP 可以用作通用寄存器,而不是 leave,编译器只会将堆栈帧大小添加到堆栈指针,这将通过帧大小减小堆栈大小.

ret

这个不用我解释了。

当您使用优化进行编译时

我相信你会用不同的优化级别重新编译所有这些,所以我会指出一些你可能会觉得奇怪的可能发生的事情。我观察到gcc 分别用puts 和fputs 替换printf 和fprintf,当格式字符串不包含任何% 并且没有传递其他参数时。这是因为(出于多种原因)拨打puts 和fputs 会便宜得多,而且最终您仍然可以得到您想要的打印结果。

【讨论】:

  • 对__alloca 的调用只是对alloca() 子系统进行初始化。
  • @caf:你能提供更多关于这意味着什么的信息吗?
【解决方案2】:

不用担心序言/后序 - 您感兴趣的部分是:

movl    $0, -4(%ebp)  
cmpl    $0, -4(%ebp)  
jne L2  
movl    $LC0, (%esp)  
call    _printf  
L2: 

这与原始 C 代码的关系应该是不言而喻的。

【讨论】:

  • 前导和后导是什么?为什么编译器需要把它放在首位?
  • 因为与函数 main() 相关的开销
  • @puffadder:前导/后导只是用于设置堆栈和各种寄存器的通用样板代码。请记住,main 通常接受两个参数并返回一个函数结果。
【解决方案3】:

第一部分是一些初始化代码,在您的简单示例中没有任何意义。此代码将使用优化标志删除。

最后一部分可以映射为C代码:

movl    $0, -4(%ebp)    // put 0 into variable i (located at -4(%ebp))
cmpl    $0, -4(%ebp)    // compare variable i with value 0
jne L2                  // if they are not equal, skip to after the printf call
movl    $LC0, (%esp)    // put the address of "testing\n" at the top of the stack
call    _printf         // do call printf
L2:  
movl    $0, %eax        // return 0 (calling convention: %eax has the return code)

【讨论】:

    【解决方案4】:

    嗯,其中大部分是与函数相关的开销。 main() 和其他函数一样,只是一个函数,所以它必须在开始时将返回地址存储在堆栈中,在结束时设置返回值等等。

    我建议使用 GCC 生成混合源代码和汇编程序,它会显示为每个源代码生成的汇编程序。

    如果您想查看 C 代码及其转换为的程序集,请使用如下命令行:

    gcc -c -g -Wa,-a,-ad [other GCC options] foo.c > foo.lst
    

    见http://www.delorie.com/djgpp/v2faq/faq8_20.html

    在 linux 上,只需使用 gcc。在 Windows 上下载 Cygwin http://www.cygwin.com/


    编辑 - 另见这个问题Using GCC to produce readable assembly?

    和http://oprofile.sourceforge.net/doc/opannotate.html

    【讨论】:

      【解决方案5】:

      您需要一些关于汇编语言的知识才能理解 C 编译器生成的汇编。

      这个tutorial 可能会有所帮助

      【讨论】:

      • 我知道基本的汇编语法...但是为什么汇编代码做了这么多事情是我想了解的...
      【解决方案6】:

      请参阅here 了解更多信息。您可以使用 C cmets 生成汇编代码以便更好地理解。

      gcc -g -Wa,-adhls your_c_file.c > you_asm_file.s
      

      这应该对你有一点帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-08-27
        • 1970-01-01
        • 2019-09-01
        • 2018-05-07
        • 1970-01-01
        • 2015-02-24
        • 2012-03-04
        • 2014-03-21
        相关资源
        最近更新 更多