Win32匯編語言語法基礎


匯編語言(assembly language)是一種用於電子計算機、微處理器、微控制器或其他可編程器件的低級語言,亦稱為符號語言.在匯編語言中,用助記符(Mnemonics)代替機器指令的操作碼,用地址符號(Symbol)或標號(Label)代替指令或操作數的地址.在不同的設備中,匯編語言對應着不同的機器語言指令集,通過匯編過程轉換成機器指令,普遍地說,特定的匯編語言和特定的機器語言指令集是相互對應的,不同平台之間不可直接移植.

該系列筆記,記錄了Win32匯編的常用語法規則,有些匯編代碼並沒有返回任何參數,如需觀察,請編譯后使用OllyDBG附加進程,單步跟蹤調試,該筆記是為二進制軟件逆向打基礎的,故簡化的很多項目開發章節的內容,如果你需要使用匯編開發項目(當然那樣很麻煩),你可以參考《Intel 匯編語言程序設計》,《琢石成器-Win32匯編語言程序設計》,當然本筆記的所有內容也是來自於這兩本書的實例,不過簡化了大量繁瑣的文字描述,只保留了比較實用的內容.

匯編語言一直被認為是最難學的語言,其原因主要是因為匯編程序員在着眼於程序邏輯實現的同時還要注重其他底層細節,這些在學習高級語言可以不必涉及到的底層工作細節,在學習匯編語言中,便成了家常便飯,使用匯編編程,要求程序員具有對底層的清晰認識,學習匯編能夠更好的理解操作系統的運作原理,從而幫助構建底層知識體系.

匯編語言是所有程序設計語言中最古老的,它與計算機機器語言最為接近,通過匯編語言可以直接訪問計算機的硬件,能夠直接與CPU對話,可以說匯編語言是所有編程語言中語法格式最自由的,但自由的代價就是需要了解計算機體系結構和操作系統的大量細節,每編寫一段程序都需要考慮各種硬件的狀態,從而導致使用匯編寫程序效率非常低.

微機體系概述

在學習匯編語言之前,我們必須要了解一下處理器的發展歷史.

自1946年第一台計算機問世以來,在短短的60多年中,已經歷了由電子管計算機(1946年),晶體管計算機(1956年),集成電路計算機(1958年),超大規模集成電路計算機(1972年),這五代的更替,而且還在不斷地向巨型化,微型化,網絡化,智能化這四個方向不斷發展.

從當今的X86架構的CPU說起,X86指令集是Intel為其第一塊16位CPU(80x86)專門開發的,IBM公司1981年推出的世界第一台PC機中的CPU—i8088(i8086簡化版)使用的也是X86指令,同時電腦中為提高浮點數據處理能力而增加的X87芯片系列協處理器則另外使用X87指令,為了提高處理器性能,就將X86指令集和X87指令集統稱為X86指令集.

雖然隨着CPU技術的不斷發展,Intel公司陸續研制出更新型的i80386、i80486、Pentium直到今天,但為了保證電腦能繼續運行以往開發的各類應用程序以保護和繼承豐富的軟件資源,所以Intel公司所生產的所有CPU仍然繼續使用X86指令集,所以它的CPU仍屬於X86系列,由於X86系列及其兼容CPU都使用X86指令集,所以就形成了今天龐大的X86系列及兼容CPU陣容.

談完了處理器的基本發展過程,再來了解一下CPU指令集的分類吧.

處理器分為兩大架構陣營,即RISC(精簡指令集計算機)CISC(復雜指令集計算機)是當前CPU的兩種架構,它們的區別在於不同的CPU設計理念和方法,CPU架構是廠商給屬於同一系列的CPU產品定的一個規范,主要目的是為了區分不同類型CPU的重要標示.

早期的CPU全部是CISC架構,它的設計目的是要用最少的機器語言指令來完成所需的計算任務.比如對於乘法運算,在CISC架構的CPU上,您可能只需要一條指令就可以得到相應的結果,這些幕后的操作全部依賴於CPU中設計的邏輯電路來完成,這種架構會增加CPU結構的復雜性和對CPU制作工藝的要求,但對於編譯器的開發卻十分有利.

相比CISC架構的系統,RISC架構則要求軟件來指定各個操作步驟,上面的乘法運算如果要在RISC架構上實現,則你需要具體指定其特定的實現步驟,使用這種架構生產CPU,可以很大程度上降低CPU的復雜性以及允許在同樣的工藝水平下生產出功能更強大的CPU,但對於編譯器的設計有更高的要求.

總結:當精簡指令集出現后,所有人都說復雜指令集已經過時,英特爾密切關注,為了謹慎.英特爾同時開發復雜指令集CPU和精簡指令集CPU.精簡指令處理器上市后,復雜指令集CPU依舊熱銷.而精簡指令集CPU因為無法兼容以前的軟件,而銷售量不好.英特爾得出復雜指令集生命依舊強大的結論,放棄在精簡指令集方面的開發工作.

機器語言匯編語言和高級語言的區別 ----> (機器語言創作了匯編)->(匯編創作了C,C撐起了整個計算機世界)

機器語言(machinelanguage)是一種指令集的體系,這種指令集被稱為機器碼(machinecode),它是電腦的CPU可直接解讀的數據,機器碼有時也被稱為原生碼(NativeCode),它與系統運行平台相關聯,機器語言是計算機的設計者通過計算機的硬件結構賦予計算機的操作功能,早期的機器語言程序員在編寫程序時只需要使用兩種符號0或1,這種編程方式太枯燥,並且需要記憶大量的0101這種二進制格式的排布方式,實在是一件很痛苦的事,就是在這種環境下匯編語言誕生了.

匯編語言(assembly language)是一種用於電子計算機、微處理器、微控制器或其他可編程器件的低級語言,亦稱為符號語言,是機器語言的符號化,也就是說以往我們需要記憶大量的0101二進制,而現在則只需要記憶相應的符號,比如mov eax,1移動指令,人們很容易理解他的含義,盡管如此,操作匯編語言還是需要了解操作系統的大量底層實現,其在應對大型應用的開發上還是不夠簡單.

高級語言(High-level programming language)相對於匯編語言,它是較接近自然語言和數學公式的編程,基本脫離了機器的硬件系統,能夠用人們更易理解的方式編寫程序,高級語言高度封裝了底層實現細節,其語法格式自然,簡單易用,屏蔽了很多底層細節的實現,開發效率明顯提高,這給人們能夠開發出大型應用系統提供了可能性.

總結:機器語言與匯編語言的關系屬於一脈相承,指令是一對一的關系,這也給軟件逆向提供了一種可能,而高級語言和匯編的關系屬於一對多的關系,高級語言的一條代碼,可能對應機器語言的數十條,甚至數百條.

直到現在,CPU處理器也只能識別兩種電位格式0或1,計算機不理解高級語言,我們必須通過編譯器轉成二進制代碼才能運行(c/c++,java...),只學會高級語言,並不等於理解計算機實際的運行步驟.


80x86處理器的幾種基本工作模式

IA-32處理器有三種基本的工作模式:實地址模式,系統管理模式,保護模式,另外還有一種模式稱為虛擬80x86模式,其實虛擬x86模式也是保護模式的一個特例,下面個將分別簡要描述這幾種系統模式:

實地址模式: 在該模式下,IA-32處理器使用20位地址線,可以訪問1048576(1MB)字節的內存,其地址范圍是0-FFFFF,但8086處理器的寄存器是16位的不能存放20位的地址,為了解決這個棘手的問題提出了一種稱為分段內存的概念,所有內存被分為了多個64kb的區域,這些區域稱為段(segment),我們使用段地址x16+偏移地址=絕對地址來計算出絕對地址.

保護模式: 在該模式下,每個程序可尋址4GB的內存,地址范圍是0-FFFFFFFF,在該模式下編程無需進行復雜的公式計算,只需要使用一個32位整數就可以存放任何指令和變量的地址,處理器會在后台進行地址的計算和轉換,這些工作對於匯編程序員變得透明了起來,保護模式下有三個段:CS:代碼段,DS:數據段,SS:堆棧段,其他的段操作系統負責維護.

虛擬x86模式: 在該模式下,實際上是處理器在保護模式下創建的一個具有1MB地址空間的虛擬機,虛擬機對運行於實地址模式下的80x86計算機進行了模擬,在Windows NT系統下,打開一個控制台窗口,就創建了一個8086虛擬機,當然你也可同時打開多個控制台,他們之間是隔離的並不互相影響.

平坦分段模式: 在該模式下,所有段都被映射到32位的物理地址空間中,一個程序至少需要2個段:代碼段(CS,數據段(DS),每個段都由一個段描述符定義,段描述符通常是一個存放在全局描述符表(GDT)中的一個64位地址.

內存分頁機制: IA-32處理器支持一種稱為分頁(paging)的特性,允許一個段被分割成稱為頁(page)的4096字節的內存塊,分頁機制允許同時運行的程序使用總內存遠大於計算機的物理內存,操作系統映射的所有頁的集合稱為虛擬內存,操作系統通常都會包含一個虛擬內存管理器的程序,分頁機制會使人產生內存無限大的錯覺,然而程序如果過度依賴於分頁的話,其運行效率會非常低下.


CPU內部的寄存器組,以及每個寄存器的作用

寄存器是CPU內部的高速存儲單元,由於是固化在CPU內部的組件,其訪問速度快於內存,在當下的處理器中寄存器分為幾種類型,其中8個通用寄存器(EAX,EBX,ECX,EDX,EBP,ESP,ESI,EDI),6個段寄存器(CS,SS,DS,ES,FS,GS),一個處理器狀態標志寄存器(EFLAGS),和一個指令指針寄存器(EIP)寄存器.

通用寄存器: CPU內部有8個通用寄存器主要用於算數運算和數據的傳送,這8個寄存器都可以作為一個32位的值或兩個16位的值來尋址使用,還可以按照8位寄存器來使用,比如通用寄存器都可以被拆分為高低寄存器來存儲數據,例如:EAX寄存器,可被拆分為(AX)16位寄存器來使用,而(AX)16位寄存器還可拆分為AH/AL(高低8位).

變址寄存器: CPU內部有2個通用寄存器ESI和EDI,寄存器ESI、EDI稱為變址寄存器(Index Register),它們主要用於存放存儲單元在段內的偏移量,用它可實現多種存儲器操作數的尋址方式,為以不同的地址形式訪問存儲單元提供方便.變址寄存器不可分割成8位寄存器,在字符串操作指令的執行過程中,對它們有特定的要求,而且還具有特殊的功能,該寄存器默認和DS數據段寄存器相關聯.

堆棧指針寄存器: CPU內部有2個通用寄存器EBP和ESP,寄存器EBP、ESP稱為指針寄存器(Pointer Register),主要用於存放堆棧內存儲單元的偏移量,它們主要用於訪問堆棧內的存儲單元並且規定,EBP為基址指針寄存器,ESP為堆棧指針寄存器,指針寄存器不可分割成8位寄存器,該寄存器默認和SS堆棧段寄存器相關聯.

指令指針寄存器: CPU內部有1個指令指針寄存器EIP,該寄存器存放下一條要執行的指令的地址,下次要執行的指令通常已被預取到指令隊列中,除非發生轉移情況,所以在理解它們的功能時,不考慮存在指令隊列的情況,默認情況下EIP不可手動修改,一般都是由特殊的指令CALL,RET,PUSH等間接性的修改.

段寄存器: 段寄存器是根據內存分段的管理模式而設置的,內存單元的物理地址由段寄存器的值和一個偏移量組合而成的,這樣可用兩個較少位數的值組合成一個可訪問較大物理空間的內存地址,常規段寄存器包括CS:代碼段寄存器,DS:數據段寄存器,SS:堆棧段寄存器,ES:附加數據段寄存器這些寄存器通常是由編譯器或這是操作系統來維護的.

標志寄存器: 標志寄存器(EFLAGS),該寄存器用來控制CPU的操作流程,或者反應CPU某些運算的結果的獨立二進制位構成,常用的標志位包括CF(進位標志),ZF(零標志),PF(奇偶標志)等.


手動編譯一段小程序

	.386p
	.model flat,stdcall
	option casemap:none

include windows.inc
include kernel32.inc
includelib kernel32.lib

MyDef equ 1024         ; 將數值指定名稱

.data
	Main WORD 1024      ; 定義可賦值的變量
.data?                  ; 定義未知初始變量
	lyshark DWORD ?

.code
	main PROC
		xor eax,eax
		invoke ExitProcess,0
	main ENDP
END main
C:\Users\lyshark> ml /c /coff lyshark.asm
C:\Users\lyshark> link /subsystem:windows lyshark.obj

數據段的定義

MASM 定義了多種內部數據類型,每種數據類型都描述了該類型的變量和表達式的取值集合,匯編語言中數據類型的基本特征是以數據位數為度量單位:8,16,32,48,64,80位,而除此之外其他的特征如(符號,指針,浮點數)主要是為了方便我們記憶變量中存儲的數據類型.

接下來看下表,表中是IEEE委員會發布的標准內部數據類型:

數據類型 作用(無符號) 數據類型 作用(有符號)
BYTE 8位無符號整數 SBYTE 8位有符號整數
WORD 16位無符號整數 SWORD 16位有符號整數
DWORD 32位無符號整數 SWORD 32位有符號整數
FWORD 48位整數(遠指針) QWORD 64位整數定義
REAL4 32位(4字節)短實數 REAL8 64位(8字節)長實數

數據類型定義語句為變量在內存中保留存儲空間,並且可以選擇為變量指定一個名字,在匯編語言中所有的數據無非就是BYTE的集合,數據的定義語句格式如下:

[變量名] 數據定義偽指令 初始值[....]

在數據定義語句中使用BYTE(定義字節)SBYTE(定義有符號字節)偽指令,可以為每一個或多個有符號或無符號字節分配存儲空間,每個初始值必須是8位整數表達式或字符常量,例如下面的定義:

.data
	var1 BYTE 'A'      ; 定義字符常量
	var2 BYTE ?        ; 定義未初始化變量
	var3 BYTE 0        ; 最小的無符號字節常量
	var4 BYTE 255      ; 最大的無符號字節常量
	var5 SBYTE -128    ; 最小的有符號字節常量
	var6 SBYTE +127    ; 最大的有符號字節常量

如果一條數據定義語句中有多個初始值,那么標號僅僅代表第一個初始值的偏移,如下我們首先定義一個BYTE數組,然后通過反匯編查看地址的偏移變化就能看到效果啦:

.data
	list BYTE 10,20,30,40,50

00E71000 | B8 0030E700        | mov eax,main.E73000                 | E73000=10
00E71005 | B8 0130E700        | mov eax,main.E73001                 | E73001=20
00E7100A | B8 0230E700        | mov eax,main.E73002                 | E73002=30
00E7100F | B8 0330E700        | mov eax,main.E73003                 | E73003=40
00E71014 | B8 0430E700        | mov eax,main.E73004                 | E73004=50

並非所有的數據定義都需要標號,如果想繼續定義以list開始的字節數組,可以在隨后的行上接着上面的定義:

.data
	list BYTE 10,20,30,40,50
	list BYTE 60,70,80,90,100

當然除了定義整數字符以外,還可以定義字符串,要想定義字符串應將一組字符用單引號或雙引號括起來.最常見的字符串是以空格結尾0h,在C/C++,JAVA中定義字符串無需添加結尾0h,這是因為編譯器會在編譯的時候自動的在字符串后面填充了0h,在匯編語言中我們需要手動添加字符串結尾的標志:

.data
	string1 BYTE "hello lyshark",0h
	string2 BYTE "good night",0h

00F23000  68 65 6C 6C 6F 20 6C 79 73 68 61 72 6B 00 67 6F hello lyshark.go 
00F23010  6F 64 20 6E 69 67 68 74 00 00 00 00 00 00 00 00 od night........ 

字符串也可以占用多行,而無須為每行都提供一個編號,如下代碼也是合法的:

.data
	string1 BYTE "welcom to the Demo program"
			BYTE "created by lyshark",0dh,0ah,
			BYTE "url:lyshark"
			BYTE "send me a copy",0dh,0ah,0

十六進制0dh,0ah也稱為CR/LF(回車換行符),或者是行結束的字符,在向標准輸出設備上寫的時候,回車換行符可以將光標移動到下一行的開頭位置,從而繼續填充新的字符串.

有時我們需要初始化一些空值的內存空間,在為內存地址分配空間的時候,DUP偽指令就顯得尤為重要,初始化和未初始化數據均可使用DUP指令定義,其定義語法如下:

.data
	string1 BYTE 20 DUP(0)       ; 分配20字節,全部填充0
		BYTE 20 DUP(?)       ; 分配20字節,且未初始化
		BYTE 50 DUP("stack") ; 分配50字節,"stackstack..."

.data
	smallArray DOWRD 10 DUP(0) ; 分配40字節
	bigArray DOWOR 5000 DUP(?) ; 分配20000字節

除了上面的例子以外,我們也可以直接定義常量,常量是不可以動態修改的數據類型,一般情況下一旦定義,那么在程序運行期間不可以被修改,常量的定義很簡單,只需要將.data換成.const即可.

.const
	var1 BYTE  "hello world",0h   ; 初始化為BYTE的字符串
	var2 DWORD 10                 ; 初始化為10的DWORD類型
	var3 DWORD 100 dup(1,2)       ; 200個DWORD的緩沖區
	var4 BYTE  1024 dup(?)        ; 1024字節的緩沖區
	var5 BYTE "welcome",0dh,0ah,0 ; 0dh,0ah為換行符

有時我們需要計算數組的大小,但手動計算顯得特別麻煩,此時我們可以使用MASM提供的$符號來進行數組大小的計算過程,如下.

.data
	list BYTE 10,20,30,40,50
	listsize = ($ - list)       ; 計算字節數據大小
.data
	list WORD 1000h,2000h,3000h,4000h
	listsize = ($ - list) /2    ; 計算字數據大小
.data
	list DWORD 100000h,200000h,300000h,400000h
	listsize = ($ - list) /4    ; 計算雙字數據大小
Post_1 equ 1000
Post_2 equ 2000
Post_3 equ 3000

標准輸入輸出

StdIn/StdOut: 使用masm32.inc提供的函數實現標准的輸入與輸出.

	.386
	.model flat, stdcall
	
	include masm32.inc
	include kernel32.inc
	includelib masm32.lib
	includelib kernel32.lib

.data
	len equ 20
	OutText dw ?
	ShowText db "請輸入一個數: ",0

.code
	main PROC
		invoke StdOut, addr ShowText    ; 輸出提示信息
		invoke StdIn, addr OutText,len  ; 等待用戶的輸入
		invoke StdOut, addr OutText     ; 輸出剛才輸入的內容
		ret
	main ENDP
END main

WriteFile: 通過調用系統的API函數,來實現具體的輸出,其過程比較復雜不推薦使用.

	.386
	.model flat, stdcall
	
	include windows.inc
	include kernel32.inc
	includelib kernel32.lib
.data
	szText db "hello lyshark!",0
.data?
	hOut dd ?     ; 保存句柄
	hLen dd ?     ; 保存字符長度
.code
	main PROC
		invoke GetStdHandle,STD_OUTPUT_HANDLE     ; 獲取設備控制台句柄
		mov hOut,eax                              ; 把獲取到的句柄給hOut
		invoke lstrlen,addr szText                ; 取出字符串的長度
		mov hLen,eax
		invoke WriteFile,hOut,addr szText,hLen,0,0 ;具體的輸出
		ret
	main ENDP
END main

crt_printf: 使用微軟C標准庫中的printf函數; msvscrt.inc 把它聲明做 crt_printf

	.386
	.model flat, stdcall
	
	include msvcrt.inc
	includelib msvcrt.lib
	
.data
	PrintText db "EAX=%d;EBX=%d;EDX=%d | InPut ->: ",0
	ScanFomat db "%s",0
	PrintTemp db ?
.code
	main PROC
		mov eax,10
		mov ebx,20
		mov ecx,30
		invoke crt_printf,addr PrintText,eax,ebx,ecx        ; 打印提示內容
		invoke crt_scanf, addr ScanFomat, addr PrintTemp    ; 輸入內容並接收參數
		invoke crt_printf, addr PrintTemp                   ; 輸出輸入的內容
		ret
	main ENDP
END main

常用匯編指令

MOV指令: 從源操作數向目標操作數之間復制數據.

00A41000 | B8 24100000        | mov eax,1024                        |
00A41005 | 8BD8               | mov ebx,eax                         |
00A41007 | 66:B9 0010         | mov cx,1000                         |

MOVZX指令: 零擴展傳送,該指令將源操作數的內容復制到目標操作數中,並將該值零擴展(zero-extend)至16位或者32位,該指令適用於無符號整數,其基本格式如下:

01301000 | 66:BB 9BA6         | mov bx,A69B                         | BX = 0A69B
01301004 | 0FB7C3             | movzx eax,bx                        | EAX = 0000A69B
01301007 | 0FB6D3             | movzx edx,bl                        | EDX = 0000009B
0130100A | 66:0FB6CB          | movzx cx,bl                         | CX = 009B

MOVSX指令: 符號擴展傳送,該指令將源操作數的內容復制到目標操作數中,並將該值符號擴展(sign-extend)至16位或者是32位,該指令只能用於有符號整數,其基本格式如下:

00FD1000 | 66:BB 9BA6         | mov bx,A69B                         | BX = 0A69B
00FD1004 | 0FBFC3             | movsx eax,bx                        | EAX = FFFFA69B
00FD1007 | 0FBED3             | movsx edx,bl                        | EDX = FFFFFF0B
00FD100A | 66:0FBECB          | movsx cx,bl                         | CX = FF9B

XCHG指令: 數據交換指令,該指令用於交換兩個操作數中的內容,但該指令不接受立即數操作數.

00D71000 | B8 00100000        | mov eax,1000                        | EAX = 1000h
00D71005 | BB 00200000        | mov ebx,2000                        | EBX = 2000h
00D7100A | 93                 | xchg ebx,eax                        | EAX = 2000h;EBX = 1000h

INC/DEC指令: 數據遞增與遞減,INC指令用於對寄存器或內存數據的遞增,DEC指令用於對寄存器或內存數據遞減.

00881000 | B8 00100000        | mov eax,1000                        | EAX = 1000h
00881005 | 40                 | inc eax                             | EAX = 1001h
00881006 | 40                 | inc eax                             | EAX = 1002h
00881007 | BB 00200000        | mov ebx,2000                        | EBX = 2000h
0088100C | 4B                 | dec ebx                             | EBX = 1FFFF
0088100D | 4B                 | dec ebx                             | EBX = 1FFFE
0088100E | 4B                 | dec ebx                             | EBX = 1FFFD

ADD指令: 操作數增加,該指令用於將源操作數和目的操作數相加,且不影響源操作數的值,而是改變目的操作數.

00BC1000 | B8 00100000        | mov eax,1000                        | EAX = 1000
00BC1005 | BB 00200000        | mov ebx,2000                        | EBX = 2000
00BC100A | 03D8               | add ebx,eax                         | EBX = EBX+EAX = 3000

SUB指令: 操作數減少,該指令用於將源操作數和目的操作數相減,且不影響源操作數的值,而是改變目的操作數.

00811000 | B8 00200000        | mov eax,2000                        | EAX = 2000
00811005 | BB 00100000        | mov ebx,1000                        | EBX = 1000
0081100A | 2BC3               | sub eax,ebx                         | EAX = EAX-EBX = 1000

AND/OR/XOR指令: 邏輯與/邏輯或/邏輯異或.

00DD100E | B8 01000000        | mov eax,1                              |
00DD1013 | BB 01000000        | mov ebx,1                              |
00DD1018 | B9 00000000        | mov ecx,0                              |
00DD101D | 21D8               | and eax,ebx                            |
00DD101F | 09CB               | or ebx,ecx                             |
00DD1021 | 31C0               | xor eax,eax                            |

OFFSET操作符: 返回數據標號的偏移地址,偏移地址代表標號距數據基址的距離,單位是字節.

.data
	var1 BYTE ?
	var2 WORD ?
	var3 DWORD ?
	var4 DWORD ?
.code
	main PROC
		mov esi,offset var1
		mov esi,offset var2
		mov esi,offset var3
		mov esi,offset var4
	main ENDP
END main

PTR操作符: 用來重載聲明操作數的默認尺寸,這在試圖以不同與變量聲明時所使用的尺寸來訪問變量時很有用.

.data
	temp DWORD 12345678h

.code
	main PROC
	mov eax,DWORD PTR [temp]  ; 將temp以雙字取值並存儲到eax
	mov ax,WORD PTR [temp]    ; 將temp以字為單位取值並存儲到ax
	mov bx,WORD PTR [temp+2]  ; 在偏移基礎上+2
	main ENDP
END main

00C11000 | A1 0030C100        | mov eax,dword ptr ds:[C13000]       | EAX = 12345678
00C11005 | 66:A1 0030C100     | mov ax,word ptr ds:[C13000]         | AX = 5678
00C1100B | 66:8B1D 0230C100   | mov bx,word ptr ds:[C13002]         | BX = 1234

LENGTHOF操作符: 計算數組元素的數目,元素由出現在的同一行的值定義.

.data
	ArrayDW DWORD 1000,2000,3000,4000,5000,6000,7000,8000,9000,0h
	ArrayBT BYTE 1,2,3,4,5,6,7,8,9,0h
.code
	main PROC

		mov eax,lengthof ArrayDW
		mov eax,lengthof ArrayBT

		push 0
		call ExitProcess
	main ENDP
END main

TYPE操作符: 返回按照字節計算的單個元素的大小.

.data
	var1 BYTE ?
	var2 WORD ?
	var3 DWORD ?
	var4 QWORD ?
.code
	main PROC
		mov eax,TYPE var1       ; 1
		mov ebx,TYPE var2       ; 2
		mov ecx,TYPE var3       ; 4
		mov edx,TYPE var4       ; 8

		push 0
		call ExitProcess
	main ENDP
END main

SIZEOF操作符: 返回等於LENGTHOF(總元素數)和TYPE(每個元素占用字節)返回值的乘基.

.data
	var1 WORD 32 DUP(0)        ; 32*2
	var2 BYTE 10,20,30,40      ; 3
	var3 WORD 30 DUP(?),0,0    ; 30+2
	var4 DWORD 1,2,3,4         ; 4

.code
	main PROC
		mov eax,SIZEOF var1
		mov eax,SIZEOF var2
		mov eax,SIZEOF var3
		mov eax,SIZEOF var4
	main ENDP
END main

LOOP循環(普通循環): 該指令檢測ECX寄存器的變化,每次循環寄存器自動減1,當ECX=0循環結束,否則繼續循環.

.code
	main PROC
		mov ecx,10      ; 計數循環寄存器初始化為10
	top:                    ; 循環標號,編譯器會將其轉換成一個地址
		xor eax,eax
		mov eax,ecx
		loop top        ; loop跳轉到指定地址,此處為top

		push 0
		call ExitProcess
	main ENDP
END main

LOOP循環(循環中使用ECX): 如果用光了所有的通用寄存器,但又必須要使用ECX的話,可以在循環開始將ECX保存.

.data
	count DWORD ?
.code
	main PROC

		mov ecx,10
	top:
		mov count,ecx       ; 將ecx寄存器放入count變量
		xor ecx,ecx
		mov ecx,1000        ; 重置ecx寄存器的數值
		add eax,ecx

		mov ecx,count       ; 處理完成后,恢復ECX寄存器
		loop top            ; 繼續循環
		
		push 0
		call ExitProcess
	main ENDP
END main

LOOP循環(嵌套循環): 在循環內部創建另一個循環的時候,必須考慮外層ECX中的外層循環計數該如何處理,把外層循環計數保存在內存中,是非常的理想的.

.data
	count DWORD ?
.code
	main PROC

		mov ecx,10        ; 設置外層循環計數
	L1:
		mov count,ecx     ; 保存外層循環計數
			mov ecx,20    ; 設置內層循環計數
		L2:
			xor eax,eax
			xor ebx,ebx
			xor edx,edx
			loop L2      ; 重復內層循環計數

		mov ecx,count    ; 恢復外層循環計數器
		loop L1          ; 執行外層循環跳轉
		push 0
		call ExitProcess
	main ENDP
END main

IF-ENDIF(偽指令):

.code
	main PROC
		mov eax,100
		mov ebx,200
		.IF (eax == ebx) && (ebx == ebx)
			xor eax,eax
			xor ebx,ebx
		.ELSEIF (eax >= 100) || (ebx == ebx)
			add eax,100
			add ebx,100
		.ENDIF
	main ENDP
END main

WHILE-ENDW(偽指令):

.data
	Count DWORD 10
	SumNum DWORD 0

.code
	main PROC
		xor eax,eax
		.WHILE (eax < Count)
			add SumNum,1
			inc eax
		.ENDW
	main ENDP
END main

REPEAT-UNTIL(偽指令): 以下代碼利用循環偽指令,完成了1-10相加.

.data
	Count DWORD 10
	SumNum DWORD 0
.code
	main PROC
		xor eax,eax
		.REPEAT
			inc eax
			add SumNum,1
		.UNTIL (eax >= Count)
	main ENDP
END main

BREAK(偽指令): 以下是個死循環,當eax寄存器的值等於5時,則執行.break結束程序的運行.

.code
	main PROC
		mov eax,10
		.while (1)
			dec eax
			.break .if(eax == 5)
		.endw
		ret
	main ENDP
END main

CONTINUE(偽指令): 當EAX的值小於等於5時執行continue,否則執行inc ebx,總循環數為10.

.code
	main PROC
		mov eax,0
		mov ebx,0
		.repeat
			inc eax
			.continue .if(eax <= 5)
				inc ebx
		.until (eax >= 10)
		ret
	main ENDP
END main

FOR 字符替換(偽指令): 該偽指令並不是循環,而是分別將指定的指令批量的替換到程序中.

.code
	main PROC
		for num,<1,2,3>
			xor eax,eax
			add eax,DWORD PTR [num]
		endm
		ret
	main ENDP
END main

FORC字串替換(偽指令): 該偽指令並不是循環,而是分別將指定的字串批量的替換到程序中.

.code
	main PROC
		forc code,<@#$%^&*()<>>
			BYTE "&code"
		endm
		ret
	main ENDP
END main

內存尋址方式

Windows系統默認運行於保護模式下,當處理器運行於保護模式下時,每個程序可以尋址4GB的內存范圍,地址范圍是從十六進制數的0-FFFFFFFF,微軟匯編器的平坦模式,適用於保護模式編程,在平坦模式下其內存尋址的方式包括,直接尋址,間接尋址,基址變址尋址,比例因子尋址等,接下來將分別來演示.

◆直接尋址◆

在聲明變量名稱的后面加上一個偏移地址,可以創建直接偏移(direct-offset)操作數,可以通過它來訪問沒有顯示標號的內存地址,接下來看一個實驗例子:

.data
	ArrayB BYTE 10h,20h,30h,40h,50h
	ArrayW WORD 100h,200h,300h,400h
	ArrayDW DWORD 1h,2h,3h,4h

.code
	main PROC
	; 針對字節的尋址操作
		mov al,[ArrayB]           ; al=10
		mov al,[ArrayB+1]         ; al=20
		mov al,[ArrayB+2]         ; al=30
	; 針對內存單元字存儲操作
		mov bx,[ArrayW]           ; bx=100
		mov bx,[ArrayW+2]         ; bx=200
		mov bx,[ArrayW+4]         ; bx=300
	; 針對內存單元雙字存儲操作
		mov eax,[ArrayDW]         ; eax=00000001
		mov eax,[ArrayDW+4]       ; eax=00000002
		mov eax,[ArrayDW+8]       ; eax=00000003
	main ENDP
END main

◆間接尋址◆

在處理數組操作時完全使用直接尋址是不切實際的,我們不大可能為數組的每個元素都提供一個不同的標號,也不太可能使用非常多的常量偏移地址去尋址數組的各個元素,處理數組唯一可行的方法是用寄存器作為指針並操作寄存器的值,這種方法稱為間接尋址(indirect addressing),操作數使用間接尋址時,就稱為間接操作數(indirect operand).

通過ESI內存尋址: 通過使用ESI寄存器,外加偏移地址(此處DWORD=4字節),實現尋址.

.data
	ArrayDW DWORD 10000h,20000h,300000h
.code
	main PROC
		mov esi,offset ArrayDW  ; 獲取數據段的內存基址
		mov eax,[esi]           ; 取出[esi]地址中的數據,並賦值給eax
		add esi,4               ; 每次esi指針加4,因為數據格式為DWORD
		mov eax,[esi]
		add esi,4
		mov eax,[esi]
	main ENDP
END main

通過ESP堆棧尋址: 通過ESP堆棧寄存器,實現尋址.

.code
	main PROC
		mov eax,100                ; eax=1
		mov ebx,200                ; ebx=2
		mov ecx,300                ; ecx=3
		push eax                   ; push 1
		push ebx                   ; push 2
		push ecx                   ; push 3

		mov edx,[esp + 8]          ; EDX = [ESP+8]=1
		mov edx,[esp + 4]          ; EDX = [ESP+4]=2 
		mov edx,[esp]              ; EDX = [ESP]=3
	main ENDP
END main

◆變址尋址◆

變址尋址,變址操作數(indexed operand)把常量和寄存器相加以得到一個有效地址,任何32位通用寄存器都可以作為變址寄存器,MASM允許使用兩種不同的變址操作數據格式.

變量名+寄存器: 通過變量名和寄存器結合,變量名代表變量偏移地址的常量,通過變更ESI寄存器的值進行數據尋址.

.data
	ArrayDW DWORD 10000h,20000h,300000h
.code
	main PROC
		mov esi,0
		mov eax,[ArrayDW + esi]     ; 通過變量名+esi寄存器尋址

		mov ebx,8                   ; 增加8字節
		mov eax,[ArrayDW + ebx]     ; 定位第三個DW數據內存
	main ENDP
END main

基址+偏移: 通過把變址寄存器內存偏移常量結合,用寄存器存放數組基址,用常量標識各個數組元素.

.data
	ArrayW WORD 1000h,2000h,3000h,4000h
.code
	main PROC
		mov esi,offset ArrayW    ; 獲取基址
		mov ax,[esi]             ; 顯示第一個數據
		mov ax,[esi + 2]         ; 顯示第二個數據
		mov ax,[esi + 4]         ; 最后一個
	main ENDP
END main

基址變址尋址: 通過計算公式,這里數組中每個元素占用4字節,所以需要乘以4,寄存器ECX為需要定位的元素偏移.

.data
	Array DWORD 1000h,2000h,3000h,4000h,0h
.code
	main PROC
		lea eax,Array
		mov ecx,2
		mov edx,DWORD PTR [eax + ecx * 4]      ;edx=3000h

		mov ecx,1
		mov edx,DWORD PTR [eax + ecx * 4]      ;edx=2000h
	main ENDP
END main

比例因子尋址: 通過使用比例因子,以下例子每個DWORD=4字節,且總元素下標=0-3,得出比例因子3* type arrayDW.

.data
	ArrayDW DWORD 1000h,2000h,3000h,4000h
.code
	main PROC

	; 第1種比例因子尋址
		mov esi,3*type ArrayDW        ;總共3個下標x每個元素的類型
		mov eax,ArrayDW[esi]
	; 第2種比例因子尋址
		mov esi,3                      ; 變更ESI下標,可實現定位不同的數據
		mov eax,ArrayDW[esi*4]         ; 其中4代表每個數據類型4字節
	; 第3種比例因子尋址
		mov esi,3
		mov eax,ArrayDW[esi*type ArrayDW]
	main ENDP
END main

指針尋址: 變量地址的變量稱為指針變量(pointer variable),Intel處理器使用兩種基本類型的指針,即near(近指針)far(遠指針),保護模式下使用Near指針,所以它被存儲在雙字變量中.

.data
	ArrayB BYTE 10,20,30,40,50
	ArrayD DWORD 1,2,3,4,5

	ptrB DWORD OFFSET ArrayB    ; 指針ptrB --> ArrayB
	ptrD DWORD OFFSET ArrayD    ; 指針ptrD --> ArrayD
.code
	main PROC
	mov esi,ptrB      ; 指向數組ArrayB
	mov al,[esi]      ; 取出 10h
	mov esi,ptrD      ; 指向數組ArrayD
	mov eax,[esi]     ; 取出 1h
	main ENDP
END main

標志測試指令

在學習數據比較指令之前,需要先來了解一下標識寄存器這個東西,標志寄存器又稱程序狀態寄存器(Program Status Word,PSW),這是一個存放條件碼標志,控制標志和系統標志的寄存器.

標志寄存器中存放的有條件標志,也有控制標志,它對於處理器的運行和整個過程的控制有着非常重要的作用.條件標志主要包括進位標志、奇偶標志、輔助進位標志、零標志、符號標志、溢出標志等,控制標志主要有跟蹤標志,因為有標志寄存器的存在才能實現各種華麗的判斷循環等,常用的標志有以下6個:

標志位 標志全稱 標志序號 標志位說明
CF(Carry Flag) 進位標志位 0 當執行一個加法(或減法)運算,使最高位產生進位(或借位)時,CF為1;否則為0
PF(Parity Flag) 奇偶標志位 2 當運算結果中,所有bit位(例:1001010)中1的個數為偶數時,則PF=1;為基數PF=0
AF(Auxiliary Flag) 輔助進位標志 4 執行加法(減法)運算,結果的低4位向高4位有進位(借位)時,則AF=1;否則AF=0
ZF(Zero Flag) 零標志位 6 若當前的運算結果為零,則ZF=1;否則ZF=0
SF(Sign Flag) 符號標志位 7 若運算結果為負數,則SF=1;若為非負數則SF=0
TF(Trap Flag) 陷阱標志位 8 為方便程序調試而設計的,TF=1單步執行指令,TF=0則CPU正常執行程序
IF(Interrupt) 中斷允許標志 9 當IF=1CPU可響應可屏蔽中斷請求,當設置IF=0則CPU不響應可屏蔽中斷請求
DF(Direction) 方向標志位 10 當DF=0時為正向傳送數據(cld),否則為逆向傳送數據(std)
OF(Overflow) 溢出標志位 11 記錄是否產生了溢出,當補碼運算有溢出時OF=1;否則OF=0

ZF零標志位: ZF標志相關指令執行后,結果為0則ZF=1;若結果不為0則ZF=0.

00C31000 | 90                   | nop                                              | ZF = 0
00C31001 | B8 01000000          | mov eax,1                                        | ZF = 0
00C31006 | 83E8 01              | sub eax,1                                        | ZF = 1

00C31000 | 90                   | nop                                              | ZF = 0
00C31001 | B8 02000000          | mov eax,2                                        | ZF = 0
00C31006 | 83E8 01              | sub eax,1                                        | ZF = 0

PF奇偶標志位: PF標志相關指令執行后,其結果所有bit位中的1若為偶數,則PF=1;若為奇數PF=0.

00C31000 | 90                   | nop                                              | PF = 0
00C31001 | B8 00000000          | mov eax,00000000                                 | PF = 0
00C31006 | 83C0 6F              | add eax,00000111                                 | PF = 1

00C31000 | 90                   | nop                                              | PF = 0
00C31001 | B8 00000000          | mov eax,00000000                                 | PF = 0
00C31006 | 83C0 6F              | add eax,00000011                                 | PF = 0

SF符號標志位: SF標志相關指令執行后,其結果是否為負,若為負則SF=1;若為非負SF=0.

00C3100B | 90                   | nop                                              | SF = 0
00C3100C | B8 E8030000          | mov eax,3E8                                      | SF = 0
00C31011 | 2D E9030000          | sub eax,3E9                                      | SF = 1

00C3100B | 90                   | nop                                              | SF = 0
00C3100C | B8 E8030000          | mov eax,3E8                                      | SF = 0
00C31011 | 2D E9030000          | sub eax,3E8                                      | SF = 0

CF進位標志位: CF標志相關指令執行后,在進行無符號運算時,如果表達式發生進位或借位則CF=1.

00C31016 | 90                   | nop                                              | CF = 0
00C31017 | 66:B8 FFFF           | mov ax,FFFF                                      | CF = 0
00C3101B | 66:83C0 01           | add ax,1                                         | CF = 1

00C31016 | 90                   | nop                                              | CF = 0
00C31017 | 66:B8 FFFF           | mov ax,FFFF                                      | CF = 0
00C3101B | 66:83C0 01           | sub ax,1                                         | CF = 0

OF溢出標志位: OF標志相關指令執行后,超出機器所能表示的范圍稱為溢出若發生了溢出OF=1;否則OF=0.

00C3101B | 90                   | nop                                              | OF = 0
00C3101C | B0 40                | mov al,64                                        | OF = 0
00C3101E | 04 40                | add al,64                                        | OF = 1

00C31020 | 90                   | nop                                              | OF = 0
00C31021 | B0 3F                | mov al,63                                        | OF = 0
00C31023 | 04 40                | add al,64                                        | OF = 0

TEST指令: 該操作與AND指令類似,唯一不同的是它不保存結果,常用來測試標志位狀態.

00DD103B | B8 01000000        | mov eax,1                              | EAX = 1
00DD1040 | BB 00000000        | mov ebx,0                              | EBX = 0
00DD1045 | 85D8               | test eax,ebx                           | ZF = 1

00DD1051 | B8 01000000        | mov eax,1                              |
00DD1056 | A9 00000000        | test eax,0                             | ZF = 1
00DD105B | 83E0 00            | and eax,0                              | ZF = 1
00DD1062 | 83C8 01            | or eax,1                               | ZF = 0

CMP指令: 在源操作數和目標操作數進行減法操作,只影響標志位.

00DD1001 | B8 00010000        | mov eax,100                            | EAX = 100
00DD1006 | BB 50000000        | mov ebx,50                             | EBX = 50
00DD100B | 39D8               | cmp eax,ebx                            | eax - ebx
00DD100D | 0F87 EDFF62FF      | ja 401000                              | jump

條件跳轉指令

注記符 跳轉條件 描述信息
JZ/JE ZF=1 為零則跳轉,(leftOp - rightOp = 0)
JNZ/JNE ZF=0 不為零則跳轉,(leftOp - rightOp != 0)
JC/JNC CF=1/0 設置進位標志則跳/未設置進位標志則跳
JO/JNO OF=1/0 設置溢出標志則跳/未設置溢出標志則跳
JS/JNS SF=1/0 設置符號標志則跳/未設置符號標志則跳
JP/JNP PF=1/0 設置奇偶標志則跳(偶)/未設置奇偶標志則跳(基)
無符號模式 有符號模式 跳轉條件 描述信息
JA JG (left > right) 大於則跳轉
JAE JGE (left >= right) 大於或等於則跳轉
JB JL (left < right) 小於則跳轉
JBE JLE (left <= right) 小於或等於則跳轉

JZ/JE通用跳轉: 檢測到ZF=1也就說明表達式返回了0,則程序跳轉,否則不跳轉.

01031001 | B8 00010000        | mov eax,64                      | eax=100
01031006 | BB 00010000        | mov ebx,64                      | ebx=100
0103100B | 39D8               | cmp eax,ebx                     | eax-ebx 
0103100D | 0F84 EDFF3CFF      | je 401000                       | jump
01031013 | 0F84 E7FF3CFF      | jz 401000                       | jump

JNZ/JNE通用跳轉: 檢測到ZF=0也就說明表達式返回了1,則程序跳轉,否則不跳轉.

01031001 | B8 00010000        | mov eax,65                      | eax=101
01031006 | BB 00010000        | mov ebx,64                      | ebx=100
0103100B | 39D8               | cmp eax,ebx                     | eax-ebx 
0103100D | 0F84 EDFF3CFF      | jne 401000                      | not jump
01031013 | 0F84 E7FF3CFF      | jnz 401000                      | not jump

JA/JB無符號跳轉: 基於無符號數的跳轉指令,JA大於則跳轉JB小於則跳轉.

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB C8000000        | mov ebx,C8                      | ebx=200
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F87 EDFF3CFF      | ja 401000                       | ebx>eax jump

0103100F | B8 64000000        | mov eax,64                      | eax=100
01031014 | BB 32000000        | mov ebx,32                      | ebx=50
01031019 | 3BD8               | cmp ebx,eax                     | ebx-eax
0103101B | 0F82 DFFF3CFF      | jb 401000                       | ebx<eax jump

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
0103100B | 3BC3               | cmp eax,ebx                     | eax-ebx
0103100D | 0F87 EDFF3CFF      | ja 401000                       | eax=ebx not jump
01031013 | 0F82 E7FF3CFF      | jb 401000                       | eax=ebx not jump

JAE/JBE無符號跳轉: 基於無符號數的跳轉指令,JAE大於等於則跳轉JBE小於等於則跳轉.

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
01031010 | 3BC3               | cmp eax,ebx                     | eax-ebx
01031012 | 0F83 E8FF3CFF      | jae 401000                      | eax>=ebx jump

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB C8000000        | mov ebx,C8                      | ebx=200
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F83 EDFF3CFF      | jae 401000                      | ebx>=eax jump

01031001 | B8 C8000000        | mov eax,C8                      | eax=200
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F86 EDFF3CFF      | jbe 401000                      | ebx<=eax jump

JG/JL有符號跳轉: 基於有符號數的跳轉指令,JG大於則跳轉JL小於則跳轉.

01031001 | B0 7F              | mov al,7F                       | al=0x7F(+127)
01031003 | B3 80              | mov bl,80                       | bl=0x80(-128)
01031005 | 3AC3               | cmp al,bl                       | (+128)-(-127)
01031007 | 0F87 F3FF3CFF      | ja 401000                       | 不跳轉,因為7Fh不大於80h
0103100D | 0F8F EDFF3CFF      | jg 401000                       | 跳轉,因為(+128)大於(-127)

01031001 | B0 9C              | mov al,9C                       | al=(-100)
01031003 | B3 32              | mov bl,32                       | bl=(50)
01031005 | 3AC3               | cmp al,bl                       | (-100)-(50)
01031007 | 0F82 F3FF3CFF      | jb 401000                       | 不跳轉,因為9ch不小於32h
0103100D | 0F8C EDFF3CFF      | jl 401000                       | 跳轉,因為(-100)小於(32)

JGE/JLE有符號跳轉: 基於有符號數的跳轉指令,JGE大於等於則跳轉JLE小於等於則跳轉.

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 64000000        | mov ebx,64                      | ebx=100
0103100B | 3BC3               | cmp eax,ebx                     | eax-ebx
0103100D | 0F83 EDFF3CFF      | jae 401000                      | 跳轉,無符號100=100
01031013 | 0F8D E7FF3CFF      | jge 401000                      | 跳轉,有符號100=100

01031001 | B8 64000000        | mov eax,64                      | eax=100
01031006 | BB 9CFFFFFF        | mov ebx,FFFFFF9C                | ebx=(-100)
0103100B | 3BD8               | cmp ebx,eax                     | ebx-eax
0103100D | 0F8E EDFF3CFF      | jle 401000                      | 跳轉,有符號數(-100)<(100)

JCXZ/JECXZ跳轉指令: 檢測ECX寄存器的值,如果等於零則執行跳轉,否則跳過執行.

01031001 | B9 01000000        | mov ecx,1                       | ecx=1
01031006 | E3 F8              | jecxz <a.EntryPoint>            | not jump

0103100A | B9 00000000        | mov ecx,0                       | ecx=0
0103100F | E3 EF              | jecxz <a.EntryPoint>            | jump

其他測試指令:

(eax==ebx)&& zero?    如果eax=ebx並且ZF=0則執行
(eax==ebx)&& !zero?   如果eax=ebx並且ZF!=0則執行

CARRY? carry位是否置位
overflow?   溢出
parity?
sign?

移位相關指令

每種匯編語言都有進行操作數移位的指令,移位和循環移位指令在控制硬件設備,加密數據,以及實現高速圖形運算時特別有用,移位指令也是匯編語言中最具特征的指令集,移位(Shifting)的含義是在操作數內向左或向右移動數據位,Intel處理器提供了多種移位指令,具體如下表所示:

指令集 含義 指令集 含義
SHL 邏輯左移(無符號數) SHR 邏輯右移(無符號數)
SAL 算數左移(有符號數) SAR 算數右移(有符號數)
ROL 循環左移(無符號數) ROR 循環右移(無符號數)
RCL 循環左移(帶進位的) RCR 循環右移(帶進位的)
SHLD 雙精度左移(無符號) SHRD 雙精度右移(無符號)

◆SHL/SHR 邏輯移位◆

SHL指令: 對目標操作數執行邏輯左移(針對無符號數)操作,其左移后最低位0填充,而移動出去的最高位則會送入CF(進位標志)中,原來的進位標志位中的值將被覆蓋.

Intel處理器中定義,執行移位的源操作數的范圍必須在0-255之間,在任何處理器上都可以使用CL寄存器存放移位位數,例如在下面的指令中,AL寄存器被左移一位,最高位被復制到了進位標志中,最低位被清零:

01251006 | B3 8F                | mov al,10001111b                            | AL = 10001111b
01251008 | D0E3                 | shl al,1                                    | CF = 1,AL = 00011110b

01251006 | B0 01                | mov al,10000000b                            | AL = 10000000b
01251008 | C0E0 02              | shl al,2                                    | CF = 0,AL = 00000000b

01251006 | B0 01                | mov al,10000000b                            | AL = 10000000b
01251008 | C0E0 01              | shl al,1                                    | CF = 1,AL = 00000000b

01251006 | B0 01                | mov al,10100000b                            | AL = 10100000b
01251008 | C0E0 03              | shl al,2                                    | CF = 0,AL = 10000000b

另外使用SHL指令還可以進行2的次冪的高速乘法運算,任何操作數左移動N位,就相當於乘以2的N次方,如下例子:

01311002 | B0 05                | mov al,5                                    | AL 左移動1位
01311004 | D0E0                 | shl al,1                                    | al*2=10

01311007 | B0 05                | mov al,5                                    | AL左移2位
01311009 | C0E0 02              | shl al,2                                    | al*4=20

01311007 | B0 05                | mov al,5                                    | AL左移3位
01311009 | C0E0 03              | shl al,3                                    | al*8=40

SHR指令: 對目標操作數執行邏輯右移(針對無符號數)操作,移出的數據位用0代替,最低位被復制到CF進位標志中,原來的進位標志位丟失.

0131100D | B0 01                | mov al,10001111b                            | AL = 10001111b
0131100F | D0E8                 | shr al,1                                    | CF = 1,AL = 01000111b

0131100D | B0 01                | mov al,10001111b                            | AL = 10001111b
0131100F | D0E8                 | shr al,2                                    | CF = 1,AL = 00100011b

另外任何無符號操作數邏輯右移N位,就相當於該操作數除以2的N次方,如下例子:

01311012 | B2 20                | mov dl,20                                   | DL 右移1位 
01311014 | D0EA                 | shr dl,1                                    | dl/2 = 10

01311012 | B2 20                | mov dl,20                                   | DL 右移2位 
01311014 | D0EA                 | shr dl,2                                    | dl/4 = 5

◆SAL/SAR 算數移位◆

SAL指令與SHL指令等價,SAR指令對目標操作數執行算數右移操作,SAR/SHR指令與SHL/SHR指令格式相同,移位動作可以根據第二個操作數中的計數進行重復.

◆ROL/ROR 循環移位◆

RCL/RCR
SHALD/SHRD

乘法/除法指令

MUL和IMUL指令分別進行有符號整數和無符號整數的乘法操作,MUL(無符號乘法)指令有三種格式.

8位乘法: 計算AL寄存器BL寄存器相乘,積數默認放在AX寄存器中,進位標志CF清零,因為AH高位等於零.

00111002  | B0 05                    | mov al,5                                 | al = 5
00111004  | B3 10                    | mov bl,10                                | bl = 10
00111006  | F6E3                     | mul bl                                   | AX=50,CF=0

16位乘法: 將16操作數2000h和100h相乘,乘積高位在DX中,低位在AX中.CF=1因為乘機高半部分DX=0

0008100F  | 66:B8 0020               | mov ax,2000                              | ax=2000
00081013  | 66:BB 0001               | mov bx,100                               | bx=100
00081017  | 66:F7E3                  | mul bx                                   | ax*bx

32位乘法: 將32操作數12345h和1000h相乘,得到64位乘積,其高位在EDX中,低位在EAX中.

0008101B  | B8 45230100              | mov eax,12345                            |
00081020  | BB 00100000              | mov ebx,1000                             |
00081025  | F7E3                     | mul ebx                                  |

擴展加法/減法


字串操作指令

移動串指令: MOVSB、MOVSW、MOVSD ;從 ESI -> EDI; 執行后, ESI 與 EDI 的地址移動相應的單位
比較串指令: CMPSB、CMPSW、CMPSD ;比較 ESI、EDI; 執行后, ESI 與 EDI 的地址移動相應的單位
掃描串指令: SCASB、SCASW、SCASD ;依據 AL/AX/EAX 中的數據掃描 EDI 指向的數據, 執行后 EDI 自動變化
儲存串指令: STOSB、STOSW、STOSD ;將 AL/AX/EAX 中的數據儲存到 EDI 給出的地址, 執行后 EDI 自動變化
載入串指令: LODSB、LODSW、LODSD ;將 ESI 指向的數據載入到 AL/AX/EAX, 執行后 ESI 自動變化
其中的 B、W、D 分別指 Byte、Word、DWord, 表示每次操作的數據的大小單位.

上述指令可以有重復前綴:
REP ECX > 0 時
REPE (或 REPZ) ECX > 0 且 ZF=1 時
REPNE(或 REPNZ) ECX > 0 且 ZF=0 時
;重復前綴可以自動按單位(1、2、4)遞減 ECX

字符串復制(movsb):

.data
	string1 db "hello lyshark",0      ; 原始字符串
	str_len equ $ - string1 -1        ; 計算出原始字符串長度
	string2 db str_len dup(?),0       ; 目標內存地址

.code
	main PROC
		cld                       ; 清除方向標志
		mov esi,offset string1    ; 取源字符串內存地址
		mov edi,offset string2    ; 取目標字符串內存地址
		mov ecx,str_len           ; 指定循環次數,為原字符串長度
		rep movsb                 ; 逐字節復制,直到ecx=0為止
		ret
	main ENDP
END main

另一種字串復制(movsb): 不使用rep重復前綴的方式完成字串復制.

.data
	string1 db "hello lyshark",0      ; 原始字符串
	str_len equ $ - string1 -1        ; 計算出原始字符串長度
	string2 db str_len dup(?),0       ; 目標內存地址

.code
	main PROC
		lea esi,string1               ; 取string1的地址
		lea edi,string2               ; 取string2的地址
		mov ecx,str_len               ; 取字符串長度,用於循環
		cld                       ; 方向->正向
	@@:	movsb                     ; 每次復制一個字節BYTE
		dec ecx                   ; 每次ecx減一
		jnz @B                    ; 如果ecx不為0則循環
		ret
	main ENDP
END main

(movsd)四字節復制字串:

.data
	ddSource DWORD 10h,20h,30h               ; 定義三個四字節數據
	ddDest   DWORD lengthof ddSource dup(?)  ; 得到目標地址

.code
	main PROC
		lea esi,ddSource
		lea edi,ddDest
		mov ecx,lengthof ddSource
		cld
		rep movsd
		ret
	main ENDP
END main

CMPSB:

.data
	Text1 db "hello lyshark",0
	Text2 db "hello lyshar1",0
.code
	main PROC
		lea esi,Text1
		lea edi,Text2
		mov ecx,lengthof Text1
		cld
		repe cmpsb
		je L1
		xor eax,eax            ; 字串不同則清空eax
		jmp L2
	L1:	xor ebx,ebx            ; 字串相同則清空ebx
	L2:	ret
	main ENDP
END main

CMPSD: 比對兩個雙字數據是否相等.

.data
	var1 DWORD 1234h
	var2 DWORD 5678h
.code
	main PROC
		lea esi,var1
		lea edi,var2
		cmpsd
		je L1
		xor eax,eax      ; 兩數如果相等則清空eax
		jmp L2
	L1:	xor ebx,ebx      ; 兩數不相等則清空ebx
	L2:	ret
	main ENDP
END main

CMPSW:

.data
	Array1 WORD 1,2,3,4,5      ; 必須全部相等才會清空ebx
	Array2 WORD 1,3,5,7,9
.code
	main PROC
		lea esi,Array1
		lea edi,Array2
		mov ecx,lengthof Array1
		
		cld
		repe cmpsw
		je L1
		xor eax,eax        ; 兩數不相等則清空eax
		jmp L2
	L1:	xor ebx,ebx        ; 兩數相等則清空ebx
	L2:	ret
	main ENDP
END main

SCASB 掃描字串: 依據 AL/AX/EAX 中的數據掃描 EDI 指向的數據, 執行后 EDI 自動變化

.data
	szText BYTE "ABCDEFGHIJK",0
.code
	main PROC
	
		lea edi,szText
		mov al,"F"
		mov ecx,lengthof szText -1
		cld
		repne scasb                 ; 如果不相等則重復
		je L1
		xor eax,eax                 ; 如果沒找到F則清空eax
		jmp L2
	L1:	sub ecx,lengthof szText -1
		neg ecx           ; 如果找得到, 這里顯示是第幾個字符; 本例結果是 6
	L2:	ret
	main ENDP
END main

STOSB 存儲字串: 將 AL/AX/EAX 中的數據儲存到 EDI 給出的地址, 執行后 EDI 自動變化

.data
	len = 10
	szText db len dup(0),0
.code
	main PROC
		lea edi,szText                   ; EDI指向字符串
		mov al,"W"                       ; 定義查找字母為W
		mov ecx,len                      ; 設置查找計數器
		cld                              ; 方向=正向
		rep stosb                        ; ecx>0則執行
		ret
	main ENDP
END main

LODSW 載入指令: 將 ESI 指向的數據載入到 AL/AX/EAX, 執行后 ESI 自動變化,如下是累加求和

.data
	Array WORD 1,2,3,4,5,6,7,8,9,10
.code
	main PROC
		lea esi,Array
		mov ecx,lengthof Array
		xor edx,edx
		xor eax,eax
	@@:	lodsw
		add edx,eax
		loop @B
		
		mov eax,edx           ; 最后將相加結果放入eax

	main ENDP
END main

初始化內存: 把String字符串中的,每一個字節均填充初始化為0FFh

.data
	Count = 100                 ; 申請空間為100
	String BYTE Count DUP(?)    ; 初始化為BYTE
.code
	main PROC
		mov al,0FFh             ; 指定要填充的數據為0FFh
		mov edi,offset String   ; EDI寄存器指向目標內存
		mov ecx,Count           ; 循環計數
		cld                     ; 初始化:方向=前方
		rep stosb               ; 以AL中的值進行填充
		ret
	main ENDP
END main

數組的乘法: 把雙字數組中的每一個元素分別乘以一個常量,程序中使用了(LODSD加載),(STOSD保存).

.data
	Array DWORD 1,2,3,4,5
	Multi DWORD 10
.code
	main PROC
		mov esi,offset Array     ; 源指針
		mov edi,esi              ; 目的指針
		
		cld                      ; 方向=向前
		mov ecx,lengthof Array   ; 循環計數器
	L1:	lodsd                    ; 加載[esi]至EAX
		mul Multi                ; 將EAX乘以10
		stosd                    ; 將結果從EAX存儲至[EDI]
		loop L1
		ret
	main ENDP
END main

計算字符串長度: 以下代碼用於計算字符串的長度,並將結果保存在EAX寄存器中.

.data
	String BYTE "hello world",0      ; 帶計算字符串
.code
	main PROC
		mov edi,offset String    ; 取出字符串的基地址
		xor eax,eax              ; 清空eax用作計數器
		
	L1:	cmp byte ptr [edi],0     ; 分別那[edi]的值和0作比較
		je L2                    ; 上一步為零則跳轉得到ret
		inc edi                  ; 否則繼續執行
		inc eax
		jmp L1
	L2:	ret
	
	main ENDP
END main

小寫字串轉大寫: 將MyString變量中的小寫字符串,依次轉換為大寫字符串.

.data
	MyString db "hello lyshark",0      ; 定義MyString字符串

.code
	main PROC
		mov esi,offset MyString        ; 取出字符串的偏移地址
	L1:	cmp byte ptr [esi],0           ; 分別拿出每一個字節,與0比較
		je L2                          ; 如果相等則跳轉到L2
		and byte ptr [esi],11011111b   ; 執行按位與操作
		inc esi                        ; 每次esi指針遞增1
		jmp L1                         ; 重復循環
	L2:	ret
	main ENDP
END main

定義二維數組: 定義一個二維數組Table,並取出第一行中的第2偏移地址的元素.

.data
	Table WORD 10h,20h,30h,40h,50h    ; 定義一個數組
	Row = ($ - Table)                 ; 取出數組每行的字節數
	      WORD 60h,70h,80h,90h,0Ah    ; 繼續定義數組
.code
	main PROC
		row_index = 1                 ; 表的偏移地址
		column_index = 2              ; 行的偏移地址
		
		mov ebx,offset Table          ; 取偏移地址給ebx
		add ebx,Row*row_index         ; 每行元素*偏移
		
		mov esi,column_index          ; 將行偏移賦值給esi
		mov ax,[ebx+esi*TYPE Table]   ; Table比例因子只能是2,4,8
	main ENDP
END main

位操作指令

符號擴展(CBW/CWDE):

.code
	main PROC
		mov al,7fh
		cbw                 ; 將 AL 擴展為 AX
		PrintHex ax ;007F
		mov al,80h
		cbw
		PrintHex ax ;FF80
		
		mov ax,7fffh
		cwde                 ; 將 AX 擴展為 EAX
		PrintHex eax
	main ENDP
END main

符號擴展(CDQ/CWD):

.code
	main PROC
		mov eax,7FFFFFFFh
		cdq                      ; 將 EAX 擴展為 64 位數 EDX:EAX
		PrintHex edx ;00000000
		PrintHex eax ;7FFFFFFF
		
		mov ax, 7FFFh
		cwd                       ; 將 AX 擴展為 DX:AX
		PrintHex dx ;0000
		PrintHex ax ;7FFF
		
	main ENDP
END main

BT、BTS、BTR、BTC: 位測試指令

;BT(Bit Test): 位測試
;BTS(Bit Test and Set): 位測試並置位
;BTR(Bit Test and Reset): 位測試並復位
;BTC(Bit Test and Complement): 位測試並取反

;它們的結果影響 CF
;它們的指令格式相同:
BT r16/r32/m16/m32, r16/r32/m16/m32
BT r16/r32/m16/m32, i8

.code
main proc
    ;BT 把 10000001b 的第七位復制到 CF, 得知是 1
    mov dx, 10000001b
    bt  dx, 7
    lahf
    PrintHex ah ;47 - 01000111b (CF=1)
    ;BT 把 10000001b 的第六位復制到 CF, 得知是 0
    bt  dx, 6
    lahf
    PrintHex ah ;86 - 10000110b (CF=0)
    
    ;BTS 在執行 BT 命令的同時, 把操作數的指定位置為 1
    mov dx, 10000001b
    bts dx, 6
    PrintHex dl ;C1 - 11000001b
    
    ;BTR 在執行 BT 命令的同時, 把操作數的指定位置為 0
    mov dx, 10000001b
    btr dx, 7
    PrintHex dl ;01 - 00000001b
    
    ;BTC 在執行 BT 命令的同時, 把操作數的指定位取反
    mov dx, 10000001b
    btc dx, 0
    PrintHex dl ;80 - 10000000b
    btc dx, 0
    PrintHex dl ;81 - 10000001b
    ret
main endp
end main

BSF、BSR: 位掃描指令

;BSF(Bit Scan Forward): 位掃描, 低 -> 高
;BSR(Bit Scan Reverse): 位掃描, 高 -> 低

;它們的結果影響 ZF

;掃描的是參數二, 找到是 1 的位后, 把位置數給參數一並置 ZF=0
;找不到(也就是參數二是 0)時, 置 ZF=1

;它們的指令格式相同:
BSF r16/r32, r16/r32/m16/m32

.code
main proc
    ;掃描到時
    mov dx, 0000111100001100b
    bsf cx, dx
    PrintDec cx ;2  - 也就是左數第 3 位
    
    bsr cx, dx
    PrintDec cx ;11 - 也就是左數第 12 位
    
    ;掃描不到時
    mov cx, 0FFFFh
    mov dx, 0
    bsf cx, dx
    lahf
    PrintHex ah ;C6 - 11000110 (ZF=1)
    PrintHex cx ;FFFF - 找不到時不會影響到目的值
    ret
main endp
end main


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM