Intel汇编程序设计-高级过程(上)
第八章?高級過程
8.1?簡介
本章主要講:
堆棧框架
變量作用域和生存期
對戰參數的類型
通過傳遞值或者傳遞引用來傳遞參數
在堆棧上創建和初始化局部變量
遞歸
編寫多模塊程序
內存模型和語言關鍵字
注意關鍵詞:
子過程=函數=方法(因不同語言導致名字不統一)
8.2堆棧框架(很重要)
? ? 堆棧框架(stack?frame)也稱活動記錄(activation?record),它是為傳遞的參數、子例程的返回地址、局部變量和保存的寄存器保留的堆棧空間。堆棧框架是按一下步驟創建的:
1.如果有傳遞的參數,則壓入堆棧。
2.子歷程被調用,字壘成的返回地址壓入堆棧。
3.子例程開始時,EBP被壓入堆棧。
4.EBP設為ESP的值,從這時開始,EBP就被座位尋址所有子例程參數的基址指針使用了。
5.如果任何寄存器需要保存,則亞茹堆棧。
? ? 堆棧框架的結構手程序的內存模式及參數傳遞約定直接影響。
8.2.1?堆棧參數
? ? 有兩種基本類型的子例程參數:寄存器參數和堆棧參數。Irvine32和Irvine16庫使用寄存器參數,本節講述如何聲明和使用堆棧參數。
? ? 被調用的子例程訪問調用子例程時亞茹堆棧的參數。使用寄存器參數可以優化程序的執行速度,但是遺憾的是,這樣可能會造成代碼的混亂,因為有些寄存器在裝入參數之前必須首先保存。例如,調用DumpMem時就是這種情況:
Pushad
Mov?esi,OFFSET?array???????????;起始偏移地址
Mov?ecx,LENGTHOF?array???????;大小
Mov?ebx,TYPE?array????????????;雙字格式
Call?DumpMem????????????????;顯示內存內容?
Popad
?
? ? 另外一種更靈活的方式是堆棧參數,在調用子例程之前,參數首先壓入堆棧。例如,假設DumpMem使用堆棧參數,那么可以使用下面的代碼進行調用:
Push?TYPE?array
Push?LENGTHOF?array
Push?PFFSET?array
Call?DumpMem
? ? 在進行子例程調用時在堆棧上壓入了兩類參數:
? ? ? 值參數(變量和常量的值)
? ? ? 引用參數(地址)
?
堆棧參數的訪問(C/C++)
? ? 在調用函數時,C/C++程序使用標準的方法初始化和訪問參數。C/C++中的函數以序言(prologue)開始,序言部分的代碼保存了EBP寄存器,并使EBP指向當時堆棧的頂部,函數還有可能把一些寄存器壓棧,這些寄存器的值將在函數返回的時候恢復。函數以收尾(epilogue)代碼結束,在這部分代碼中,EBP寄存器被恢復,RET指令從函數返回。
例子AddTwo
C:
Int?AddTwo(int?x?,int?y){
????Return?x?+?y;
}
對應匯編:
?
AddTwo?PROC
Push?ebp
Mov?ebp,esp??????????;堆棧框架的基址
Mov?eax,[ebp+12]?????;第二個參數
Mov?eax,[ebp+8]??????;第一個參數
Pop??ebp???????
Ret
AddTwo?ENDP
自己用vs2012看了下反匯編(DeBug模式)
調用部分:
?
函數部分
堆棧的清理
? ? 在子例程返回時,必須要有某種方法清除堆棧上的參數,否則就會導致內存泄漏以及堆棧的破壞。假設main中調用AddTwo的語句如下:
Push?5
Push?5
Call?AddTwo
下面是從調用返回后堆棧的示意圖:
?
? ? 如果沒有清理,那么函數結束的時候就會從棧里拿出來一個地址,然后跳轉過去。那么上面就直接跳轉到存儲5的地 址了,這樣就發生問題了。
? ?對于這個問題,一種簡單的解決方法是在CALL指令后使用一條ADD指令給ESP加上一個值,以使ESP指向正確的返回地址:
Example1?PROC
????Push??5
????Push??6
????Call??AddTwo
????Add??esp,8
????Ret
Example1?ENDP
? ? 這實際上也是C++使用的一種方法。
????STDCALL調用約定(Calling?Convention):處理堆棧清理問題的另一種方法是使用STDCALL調用約定,可以在AddTwo過程中的RET指令后提供一個整數參數以修復ESP的值,這個整數值必須等于堆棧參數小號的堆棧空間字節數。
? ? 大體是下面這樣的姿勢:
AddTwo?PROC
Push??ebp
Mov??ebp,esp
Mov??eax,[ebp+12]
Add??eax,[ebp+8]
Pop?ebp
Ret?8
AddTwo?ENDP
? ? 這樣一來,上面堆棧清理問題就簡化了:誰應該對清理堆棧負責?是調用子例程的代碼,還是子例程本身?這兩種方式都有各自的優缺點:STDCALL減少了為子例程調用生成代碼數量(只有一條指令)并且能夠確保調用者永遠不會忘記清理堆棧;另一方面,C調用約定允許子例程生命可變數目的參數,由調用者決定要傳遞多少參數。例子之一是printf函數,這種類型的,清理堆棧的職責職能留給調用者了。
? ? 通過堆棧傳遞8位和16位的參數
? ? 在保護模式下傳遞參數時,最好使用32位的操作數,雖然可以砸IDUI站上壓入16位的操作數,但這樣會似的ESP無法對其在雙字地址邊界上,由此可能會導致發生頁故障,程序的性能也能會降低。因此在傳遞8位或16位對扎你參數時,應把它擴展到32位在壓棧。
So需要把一些小寬度參數擴展成32位的:movzx?eax,word1
那如果是大于32位的怎么辦?:這個我們可以分開傳遞,先傳32位,再傳32位...
?
USER操作符對堆棧的影響
之前應該說過USER,它可以幫助保存和恢復一些寄存器的值。例如:
MySub1?PROC?USES?ecx?,edx
Ret
MySub1?ENDP
下面是匯編時產生的代碼:
Push?ecx
Push?edx
Pop??edx
Pop??ecx
Ret
? ? 假設在MySub2中把USES和堆棧參數一起使用,我們預期第一個參數在堆棧位置EBP+8處:
MySub2?PROC?USES?ecx?,edx
Push?ebp
Mov?ebp,esp
Mov?eax,[ebp+8]
Pop?ebp
Ret?4
MySub2?ENDP
下面是生成的匯編代碼
push?ecx
Push?edx
Push?ebp
Mov?ebp,esp
Mov?eax,dword?ptr[ebp+8]??;錯誤的位置!
Pop?ebp
Pop?edx
Pop?ecx
Ret?4
總結
以上是生活随笔為你收集整理的Intel汇编程序设计-高级过程(上)的全部內容,希望文章能夠幫你解決所遇到的問題。