|
1. c語(yǔ)言的函數(shù)調(diào)用與對(duì)應(yīng)的匯編代碼 1.1 調(diào)用規(guī)則
比如調(diào)用函數(shù) function(parameter1, parameter2, parameter3)
Pascal調(diào)用規(guī)則 | _cdecl調(diào)用規(guī)則 | _stdcall調(diào)用規(guī)則 | PUSH parameter1
PUSH parameter2
PUSH parameter3
CALL function | PUSH parameter3
PUSH parameter2
PUSH parameter1
CALL function
ADD ESP, 0CH | PUSH parameter3
PUSH parameter2
PUSH parameter1
CALL function | 參數(shù)從左到右傳遞壓棧,由被調(diào)用函數(shù)清理堆棧 | 參數(shù)從右到左傳遞壓棧,由調(diào)用函數(shù)負(fù)責(zé)清理堆棧 | 參數(shù)從右到左傳遞壓棧,由被調(diào)用函數(shù)負(fù)責(zé)清理堆棧 | 用于Win16平臺(tái) | C/C++調(diào)用標(biāo)準(zhǔn) | Windows API 使用 |
1.2 匯編代碼 對(duì)調(diào)用的方式,舉一個(gè)例子,對(duì)c語(yǔ)言常用的printf:
printf(“%d", a);
解析成匯編代碼如:
PUSH a
PUSH OFFSET String "%d"
CALL printf
ADD ESP, 8
CALL指令和RET指令
段內(nèi)調(diào)用
對(duì)CALL指令來(lái)說(shuō),其執(zhí)行的步驟一般包括:
- 將IP壓入棧(即寄存器EIP的值,指向CALL指令之后的第一條指令)
- 將IP置為跳轉(zhuǎn)到的地址,開(kāi)始執(zhí)行
對(duì)應(yīng) 的RET指令為:
段間調(diào)用
如果是段間函數(shù)調(diào)用,則CALL的執(zhí)行過(guò)程一般是:
- 將CS(段地址)壓入棧
- 將IP(即EIP的值)壓入棧
- 將IP置為跳轉(zhuǎn)到的函數(shù)地址,開(kāi)始執(zhí)行
對(duì)應(yīng)的RET指令執(zhí)行步驟:
被調(diào)用函數(shù)的執(zhí)行步驟
PUSH EBP ; 保存當(dāng)前堆棧基址ebp,以作返回用
MOVE EBP, ESP ; 將當(dāng)前esp的值賦給ebp,作為新的基址,即進(jìn)入函數(shù)內(nèi)部
SUB ESP, 0CCH ; 將esp往下移動(dòng)一個(gè)范圍,開(kāi)辟一片新的堆棧空間給當(dāng)前函數(shù)使用
; 這是由于堆棧從高地址往低地址增長(zhǎng),所以,減一個(gè)值意味著開(kāi)辟了
; 新的空間
................. ; 保存其他寄存器的值
.................
................. ; 恢復(fù)壓棧的其他寄存器的值
MOVE ESP, EBP ; 恢復(fù)esp的值為原來(lái)的堆棧棧頂值
POP EBP ; 恢復(fù)堆棧基址為原基址位置
RET
一般來(lái)說(shuō),函數(shù)的返回值會(huì)放在EAX寄存器中返回。
2. c語(yǔ)言特殊語(yǔ)句塊的匯編代碼
1)For循環(huán)的匯編代碼模板
mov <循環(huán)變量>, <初始值> ; 循環(huán)變量賦初值
jmp B ; 直接跳轉(zhuǎn)到循環(huán)控制測(cè)試部分代碼
A: (改動(dòng)循環(huán)變量) ; 修改循環(huán)變量值的部分代碼
......
B: cmp <循環(huán)變量>, <限制變量> ; 將循環(huán)變量的值進(jìn)行測(cè)試、跳轉(zhuǎn)
jge 跳出循環(huán) ; 符合終止條件,則跳出循環(huán)體 ; (注意,這里的jl指令可以是其他的jge等,一具判斷條件而定)
(循環(huán)體代碼) ; 否則,進(jìn)入循環(huán)體代碼執(zhí)行
...
jmp A ; 循環(huán)體結(jié)束的最后,是一個(gè)無(wú)條件跳轉(zhuǎn)語(yǔ)句,調(diào)回直接
; 修改循環(huán)變量的代碼
2)do循環(huán)的匯編代碼模板
A: (循環(huán)體) ; 直接是循環(huán)體代碼
....
cmp <循環(huán)變量>, <限制變量> ; 判斷是否需要終止循環(huán)
jl <循環(huán)開(kāi)始處> ; 如果不符合終止條件,直接調(diào)回循環(huán)體開(kāi)始處繼續(xù)執(zhí)行循環(huán)體
; 代碼(注意,這里的jl指令可以是其他的jge等,一具判斷條件而定)
3) while循環(huán)的匯編代碼模板
A: cmp <循環(huán)變量>, <限制變量> ; 先比較循環(huán)變量,是否需要進(jìn)行循環(huán)
jge B ; 如果滿足停止循環(huán),則直接跳到B,即循環(huán)體后的第一個(gè)指令
(循環(huán)體)
……
jmp A ; 循環(huán)體的最后一條指令,是無(wú)條件跳轉(zhuǎn)到循環(huán)控制判斷指令A(yù)處
B: (循環(huán)結(jié)束了)
4)if-else的匯編代碼模板(待續(xù)...)
理解調(diào)用棧最重要的兩點(diǎn)是:棧的結(jié)構(gòu),EBP寄存器的作用。
首先要認(rèn)識(shí)到這樣兩個(gè)事實(shí):
1、一個(gè)函數(shù)調(diào)用動(dòng)作可分解為:零到多個(gè)PUSH指令(用于參數(shù)入棧),一個(gè)CALL指令。CALL指令內(nèi)部其實(shí)還暗含了一個(gè)將返回地址(即CALL指令下一條指令的地址)壓棧的動(dòng)作。
2、幾乎任何本地編譯器都會(huì)在每個(gè)函數(shù)體之前插入類似如下指令:PUSH EBP; MOV EBP ESP;
即,在程序執(zhí)行到一個(gè)函數(shù)的真正函數(shù)體時(shí),已有以下數(shù)據(jù)順序入棧:參數(shù),返回地址,EBP。
由此得到類似如下的棧結(jié)構(gòu)(參數(shù)入棧順序跟調(diào)用方式有關(guān),這里以C語(yǔ)言默認(rèn)的CDECL為例):
+| (棧底方向,高位地址) |
| ....................|
| ....................|
| 參數(shù)3 |
| 參數(shù)2 |
| 參數(shù)1 |
| 返回地址 |
-| 上一層[EBP] |
| 局部變量1 |
| 局部變量2 |
|.....................|
補(bǔ)充:棧一直隨著函數(shù)調(diào)用的深入,一直想棧頂方向壓下去。每次調(diào)用函數(shù)時(shí)候,先壓函數(shù)參數(shù)(從右往左順序壓),再壓入函數(shù)調(diào)用下條指令的地址(由call完成)。接著進(jìn)入調(diào)用函數(shù)體中先執(zhí)行PUSH EBP; MOV EBP ESP;(一般已經(jīng)由編譯器加入到函數(shù)頭中了),接著就是吧函數(shù)體中的局部變量壓入棧中。再遇到函數(shù)的調(diào)用的嵌套則依此類推。(added by smsong)
“PUSH EBP”“MOV EBP ESP”這兩條指令實(shí)在大有深意:首先將EBP入棧,然后將棧頂指針ESP賦值給EBP!癕OV EBP ESP”這條指令表面上看是用ESP把EBP原來(lái)的值覆蓋了,其實(shí)不然——因?yàn)榻oEBP賦值之前,原EBP值已被壓棧(位于棧頂),而新的EBP又恰恰指向棧頂。
此時(shí)EBP寄存器就已處于一個(gè)很重要的地位,該寄存器中存儲(chǔ)著棧中的一個(gè)地址(原EBP入棧后的棧頂),從該地址為基準(zhǔn),向上(棧底方向)能獲取返回地址、參數(shù)值,向下(棧頂方向)能獲取函數(shù)局部變量值,而該地址處又存儲(chǔ)著上一層函數(shù)調(diào)用時(shí)的EBP值!
一般而言,ss:[ebp+4]處為返回地址,ss:[ebp+8]處為第一個(gè)參數(shù)值(最后一個(gè)入棧的參數(shù)值,此處假設(shè)其占用4字節(jié)內(nèi)存),ss:[ebp-4]處為第一個(gè)局部變量,ss:[ebp]處為上一層EBP值。
由于EBP中的地址處總是“上一層函數(shù)調(diào)用時(shí)的EBP值”,而在每一層函數(shù)調(diào)用中,都能通過(guò)當(dāng)時(shí)的EBP值“向上(棧底方向)能獲取返回地址、參數(shù)值,向下(棧頂方向)能獲取函數(shù)局部變量值”。
如此形成遞歸,直至到達(dá)棧底。這就是函數(shù)調(diào)用棧。
編譯器對(duì)EBP的使用實(shí)在太精妙了。
從當(dāng)前EBP出發(fā),逐層向上找到任何的EBP是很容易的:
unsigned int _ebp;
__asm _ebp, ebp;
while (not stack bottom)
{
//...
_ebp = *(unsigned int*)_ebp;
}
假如要寫(xiě)一個(gè)簡(jiǎn)單的調(diào)試器的話,注意需在被調(diào)試進(jìn)程(而非當(dāng)前進(jìn)程——調(diào)試器進(jìn)程)中讀取內(nèi)存數(shù)據(jù)。
8個(gè)通用寄存器:
數(shù)據(jù)寄存器:AX,BX,CX,DX
指針寄存器:SP(堆棧指針),BP(基址指針)
變址寄存器:SI(原地址),DI(目的地址)
1、通用寄存器
數(shù)據(jù)寄存器,指針寄存器和變址寄存器統(tǒng)稱為通用寄存器。這些寄存器除了各自專門用途外,它們均可用于傳送和暫存數(shù)據(jù),可以保存算術(shù)邏輯運(yùn)算中的操作數(shù)和運(yùn)算結(jié)果。
(1)數(shù)據(jù)寄存器
數(shù)據(jù)寄存器主要用來(lái)保存操作數(shù)或運(yùn)算結(jié)果等信息,它們的存在節(jié)省了為存取操作數(shù)所需占用總線和訪問(wèn)存儲(chǔ)器的時(shí)間。
(2)變址和指針寄存器
變址和指針寄存器主要用于存放某個(gè)存儲(chǔ)單元地址的偏移,或某組存儲(chǔ)單元地址的偏移,即作為存儲(chǔ)器(短)指針使用。作為通用寄存器,它們可以保存16位算術(shù)邏輯運(yùn)算中的操作數(shù)和運(yùn)算結(jié)果,有時(shí)運(yùn)算結(jié)果就是需要的存儲(chǔ)單元地址的偏移。
2、控制寄存器(2個(gè))
(1)指令指針寄存器
8086/8088CPU中的指令指針I(yè)P也是16位的。
指令指針I(yè)P給出接著要執(zhí)行的指令在代碼段中的偏移。
(2)標(biāo)志寄存器
8086/8088CPU中有一個(gè)16位的標(biāo)志寄存器,包含了9個(gè)標(biāo)志,主要用于反映處理器的狀態(tài)和運(yùn)算結(jié)果的某些特征。6個(gè)條件標(biāo)志+3個(gè)方向標(biāo)志
3、段寄存器(4個(gè))
8086/8088CPU依賴其內(nèi)部的四個(gè)段寄存器實(shí)現(xiàn)尋址1M字節(jié)物理地址空間。
8086/8088把1M字節(jié)地址空間分成若干邏輯段,當(dāng)前使用的段值存放在段寄存器中。
由于8086/8088有這四個(gè)段寄存器,所以有四個(gè)當(dāng)前使用段可以直接存取,這四個(gè)當(dāng)前段分別稱為代碼段,數(shù)據(jù)段,堆棧段和附加段。
(1)代碼段
(2)數(shù)據(jù)段
(3)堆棧段
(4)附加段
|
|