2010-03-17 125 views
2

所以...我編譯成彙編程序,用gcc -S -02 -m32:subl在這裏做什麼?

void h(int y){int x; x=y+1; f(y); f(2); } 

,它給我下面的:

.file "sample.c" 
.text 
.p2align 4,,15 
.globl h 
.type h, @function 
h: 
pushl %ebp 
movl %esp, %ebp 
subl $24, %esp 
movl 8(%ebp), %eax 
movl %eax, (%esp) 
call f 
movl $2, 8(%ebp) 
leave 
jmp f 
.size h, .-h 
.ident "GCC: (GNU) 4.4.3 20100127 (Red Hat 4.4.3-4)" 
.section .note.GNU-stack,"",@progbits 

現在我知道什麼pushl和MOVEL :它們將當前幀指針存儲到堆棧中,然後將幀指針寄存器的值設置爲堆棧指針的值。

  1. 但我不知道是什麼subl $ 24,%esp是。我知道它將堆棧指針向下移動了24個字節。正確?
  2. 這是怎麼回事?
  3. 爲什麼movl 8(%ebp),%eax使用8?它是8字節嗎?這是爲了適應返回值+參數到h?或者我完全離開這裏。所以這意味着從堆棧指針回看8個字節?
  4. 什麼是movl $ 2,8(%ebp)做什麼?它將Contant 2複製到幀指針之前的8個字節位置。當我們調用f時幀指針是否改變?如果是 - 那麼8(%ebp)指向f的參數位置。
  5. 什麼是假嗎?它如何「移除」堆棧框架?我的意思是你不能刪除一塊內存。在文檔中,它說mov(esp,ebp),pop ebp

謝謝!

+0

有趣的是,下面的答案被標記爲接受,儘管它實際上沒有給出問題1的解釋。[Here]( http://stackoverflow.com/questions/1061818/stack-allocation-padding-and-alignment)是另一個問題/答案,給出瞭解釋1。 – andreee 2015-12-15 20:24:00

回答

4

編譯器爲堆棧預留本地空間以及其他可能需要的內容。我不確定它爲什麼保留了24個字節(它似乎並不需要或不使用它)。

當調用函數f(),而不是使用推送指令把參數堆棧上,它使用簡單movl到最後位置時,它保留:

movl 8(%ebp), %eax ; get the value of `y` passed in to `h()` 
movl %eax, (%esp)  ; put that value on the stack for call to `f()` 

一個更有趣的(​​在我看來)這裏發生的事情是編譯器是如何處理呼叫到f(2)

movl $2, 8(%ebp)  ; store 2 in the `y` argument passed to `h()` 
         ;  since `h()` won't be using `y` anymore 
leave     ; get rid of the stackframe for `h()` 
jmp f     ; jump to `f()` instead of calling it - it'll return 
         ;  directly to whatever called `h()` 

要回答你的問題,「我mmed的方式?「 - 指令引用用來指示該值在指令操作碼中編碼,而不是像寄存器或內存位置那樣到達其他地方。

+0

它爲什麼減去?看到我的問題更新... – drozzy 2010-03-17 19:33:52

+2

從'esp'寄存器中減去本質上'保留'堆棧空間。就好像你已經將24個字節的東西(6個32位值)推入堆棧 - 不同之處在於你不能依賴堆棧中那些「推送」值 - 你知道的只是您可以使用該內存而不必擔心隨後的推送會覆蓋它。 – 2010-03-17 19:38:26

+0

因爲堆棧在內存中向下「增長」而減少。 'immed'是一個直接價值,就像你引用的24美元一樣。 – mocj 2010-03-17 19:39:25

4

爲了回答那些編號的問題:

1)subl $24,%esp

意味着esp = esp - 24

GNU AS使用AT & T語法,這是英特爾的語法的相反。 AT & T在右側有目的地,英特爾在左側有目的地。另外AT & T明確表示參數的大小。英特爾試圖推斷它或迫使你明確。

該堆棧在內存中增長下來,內存處於處,其中esp是堆棧內容,低於esp的地址是未使用的堆棧空間。 esp指向推入堆棧的最後一個東西。

2) x86指令編碼大多允許以下:

movl rm,r ' move value from register or memory to a register 
movl r,rm ' move a value from a register to a register or memory 
movl imm,rm ' Move immediate value. 

沒有存儲器到存儲器的指令格式。 (嚴格說來,你可以做內存到內存的操作與movspush mempop mem,但也採取相同的指令兩個內存操作數)

「即時」是指值編碼權到指令。例如,爲了在地址存儲15 EBX:

movl $15,(%ebx)

15是 「立即」 值。

括號使它使用寄存器作爲指向內存的指針。

3)movl 8(%ebp),%eax

手段,

  • 採取EBP
  • 的值添加8到它(不修改EBP雖然),
  • 使用它作爲一個地址(括號),
  • 從該地址讀取32位值,
  • 並將值存儲在eax中

esp是堆棧指針。 在32位模式下,堆棧上的每個推入和彈出消息都是4個字節寬。通常,大多數變量總是佔用4個字節。所以你可以說8(%ebp)的意思是從棧頂開始,給我的值2(4 x 2 = 8)int到堆棧中。

通常,32位代碼使用ebp指向函數中局部變量的開始。在16位x86代碼中,沒有辦法將堆棧指針用作指針(很難相信,對嗎?)。所以人們所做的是將sp複製到bp並使用bp作爲本地幀指針。當32位模式出現時(80386),這變得完全沒有必要,它確實有辦法直接使用堆棧指針。不幸的是,ebp使得調試變得更容易,所以我們最終繼續在32位代碼中使用ebp(如果使用ebp,那麼進行堆棧轉儲非常容易)。

謝天謝地,amd64給了我們一個新的ABI,它不使用ebp作爲幀指針,64位代碼通常使用esp來訪問局部變量,ebp可用來存放變量。

4)解釋上述

5)leave是一個古老的指令,僅僅確實movl %ebp,%esppopl %ebp和保存幾個碼字節。它實際上做的是撤消對堆棧的更改並恢復調用者的ebp。被調用函數必須在x86 ABI中保存ebp

在進入該函數時,編譯器做了subl $ 24,%esp來爲本地變量騰出空間,有時候它沒有足夠的寄存器來存放臨時存儲空間。

想象一下堆棧框架在你腦海中的最好方法就是將它看作一個坐在堆棧上的結構。想象結構的第一個成員是最近「推動」的價值觀。所以當你推入堆棧時,想象一下在結構的開始處插入一個新成員,而其他成員沒有移動。當你從堆棧中「彈出」時,你會得到虛構結構的第一個成員的值,並且該結構的第一行會從存在中消失。

堆棧幀操作大多隻是移動堆棧指針,以使我們稱之爲堆棧幀的虛構結構或多或少。從堆棧指針中減去只是在結構的起始處一步放入多個虛構成員。添加到堆棧指針使得第一個這麼多的成員消失。

您發佈的代碼的結尾並不典型。那jmp通常是ret。編譯器很聰明,做了「尾部調用優化」,這意味着它只是清理它對堆棧做了什麼並跳轉到f。當f(2)返回時,它實際上會直接返回給調用者(不會返回到您發佈的代碼)