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