最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

5.10 匯編語言:匯編過程與結(jié)構(gòu)

2023-08-24 10:22 作者:bili_42682284418  | 我要投稿

過程的實(shí)現(xiàn)離不開堆棧的應(yīng)用,堆棧是一種后進(jìn)先出`(LIFO)`的數(shù)據(jù)結(jié)構(gòu),最后壓入棧的值總是最先被彈出,而新數(shù)值在執(zhí)行壓棧時(shí)總是被壓入到棧的最頂端,棧主要功能是暫時(shí)存放數(shù)據(jù)和地址,通常用來保護(hù)斷點(diǎn)和現(xiàn)場。


棧是由`CPU`管理的線性內(nèi)存數(shù)組,它使用兩個(gè)寄存器`(SS和ESP)`來保存棧的狀態(tài),SS寄存器存放段選擇符,而ESP寄存器的值通常是指向特定位置的一個(gè)32位偏移值,我們很少需要直接操作ESP寄存器,相反的ESP寄存器總是由`CALL,RET,PUSH,POP`等這類指令間接性的修改。


CPU提供了兩個(gè)特殊的寄存器用于標(biāo)識位于系統(tǒng)棧頂端的棧幀。

? - ESP 棧指針寄存器:棧指針寄存器,其內(nèi)存放著一個(gè)指針,該指針永遠(yuǎn)指向系統(tǒng)棧最上面一個(gè)棧幀的棧頂。

? - EBP 基址指針寄存器:基址指針寄存器,其內(nèi)存放著一個(gè)指針,該指針永遠(yuǎn)指向系統(tǒng)棧最上面一個(gè)棧幀的底部。


在通常情況下ESP是可變的,隨著棧的生成而逐漸變小,而EBP寄存器是固定的,只有當(dāng)函數(shù)的調(diào)用后,發(fā)生入棧操作而改變。


?- 執(zhí)行PUSH壓棧時(shí),堆棧指針自動(dòng)減4,再將壓棧的值復(fù)制到堆棧指針?biāo)赶虻膬?nèi)存地址。

?- 執(zhí)行POP出棧時(shí),從棧頂移走一個(gè)值并將其復(fù)制給內(nèi)存或寄存器,然后再將堆棧指針自動(dòng)加4。

?- 執(zhí)行CALL調(diào)用時(shí),CPU會用堆棧保存當(dāng)前被調(diào)用過程的返回地址,直到遇到RET指令再將其彈出。


### 10.1 PUSH/POP


PUSH和POP是匯編語言中用于堆棧操作的指令,它們通常用于保存和恢復(fù)寄存器的值,參數(shù)傳遞和函數(shù)調(diào)用等。


PUSH指令用于將操作數(shù)壓入堆棧中,它執(zhí)行的操作包括將操作數(shù)復(fù)制到堆棧的棧頂,并將堆棧指針(ESP)減去相應(yīng)的字節(jié)數(shù)。指令格式如下:


```ASM

PUSH operand

```


其中,operand可以是8位,16位或32位的寄存器,立即數(shù),以及內(nèi)存中的某個(gè)值。例如,要將寄存器EAX的值壓入堆棧中,可以使用以下指令:


```ASM

PUSH EAX

```


從匯編代碼的角度來看,PUSH指令將操作數(shù)存儲到堆棧中,它實(shí)際上是一個(gè)入棧操作。


POP指令用于將堆棧中棧頂?shù)闹祻棾龅街付ǖ哪康牟僮鲾?shù)中,它執(zhí)行的操作包括將堆棧頂部的值移動(dòng)到指定的操作數(shù),并將堆棧指針增加相應(yīng)的字節(jié)數(shù)。指令格式如下:


```ASM

POP operand

```


其中,operand可以是8位,16位或32位的寄存器,立即數(shù),以及內(nèi)存中的某個(gè)位置。例如,要將從堆棧中彈出的值存儲到BX寄存器中,可以使用以下指令:


```ASM

POP EBX

```


從匯編代碼的角度來看,POP指令將從堆棧中取出一個(gè)值,并將其存儲到目的操作數(shù)中,它是一個(gè)出棧操作。


在函數(shù)調(diào)用時(shí),PUSH指令被用于向堆棧中推送函數(shù)的參數(shù),這些參數(shù)可以是寄存器、立即數(shù)或者內(nèi)存中的某個(gè)值。在函數(shù)返回之前,POP指令被用于將堆棧頂部的值彈出,并將其存儲到寄存器或者內(nèi)存中。


讀者需要特別注意,在使用`PUSH`和`POP`指令時(shí)需要保證堆棧的平衡,也就是說,每個(gè)`PUSH`指令必須有對應(yīng)的`POP`指令,否則堆棧會失去平衡,最終導(dǎo)致程序出現(xiàn)錯(cuò)誤。


在讀者了解了這兩條指令時(shí)則可以執(zhí)行一些特殊的操作,如下代碼我們以數(shù)組入棧與出棧為例,執(zhí)行`PUSH`指令時(shí),首先減小`ESP`的值,然后把源操作數(shù)復(fù)制到堆棧上,執(zhí)行`POP`指令則是先將數(shù)據(jù)彈出到目的操作數(shù)中,然后再執(zhí)行`ESP`值增加4,并以此分別將數(shù)組中的元素壓入棧,最終再通過POP將元素反彈出來。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib

include msvcrt.inc

includelib msvcrt.lib


.data

? Array DWORD 1,2,3,4,5,6,7,8,9,10

? szFmt BYTE '%d ',0dh,0ah,0

.code

? main PROC

? ? ; 使用Push指令將數(shù)組正向入棧

? ? mov eax,0

? ? mov ecx,10

? S1:

? ? push dword ptr ds:[Array + eax * 4]

? ? inc eax

? ? loop S1

? ??

? ? ; 使用pop指令將數(shù)組反向彈出

? ? mov ecx,10

? S2:

? ? push ecx? ? ? ? ? ? ? ? ? ? ? ? ?; 保護(hù)ecx

? ? pop ebx? ? ? ? ? ? ? ? ? ? ? ? ? ; 將Array數(shù)組元素彈出到ebx

? ? invoke crt_printf,addr szFmt,ebx

? ? pop ecx? ? ? ? ? ? ? ? ? ? ? ? ? ; 彈出ecx

? ? loop S2

? ??

? ? int 3

? main ENDP

END main

```

至此當(dāng)讀者理解了這兩個(gè)指令之后,那么利用堆棧的先進(jìn)后出特定,我們就可以實(shí)現(xiàn)將特殊的字符串反轉(zhuǎn)后輸出的效果,首先我們循環(huán)將字符串壓入堆棧,然后再從堆棧中反向彈出來,這樣就可以實(shí)現(xiàn)字符串的反轉(zhuǎn)操作,這段代碼的實(shí)現(xiàn)也相對較為容易;


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib

include msvcrt.inc

includelib msvcrt.lib


.data

? MyString BYTE "hello lyshark",0

? NameSize DWORD ($ - MyString) - 1

? szFmt BYTE '%s',0dh,0ah,0

.code

? main PROC

? ? ; 正向壓入字符串

? ? mov ecx,dword ptr ds:[NameSize]

? ? mov esi,0

? S1: movzx eax,byte ptr ds:[MyString + esi]

? ? push eax

? ? inc esi

? ? loop S1


? ? ; 反向彈出字符串

? ? mov ecx,dword ptr ds:[NameSize]

? ? mov esi,0

? S2: pop eax

? ? mov byte ptr ds:[MyString + esi],al

? ? inc esi

? ? loop S2

? ??

? ? invoke crt_printf,addr szFmt,addr MyString

? ? int 3

? main ENDP

END main

```


### 10.2 PROC/ENDP


PROC/ENDP 偽指令是用于定義過程(函數(shù))的偽指令,這兩個(gè)偽指令可分別定義過程的開始和結(jié)束位置。此處讀者需要注意,這兩條偽指令并非是匯編語言中所兼容的,而是`MASM`編譯器為我們提供的一個(gè)宏,是`MASM`的一部分,它允許程序員使用匯編語言定義過程(函數(shù))可以像標(biāo)準(zhǔn)匯編指令一樣使用。


對于不使用宏定義來創(chuàng)建函數(shù)時(shí)我們通常會自己管理函數(shù)棧參數(shù),而有了宏定義這些功能都可交給編譯器去管理,下面的一個(gè)案例中,我們通過使用過程創(chuàng)建`ArraySum`函數(shù),實(shí)現(xiàn)對整數(shù)數(shù)組求和操作,函數(shù)默認(rèn)將返回值存儲在`EAX`中,并打印輸出求和后的參數(shù)。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib

include msvcrt.inc

includelib msvcrt.lib


.data

? MyArray? DWORD 1,2,3,4,5,6,7,8,9,10

? Sum? ? ? DWORD ?

? szFmt? ? BYTE '%d',0dh,0ah,0

.code

? ; 數(shù)組求和過程

? ArraySum PROC

? ? push esi? ? ? ? ? ? ? ? ? ? ?; 保存ESI,ECX

? ? push ecx

? ? xor eax,eax

? ??

? S1: add eax,dword ptr ds:[esi]? ?; 取值并相加

? ? add esi,4? ? ? ? ? ? ? ? ? ? ; 遞增數(shù)組指針

? ? loop S1

? ? pop ecx? ? ? ? ? ? ? ? ? ? ? ; 恢復(fù)ESI,ECX

? ? pop esi

? ? ret

? ArraySum endp


? main PROC

? ? lea esi,dword ptr ds:[MyArray]? ?; 取出數(shù)組基址

? ? mov ecx,lengthof MyArray? ? ? ? ?; 取出元素?cái)?shù)目

? ? call ArraySum? ? ? ? ? ? ? ? ? ? ; 調(diào)用方法

? ? mov dword ptr ds:[Sum],eax? ? ? ?; 得到結(jié)果

? ? invoke crt_printf,addr szFmt,Sum

? ? int 3

? main ENDP

END main

```

接著我們來實(shí)現(xiàn)一個(gè)具有獲取隨機(jī)數(shù)功能的案例,在C語言中如果需要獲得一個(gè)隨機(jī)數(shù)一般會調(diào)用`Seed`函數(shù),如果讀者逆向分析過這個(gè)函數(shù)的實(shí)現(xiàn)原理,那么讀者應(yīng)該能理解,在調(diào)用取隨機(jī)數(shù)之前會生成一個(gè)隨機(jī)數(shù)種子,這個(gè)隨機(jī)數(shù)種子的生成則依賴于`0x343FDh`這個(gè)特殊的常量地址,當(dāng)我們每次訪問該地址都會產(chǎn)出一個(gè)隨機(jī)的數(shù)據(jù),當(dāng)?shù)玫皆摂?shù)據(jù)后,我們再通過除法運(yùn)算取出溢出數(shù)據(jù)作為隨機(jī)數(shù)使用實(shí)現(xiàn)了該功能。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib

include msvcrt.inc

includelib msvcrt.lib


.data

? seed DWORD 1

? szFmt? ? BYTE '隨機(jī)數(shù): %d',0dh,0ah,0

.code

? ; 生成 0 - FFFFFFFFh 的隨機(jī)種子

? Random32 PROC

? ? push? edx

? ? mov? ?eax, 343FDh

? ? imul? seed

? ? add? ?eax, 269EC3h

? ? mov? ?seed, eax

? ? ror? ?eax,8

? ? pop? ?edx

? ? ret

? Random32 endp

??

? ; 生成隨機(jī)數(shù)

? RandomRange PROC

? ? push? ebx

? ? push? edx

? ??

? ? mov? ?ebx,eax

? ? call? Random32

? ? mov? ?edx,0

? ? div? ?ebx

? ? mov? ?eax,edx


? ? pop? ?edx

? ? pop? ?ebx

? ? ret

? RandomRange endp


? main PROC

??

? ? ; 調(diào)用后取出隨機(jī)數(shù)

? ? call RandomRange

? ? invoke crt_printf,addr szFmt,eax

? ? int 3

? main ENDP

END main

```


### 10.3 局部參數(shù)傳遞


在匯編語言中,可以使用堆棧來傳遞函數(shù)參數(shù)和創(chuàng)建局部變量。當(dāng)程序執(zhí)行到函數(shù)調(diào)用語句時(shí),需要將函數(shù)參數(shù)傳遞給被調(diào)用函數(shù)。為了實(shí)現(xiàn)參數(shù)傳遞,程序會將參數(shù)壓入棧中,然后調(diào)用被調(diào)用函數(shù)。被調(diào)用函數(shù)從棧中彈出參數(shù)并執(zhí)行,然后將返回值存儲在寄存器中,最后通過跳轉(zhuǎn)返回到調(diào)用函數(shù)。


局部變量也可以通過在棧中分配內(nèi)存來創(chuàng)建。在函數(shù)開始時(shí),可以使用push指令將局部變量壓入棧中。在函數(shù)結(jié)束時(shí),可以使用pop指令將變量從棧中彈出。由于棧是后進(jìn)先出的數(shù)據(jù)結(jié)構(gòu),局部變量的創(chuàng)建可以很方便地通過在棧上壓入一些數(shù)據(jù)來實(shí)現(xiàn)。


局部變量是在程序運(yùn)行時(shí)由系統(tǒng)動(dòng)態(tài)的在棧上開辟的,在內(nèi)存中通常在基址指針`(EBP)`之下,盡管在匯編時(shí)不能給定默認(rèn)值,但可以在運(yùn)行時(shí)初始化,如下一段C語言偽代碼:


```C

void MySub()

{

? int var1 = 10;

? int var2 = 20;

}

```

上述的代碼經(jīng)過C編譯后,會變成如下匯編指令,其中`EBP-4`必須是4的倍數(shù),因?yàn)槟J(rèn)就是4字節(jié)存儲,如果去掉了`mov esp,ebp`,那么當(dāng)執(zhí)行`pop ebp`時(shí)將會得到`EBP`等于10,執(zhí)行`RET`指令會導(dǎo)致控制轉(zhuǎn)移到內(nèi)存地址10處執(zhí)行,從而程序會崩潰。

```ASM

MySub PROC

? push ebp? ? ? ? ? ? ? ? ? ; 將EBP存儲在棧中

? mov ebp,esp? ? ? ? ? ? ? ?; 堆棧框架的基址

? sub esp,8? ? ? ? ? ? ? ? ?; 創(chuàng)建局部變量空間(分配2個(gè)局部變量)


? mov DWORD PTR [ebp-8],10? ; var1 = 10

? mov DWORD PTR [ebp-4],20? ; var2 = 20


? mov esp,ebp? ? ? ? ? ? ? ?; 從堆棧上刪除局部變量

? pop ebp? ? ? ? ? ? ? ? ? ?; 恢復(fù)EBP指針

? ret 8? ? ? ? ? ? ? ? ? ? ?; 返回,清理堆棧

MySub ENDP

```


**********


為了使上述代碼片段更易于理解,可以在上述的代碼的基礎(chǔ)上給每個(gè)變量的引用地址都定義一個(gè)符號,并在代碼中使用這些符號,如下代碼所示,代碼中定義了一個(gè)名為`MySub`的過程,該過程將兩個(gè)局部變量分別設(shè)置為`10`和`20`。


在該過程中,首先使用`push ebp`指令將舊的基址指針壓入棧中,并將`ESP`寄存器的值存儲到`ebp`中。這個(gè)舊的基址指針將在函數(shù)執(zhí)行完畢后被恢復(fù)。然后,我們使用`sub esp,8`指令將`8`字節(jié)的空間分配給兩個(gè)局部變量。在堆棧上分配的空間可以通過`var1_local`和`var2_local`符號來訪問。在這里,我們定義了兩個(gè)符號,將它們與`ebp`寄存器進(jìn)行偏移以訪問這些局部變量。`var1_local`的地址為`[ebp-8]`,`var2_local`的地址為`[ebp-4]`。然后,我們使用`mov`指令將`10`和 `20`分別存儲到這些局部變量中。最后,我們將`ESP`寄存器的值存儲回`ebp`中,并使用`pop ebp`指令將舊的基址指針彈出堆?!,F(xiàn)在,棧頂指針(ESP)下移恢復(fù)上面分配的8個(gè)字節(jié)的空間,最后通過`ret 8`返回到調(diào)用函數(shù)。


在使用堆棧傳參和創(chuàng)建局部變量時(shí),需要謹(jǐn)慎考慮棧指針的位置,并確保遵守調(diào)用約定以確保正確地傳遞參數(shù)和返回值。


```ASM

var1_local EQU DWORD PTR [ebp-8]? ?; 添加符號1

var2_local EQU DWORD PTR [ebp-4]? ?; 添加符號2


MySub PROC

? push ebp

? mov ebp,esp

? sub esp,8

? mov var1_local,10

? mov var2_local,20

? mov esp,ebp

? pop ebp

? ret 8

MySub ENDP

```


**********


接著我們來實(shí)現(xiàn)一個(gè)具有功能的案例,首先為了能更好的讓讀者理解我們先使用C語言方式實(shí)現(xiàn)`MakeArray()`函數(shù),該函數(shù)的內(nèi)部是動(dòng)態(tài)生成的一個(gè)`MyString`數(shù)組,并通過循環(huán)填充為星號字符串,最后使用`POP`彈出,并輸出結(jié)果,觀察后嘗試用匯編實(shí)現(xiàn)。


```C

void makeArray()

{

? char MyString[30];

? for(int i=0;i<30;i++)

? {

? ? myString[i] = "*";

? }

}


call makeArray()

```

上述C語言代碼如果翻譯為匯編格式則如下所示,代碼使用匯編語言實(shí)現(xiàn)`makeArray`的程序,該程序開辟了一個(gè)長度為`30`的數(shù)組,將其中的元素填充為`*`,然后彈出兩個(gè)元素,并將它們輸出到控制臺。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib

include msvcrt.inc

includelib msvcrt.lib


.data

? szFmt BYTE '出棧數(shù)據(jù): %x ',0dh,0ah,0

.code

? makeArray PROC

? ? push ebp

? ? mov ebp,esp

? ??

? ? ; 開辟局部數(shù)組

? ? sub esp,32? ? ? ? ? ? ? ? ? ? ; MyString基地址位于 [ebp - 30]

? ? lea esi,[ebp - 30]? ? ? ? ? ? ; 加載MyString的地址

? ??

? ? ; 填充數(shù)據(jù)

? ? mov ecx,30? ? ? ? ? ? ? ? ? ? ; 循環(huán)計(jì)數(shù)

? S1: mov byte ptr ds:[esi],'*'? ? ?; 填充為*

? ? inc esi? ? ? ? ? ? ? ? ? ? ? ?; 每次遞增一個(gè)字節(jié)

? ? loop S1

? ??

? ? ; 彈出2個(gè)元素并輸出,出棧數(shù)據(jù)

? ? pop eax

? ? invoke crt_printf,addr szFmt,eax

? ??

? ? pop eax

? ? invoke crt_printf,addr szFmt,eax??

? ??

? ? ; 以下平棧,由于我們手動(dòng)彈出了2個(gè)數(shù)據(jù)

? ? ; 則平棧 32 - (2 * 4) = 24?

? ? add esp,24? ? ? ? ? ? ? ? ? ? ; 平棧

? ? mov esp,ebp

? ? pop ebp? ? ? ? ? ? ? ? ? ? ? ?; 恢復(fù)EBP

? ? ret

? makeArray endp


? main PROC

? ? call makeArray

? ? invoke ExitProcess,0

? main ENDP

END main

```

在該程序的開始部分,我們首先通過`push ebp`和`mov ebp,esp`指令保存舊的基址指針并將當(dāng)前棧頂指針`(ESP)`存儲到`ebp`中。然后,我們使用`sub esp, 32`指令開辟一個(gè)長度為`30`的數(shù)組`MyString`。我們將`MyString`數(shù)組的基地址存儲在`[ebp - 30]`的位置。使用`lea esi, [ebp - 30]`指令將`MyString`的基地址加載到`esi`寄存器中。該指令偏移`ebp-30`是因?yàn)閌ebp-4`是`MakeArray`函數(shù)的第一個(gè)參數(shù)的位置,因此需要增加四個(gè)字節(jié)。我們利用`MOV byte ptr ds:[esi],'*'`指令將`MyString`中的所有元素填充為`*`。


然后,使用`pop eax`和`invoke crt_printf, addr szFmt, eax`指令兩次彈出兩個(gè)元素,并使用`crt_printf`函數(shù)輸出這些元素。該函數(shù)在`msvcrt.dll`庫中實(shí)現(xiàn),用于將格式化的信息輸出到控制臺。在輸出數(shù)據(jù)之后,我們通過`add esp,24`和`mov esp,ebp`指令將堆棧平衡,恢復(fù)舊的基址指針`ebp`,然后從堆棧中彈出`ebp`,并通過`ret`指令返回到調(diào)用程序。


**********


接著我們繼續(xù)來對比一下堆棧中參數(shù)傳遞的異同點(diǎn),平棧的方式一般可分為調(diào)用者平棧和被調(diào)用者平棧,在使用堆棧傳參時(shí),需要平衡棧以恢復(fù)之前的堆棧指針位置。


- 當(dāng)平棧由被調(diào)用者完成時(shí),被調(diào)用函數(shù)使用`ret`指令將控制權(quán)返回到調(diào)用函數(shù),并從堆棧中彈出返回地址。此時(shí),被調(diào)用函數(shù)需要將之前分配的局部變量從堆棧中彈出,以便調(diào)用函數(shù)能夠恢復(fù)堆棧指針的位置。因此,被調(diào)用函數(shù)必須知道其在堆棧上分配的內(nèi)存大小,并將該大小與其`ret`指令中的參數(shù)相匹配,以便調(diào)用函數(shù)可以正確恢復(fù)堆棧指針位置。


- 當(dāng)平棧由調(diào)用者完成時(shí),調(diào)用函數(shù)需要在調(diào)用子函數(shù)之前平衡堆棧。因此,調(diào)用函數(shù)需要知道子函數(shù)在堆棧上分配的內(nèi)存大小,并在調(diào)用子函數(shù)之前向堆棧提交額外的空間。調(diào)用函數(shù)可以使用`add esp, N`指令來恢復(fù)堆棧指針的位置,其中 N 是被調(diào)用函數(shù)在堆棧上分配的內(nèi)存大小。然后,調(diào)用函數(shù)調(diào)用被調(diào)用函數(shù),該函數(shù)將返回并將堆棧指針恢復(fù)到調(diào)用函數(shù)之前的位置。


如下這段匯編代碼中筆者分別實(shí)現(xiàn)了兩種調(diào)用方式,其中`MyProcA`函數(shù)是一種被調(diào)用者平棧,由于調(diào)用者并沒有堆棧修正所以需要在函數(shù)內(nèi)部通過使用`ret 12`的方式平棧,之所以是12是因?yàn)槲覀兪褂昧巳齻€(gè)局部變量,而第二個(gè)`MyProcB`函數(shù)則是調(diào)用者平棧,該方式在函數(shù)內(nèi)部并沒有返回任何參數(shù),所以在調(diào)用函數(shù)結(jié)束后需要通過`add esp,4`的方式對堆棧進(jìn)行修正。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib

include msvcrt.inc

includelib msvcrt.lib


.data

? szFmt BYTE '數(shù)據(jù): %d ',0dh,0ah,0

.code

? ; 第一種方式:被調(diào)用者平棧

? MyProcA PROC

? ? push ebp

? ? mov ebp,esp

? ??

? ? xor eax,eax

? ? mov eax,dword ptr ss:[ebp + 16]? ?; 獲取第一個(gè)參數(shù)

? ? mov ebx,dword ptr ss:[ebp + 12]? ?; 獲取第二個(gè)參數(shù)

? ? mov ecx,dword ptr ss:[ebp + 8]? ? ; 獲取第三個(gè)參數(shù)

? ??

? ? add eax,ebx

? ? add eax,ebx

? ? add eax,ecx

? ??

? ? mov esp,ebp

? ? pop ebp

? ? ret 12? ? ? ?; 此處ret12可平棧,也可使用 add ebp,12

? MyProcA endp


? ; 第二種方式:調(diào)用者平棧

? MyProcB PROC

? ? push ebp

? ? mov ebp,esp

? ??

? ? mov eax,dword ptr ss:[ebp + 8]

? ? add eax,10

? ??

? ? mov esp,ebp

? ? pop ebp

? ? ret

? MyProcB endp


? main PROC

? ? ; 第一種被調(diào)用者M(jìn)yProcA平棧 3*4 = 12

? ? push 1

? ? push 2

? ? push 3

? ? call MyProcA

? ? invoke crt_printf,addr szFmt,eax

? ??

? ? ; 第二種方式:調(diào)用者平棧

? ? push 10

? ? call MyProcB

? ? add esp,4

? ? invoke crt_printf,addr szFmt,eax

? ??

? ? int 3

? main ENDP

END main

```

當(dāng)然了如果讀者認(rèn)為自己維護(hù)堆棧很繁瑣,則此時(shí)可以直接使用`MASM`匯編器提供的`PROC`定義過程,使用該偽指令匯編器會自行計(jì)算所需要使用的變量數(shù)量并自行在結(jié)尾處添加對應(yīng)的平棧語句,這段代碼實(shí)現(xiàn)起來將變得非常容易理解。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib

include msvcrt.inc

includelib msvcrt.lib


.data

? szFmt BYTE '計(jì)算參數(shù): %d ',0dh,0ah,0


.code

? my_proc PROC x:DWORD,y:DWORD,z:DWORD? ?; 定義過程局部參數(shù)

? ? LOCAL @sum:DWORD? ? ? ? ? ? ? ?; 定義局部變量存放總和

? ??

? ? mov eax,dword ptr ds:[x]

? ? mov ebx,dword ptr ds:[y]? ? ? ?; 分別獲取到局部參數(shù)

? ? mov ecx,dword ptr ds:[z]

? ??

? ? add eax,ebx

? ? add eax,ecx? ? ? ? ? ? ? ? ? ? ; 相加后放入eax

? ? mov @sum,eax

? ? ret

? my_proc endp


? main PROC

? ? LOCAL @ret_sum:DWORD

? ? push 10

? ? push 20

? ? push 30? ? ? ? ? ; 傳遞參數(shù)

? ? call my_proc

? ? mov @ret_sum,eax ; 獲取結(jié)果并打印

? ??

? ? invoke crt_printf,addr szFmt,@ret_sum

? ??

? ? int 3

? main ENDP

END main

```


這里筆者還需要擴(kuò)展一個(gè)偽指令`LOCAL`,LOCAL是一種匯編語言中的偽指令,用于定義存儲在堆棧上的局部變量。使用`LOCAL`指令定義的局部變量只在函數(shù)執(zhí)行時(shí)存在,當(dāng)函數(shù)返回后,該變量將被刪除。根據(jù)使用`LOCAL`指令時(shí)指定的內(nèi)存空間大小,匯編器將為每個(gè)變量保留足夠的空間。


例如,下面是一個(gè)使用LOCAL定義局部變量的示例:


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


.code

? main PROC

? ? ; 定義局部變量,自動(dòng)壓棧/平棧

? ? LOCAL var_byte:BYTE,var_word:WORD,var_dword:DWORD

? ? LOCAL var_array[3]:DWORD

? ??

? ? ; 填充局部變量

? ? mov byte ptr ds:[var_byte],1

? ? mov word ptr ds:[var_word],2

? ? mov dword ptr ds:[var_dword],3

? ??

? ? ; 填充數(shù)組方式1

? ? lea esi,dword ptr ds:[var_array]

? ? mov dword ptr ds:[esi],10

? ? mov dword ptr ds:[esi + 4],20

? ? mov dword ptr ds:[esi + 8],30

? ??

? ? ; 填充數(shù)組方式2

? ? mov var_array[0],100

? ? mov var_array[1],200

? ? mov var_array[2],300

? ??

? ? invoke ExitProcess,0

? main ENDP

END main

```


在上述示例代碼中,`main`過程使用`LOCAL`指令定義了幾個(gè)局部變量,包括一個(gè)字節(jié)類型的變量`var_byte`、一個(gè)字類型的變量`var_word`、一個(gè)雙字類型的變量`var_dword`和一個(gè)包含三個(gè)雙字元素的數(shù)組`var_array`。


在代碼中,我們使用`mov`指令填充這些變量的值。對于字節(jié)類型、字類型和雙字類型的變量,使用`mov byte ptr ds:[var_byte], 1`、`mov word ptr ds:[var_word], 2`和`mov dword ptr ds:[var_dword], 3`指令將相應(yīng)的常數(shù)值存儲到變量中。在填充數(shù)組時(shí),分別使用了兩種不同的方式。一種方式是使用`lea`指令將數(shù)組的地址加載到`esi`寄存器中,然后使用`mov dword ptr ds:[esi],10`等指令將相應(yīng)的常數(shù)值存儲到數(shù)組中。另一種方式是直接訪問數(shù)組元素,如`mov var_array[0], 100`等指令。需要注意,由于數(shù)組元素在內(nèi)存中是連續(xù)存儲的,因此可以使用`[]`操作符訪問數(shù)組元素。


在匯編中使用`LOCAL`偽指令來實(shí)現(xiàn)自動(dòng)計(jì)算局部變量空間,以及最后的平棧操作,將會極大的提高開發(fā)效率。


### 10.4 USES/ENTER


USES是匯編語言中的偽指令,用于保存一組寄存器的狀態(tài),以便函數(shù)調(diào)用過程中可以使用這些寄存器。使用USES時(shí),程序可以保存一組需要保護(hù)的寄存器,匯編器將在程序入口處自動(dòng)向堆棧壓入這些寄存器的值。讀者需注意,我們可以在需要保存寄存器的程序段中使用USES來保護(hù)寄存器,但不應(yīng)在整個(gè)程序中重復(fù)使用寄存器。


ENTER也是一種偽指令,用于創(chuàng)建函數(shù)調(diào)用過程中的堆棧幀。使用ENTER時(shí),程序可以定義一個(gè)名為ENTER的指定大小的堆棧幀。該指令會將新的基準(zhǔn)指針ebp 壓入堆棧同時(shí)將當(dāng)前的基準(zhǔn)指針ebp存儲到另一個(gè)寄存器ebx中,然后將堆棧指針esp減去指定大小的值,獲取新的基地址,并將新的基地址存儲到ebp 中。之后,程序可以在此幀上創(chuàng)建和訪問局部變量,并使用LEAVE指令將堆棧幀刪除,將ebp恢復(fù)為舊的值,同時(shí)將堆棧指針平衡。


在使用USES和ENTER指令時(shí),需要了解這些指令在具體的平臺上的支持情況,以及它們適用的調(diào)用約定。通常情況下,在函數(shù)開頭,我們將使用ENTER創(chuàng)建堆棧幀,然后使用USES指定需要保護(hù)的寄存器。在函數(shù)末尾,我們使用LEAVE刪除堆棧幀。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


.code

? ; USES 自動(dòng)壓入 eax,ebx,ecx,edx

? my_proc PROC USES eax ebx ecx edx x:DWORD,y:DWORD

? ? enter 8,0? ? ? ? ? ; 自動(dòng)保留8字節(jié)堆??臻g

? ? add eax,ebx

? ? leave

? my_proc endp


? main PROC

? ? mov eax,10

? ? mov ebx,20

? ? call my_proc

? ??

? ? int 3

? main ENDP

END main

```


### 10.5 STRUCT/UNION


STRUCT和UNION是匯編語言中的數(shù)據(jù)類型,STRUCT是一種復(fù)合數(shù)據(jù)類型,它將多個(gè)不同類型的變量按順序放置在一起,并使用單個(gè)名稱來引用集合。使用STRUCT時(shí),我們可以將不同類型的變量組合成一個(gè)結(jié)構(gòu)體并定義其屬性,如結(jié)構(gòu)體中包含的成員變量的數(shù)據(jù)類型、名稱和位置。


例如,下面是一個(gè)使用STRUCT定義自定義類型的示例:


```ASM

; 定義一個(gè)名為 MyStruct 的結(jié)構(gòu)體,包含兩個(gè)成員變量。

MyStruct STRUCT

? ?Var1 DWORD ?

? ?Var2 WORD ?

MyStruct ENDS

```


在上述示例代碼中,我們使用`STRUCT`定義了一個(gè)名為`MyStruct `的結(jié)構(gòu)體,其中包含兩個(gè)成員變量`Var1`和`Var2`。其中,`Var1`是`DWORD`類型的數(shù)據(jù)類型,以問號`?`形式指定了其默認(rèn)值,`Var2`是`WORD`類型的數(shù)據(jù)類型。


另一個(gè)數(shù)據(jù)類型是`UNION`,它也是一種復(fù)合數(shù)據(jù)類型,用于將多個(gè)不同類型的變量疊加在同一內(nèi)存位置上。使用`UNION`時(shí),程序內(nèi)存中的數(shù)據(jù)將只占用所有成員變量中最大的數(shù)據(jù)類型變量的大小。與結(jié)構(gòu)體不同,聯(lián)合中的所有成員變量共享相同的內(nèi)存位置。我們可以使用一種成員變量來引用內(nèi)存位置,但在任何時(shí)候僅能有一種成員變量存儲在該內(nèi)存位置中。


例如,下面是一個(gè)使用UNION定義自定義類型的示例:


```ASM

; 定義一個(gè)名為 MyUnion 的聯(lián)合,包含兩個(gè)成員變量。

MyUnion UNION

? ?Var1 DWORD ?

? ?Var2 WORD ?

MyUnion ENDS

```


在上述示例代碼中,我們使用`UNION`定義了一個(gè)名為`MyUnion`的聯(lián)合,其中包含兩個(gè)不同類型的成員變量`Var1`和`Var2`,將它們相對應(yīng)地置于聯(lián)合的同一內(nèi)存位置上。


讀者在使用`STRUCT`和`UNION`時(shí),需要根據(jù)內(nèi)存分布和變量類型來正確訪問成員變量的值。在匯編語言中,結(jié)構(gòu)體和聯(lián)合主要用于定義自定義數(shù)據(jù)類型、通信協(xié)議和系統(tǒng)數(shù)據(jù)結(jié)構(gòu)等,如下一段代碼則是匯編語言中實(shí)現(xiàn)結(jié)構(gòu)體賦值與取值的總結(jié)。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


; 定義坐標(biāo)結(jié)構(gòu)

MyPoint Struct

? pos_x DWORD ?

? pos_y DWORD ?

? pos_z DWORD ?

MyPoint ends


; 定義人物結(jié)構(gòu)

MyPerson Struct

? Fname db 20 dup(0)

? fAge? db 100

? fSex? db 20

MyPerson ends


.data

? ; 聲明結(jié)構(gòu): 使用 <>,{}符號均可

? PtrA MyPoint <10,20,30>

? PtrB MyPoint {100,200,300}

??

? ; 聲明結(jié)構(gòu): 使用MyPerson聲明結(jié)構(gòu)

? UserA MyPerson <'lyshark',24,1>


.code

? main PROC

? ? ; 獲取結(jié)構(gòu)中的數(shù)據(jù)

? ? lea esi,dword ptr ds:[PtrA]

? ? mov eax,(MyPoint ptr ds:[esi]).pos_x

? ? mov ebx,(MyPoint ptr ds:[esi]).pos_y

? ? mov ecx,(MyPoint ptr ds:[esi]).pos_z

? ??

? ? ; 向結(jié)構(gòu)中寫入數(shù)據(jù)

? ? lea esi,dword ptr ds:[PtrB]

? ? mov (MyPoint ptr ds:[esi]).pos_x,10

? ? mov (MyPoint ptr ds:[esi]).pos_y,20

? ? mov (MyPoint ptr ds:[esi]).pos_z,30

? ??

? ? ; 直接獲取結(jié)構(gòu)中的數(shù)據(jù)

? ? mov eax,dword ptr ds:[UserA.Fname]

? ? mov ebx,dword ptr ds:[UserA.fAge]

? ? int 3

? main ENDP

END main

```


接著我們來實(shí)現(xiàn)一個(gè)輸出結(jié)構(gòu)體數(shù)組的功能,結(jié)構(gòu)數(shù)組其實(shí)就是一維的空間,因此使用兩個(gè)比例因子即可實(shí)現(xiàn)尋址操作,如下代碼我們先來實(shí)現(xiàn)一個(gè)簡單的功能,只遍歷第一層,結(jié)構(gòu)數(shù)組外層的數(shù)據(jù)。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib

include msvcrt.inc

includelib msvcrt.lib


; 定義坐標(biāo)結(jié)構(gòu)

MyPoint Struct

? pos_x DWORD ?

? pos_y DWORD ?

? pos_z DWORD ?

MyPoint ends


; 定義循環(huán)結(jié)構(gòu)

MyCount Struct

? count_x DWORD ?

? count_y DWORD ?

MyCount ends


.data

? ; 聲明結(jié)構(gòu): 使用 <>,{}符號均可

? PtrA? MyPoint <10,20,30>,<40,50,60>,<70,80,90>,<100,110,120>

? Count MyCount <0,0>

? szFmt BYTE '結(jié)構(gòu)數(shù)據(jù): %d',0dh,0ah,0


.code

? main PROC

? ? ; 獲取結(jié)構(gòu)中的數(shù)據(jù)

? ? lea esi,dword ptr ds:[PtrA]

? ? mov eax,(MyPoint ptr ds:[esi]).pos_x? ? ? ? ? ; 獲取第一個(gè)結(jié)構(gòu)X

? ? mov eax,(MyPoint ptr ds:[esi + 12]).pos_x? ? ?; 獲取第二個(gè)結(jié)構(gòu)X

? ??

? ? ; while 循環(huán)輸出結(jié)構(gòu)的每個(gè)首元素元素

? ? mov (MyCount ptr ds:[Count]).count_x,0

? S1: cmp (MyCount ptr ds:[Count]).count_x,48? ? ? ? ; 12 * 4 = 48

? ? jge lop_end

? ??

? ? mov ecx,(MyCount ptr ds:[Count]).count_x

? ? mov eax,dword ptr ds:[PtrA + ecx]? ? ? ? ? ? ? ; 尋找首元素

? ? invoke crt_printf,addr szFmt,eax

? ??

? ? mov eax,(MyCount ptr ds:[Count]).count_x

? ? add eax,12? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?; 每次遞增12

? ? mov (MyCount ptr ds:[Count]).count_x,eax

? ? jmp S1


? lop_end:

? ? int 3

? main ENDP

END main

```

接著我們遞增難度,通過每次遞增將兩者的偏移相加,獲得比例因子,通過因子嵌套雙層循環(huán)實(shí)現(xiàn)尋址打印。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib

include msvcrt.inc

includelib msvcrt.lib


; 定義坐標(biāo)結(jié)構(gòu)

MyPoint Struct

? pos_x DWORD ?

? pos_y DWORD ?

? pos_z DWORD ?

MyPoint ends


; 定義循環(huán)結(jié)構(gòu)

MyCount Struct

? count_x DWORD ?

? count_y DWORD ?

MyCount ends


.data

? ; 聲明結(jié)構(gòu): 使用 <>,{}符號均可

? PtrA? MyPoint <10,20,30>,<40,50,60>,<70,80,90>,<100,110,120>

? Count MyCount <0,0>

? szFmt BYTE '結(jié)構(gòu)數(shù)據(jù): %d',0dh,0ah,0


.code

? main PROC

? ? ; 獲取結(jié)構(gòu)中的數(shù)據(jù)

? ? lea esi,dword ptr ds:[PtrA]

? ? mov eax,(MyPoint ptr ds:[esi]).pos_x? ? ? ? ? ; 獲取第一個(gè)結(jié)構(gòu)X

? ? mov eax,(MyPoint ptr ds:[esi + 12]).pos_x? ? ?; 獲取第二個(gè)結(jié)構(gòu)X

? ??

? ? ; while 循環(huán)輸出結(jié)構(gòu)的每個(gè)首元素元素

? ? mov (MyCount ptr ds:[Count]).count_x,0

? S1: cmp (MyCount ptr ds:[Count]).count_x,48? ? ? ? ; 12 * 4 = 48

? ? jge lop_end

? ??

? ? mov (MyCount ptr ds:[Count]).count_y,0

? S3: cmp (MyCount ptr ds:[Count]).count_y,12? ? ? ? ; 3 * 4 = 12

? ? jge S2

? ??

? ? mov eax,(MyCount ptr ds:[Count]).count_x

? ? add eax,(MyCount ptr ds:[Count]).count_y? ? ? ?; 相加得到比例因子

? ??

? ? mov eax,dword ptr ds:[PtrA + eax]? ? ? ? ? ? ? ; 使用相對變址尋址

? ? invoke crt_printf,addr szFmt,eax

? ??

? ? mov eax,(MyCount ptr ds:[Count]).count_y

? ? add eax,4? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ; 每次遞增4

? ? mov (MyCount ptr ds:[Count]).count_y,eax

? ? jmp S3?

? ??

? S2: mov eax,(MyCount ptr ds:[Count]).count_x

? ? add eax,12? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?; 每次遞增12

? ? mov (MyCount ptr ds:[Count]).count_x,eax

? ? jmp S1


? lop_end:

? ? int 3

? main ENDP

END main

```

結(jié)構(gòu)體同樣支持內(nèi)嵌的方式,如下`Rect`指針中內(nèi)嵌兩個(gè)`MyPoint`分別指向左子域和右子域,這里順便定義一個(gè)`MyUnion`聯(lián)合體把,其使用規(guī)范與結(jié)構(gòu)體完全一致,只不過聯(lián)合體只能存儲一個(gè)數(shù)據(jù).

```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib


; 定義坐標(biāo)結(jié)構(gòu)

MyPoint Struct

? pos_x DWORD ?

? pos_y DWORD ?

? pos_z DWORD ?

MyPoint ends


; 定義左右結(jié)構(gòu)

Rect Struct

? Left MyPoint <>

? Right MyPoint <>

Rect ends


; 定義聯(lián)合體

MyUnion Union

? my_dword DWORD ?

? my_word WORD ?

? my_byte BYTE ?

MyUnion ends


.data

? PointA Rect <>

? PointB Rect {<10,20,30>,<100,200,300>}

? test_union MyUnion {1122h}

? szFmt BYTE '結(jié)構(gòu)數(shù)據(jù): %d',0dh,0ah,0

.code

? main PROC

? ? ; 嵌套結(jié)構(gòu)的賦值

? ? mov dword ptr ds:[PointA.Left.pos_x],100

? ? mov dword ptr ds:[PointA.Left.pos_y],200

? ? mov dword ptr ds:[PointA.Right.pos_x],100

? ? mov dword ptr ds:[PointA.Right.pos_y],200

? ??

? ? ; 通過地址定位

? ? lea esi,dword ptr ds:[PointB]

? ? mov eax,dword ptr ds:[PointB]? ? ? ? ; 定位第一個(gè)MyPoint

? ? mov eax,dword ptr ds:[PointB + 12]? ?; 定位第二個(gè)內(nèi)嵌MyPoint


? ? ; 聯(lián)合體的使用

? ? mov eax,dword ptr ds:[test_union.my_dword]

? ? mov ax,word ptr ds:[test_union.my_word]

? ? mov al,byte ptr ds:[test_union.my_byte]

? main ENDP

END main

```


當(dāng)然有了結(jié)構(gòu)體這一成員的加入,我們同樣可以在匯編層面實(shí)現(xiàn)鏈表的定義與輸出,如下代碼所示,首先定義一個(gè)`ListNode`用于存儲鏈表結(jié)構(gòu)的數(shù)據(jù)域與指針域,接著使用`TotalNodeCount`定義鏈表節(jié)點(diǎn)數(shù)量,最后使用`REPEAT`偽指令開辟`ListNode`對象的多個(gè)實(shí)例,其中的`NodeData`域包含一個(gè)`1-15`的數(shù)據(jù),后面的`($ + Counter * sizeof ListNode)`則是指向下一個(gè)鏈表的頭指針,通過不斷遍歷則可輸出整個(gè)鏈表。


```ASM

? .386p

? .model flat,stdcall

? option casemap:none


include windows.inc

include kernel32.inc

includelib kernel32.lib

include msvcrt.inc

includelib msvcrt.lib


ListNode Struct

? NodeData DWORD ?

? NextPtr? DWORD ?

ListNode ends


TotalNodeCount = 15

Counter = 0


.data

? LinkList LABEL PTR ListNode

? REPEAT TotalNodeCount

? ? Counter = Counter + 1

? ? ListNode <Counter,($ + Counter * sizeof ListNode)>

? ENDM

? ListNode<0,0>? ? ? ? ? ? ? ? ; 標(biāo)志著結(jié)構(gòu)鏈表的結(jié)束


? szFmt BYTE '結(jié)構(gòu)地址: %x 結(jié)構(gòu)數(shù)據(jù): %d',0dh,0ah,0

.code

? main PROC

? ? mov esi,offset LinkList

? ??

? ? ; 判斷下一個(gè)節(jié)點(diǎn)是否為<0,0>

? L1: mov eax,(ListNode PTR [esi]).NextPtr

? ? cmp eax,0

? ? je lop_end

? ??

? ? ; 顯示節(jié)點(diǎn)數(shù)據(jù)

? ? mov eax,(ListNode PTR [esi]).NodeData

? ? invoke crt_printf,addr szFmt,esi,eax

? ??

? ? ; 獲取到下一個(gè)節(jié)點(diǎn)的指針

? ? mov esi,(ListNode PTR [esi]).NextPtr

? ? jmp L1

? ??

? lop_end:

? ? int 3

? ??

? main ENDP

END main

```


本文作者: 王瑞

本文鏈接: https://www.lyshark.com/post/e43f6d19.html

版權(quán)聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協(xié)議。轉(zhuǎn)載請注明出處!


5.10 匯編語言:匯編過程與結(jié)構(gòu)的評論 (共 條)

分享到微博請遵守國家法律
繁昌县| 乐至县| 乾安县| 隆德县| 金坛市| 普安县| 会宁县| 宁波市| 潮州市| 安平县| 封丘县| 东兰县| 甘肃省| 顺平县| 基隆市| 邳州市| 安陆市| 化州市| 沙坪坝区| 南涧| 阜新| 姜堰市| 奉节县| 洱源县| 富民县| 张家口市| 克拉玛依市| 尼玛县| 信丰县| 兴安盟| 巍山| 治县。| 茶陵县| 调兵山市| 正定县| 白水县| 桃江县| 乐安县| 霍州市| 常宁市| 兴海县|