ESP和EBP寄存器在执行新程序时如何操作?

时间:2017-02-25 20:40:00

标签: c assembly x86 gas

我最近通过 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,我可以理解它在某种程度上被用作本地堆栈,因此通过推入堆栈来保存它的值。

请详细说明以上代码段吗?

1 个答案:

答案 0 :(得分:0)

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

这意味着入门序列代码正在做这样的事情 (重写使用英特尔语法,坦率地说更有意义 比AT&amp; 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