解码C代码的等效汇编代码

时间:2010-06-07 10:08:38

标签: c gcc assembly

想要查看某些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进行比较并采取适当的措施。但是大会上发生了什么?

6 个答案:

答案 0 :(得分:6)

由于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正在将{1}}的字符串地址移动到堆栈的顶部位置,以便printf可以对其进行重新审核。它必须在堆栈上传递,以便printf可以使用它的地址来推断printf的其他参数的地址(如果有的话,在这种情况下不存在)。

leave  

该指令删除前面谈到的堆栈帧。它基本上是movl %ebp, %esp,然后是popl %ebp。还有一个enter指令可用于构造堆栈帧,但gcc没有使用它。当没有显式使用堆栈帧时,EBP可以用作一般的puropose寄存器而不是leave编译器只会将堆栈帧大小添加到堆栈指针,这将减少堆栈大小按帧大小。

ret

我不需要解释这个。

使用优化进行编译时

我相信你会用不同的优化级别重新编译所有这些,所以我会指出可能会发生的事情你可能会发现很奇怪。当格式字符串不包含任何gcc时,我发现printf分别用fprintfputs取代fputs%没有传递额外的参数。这是因为(出于多种原因)调用putsfputs要便宜得多,最后你仍然可以获得你想要的内容。

答案 1 :(得分:3)

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

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

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

答案 2 :(得分:2)

第一部分是一些初始化代码,在你的简单例子中没有任何意义。此代码将通过优化标记删除。

最后一部分可以映射到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)

答案 3 :(得分:2)

嗯,其中很大一部分是与该功能相关的开销。 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

答案 4 :(得分:1)

您需要一些有关汇编语言的知识才能理解C编译器所获得的汇编。

tutorial可能会有所帮助

答案 5 :(得分:1)

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

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

这应该对你有所帮助。