日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

Intel汇编语言程序设计学习-第五章 过程-下

發(fā)布時間:2025/6/17 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Intel汇编语言程序设计学习-第五章 过程-下 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

5.3.3 ?庫測試程序

測試程序#1:整數(shù)I/O

? ? 該測試程序把輸出文本的顏色改為藍底黃字,然后以十六進制數(shù)顯示七個數(shù)組的內(nèi)容,最后提示用戶輸入一個有符號整數(shù),再分別以十進制、十六進制和二進制格式重復顯示該整數(shù):

TITLE Library Test #1: Integer I/O (TestLib1.asm)

;Test the Clrscr,Crlf,DumpMem,ReadInt,

;SetTextColor,WaiMsg,WriteBin,WriteHex,

;and WriteString procedures.

INCLUDE Irvine32.inc

.data

arrayD DWORD 1000h,2000h,3000h

prompt1 BYTE "Enter a 32-bit stgned integer:",0

dwordVal DWORD ?

.code

start: call main

main PROC

mov eax ,yellow + (blue * 16)

call SetTextColor

call Clrscr ????????????????????

?

mov esi ,OFFSET arrayD

mov ecx ,LENGTHOF arrayD

mov ebx ,TYPE arrayD

call DumpMem

call Crlf

?

mov edx ,OFFSET prompt1

call WriteString

call ReadInt

mov dwordVal ,eax

?

call Crlf

call WriteInt

call Crlf

call WriteHex

call Crlf

call WriteBin

call Crlf

call WaitMsg

?

mov eax ,lightGray + (black * 16)

call SetTextColor

call Clrscr

exit

main ENDP

end start

運行結果:

?

測試程序#2:隨機整數(shù)

2個庫測試程演示隨機數(shù)使用過程。首先,隨機產(chǎn)生10個在0~4294967294內(nèi)的無符號整數(shù),接著隨機再生成10個在范圍-50~+49內(nèi)的有符號整數(shù):

TITLE Link Library Test #2 (TestLib2.asm)

INCLUDE Irvine32.inc

TAB = 9

.code

main PROC

????call ?Randomize

call ?Rand1

call ?Rand2

exit

main ENDP

?

Rand1 PROC

????mov ?ecx ,10

L1: call ?Random32

????call ?WriteDec

mov ??al ,TAB

call ?WriteChar

loop L1

call Crlf

ret

Rand1 ENDP

?

Rand2 PROC

????mov ?ecx ,10

L1 :mov ?eax ,100

????call RandomRange

sub ?eax ,50

call WriteInt

mov ?al ,TAB

call WriteChar

loop L1

call Crlf

ret

Rand2 ENDP

END main

運行結果:


測試程序#3:性能度量

? ? 匯編語言常用于優(yōu)化對程序性能而言至關重要的代碼。GetMseconds過程返回自午夜以來逝去的毫秒數(shù),在循環(huán)之前調(diào)用了GetMseconds過程,然后執(zhí)行嵌套循環(huán)約170億次,在循環(huán)結束后再次調(diào)用GetMsgconds過程并報告用掉的時間:

TITLE Link Library Test #3

INCLUDE Irvine32.inc

OUTER_LOOP_COUNT = 3

.data

startTime DWORD ?

msg1 BYTE "Please wait..." ,0dh ,0ah ,0;

msg2 BYTE "Elapsed milliseconds:" ,0

.code

main PROC

????mov ?edx ,OFFSET msg1

call WriteString

call GetMSeconds

mov ?startTime ,eax

mov ?ecx ,OUTER_LOOP_COUNT

L1: call innerLoop

????loop L1

call GetMSeconds

sub ?eax ,startTime

mov ?edx ,OFFSET msg2

call WriteString

call WriteDec

call Crlf

exit

main ENDP

innerLoop PROC

????push ecx

mov ?ecx ,0FFFFFFFFh

L1: mov ?eax ,eax

????loop L1

pop ecx

ret

innerLoop ENDP

END main


執(zhí)行結果:


5.4 ?堆棧操作

堆棧的定義不解釋了,后進先出。

5.4.1 ?運行時棧

運行時棧是由CPU直接管理的內(nèi)存數(shù)組,它使用兩個寄存器:SSESP。在保護模式下,SS寄存器存放的是段選擇子,用戶模式程序不應對其進行修改。ESP寄存器存放的是指向堆棧內(nèi)特定位置的一個32位偏移值。我們很少需要直接操縱ESP的值,相反,ESP寄存器的值通常是由CALLRETPUSHPOP等指令間接修改的。

堆棧指令寄存器(ESP)指向最后壓入(或添加)堆棧上的數(shù)據(jù)。


? ? 這里討論的運行時棧同程序設計課程中講述的堆棧抽象數(shù)據(jù)類型(stack ADT)是不同的。運行時棧在系統(tǒng)層上(由硬件直接實現(xiàn))處理子過程調(diào)用;堆棧抽象數(shù)據(jù)類型通常用于實現(xiàn)依賴于先進后出操作的算法,一般使用高級語言如C++Java等編寫。

壓棧操作

32位的壓棧(PUSH)操作首先將堆棧指針減4,然后把要壓棧的值賦值到堆棧指針所指向的位置處。

?

出棧操作

????出棧與壓棧相反


? ??堆棧中ESP之下的區(qū)域從邏輯上講是空白的,在程序下次執(zhí)行任何要壓棧的指令時該區(qū)域?qū)⒈桓采w重寫。

堆棧的應用

寄存器在做多種用途的時候,堆棧可以方便的作為臨時保存區(qū)域,在寄存器使用完畢之后,可通過堆棧恢復其原始值。

CALL指令執(zhí)行的時候,CPU用堆棧保存當前被調(diào)用過程的返回地址。

調(diào)用過程的時候,可以通過壓棧傳遞輸入值(成為參數(shù))。

過程內(nèi)的局部變量在堆棧上創(chuàng)建,過程結束時,這些變量被丟棄。

5.4.2 ?PUSHPOP指令

PUSH指令

PUSH指令首先減少ESP的值,然后再把一個16位或32位的源操作數(shù)復制到堆棧上。對于16位的操作數(shù),ESP值將減2;對于32位操作數(shù),ESP值將減4.PUSH指令有一下三種格式:

PUSH ?r/m16

PUSH ?r/m32

PUSH ?imm32

如果程序調(diào)用Irvine32中的過程,應總是壓入32位值,否則庫中使用的Win32控制數(shù)將不能正常運行。如果程序調(diào)用Irvine16中的庫過程(實地址模式下)則可壓入16位或32位的值。

在保護模式下立即數(shù)總是32位。在實地址模式下,如果未使用.386(或更高的)處理器偽指令,立即數(shù)默認是16位的。

POP指令

POP指令首先將ESP所指的堆棧元素復制到16位或32位的目的操作數(shù)中,然后增加ESP的值。如果操作數(shù)是16位的,ESP值將加2;如果操作數(shù)是32位的,ESP值將加4.其格式如下:

POP ?r/m16

POP ?r/m32

PUSHFDPOPFD指令

PUSHFD指令在堆棧上壓入32位的EFLAGS寄存器的值,POPFD指令將堆棧頂部彈出并送至EFLAGS寄存器:

pushfd

popfd

實地址則是16位的。

MOV指令不能復制標志寄存器的值至變量或寄存器中,因此使用PUSHFD指令可能就是保存寄存器的最佳方式了。有時保存標志的備份以便后面進行恢復是很有用的,這通常可以是用PUSHFDPOPFD指令把一塊指令包圍起來:

pushfd ?????????????;保存標志

;

;......

;

popfd ??????????????;恢復標志

在使用這種類型的標志壓棧和標志出棧指令的時候,必須確保程序的執(zhí)行路徑不會跳過POPFD指令。隨著時間的推移,在修改程序時很難激情所有的壓棧指令放在哪里。因此,編寫準確的文檔是非常關鍵的!

可以完成同樣功能但或許可減少犯錯誤的方法是將標志保存在變量中:

.data

saveFlags DWORD

.code

pushfd ????????????????;標志入棧

pop saveFlags ??????????;復制到變量

下列語句從同一變量中回復標志值:

push saveFlags ??????????;將保存的標志入棧

popfd ?????????????????;回復標志

PUSHAD,PUSHA,POPADPOPA指令

PUSHAD指令在堆棧上按下列循序壓入所有的32為通用寄存器:

EAX,ECX,EDX,EBX,RSP(執(zhí)行PUSHAD指令之前的值),EBP,ESIEDIPOPAD指令以相反的循序從堆棧中彈出這些通用寄存器。于此類似,80286處理器引入的PUSHA指令以括號中列表的循序壓入所有的16位寄存器(AX,CX,DX,BX,SP,BP,SIDI)。POPA指令則以相反順序彈出這些寄存器。

如果在過程中修改了很多32位寄存器,那么可以在過程的開始和結束的位置分別用PUSHADPOPAD指令保存和恢復寄存器的值。

?

MySub PROC

????pushad

????.

????.

????mov ?eax ,...

????mov ?edx ,...

????mov ?ecx ,...

????.

????.

????popad

????ret

MySub ENDP

對上面的例子,有一個例外情況必須指出:過程通過一個或多個寄存器返回結果時不應該使用PUSHAPUSHAD指令。假設下面的RcadValue過程想要通過EAX返回一個整數(shù)但對POPAD的調(diào)用將覆蓋EAX中的返回值:

ReadValue PROC

????pushad ?????????????;保存通用寄存器

????.

????.

????mov ?eax ,return_value

????.

????.

????popad

????ret

?ReadValue ENDP

例子:反轉(zhuǎn)字符串

RevStr.asm程序循環(huán)遍歷字符串并把每個字符串都壓入堆棧,然后取出來。

TITLE Reversing a String ?(RevStr.asm)

INCLUDE ?Irvine32.inc

.data

aName BYTE "Abraham Lincoln",0

nameSize = ($ - aName) - 1

.code

main PROC

?????mov ?ecx ,nameSize

mov ?esi ,0

mov ?eax ,0

L1: mov ?al,aName[esi]

????push eax

inc ?esi

loop L1

????

mov ?ecx ,nameSize

mov ?esi ,0

L2: pop ?eax

????mov ?aName[esi] ,al

inc ?esi

loop L2

mov ?edx ,OFFSET aName

call WriteString

call Crlf

exit

main ENDP

END main

運行結果:

?

? ? TIP:書中上面的代碼有第一個地方有問題(L1: mov eax,aName[esi],如果這么寫編譯器會彈出編譯錯誤,原因是aName[esi]8位,eax32位的,但是eax32位,ax是第16位,al是第八位,所以直接eax換成al就行了,但是記住之前要把eax清零,因為堆棧是接收32位的,我們處理al之后把eax壓入堆棧可能把前面的高位壓進去(但是上面這個程序壓進去結果也看不出來,因為我們始終只操作al),但是壓入eax進棧本身就是錯誤的。所以需要eax清零。在使用低位。

5.5 ?過程的定義和使用

可以理解成是C++或是其它語言里的函數(shù)或者方法等。

5.5.1 ?PROC偽指令

過程的定義

可以把過程非正式地定義為以返回語句結束的命令語句塊。過程使用PROC偽指令和ENDP偽指令來聲明,另外還必須給過程定義一個名字。到現(xiàn)在寫的所有程序都包含一個名為main的過程,例如:

main PROC

.

.

.

main ENDP

程序啟動過程之外的其他過程以RET指令結束,以強制CPU返回到過程被調(diào)用的地方:

sample ?PROC

??.

??.

??ret

sample ?ENDP

啟動過程(main)是個特例,它以exit語句結束。如果程序中使用了INCLUD Irvine32.inc語句的話,exit語句實際上就是對ExitProcess函數(shù)的調(diào)用,ExitProcess是用來終止程序的系統(tǒng)函數(shù)

INVOKE ExitProcess ,0

????如果在程序中使用了INCLUDE Irvine16.inc語句,那么exit被翻譯成.EXIT偽指令。匯編器為.EXIT生成下面兩條語句:

????mov ?ah,4C00h ??;調(diào)用MS-DOS4c00h功能

int 21h ?????????;終止程序

例子:三個整數(shù)之和

我們創(chuàng)建一個名為SumOf的過程來計算332位整數(shù)之和,假設合適的整數(shù)在過程被調(diào)用以前已經(jīng)存放在EAXEBXECX寄存器中了,過程在EAX中發(fā)回和:

SumOf PROC

????add ?eax ,ebx

????add ?eax ,ecx

????ret

SumOf ENDP

為過程添加文檔

應該養(yǎng)成的良好編程習慣之一就是為程序添加清晰易讀的文檔。下面是對放在每個過程開始處的文檔信息的幾點建議:

過程完成的所有任務的描述。

輸入?yún)?shù)的清單使用方法。

過程返回值的描述。

列出特殊要求。

;------------------------------------------------------------

SumOf PROC

;

;Calculates and returns the sum of three 32-bit integers.

;Receines:EAX,EBX,ECX,the three integers,May be signed or unsigned.

;Retuens: EAX = sum

;--------------------------------------------------------------

????add ?eax ,ebx

????add ?eax ,ecx

????ret

SumOf ENDP

????C/C++之類的高級語言編寫的函數(shù),典型情況下在AL中返回8位值,在AX中返回16位置,在EAX中返回32位值。

5.5.2 ?CALLRET指令

CALL指令只是處理器在新的內(nèi)存地址執(zhí)行指令,以實現(xiàn)過程的調(diào)用。過程使用RET(從過程返回)指令使處理器返回到程序過程被調(diào)用的地方繼續(xù)執(zhí)行。從底層細節(jié)角度來講,CALL指令把返回地址壓入堆棧并把被調(diào)用過程的地址復制到指令寄存器中。當程序返回時,RET指令從堆棧中彈出返回地址并送到指令寄存器中。在32位模式下,CPU總是執(zhí)行EIP(指令指針寄存器)所指向的內(nèi)存出的指令;在16位模式下,CPU總是執(zhí)行IP寄存器指向的指令。

調(diào)用和返回的例子

假設在main中,CALL語句位于偏移00000020處。通常CALL指令的機器碼需要5字節(jié),因此下一條指令位于偏移00000025處:

??????main PROC

00000020 ?????call ?MySub

00000025 ?????mov eax ,ebx

接下來,假設MySub中的第一條指令位于偏移00000040處:

??????????MySub PROC

00000040 ???mov ?eax ,edx

????????????.

????????????.

????????????ret

???????????MySub ENDP

CALL指令執(zhí)行的時候,金針CALL指令的地址(00000025)被壓入堆棧,而MySub的地址被裝入EIPMySub內(nèi)的指令開始執(zhí)行,一直到RET指令位置。當RET指令被執(zhí)行的時候,ESP所指的堆棧值被彈出并送至EIP。第二部,ESP的值將減少以指向堆棧上的前一個值。

?

Sub3過程結束的時候執(zhí)行RET指令,從堆棧中彈出[ESP]處的值送至指令寄存器,這將使得CPU從緊跟調(diào)用Sub3之后的指令處恢復執(zhí)行,下圖顯示了在從Sub3過程返回之前的堆棧狀況:

?

返回之后,ESP指向相鄰的堆棧表項,在Sub2末尾RET指令準備執(zhí)行時,堆棧如下表示:


? ? TIP:看到這我就一直在想一個問題,如果我自己寫了一個函數(shù),然后我在里面直接更改了堆棧,但是我并沒有還原相關,也就是我更改堆棧會不會導致這個函數(shù)return不回去(因為我不確定我用的堆棧和CPU調(diào)度用的堆棧是不是同一個堆棧,也就是作用域的問題),于是我做了這個嘗試:

TITLE TEST STACK (teststack.asm)

INCLUDE Irvine32.inc

.data

strStart BYTE "Start!" ,0dh ,0ah,0

strEnd ??BYTE "End!" ,0dh ,0ah ,0

strTest ?BYTE "RunTestFun!",0dh ,0ah ,0

.code

main PROC

????mov ?edx ,OFFSET strStart

????call WriteString

????call TestFun

????mov ?edx ,OFFSET strEnd

????call WriteString

????exit

main ENDP

?

TESTFun PROC

????mov edx ,OFFSET strTest

call WriteString

pop edx

ret

TESTFun ENDP

END main

????我在函數(shù)里直接POP了棧里的東西,如果用的是同一個棧,那么這樣應該是跳轉(zhuǎn)不回去的。結果也應該是不可預知的。然后操作的結果卻是是這樣。直接沒有return成功,我還用vs反匯編看了下地址,在函數(shù)里面POP出來的那個值就是call TestFun接下來那個call writestring的地址。so...

過程的嵌套調(diào)用

被調(diào)用的過程在返回之前又調(diào)用了其他過程時,就發(fā)生了過程嵌套調(diào)用。假設main調(diào)用了過程Sub1Sub1執(zhí)行的時候又調(diào)用了過程Sub2,Sub2執(zhí)行的時候又調(diào)用了Sub3,這個過程如下圖:


最后,當Sub1返回時,堆棧中的[ESP]被彈出送指令指針寄存器,CPUmain中回復繼續(xù)執(zhí)行:


顯然,堆棧已經(jīng)被證明是存儲信息(如嵌套過程調(diào)用的相關信息)的有效工具。通常堆棧適用于程序要以特定順序回溯執(zhí)行某些步驟的情況。

向過程傳遞參數(shù)

如果想要編寫一個執(zhí)行某些標準操作的過程,如計算整理數(shù)組之和的過程,那么在過程之內(nèi)引用特定的變量并不是什么好主意。如果那么做的話,該過程就不可能用于其他數(shù)組了。一個較好的辦法就是向過程傳遞參數(shù)。在匯編語言中,通過通用寄存器傳遞參數(shù)的做法是很普遍的。

上節(jié)中我們編寫了一個把EAXEBXECX寄存器中整數(shù)相加的過程SumOf。在main中調(diào)用SumOf之前,首先為EAX,EBXECX寄存器賦值:

data

theSum ?DWORD ?

.code

main PROC

??mov eax ,10000h

??mov ebx ,20000h

??mov ecx ,30000h

??call SumOf

??mov theSum ,eax

CALL語句之后,可以把EAX中的和復制到一個變量中保存。

5.5.3 ?例子:對整數(shù)數(shù)組求和

一種非常常見的類型的循環(huán)是計算整數(shù)數(shù)組之和,或許讀者用C++Java編寫過,在匯編語言中是非常易于實現(xiàn)的,經(jīng)過精心編寫,循環(huán)可以以盡可能快的速度運行。比如我們可以在循環(huán)中使用寄存器而不是變量。

下面創(chuàng)建一個名為AraySum的過程,它從調(diào)用程序那里接受連個參數(shù):一個指向32位整數(shù)數(shù)組的指針和一個包含數(shù)組元素數(shù)目的技術,ArraySum計算數(shù)組之和并通過EAX寄存器返回:

;----------------------------------

ArraySum PROC

;

;calculates the sum of array of 32-bit integers.

;Receives: ESI = the array offset

; ?????????ECX = number of elements in the array

;Returns : EAX = sum of the array elements

;-----------------------------------

?push ?esi

push ?ecx

mov ??eax ,0

L1:

????add ??eax ,[esi]

add ??esi ,TYPE DWORD

loop ?L1

pop ?ecx

pop ?esi

ret

ArraySum ?ENDP

注意該過程中沒有任何東西與特定數(shù)組的名字或大小相關,所以它可用于任何需要計算32位整數(shù)數(shù)組和的程序。無論何時只要有可能的話,讀者應盡量編寫靈活和易于修改的過程。

?調(diào)用ArraySum:下面是一個調(diào)用ArraySum的過程的例子,通過ESI傳遞array的地址,并通過ECX傳遞數(shù)組元素數(shù)目。在調(diào)用之后,把EAX中的和復制到一個變量中。

INCLUDE Irvine32.inc

.data

????array ?DWORD 10000h ,20000h ,30000h ,40000h ,50000h

????theSum DWORD ?

.code

main PROC

????mov esi ,OFFSET array

????mov ecx ,LENGTHOF array

????call ArraySum

????mov theSum ,eax

main ENDP

5.5.4 ?流程圖

流程圖是以圖形化的方式描述程序邏輯的有效方法。流程圖中的每個圖形都表示一個邏輯步驟,把圖形連接起來的帶箭頭的先顯示了邏輯步驟之間的次序:


來一個ArraySum過程設計一個簡單的流程圖。


5.5.5 ?保存和恢復寄存器

讀者已經(jīng)可能注意到在ArraySum過程的開始處ECXESI被壓入堆棧,過程結束的時候又被彈出,絕大多數(shù)修改寄存器值的過程都使用這種方式。修改寄存器值的過程應該總是保存和恢復寄存器值,以確保調(diào)用程序本身的寄存器值不會覆蓋改寫。這個規(guī)則的一種例外情況是用寄存器發(fā)回結果時,這時不要對這個寄存器進行保存和恢復工作。

USER操作符

PROC偽指令配套使用的USER操作符允許列出被過程修改的所有寄存器,它只是編譯器做兩件事:首先,在過程開始處生成PUSH指令在堆棧上保存寄存器;其次,在過程結束的處生成POP指令恢復這些寄存器的值。USER操作符應該緊跟PROC偽指令,其后跟由空格和制表符分割的寄存器列表。

5.5.3節(jié)中的ArraySum過程使用PUSHPOP指令保存和恢復被過程修改的寄存器ESIECX。使用USER操作符做相同的事情更簡單一些:

ArraySum PROC USER esi ,ecx

???mov ?eax ,0

L1:

???add ?eax ,[esi]

???????add ?esi ,4

???????loop L1

???????ret

ArraySum ENDP

匯編生成的相應代碼顯示了使用USER操作符的效果:

?

Array PROC

push ?esi

push ?ecx

mov ??eax ,0

L1:

add ?eax ,[esi]

add ?esi ,4

loop ?L1

pop ?ecx

pop ?esi

ret

Array ENDP

5.6.1 ?整數(shù)求和程序(設計)

寫一個程序,提示用戶輸入332位整數(shù),將其保存在數(shù)組中,計算數(shù)組內(nèi)的元素的和并在屏幕上顯示。

TITLE Integer Summation Program ?(Sum2.asm)

; This program prompts the user for three integers,

; stores them in an array, calculates the sum of the

; array ,and displays the sum.

INCLUDE Irvine32.inc

INTEGER_COUNT = 3

.data

str1 ?BYTE ?"Enter a signed integer:" ,0

str2 ?BYTE ?"The sum of the integers is:" ,0

array DWORD INTEGER_COUNT DUP(?)

.code

main PROC

???call Clrscr

mov ?esi ,OFFSET array

mov ?ecx ,INTEGER_COUNT

call PromptForIntegers

call ArraySum

call DisplaySum

exit

main ENDP

?

;-----------------------------------------

PromptForIntegers PROC USES ecx edx esi

;

; Prompts the user for an arbitrary number of integers

; and inserts the integers into an array.

; Receives: ESI points to the array ,ECX = array size

; Return: nothing

;-----------------------------------------

???mov ?edx ,OFFSET str1

L1: call WriteString

???call ReadInt

call Crlf

mov ?[esi] ,eax

add ?esi ,TYPE DWORD

loop L1

ret

PromptForIntegers ENDP

?

;----------------------------------------

ArraySum PROC USES esi ecx

;

; Calculates the sum of an array of 32-bit integers.

; Receives : ESI points to the array, ECX = number

; ?of array elements

; Returns: EAX = sum of the array elements

;------------------------------------------

???mov ?eax ,0

L1: add ?eax ,[esi]

???add ?esi ,TYPE DWORD

loop L1

ret

ArraySum ENDP

?

;------------------------------------------

DisplaySum PROC USES edx

;

; Displays the sum on the screen

; Receives :EAX = the sum

; Returns ?nothing

;-------------------------------------------

???mov ?edx ,OFFSET str2

call WriteString

call WriteInt

call Crlf

ret

DisplaySum ENDP

END main

運行結果:

?

?5.7 ?本章小結

?

?

總結

以上是生活随笔為你收集整理的Intel汇编语言程序设计学习-第五章 过程-下的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。