[英]How does ESP and EBP registers act when a new program is executed?
我最近瀏覽了一本 理查德·布魯姆 ( 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
我無法預測ESP
和EBP
寄存器會發生什么情況。 關於EBP
,我可以在某種程度上了解它被用作本地堆棧,因此可以通過推入堆棧來保存它的價值。
您能詳細說明上面的代碼段嗎?
這是函數輸入序列的一種特殊形式,適用於main()函數。 編譯器知道main()
實際上稱為main(int argc, char **argv, char **envp)
,並根據非常特殊的行為來編譯此函數。 因此,到達此代碼時,堆棧上的內容是四個長尺寸值,其順序為: envp
, argv
, argc
, return_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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.