2009-07-18 22 views
15

您好我有拆解一些程序(Linux)的我寫的更好的理解它是如何工作的,我注意到,主要功能總是開頭:試圖瞭解主拆卸第一指令

lea ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of the main...why ? 
and esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ??? 
push DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ? 
push ebp     
mov ebp,esp 
push ecx ;why is ecx pushed too ?? 

所以我的問題是:爲什麼所有這些工作都完成了? 我才明白使用:

push ebp     
mov ebp,esp 

休息似乎對我沒用......

+1

你正在使用什麼編譯器,你能提供完整的反彙編主子程序嗎? – Inshallah 2009-07-18 15:39:20

回答

25

我在它有一個去:

;# As you have already noticed, the compiler wants to align the stack 
;# pointer on a 16 byte boundary before it pushes anything. That's 
;# because certain instructions' memory access needs to be aligned 
;# that way. 
;# So in order to first save the original offset of esp (+4), it 
;# executes the first instruction: 
lea ecx,[esp+0x4] 

;# Now alignment can happen. Without the previous insn the next one 
;# would have made the original esp unrecoverable: 
and esp,0xfffffff0 

;# Next it pushes the return addresss and creates a stack frame. I 
;# assume it now wants to make the stack look like a normal 
;# subroutine call: 
push DWORD PTR [ecx-0x4] 
push ebp 
mov ebp,esp 

;# Remember that ecx is still the only value that can restore the 
;# original esp. Since ecx may be garbled by any subroutine calls, 
;# it has to save it somewhere: 
push ecx 
+1

+1,這就是發生了什麼+一個很好的解釋。 16字節邊界是理想的,因爲它們是使用SIMD(MMX/SSE/SSE2等)指令所必需的。如果您嘗試在堆棧上使用非16字節對齊值的對齊SIMD指令,則會發生段錯誤。 – Falaina 2009-07-18 16:47:51

5

這樣做是爲了保持堆棧對齊到一個16字節的邊界。某些指令要求某些數據類型在16個字節的邊界上對齊。爲了滿足這個要求,GCC確保堆棧最初是16字節對齊的,並且以16字節的倍數分配堆棧空間。這可以使用選項-mpreferred-stack-boundary=num進行控制。如果您使用-mpreferred-stack-boundary = 2(對於一個2字節對齊),則不會生成此對齊碼,因爲堆棧始終至少與4字節對齊。然而,如果你的程序使用任何需要更強對齊的數據類型,那麼你可能會遇到麻煩。

根據GCC手冊:

在Pentium和PentiumPro,double和long double值應該對齊到8字節邊界(見-malign雙)或遭受顯著運行時性能損失。在Pentium III上,如果不是16字節對齊,則數據流SIMD擴展(SSE)數據類型__m128可能無法正常工作。

爲確保此值在堆棧上正確對齊,堆棧邊界必須與存儲在堆棧中的任何值所需的堆棧邊界保持一致。此外,必須生成每個函數,使其保持對齊。因此,調用一個函數,該函數用一個較低的首選堆棧邊界編譯的函數編譯爲較高的首選堆棧邊界,這很可能會錯位堆棧。建議使用回調的庫始終使用默認設置。

這種額外的對齊確實會消耗額外的堆棧空間,並且通常會增加代碼大小。對棧空間使用敏感的代碼(例如嵌入式系統和操作系統內核)可能希望將首選對齊減少到-mpreferred-stack-boundary = 2。

lea負載原始堆棧指針(從呼叫到main之前)插入ecx,由於堆棧指針即將修改。這是用於兩個目的:

  1. 訪問參數給main功能,因爲它們從main
返回時,相對於原來的堆棧指針
  • 堆棧指針恢復到其原來的價值
  • 4
    lea ecx,[esp+0x4] ; I assume this is for getting the adress of the first argument of  the main...why ? 
    and esp,0xfffffff0 ; ??? is the compiler trying to align the stack pointer on 16 bytes ??? 
    push DWORD PTR [ecx-0x4] ; I understand the assembler is pushing the return adress....why ? 
    push ebp     
    mov ebp,esp 
    push ecx ;why is ecx pushed too ?? 
    

    即使每條指令都工作得很好,儘管任意對齊的操作數都沒有速度懲罰,但對齊仍然會提高性能。設想一個引用16字節數量的循環,只是重疊兩個緩存行。現在,爲了將這個小的wchar加載到緩存中,必須驅逐兩條完整的緩存行,並且如果你需要它們在同一個循環中呢?緩存比RAM快得多,緩存性能總是至關重要的。

    此外,移位未對齊的操作數通常會有速度懲罰。 鑑於堆疊正在重新排列,我們自然必須保存舊的對齊,以便遍歷參數的堆棧幀並返回。

    ecx是一個臨時寄存器,所以它必須被保存。另外,根據優化級別的不同,某些幀連接操作似乎並不是運行該程序所必需的,這對於設置可跟蹤就緒的幀鏈非常重要。