【问题标题】:What is the use of "push %ebp; movl %esp, %ebp" generated by GCC for x86?GCC为x86生成的“push %ebp; movl %esp, %ebp”有什么用?
【发布时间】:2011-01-31 17:12:38
【问题描述】:

这两条指令对gcc for x86机器生成的汇编代码有什么影响:

push %ebp
movl %esp, %ebp

【问题讨论】:

    标签: gcc assembly x86


    【解决方案1】:

    unwind 的解释是字面上的事实(尽管有一个小的方向错误),但没有解释原因。

    %ebp 是堆栈帧的“基指针”。它是 C 运行时用来访问堆栈上的局部变量和参数的指针。下面是 GCC 生成的一些典型的函数序言代码(准确地说是 g++) 首先是 C++ 源代码。

    // junk.c++
    int addtwo(int a)
    {
        int x = 2;
    
        return a + x;
    }
    

    这会生成以下汇编程序。

    .file   "junk.c++"
        .text
    .globl _Z6addtwoi
        .type   _Z6addtwoi, @function
    _Z6addtwoi:
    .LFB2:
        pushl   %ebp
    .LCFI0:
        movl    %esp, %ebp
    .LCFI1:
        subl    $16, %esp
    .LCFI2:
        movl    $2, -4(%ebp)
        movl    -4(%ebp), %edx
        movl    8(%ebp), %eax
        addl    %edx, %eax
        leave
        ret
    .LFE2:
        .size   _Z6addtwoi, .-_Z6addtwoi
        .ident  "GCC: (Ubuntu 4.3.3-5ubuntu4) 4.3.3"
        .section    .note.GNU-stack,"",@progbits
    

    现在解释一下序言代码(.LCFI2: 之前的所有内容),首先:

    1. pushl %ebp调用函数的堆栈帧存储在堆栈上。
    2. movl %esp, %ebp 获取当前堆栈指针并将其用作调用函数的框架。
    3. subl $16, %esp 为局部变量留出了空间。

    现在您的函数已准备就绪。任何与%ebp% 寄存器的偏移量为负的引用都是您的局部变量(在此示例中为x)。任何与%ebp% 寄存器有正偏移的引用都是您传入的参数。

    最后一点是leave 指令,它是一个x86 汇编指令,负责恢复调用函数的堆栈帧。这通常被优化为 C 代码中更快的move %ebp %esppop %ebp% 序列。但是,出于说明目的,我根本没有进行任何优化。

    【讨论】:

    • 为了补充这个答案,有各种调用约定来控制输入参数被压入堆栈的顺序以及函数完成后谁来清理。
    【解决方案2】:

    这是您在函数开头看到的典型代码。

    它将EBP寄存器的内容保存在堆栈中,然后将当前堆栈指针的内容存储在EBP中。

    在函数调用期间使用堆栈来存储本地参数。但是在函数中,堆栈指针可能会因为值存储在堆栈上而发生变化。

    如果您保存堆栈的原始值,您可以通过 EBP 寄存器引用存储的参数,同时您仍然可以使用(添加值到)堆栈。

    在函数的最后你可能会看到命令

    pop %ebp   ; restore original value 
    ret        ; return 
    

    【讨论】:

    • movl %ebp,%esp / popl %ebp / ret-->先恢复ESP然后弹出EBP
    【解决方案3】:
    push %ebp
    

    这会将 32 位(扩展)基指针寄存器压入堆栈,即堆栈指针 (%esp) 减 4,然后将 %ebp 的值复制到堆栈指针指向的位置。

    movl %esp, %ebp
    

    这会将堆栈指针寄存器复制到基指针寄存器。

    将堆栈指针复制到基指针的目的是创建一个堆栈帧,即堆栈上的一个区域,子程序可以存储本地数据。子程序中的代码将使用基指针来引用数据。

    【讨论】:

    • 最后一句话之前没有描述。感谢那!赞成。
    【解决方案4】:

    它是所谓的function prolog 的一部分。

    它保存函数结束时要检索的当前基指针,并将新的 ebp 设置为新帧的开头。

    【讨论】:

      【解决方案5】:

      我也认为重要的是要经常注意 push %ebpmovl %esp, %ebp 该程序集将具有push %ebxpush %edx。这些是寄存器%ebx%edx 的调用者保存。在例程调用结束时,寄存器将恢复其原始值。

      另外 - %ebx, %esi, %edi 都是被调用者保存寄存器。所以如果你想覆盖它们,你需要先保存它们,然后再恢复它们。

      【讨论】:

        【解决方案6】:

        这段代码为您的程序设置堆栈。

        在 x86 中,堆栈信息由两个寄存器保存。

        
            Base pointer (bp): Holds starting address of the stack
            Stack pointer (sp): Holds the address in which next value will be stored
        

        这些寄存器在不同的模式下有不同的名称:

        
                                    Base pointer           Stack pointer
            16 bit real mode:       bp                     sp
            32 bit protected mode:  ebp(%ebp)              esp(%esp)
            64 bit mode:            rbp                    rsp
        

        当你建立一个堆栈时,堆栈指针和基指针在开始时获得相同的地址。

        现在来解释你的代码,

        
            push %ebp
        

        此代码将当前堆栈地址压入堆栈,以便函数可以正确“退出”或“返回”。

        
            movl %esp, %ebp
        

        此代码为您的函数设置堆栈。

        有关详细信息,请参阅此question

        希望对您有所帮助!

        【讨论】:

          猜你喜欢
          • 2011-08-23
          • 1970-01-01
          • 2014-05-15
          • 2012-04-03
          • 2011-07-25
          • 2013-02-07
          • 1970-01-01
          相关资源
          最近更新 更多