5.3 匯編語言:字符串操作指令
本章將深入研究字符串操作指令,這些指令在匯編語言中具有重要作用,用于處理字符串數(shù)據(jù)。我們將重點介紹幾個關鍵的字符串操作指令,并詳細解釋它們的功能和用法。通過清晰的操作示例和代碼解析,讀者將了解如何使用這些指令進行字符串比較、復制、填充等常見操作。我們還將探討不同指令之間的區(qū)別,并提供實際的示例程序,展示字符串操作指令在實際場景中的應用。通過學習本章,讀者將能夠拓展匯編技能,為處理字符串數(shù)據(jù)提供高效而精確的解決方案。
常見的字符串操作指令包括:
?- MOVSB / MOVSW / MOVSX:在兩個存儲器地址之間復制一個字節(jié)、一個字或一個雙字。其中 MOVSB 復制一個字節(jié),MOVSW 復制一個字,MOVSX 復制一個雙字。
?- CMPSB / CMPSW / CMPSD:比較兩個存儲器地址中的一個字節(jié)、一個字或一個雙字,并將比較結(jié)果存儲在條件碼寄存器中。其中 CMPSB 比較一個字節(jié),CMPSW 比較一個字,CMPSD 比較一個雙字。
?- LODSB / LODSW / LODSD:從存儲器中讀取一個字節(jié)、一個字或一個雙字,并將其存儲在累加器中。其中 LODSB 讀取一個字節(jié),LODSW 讀取一個字,LODSD 讀取一個雙字。
?- STOSB / STOSW / STOSD:將一個字節(jié)、一個字或一個雙字寫入存儲器,并將累加器的值相應地更新。其中 STOSB 寫入一個字節(jié),STOSW 寫入一個字,STOSD 寫入一個雙字。
?- SCASB / SCASW / SCASD:在存儲器地址中掃描一個字節(jié)、一個字或一個雙字,并將掃描結(jié)果存儲在條件碼寄存器中。其中 SCASB 掃描一個字節(jié),SCASW 掃描一個字,SCASD 掃描一個雙字。
這些字符串操作指令通常是通過累加器(即 AH、AL、AX 或 EAX 等寄存器)來控制讀取或?qū)懭氲臄?shù)據(jù)大小,同時還需要通過 DF 標志位來控制是向存儲地址增加還是減小。在使用字符串操作指令時,需要仔細理解這些指令的語法和操作方式,以便正確地處理字符串數(shù)據(jù)。
### 3.1 MOVSB/MOVSW/MOVSD
移動串指令包括了`MOVSB、MOVSW、MOVSD`這三條指令,該指令的原理為從`ESI`到`EDI`中,執(zhí)行后將ESI地址里面的內(nèi)容移動到EDI指向的內(nèi)存空間中,該指令常用于對特定字符串的復制操作。
?- MOVSB指令:將一個字節(jié)從ESI地址指向的內(nèi)存單元復制到EDI地址指向的內(nèi)存單元,同時增加或減少ESI和EDI(取決于方向標志位的狀態(tài))。
?- MOVSW指令:將兩個字節(jié)從ESI地址指向的內(nèi)存單元復制到EDI地址指向的內(nèi)存單元,
?- MOVSD指令:將四個字節(jié)從ESI地址指向的內(nèi)存單元復制到EDI地址指向的內(nèi)存單元。這些指令都可用于復制字符串或移動緩沖區(qū)。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? ; 逐字節(jié)拷貝
? SrcString? ? BYTE "hello lyshark",0h? ? ? ; 源字符串
? SrcStringLen EQU $ - SrcString - 1? ? ? ? ; 計算出原始字符串長度
? DstString? ? BYTE SrcStringLen dup(?),0h? ; 目標內(nèi)存地址
? szFmt BYTE '字符串: %s 長度: %d ',0dh,0ah,0
??
? ; 四字節(jié)拷貝
? ddSource DWORD 10h,20h,30h? ? ? ? ? ? ? ?; 定義三個四字節(jié)數(shù)據(jù)
? ddDest? ?DWORD lengthof ddSource dup(?)? ; 得到目標地址
.code
? main PROC
? ? ; 第一種情況: 實現(xiàn)逐字節(jié)拷貝
? ? cld? ? ? ? ? ? ? ? ? ? ? ? ?; 清除方向標志
? ? mov esi,offset SrcString? ? ; 取源字符串內(nèi)存地址
? ? mov edi,offset DstString? ? ; 取目標字符串內(nèi)存地址
? ? mov ecx,SrcStringLen? ? ? ? ; 指定循環(huán)次數(shù),為原字符串長度
? ? rep movsb? ? ? ? ? ? ? ? ? ?; 逐字節(jié)復制,直到ecx=0為止
? ??
? ? lea eax,dword ptr ds:[DstString]
? ? mov ebx,sizeof DstString
? ? invoke crt_printf,addr szFmt,eax,ebx
? ??
? ? ; 第二種情況: 實現(xiàn)4字節(jié)拷貝
? ? lea esi,dword ptr ds:[ddSource]
? ? lea edi,dword ptr ds:[ddDest]
? ? cld
? ? rep movsd
? ??
? ? ; 使用loop循環(huán)逐字節(jié)復制
? ? lea esi,dword ptr ds:[SrcString]
? ? lea edi,dword ptr ds:[DstString]
? ? mov ecx,SrcStringLen
? ? cld? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?; 設置方向為正向復制
? @@: movsb? ? ? ? ? ? ? ? ? ? ? ? ? ? ?; 每次復制一個字節(jié)
? ? dec ecx? ? ? ? ? ? ? ? ? ? ? ? ? ?; 循環(huán)遞減
? ? jnz @B? ? ? ? ? ? ? ? ? ? ? ? ? ? ; 如果ecx不為0則循環(huán)
? ??
? ? lea eax,dword ptr ds:[DstString]
? ? mov ebx,sizeof DstString
? ? invoke crt_printf,addr szFmt,eax,ebx
? ??
? ? invoke ExitProcess,0
? main ENDP
END main
```
### 3.2 CMPSB/CMPSW/CMPSD
比較串指令包括`CMPSB、CMPSW、CMPSD`比較`ESI、EDI`執(zhí)行后將ESI指向的內(nèi)存操作數(shù)同EDI指向的內(nèi)存操作數(shù)相比較,其主要從ESI指向內(nèi)容減去EDI的內(nèi)容來影響標志位。這些指令通常用于比較字符串中的字符,可影響方向標志、零標志和符號標志位的狀態(tài)。
CMPSB指令是將ESI和EDI地址指向的內(nèi)存單元中的一個字節(jié)進行比較,同時增加或減少ESI和EDI(取決于方向標志位的狀態(tài))。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? ; 逐字節(jié)比較
? SrcString? ? BYTE "hello lyshark",0h
? DstStringA? ?BYTE "hello world",0h
.const
? szFmt BYTE '字符串: %s',0dh,0ah,0
? YES BYTE "相等",0
? NO? BYTE "不相等",0
??
.code
? main PROC
? ? ; 實現(xiàn)字符串對比,相等/不相等輸出
? ? lea esi,dword ptr ds:[SrcString]
? ? lea edi,dword ptr ds:[DstStringA]
? ? mov ecx,lengthof SrcString
? ? cld
? ? repe cmpsb
? ? je L1
? ? jmp L2
? L1: lea eax,YES
? ? invoke crt_printf,addr szFmt,eax
? ? jmp lop_end
? L2: lea eax,NO
? ? invoke crt_printf,addr szFmt,eax
? ? jmp lop_end
? lop_end:
? ? int 3
? ? invoke ExitProcess,0
? main ENDP
END main
```
CMPSW 是對比一個字類型的數(shù)組,指令是將ESI和EDI地址指向的內(nèi)存單元中的兩個字節(jié)進行比較,只有當數(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
.data
? Array1 WORD 1,2,3,4,5? ? ? ; 必須全部相等才會清空ebx
? Array2 WORD 1,3,5,7,9
.const
? szFmt BYTE '數(shù)組: %s',0dh,0ah,0
? YES BYTE "相等",0
? NO? BYTE "不相等",0
??
.code
? main PROC
? ? lea esi,Array1
? ? lea edi,Array2
? ? mov ecx,lengthof Array1
? ??
? ? cld
? ? repe cmpsw
? ? je L1
? ? lea eax,NO
? ? invoke crt_printf,addr szFmt,eax
? ? jmp lop_end
? L1: lea eax,YES
? ? invoke crt_printf,addr szFmt,eax
? ? jmp lop_end
??
? lop_end:
? ? int 3
? ? invoke ExitProcess,0
? main ENDP
END main
```
CMPSD則是比較雙字數(shù)據(jù),指令將ESI和EDI地址指向的內(nèi)存單元中的四個字節(jié)進行比較,同樣可用于比較數(shù)組,這里就演示一下比較單數(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
? var1 DWORD 1234h
? var2 DWORD 5678h
.const
? szFmt BYTE '兩者: %s',0dh,0ah,0
? YES BYTE "相等",0
? NO? BYTE "不相等",0
??
.code
? main PROC
? ? lea esi,dword ptr ds:[var1]
? ? lea edi,dword ptr ds:[var2]
? ??
? ? cmpsd
? ? je L1
? ? lea eax,dword ptr ds:[YES]
? ? invoke crt_printf,addr szFmt,eax
? ? jmp lop_end
? ??
? L1: lea eax,dword ptr ds:[NO]
? ? invoke crt_printf,addr szFmt,eax
? ? jmp lop_end
? lop_end:
? ? int 3
? ? invoke ExitProcess,0
? main ENDP
END main
```
### 3.3 SCASB/SCASW/SCASD
掃描串指令包括`SCASB、SCASW、SCASD`其作用是把`AL/AX/EAX`中的值同EDI尋址的目標內(nèi)存中的數(shù)據(jù)相比較,這些指令在一個長字符串或者數(shù)組中查找一個值的時候特別有用。
?- SCASB指令:將AL寄存器中的值與EDI地址指向的內(nèi)存單元中的一個字節(jié)進行比較,同時增加或減少EDI(取決于方向標志位的狀態(tài))。
?- SCASW指令:將AX寄存器中的值與EDI地址指向的內(nèi)存單元中的兩個字節(jié)進行比較。
?- SCASD指令:將EAX寄存器中的值與EDI地址指向的內(nèi)存單元中的四個字節(jié)進行比較。這些指令通常用于在一個長字符串或數(shù)組中查找一個特定值的位置,可影響方向標志、零標志和符號標志位的狀態(tài)。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? szText BYTE "ABCDEFGHIJK",0
.const
? szFmt BYTE '字符F所在位置: %d',0dh,0ah,0
.code
? main PROC
? ? ; 尋找單一字符找到會返回第幾個字符
? ? lea edi,dword ptr ds:[szText]
? ? mov al,"F"
? ? mov ecx,lengthof szText -1
? ? cld
? ? repne scasb? ? ? ? ? ? ? ? ?; 如果不相等則重復掃描
? ? je L1
? ? xor eax,eax? ? ? ? ? ? ? ? ?; 如果沒找到F則清空eax
? ? jmp lop_end
? ??
? L1: sub ecx,lengthof szText -1
? ? neg ecx? ? ? ? ? ? ? ? ? ? ?; 如果找到輸出第幾個字符
? ? invoke crt_printf,addr szFmt,ecx
??
? lop_end:
? ? int 3
? main ENDP
END main
```
如果我們想要對數(shù)組中某個值是否存在做判斷,則可以使用SCASD指令掃描一個數(shù)組中是否存在一個特定的值,通過循環(huán)指令(如LOOP或JECXZ)逐個4字節(jié)掃描,來檢查EAX寄存器中的值是否與目標數(shù)組中的值匹配。如果匹配成功,則方向標志位將被設置為與掃描方向相反的方向,如果沒有找到匹配項,方向標志位將保持不變。
在使用循環(huán)指令時,需要在每次循環(huán)中比較數(shù)組當前位置的值是否與目標值相等,如果相等就跳出循環(huán),如果沒有找到匹配項,就繼續(xù)循環(huán)指令知道數(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 65,88,93,45,67,89,34,67,89,22
.const
? szFmt BYTE '數(shù)值: %d 存在',0dh,0ah,0
.code
? main PROC
? ? lea edi,dword ptr ds:[MyArray]
? ? mov eax,34
? ? mov ecx,lengthof MyArray - 1
? ? cld
? ? repne scasd
? ? je L1
? ? xor eax,eax
? ? jmp lop_end
? L1: sub ecx,lengthof MyArray - 1
? ? neg ecx
? ? invoke crt_printf,addr szFmt,ecx,eax
? lop_end:
? ? int 3
? main ENDP
END main
```
### 3.4 STOSB/STOSW/STOSD
存儲指令主要包括`STOSB、STOSW、STOSD`其作用是把`AL/AX/EAX`中的數(shù)據(jù)儲存到EDI給出的地址中,執(zhí)行后EDI的值根據(jù)方向標志的增加或減少,該指令常用于初始化內(nèi)存或堆棧。
?- STOSB指令:將AL寄存器中的值存儲到EDI地址指向的內(nèi)存單元中,同時增加或減少EDI(取決于方向標志位的狀態(tài))。
?- STOSW指令:將AX寄存器中的值存儲到EDI地址指向的兩個字節(jié)內(nèi)存單元中。
?- STOSD指令:將EAX寄存器中的值存儲到EDI地址指向的四個字節(jié)內(nèi)存單元中。這些指令常用于初始化內(nèi)存、堆棧和緩沖區(qū)。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? Count? DWORD 100
? String BYTE 100 DUP(?),0
.code
? main PROC
??
? ? ; 利用該指令初始化字符串
? ? mov al,0ffh? ? ? ? ? ? ? ? ? ?; 初始化填充數(shù)據(jù)
? ? lea di,byte ptr ds:[String]? ?; 待初始化地址
? ? mov ecx,Count? ? ? ? ? ? ? ? ?; 初始化字節(jié)數(shù)
? ? cld? ? ? ? ? ? ? ? ? ? ? ? ? ?; 初始化:方向=前方
? ? rep stosb? ? ? ? ? ? ? ? ? ? ?; 循環(huán)填充
? ??
? ? ; 存儲字符串: 使用A填充內(nèi)存
? ? lea edi,dword ptr ds:[String]
? ? mov al,"A"
? ? mov ecx,Count
? ? cld
? ? rep stosb
? ? int 3
? main ENDP
END main
```
### 3.5 LODSB/LODSW/LODSD
載入指令主要包括`LODSB、LODSW、LODSD`起作用是將ESI指向的內(nèi)存位置向`AL/AX/EAX`中裝載一個值,同時ESI的值根據(jù)方向標志值增加或減少,如下分別完成加法與乘法計算,并回寫到內(nèi)存中。
?- LODSB指令:將ESI地址指向的一個字節(jié)復制到AL寄存器中,同時增加或減少ESI(取決于方向標志位的狀態(tài))。
?- LODSW指令:將ESI地址指向的兩個字節(jié)復制到AX寄存器中
?- LODSD指令:將ESI地址指向的四個字節(jié)復制到EAX寄存器中。
```ASM
? .386p
? .model flat,stdcall
? option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
include msvcrt.inc
includelib msvcrt.lib
.data
? ArrayW? ? ? WORD 1,2,3,4,5,6,7,8,9,10
? ArrayDW? ? ?DWORD 1,2,3,4,5
? ArrayMulti? DWORD 10
??
? szFmt BYTE '計算結(jié)果: %d ',0dh,0ah,0
.code
? main PROC
? ? ; 利用載入命令計算數(shù)組加法
? ? lea esi,dword ptr ds:[ArrayW]
? ? mov ecx,lengthof ArrayW
? ? xor edx,edx
? ? xor eax,eax
? @@: lodsw? ? ? ? ? ; 將輸入加載到EAX
? ? add edx,eax
? ? loop @B
? ??
? ? mov eax,edx? ? ; 最后將相加結(jié)果放入eax
? ? invoke crt_printf,addr szFmt,eax
? ??
? ? ; 利用載入命令(LODSD)與存儲命令(STOSD)完成乘法運算
? ? mov esi,offset ArrayDW? ?; 源指針
? ? mov edi,esi? ? ? ? ? ? ? ; 目的指針
? ? cld? ? ? ? ? ? ? ? ? ? ? ; 方向=向前
? ??
? ? mov ecx,lengthof ArrayDW ; 循環(huán)計數(shù)器
? L1: lodsd? ? ? ? ? ? ? ? ? ? ; 加載[esi]至EAX
? ? mul ArrayMulti? ? ? ? ? ?; 將EAX乘以10
? ? stosd? ? ? ? ? ? ? ? ? ? ; 將結(jié)果從EAX存儲至[EDI]
? ? loop L1
? ??
? ? ; 循環(huán)讀取數(shù)據(jù)(存在問題)
? ? mov esi,offset ArrayDW? ? ?; 獲取基地址
? ? mov ecx,lengthof ArrayDW? ?; 獲取長度
? ? xor eax,eax
? @@: lodsd
? ? invoke crt_printf,addr szFmt,eax
? ? dec ecx
? ? loop @B?
? ? int 3
? main ENDP
END main
```
本文作者: 王瑞
本文鏈接: https://www.lyshark.com/post/f6603608.html
版權聲明: 本博客所有文章除特別聲明外,均采用 BY-NC-SA 許可協(xié)議。轉(zhuǎn)載請注明出處!