【问题标题】:How does ESP and EBP registers act when a new program is executed?执行新程序时 ESP 和 EBP 寄存器如何工作?
【发布时间】:2017-02-25 20:39:59
【问题描述】:

我最近浏览了 Richard Blum 撰写的汇编语言书籍,其中有一门关于 C 程序到汇编转换的主题。

考虑以下 C 程序:

#include <stdio.h>

int main(){
     int a=100;
     int b=25;
     if (a>b)
       printf("The higher value is %d\n", a);
     else
       printf("The higher value is %d\n", b);
     return 0;
}

当我使用-S参数编译上述程序时:

gcc -S abc.c

我得到了以下结果:

        .file   "abc.c"
        .section        .rodata
.LC0:
        .string "The higher value is %d\n"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        leal    4(%esp), %ecx
        .cfi_def_cfa 1, 0
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        .cfi_escape 0x10,0x5,0x2,0x75,0
        movl    %esp, %ebp
        pushl   %ecx
        .cfi_escape 0xf,0x3,0x75,0x7c,0x6
        subl    $20, %esp
        movl    $100, -16(%ebp)
        movl    $25, -12(%ebp)
        movl    -16(%ebp), %eax
        cmpl    -12(%ebp), %eax
        jle     .L2
        subl    $8, %esp
        pushl   -16(%ebp)
        pushl   $.LC0
        call    printf
        addl    $16, %esp
        jmp     .L3
.L2:
        subl    $8, %esp
        pushl   -12(%ebp)
        pushl   $.LC0
        call    printf
        addl    $16, %esp
.L3:
        movl    $0, %eax
        movl    -4(%ebp), %ecx
        .cfi_def_cfa 1, 0
        leave
        .cfi_restore 5
        leal    -4(%ecx), %esp
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .ident  "GCC: (Ubuntu 6.2.0-5ubuntu12) 6.2.0 20161005"
        .section        .note.GNU-stack,"",@progbits

我无法理解的是:

片段

.LFB0:
        .cfi_startproc
        leal    4(%esp), %ecx
        .cfi_def_cfa 1, 0
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        .cfi_escape 0x10,0x5,0x2,0x75,0
        movl    %esp, %ebp
        pushl   %ecx
        .cfi_escape 0xf,0x3,0x75,0x7c,0x6
        subl    $20, %esp

我无法预测 ESPEBP 寄存器会发生什么。关于EBP,在某种程度上我可以理解,它是作为一个本地栈使用的,所以它的值是通过压栈来保存的。

你能详细说明一下上面的sn-p吗?

【问题讨论】:

  • 只是将堆栈指针对齐到 16 字节边界。 ebp 被压入堆栈,因为它是一个被调用者保存的寄存器。它不需要用作帧指针,尽管在此代码中它是。

标签: c assembly x86 gnu-assembler


【解决方案1】:

这是一种特殊形式的函数入口序列,适用于 main() 功能。编译器知道main() 确实被称为main(int argc, char **argv, char **envp),并根据那个非常特殊的行为来编译这个函数。因此,当到达此代码时,堆栈上的内容是四个长尺寸值,按以下顺序排列:envpargvargcreturn_address

这意味着入口序列代码正在做这样的事情 (重写为使用 Intel 语法,坦率地说,这更有意义 比 AT&T 语法):

; Copy esp+4 into ecx.  The value at [esp] has the return address,
; so esp+4 is 'argc', or the start of the function's arguments.
lea ecx, [esp+4]

; Round esp down (align esp down) to the nearest 16-byte boundary.
; This ensures that regardless of what esp was before, esp is now
; starting at an address that can store any register this processor
; has, from the one-byte registers all the way up to the 16-byte xmm
; registers
and esp, 0xFFFFFFF0

; Since we copied esp+4 into ecx above, that means that [ecx] is 'argc',
; [ecx+4] is 'argv', and [ecx+8] is 'envp'.  For whatever reason, the
; compiler decided to push a duplicate copy of 'argv' onto the function's
; new local frame.
push dword ptr [ecx+4]

; Preserve 'ebp'.  The C ABI requires us not to damage 'ebp' across
; function calls, so we save its old value on the stack before we
; change it.
push ebp

; Set 'ebp' to the current stack pointer to set up the function's
; stack frame for real.  The "stack frame" is the place on the stack
; where this function will store all its local variables.
mov ebp, esp

; Preserve 'ecx'.  Ecx tells us what 'esp' was before we munged 'esp'
; in the 'and'-instruction above, so we'll need it later to restore
; 'esp' before we return.
push ecx

; Finally, allocate space on the stack frame for the local variables,
; 20 bytes worth.  'ebp' points to 'esp' plus 24 by this point, and
; the compiler will use 'ebp-16' and 'ebp-12' to store the values of
; 'a' and 'b', respectively.  (So under 'ebp', going down the stack,
; the values will look like this:  [ecx, unused, unused, a, b, unused].
; Those unused slots are probably used by the .cfi pseudo-ops for
; something related to exception handling.)
sub esp, 20

在函数的另一端,使用逆运算 堆栈恢复到调用函数之前的状态;可能是 有助于检查他们正在做什么以及了解正在发生的事情 开头:

; Return values are always passed in 'eax' in the x86 C ABI, so set
; 'eax' to the return value of 0.
mov eax, 0

; We pushed 'ecx' onto the stack a while back to save it.  This
; instruction pulls 'ecx' back off the stack, but does so without
; popping (which would alter 'esp', which doesn't currently point
; to the right location).
mov ecx, [ebp+4]

; Magic instruction! The 'leave' instruction is designed to shorten
; instruction sequences by "undoing" the stack in a single op.
; So here, 'leave' means specifically to do the following two
; operations, in order:  esp = ebp / pop ebp
leave

; 'esp' is now set to what it was before we pushed 'ecx', and 'ebp'
; is back to the value that was used when this function was called.
; But that's still not quite right, so we set 'esp' specifically to
; 'ecx+4', which is the exact opposite of the very first instruction
; in the function.
lea esp, [ecx+4]

; Finally, the stack is back to the way it was when we were called,
; so we can just return.
ret

【讨论】:

  • 无意冒犯,但即使是一点点研究也会为您回答这个问题。谷歌gcc omit frame pointer,页面上的第一个链接(gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html)会指出你只需添加-fomit-frame-pointer选项,当你使用-O优化选项时也默认开启。
猜你喜欢
  • 2012-01-11
  • 2016-06-07
  • 2011-08-23
  • 2015-02-03
  • 2012-01-28
  • 2013-11-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多