2017-02-25 82 views
2

我最近通过汇编语言书Richard Blum其中C程序中有一个主题用于汇编转换。执行新程序时ESP和EBP寄存器如何操作?

考虑下面的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

它的堆栈指针正好对准16字节边界。 'ebp'被压入堆栈,因为它是一个被调用者保存的寄存器。它不需要被用作帧指针,虽然在这个代码中它是。 – Jester

回答

0

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

因此,这意味着该条目序列码做这样的事情 (重写使用Intel语法,坦率地说,使很多更有意义 比& 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 
+0

没有冒犯,但即使是一小部分的研究也会为你回答这个问题。 Google'gcc omit frame pointer',以及页面上的第一个链接(https://gcc.gnu.org/onlinedocs/gcc-3.4.4/gcc/Optimize-Options.html)会指出你只需要添加' -fomit-frame-pointer“选项,当您使用”-O“优化选项时,默认情况下该选项也处于打开状态。 –