x86匯編之十(使用字符串)
轉自網絡,出處不詳
一、傳送字符串
Intel提供了完整的字符串傳送指令,就像是MOV指令一樣。
1、MOVS指令
1)movs指令格式
把字符串從一個位內存位置傳送到另一個內存內置,其指令隱含了源操作數與目的操作數。ATT有3條傳字符串的指令的,分別是MOVSB,MOVSW,MOVSL。
指令 |
含義 |
源址 |
目址 |
MOVSB |
傳一個字節的字符 |
(%ESI) |
(%EDI) |
MOVSW |
傳一個字的字符 |
(%ESI) |
(%EDI) |
MOVSL |
傳4個字節的字符 |
(%ESI) |
(%EDI) |
說明,在指令源址與目址隱含,不需要給出。ESI與EDI是保存字符串的地址,那怎樣把字符串地址傳給ESI與EDI呢?有兩種方式。一種是 movl $ 標簽名 ,%ESI方式,一種是使用LEA指令。
2)LEA指令
32位程序下,地址為4個字節,所以指令就是LEAL。如加載一個字符串地址到ESI寄存器,假設字符串標簽為demoStr。使用就是:leal demoStr,%esi.
3)movs與lea指令示例
# file movstest1.s --an example of MOVS指令使用例子 .section .data srcStr: .string "abcdefghigklmn" .section .bss .lcomm destStr,14 .section .text .global _start _start: nop; leal srcStr,%esi #標簽前不需要加$符號。 leal destStr,%edi movsb movsw movsl
movl $1,%eax ret
|
通過gdb調式如下:
(gdb) n 14 movsw (gdb) x/s &destStr 0x403000 <destStr>: "a" (gdb) n 15 movsl (gdb) x/s &destStr 0x403000 <destStr>: "abc" (gdb) n 17 movl $1,%eax (gdb) x/s &destStr 0x403000 <destStr>: "abcdefg" (gdb) print /x $edi $3 = 0x403007 (gdb) x/s 0x403007 0x403007 <destStr+7>: "" (gdb) print /x $esi $4 = 0x402007 (gdb) x/s 0x402007 0x402007 <srcStr+7>: "higklmn" (gdb) |
通過調試可以看到,通過執行movsb,movsw,movsl,從源串傳送了7個字符給目的串。通過觀察,每次用movs指令,esi中的值會進行變化,如movsw指令,esi=esi+2,同樣edi也是如此。
4)ESI與EDI的方向。
ESI與EDI是增地址還是減地址,取決於EFLAGS寄存器的標識位DF,如果DF是0,那就是增方向,如果DF是1那就是減方向。英特爾提供兩條指令設置DF,STD設置DF=1,CLD設置DF=0;使用STD后,每使用MOVS指令,會使ESI/EDI中的地址遞減。
示例:
# file movstest2.s --an example of std,cld,leal,movsl指令使用例子 .section .data srcStr: .ascii "abcdefghigklmn" #tmpStr: # .set strLen,tmpStr-srcStr .section .bss .lcomm destStr,14 .section .text .global _start _start: nop; #movl $strLen,%eax leal srcStr+13,%esi /*#實際開始地址要長度減一才得。因為地址是從0開始*/ leal destStr+13,%edi std; movsb movsw movsl
movl $1,%eax ret |
用gdb調式,輸出destStr的結果如下:
(gdb) x/14b &destStr 0x403000 <destStr>: "" 0x403001 <destStr+1>: "" 0x403002 <destStr+2>: "" 0x403003 <destStr+3>: "" 0x403004 <destStr+4>: "" 0x403005 <destStr+5>: "" 0x403006 <destStr+6>: "" 0x403007 <destStr+7>: "" 0x403008 <destStr+8>: "" 0x403009 <destStr+9>: "" 0x40300a <destStr+10>: "klmn" 0x40300f <destStr+15>: "" 0x403010: "" 0x403011: "" |
大家看到結果可能會有疑問,明明傳了7個字節,為什么只有三個字節呢?答案是超過一個字節范圍的movsw、movsl指令其取字節的方向還是還是按照內存地址從低向高的方向取。其傳輸出過程如下:
指令 |
傳送字節位置 |
destStr結果 |
movsb |
傳送13至13的位置。位置減1到達12 |
13:n |
movsw |
傳送12至13的位置,位置減2到10 |
12:m 13:n |
movsl |
傳送10至13的位置,位置到達6 |
10:k 11:l 12:m 13:n |
下面用gdb命令中跟蹤整個過程
(gdb) n 18 movsb (gdb) n 19 movsw #注:movsb執行完成,movsw還沒有執行 (gdb) x/14b &destStr 0x403000 <destStr>: "" 0x403001 <destStr+1>: "" 0x403002 <destStr+2>: "" 0x403003 <destStr+3>: "" 0x403004 <destStr+4>: "" 0x403005 <destStr+5>: "" 0x403006 <destStr+6>: "" 0x403007 <destStr+7>: "" 0x403008 <destStr+8>: "" 0x403009 <destStr+9>: "" 0x40300a <destStr+10>: "" 0x40300b <destStr+11>: "" 0x40300c <destStr+12>: "" 0x40300d <destStr+13>: "n" (gdb) n 20 movsl #注:movsw執行完成,movsl還沒有執行 (gdb) x/14b &destStr 0x403000 <destStr>: "" 0x403001 <destStr+1>: "" 0x403002 <destStr+2>: "" 0x403003 <destStr+3>: "" 0x403004 <destStr+4>: "" 0x403005 <destStr+5>: "" 0x403006 <destStr+6>: "" 0x403007 <destStr+7>: "" 0x403008 <destStr+8>: "" 0x403009 <destStr+9>: "" 0x40300a <destStr+10>: "" 0x40300b <destStr+11>: "" 0x40300c <destStr+12>: "mn" 0x40300f <destStr+15>: "" (gdb) n 22 movl $1,%eax #注:movsl執行完成,mov $1……還沒有執行 (gdb) x/14b &destStr 0x403000 <destStr>: "" 0x403001 <destStr+1>: "" 0x403002 <destStr+2>: "" 0x403003 <destStr+3>: "" 0x403004 <destStr+4>: "" 0x403005 <destStr+5>: "" 0x403006 <destStr+6>: "" 0x403007 <destStr+7>: "" 0x403008 <destStr+8>: "" 0x403009 <destStr+9>: "" 0x40300a <destStr+10>: "klmn" 0x40300f <destStr+15>: "" 0x403010: "" 0x403011: "" (gdb) print /x $edi #注從0x40300a移到0x403006 $9 = 0x403006 (gdb) |
從以上可以看出,movs系列指令是先專后移動位置,不是先移后轉傳字符。
5)傳足夠長的字符串
如果有一個字符串有5000個字符,即使采用movsl指令也得傳1250次才能傳給另一個字符串,難准我們要在代碼中重復寫上1250次相同的代碼,顯然這樣做是不可做的。intel提供了一種循環方式來解決長字符串的傳送。使用這種指令要把字符串長度放在ecx寄存器,用loop指令來判斷ecx的值是否為0來進行傳送。
示例:
# file movstest3.s --an example of 傳送一定長度的字符串指令 .section .data srcStr: .ascii "abcdefghigklmnopqrstuvwxyz我是中國人\0" tmpStr: .set strLen,tmpStr-srcStr .section .bss .lcomm destStr,100 .section .text .global _start _start: nop pushl %ebp movl %esp,%ebp andl $-16,%esp subl $32,%esp movl $strLen,%ecx /*.set 設定的相當於常量,引用要加$符與.equ 類同 */ leal srcStr,%esi leal destStr,%edi cld loop1: movsb loop loop1
pushl $destStr call _printf addl $4,%esp #清棧
movl $1,%eax leave ret |
2、REP前輟
這條指令是MOVS指令的前輟,按照特定次數,重復把字符從源串傳到目的串,直到ecx中的值為0。
1)逐字節地傳送字符串
movsb與rep還有ecx相結合,進行傳送。示例:
.set strLen,tmpStr-srcStr .section .bss .lcomm destStr,100 .section .text .global _start _start: nop pushl %ebp movl %esp,%ebp andl $-16,%esp subl $32,%esp movl $strLen,%ecx
leal srcStr,%esi leal destStr,%edi cld #設定傳定方向
rep movsb
pushl $destStr call _printf addl $4,%esp #清棧
movl $1,%eax leave ret |
用gdb進行調試,觀察edi與esi中地址的變化, 結果如下:
24 rep movsb 4: /x $edi = 0x403000 2: /x $esi = 0x402000 (gdb) n 26 pushl $destStr #rep movsb執行完畢,移位26(1a),指向一個空位,最后一值z的位置為0x403019,因movs是先傳后對edi/esi進行計算 4: /x $edi = 0x40301a 2: /x $esi = 0x40201a (gdb) n |
2)逐塊進傳送字符串
利用 movsw,movsl指令,可以一次性傳2個字節或4個字節,這樣可以減少傳送次數。如一個度為24了字符串傳按rep movsl,只需傳6次即可,這樣可以把ecx的值設定為6,比按movsb傳送節省了18次操作。但是ecx設置不當,會使讀取源串個它本身的范圍大小或是目的串也超過它本身大小。如字符串長26,按rep movsl傳,設ecx為7,這樣會多傳2個字節。兩個字節可能是不可預料的結果。
# file reptest2.s --逐塊傳送字符串,也就是rep movsw,rep movsl .section .data srcStr: .ascii "abcdefghigklmnopqrstuvwxyz" srcStr2: .asciz "not ok"
.section .bss .lcomm destStr,100 .section .text .global _start _start: nop pushl %ebp movl %esp,%ebp andl $-16,%esp subl $32,%esp movl $8,%ecx /*每次傳4個,共傳32次,立即數一定要加8,要不然會當地址*/ leal srcStr,%esi leal destStr,%edi cld #設定傳定方向 rep movsl pushl $destStr call _printf addl $4,%esp #清棧
movl $1,%eax leave ret |
本次輸出結果如下:
abcdefghigklmnopqrstuvwxyznot ok # |
3)傳送大型字符串
此對上節提的逐塊傳送可能會是esi或edi指向字符串不存在的內存位置,建議用rep movsb 與rep movsl指令結合進行字符串傳送。把字符串的長度除以4,得到的商用rep movsl進行,得到的余數用rep movsb進行傳。其中用到一個數學技巧,如果一個除數是2的n方的結果,那么他的余數可以把它本身的值減去一與被除數進進相與得到。如:
被除數(edx) |
除數 (eax) |
快速求余(edx) |
117 |
4 |
andl $3,%edx |
119 |
8 |
and $7,%edx |
示例如下:
# file reptest3.s --傳送字符串的技巧 .section .data srcStr: .ascii "abcdefghigklmnopqrstuvwxyz" srcStr2: .asciz "not ok" len: .int 26 .section .bss .lcomm destStr,100 .section .text .global _start _start: nop pushl %ebp movl %esp,%ebp andl $-16,%esp subl $32,%esp movl len,%ecx shrl $2,%ecx /*右移2位相當於除以4*/ leal srcStr,%esi leal destStr,%edi cld #設定傳定方向 rep movsl movl len,%ecx andl $3,%ecx /*求余數*/ rep movsb pushl $destStr call _printf addl $4,%esp #清棧 movl $1,%eax leave ret |
gdb調試,觀察rep movsl與rep movsb指令執行之后的desStr值。
(gdb) n 25 rep movsl (gdb) n 27 movl len,%ecx #注rep movsl執行完成 (gdb) x/s &destStr 0x403000 <destStr>: "abcdefghigklmnopqrstuvwx" (gdb) n 28 andl $3,%ecx /**/ (gdb) n 29 rep movsb (gdb) print /d $ecx $1 = 2 (gdb) n 32 pushl $destStr #注 rep movsb執行完成 (gdb) x/s &destStr 0x403000 <destStr>: "abcdefghigklmnopqrstuvwxyz" |
4)按照相反順序用rep進行傳送
同樣可以從字符串末端進行字符串送傳,示例如下:
# file reptest4.s --反向傳送字符串的技巧 .section .data srcStr: .ascii "abcdefghigklmnopqrstuvwxyz" srcStr2: .asciz "not ok" len: .int 26 .section .bss .lcomm destStr,100 .section .text .global _start _start: nop pushl %ebp movl %esp,%ebp andl $-16,%esp subl $32,%esp movl len,%ecx shrl $2,%ecx /*右移2位相當於除以4*/ leal srcStr+25,%esi leal destStr+25,%edi
std #設定傳定方向 rep movsl movl len,%ecx andl $3,%ecx /*求余數*/ rep movsb movl $1,%eax leave ret |
3、有條件的rep指令
intel依據標志位ZF是否為0,設置4個指令,列表如下:
指令 |
含義 |
REPE |
等於0時重復 |
REPNE |
不為0時重復 |
REPZ |
等於0時重得 |
REPNZ |
不為0時重復 |
二、存儲與加載字符串
加載與存儲字符串指令,提供了字符串到寄存器,寄存器到字符串的操作。
1、加載指令lods
指令說明如下:
指令 |
含義 |
Lodsb |
加載一個字節到al中,Esi中的地址值按df的方向進行加1或減1 |
lodsw |
加載一個字到ax中,esi中的地址值按df的方向進行加2或減2 |
lodsl |
加載二個字到eax中,加載之后esi中的地址值加4或減4 |
加載指令可以與rep配合使用,但是默認是加載到eax寄存器,失去了用rep的意義。
2、存儲指令STOS
stos指令用於把在 eax寄存器上的值存到edi指定的位置上。這個指令與上相同,源操作數與目的操作數隱藏。該指令相當於:
stosb al,(%edi)/stosw ax,(%edi)/stosl eax,(%edi)。為什么英特爾不把這個指令弄直接些呢?
指令 |
含義 |
stosb |
從al存一個字節到edi指定的位置,edi的位置在存后或向前移位1個 |
stosw |
從ax存一個字節到edi指定的位置,edi的位置在存向后或向前移位2個 |
stosl |
從eax存一個字節到edi指定的位置,edi的位置在向后或向前移位4個 |
示例:
# filename:stostest1 --演示把從內存加載一個字符到寄存器,重復n次,返回到內存 .section .data srcStr: .ascii " " .section .bss .lcomm destStr,256 .section .text .global _start _start: nop pushl %ebp movl %esp,%ebp andl $-16,%esp subl $32,%esp
leal srcStr,%esi leal destStr,%edi
cld#設置字符串從低到高 lodsb #加載一個字節到al
movl $256,%ecx rep stosb
movl $1,%eax leave ret |
通過gdb調試觀察其結果
(gdb) n 20 lodsb #al (gdb) print /x $esi #注:看源串地址 $6 = 0x402000 (gdb) print /x $edi $7 = 0x403000 (gdb) n 22 movl $256,%ecx #注:執行lodsb指令后 (gdb) print /x $esi $8 = 0x402001 #注移動1位 (gdb) print /x $edi $9 = 0x403000 (gdb) n 23 rep stosb (gdb) n 25 movl $1,%eax (gdb) print /x $esi $10 = 0x402001 (gdb) print /x $edi $11 = 0x403100 #注移動265,16進制100 (gdb) x/0x100b &destStr (gdb) x/256b &destStr 0x403000 <destStr>: 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x403008 <destStr+8>: 0x20 0x20 0x20 0x20 0x20 0x20 0x20 ……#注020就是空格的ascii碼值 (gdb) x/bt 0x403100 0x403100: 00000000 #注:edi總是指向下一個要執行的位置,有點與eip相同 |
3、構建自己的字符串應用(字符小寫轉大寫)
通過lods,可以把字符串從內存中取出來放在寄存器中,可以調用相關的函數對字符進行處理。如加密、解密、壓縮、反壓縮等。其實cpu是分不清字符串,lods本意就是加載二進制代碼。如通過lods與stos可以把一段機器代碼加載到程序的代碼執行內存空間區,可以形成病毒注入機制。以面這個示例展示把小寫字母轉換成大寫字母:
# filename:stostest2 --演示把小寫字符串轉變成大寫字符串 # 大寫字母的ASCILL碼是從65至90結束,小寫字97開始122結束 .section .data srcStr: .ascii "This 我們的 a test programmer convert to Upper\0" endSrc: .set srcSize,endSrc-srcStr #獲得源串的長度 hint1: .string "源字符串是:%s\n" hint2: .string "目標字行串是:%s\n" .section .bss .lcomm destStr,srcSize+1 .section .text .global _start _start: nop pushl %ebp movl %esp,%ebp andl $-16,%esp subl $32,%esp
leal srcStr,%esi leal destStr,%edi movl $srcSize,%ecx cld#設置字符串從低到高 loop1: lodsb #加載一個字節到al cmpb $97,%al jge upper stosb jmp loopend upper: cmpb $122,%al jg loopend subb $0x20,%al stosb
loopend: loop loop1
pushl $srcStr pushl $hint1 call _printf addl $8,%esp
pushl $destStr pushl $hint2 call _printf addl $8,%esp
fileend: movl $1,%eax leave ret |
編譯運行程序:
as -o stostest2.o stostest2.s –gstabs
ld -o stostest2.exe stostest2.o msvcrt.dll
為了方便需要把msvcrt.dll copy到您的源代碼所在的目錄。
三、比較字符串cmps
intel提供了一個比較字符串指指令,cmpsx,其實一次只能比較1個或2個或4個字節,如果多次重復就可以把字符串比較完。比較兩個字符串的內容是否相等,這個在編程中是大量應用的。
1、cmps指令介紹
這個指令可以當cmp用,只是它的源操作數位置與目的操作位置固定而已。如16個字節以上的特大整數對比是否相等就可以用cmps來實現,該操作的源操作地址放在esi寄存器,目的操作數的地址放在edi寄存器。每次cmp使用之后,依據EFLAGS中的DF位置來決定esi/edi中的寄存器的地址值是否加1還是減1.
非常重要:cmps是源字串減去目標字串,指令會適當地設置EFLAGS寄存器的中的DF\ZF\OF\CF標志,通過這些標志,結合跳轉指令就能寫出有分支的應用程序來。
指令 |
含義 |
cmpsb |
比較一個字節,源地址:esi,目地址:edi。執行之后esi與edi進行加1或減1。會影響到ZF、CF、OF等標識位 |
cmpsw |
比較一個字。源地址:esi,目地址:edi。執行之后esi與edi進行加2或減2。會影響到ZF、CF、OF等標識位 |
cmpsl |
比較二個字。源地址:esi,目地址:edi。執行之后esi與edi進行加4或減4。會影響到ZF、CF、OF等標識位 |
2、cmps指令應用實例
比較兩個字符串的內容是否相等。思路時利用repz指令,如果字符串的字節相等就繼續比較,直到比較完,源代碼如下:
# filename:cmpstest1 --比較兩個字符串是否相等 # 此比較存大缺陷,如果源串大於目標串或小於目標串可能會引起非法內存訪問 .section .data srcStr: .ascii "This A test programmer convert to upper\0" endSrc: .set srcSize,endSrc-srcStr #獲得源串的長度 destStr: .ascii "This a test programmer convert to Upper\0" hint1: .string "兩個字符串相等\n" hint2: .string "兩個字符串不等\n" .section .text .global _start _start: nop pushl %ebp movl %esp,%ebp andl $-16,%esp #讓當前函數棧的地址為16的整數倍 subl $32,%esp #為當前留出32字節棧空間用來保存局部變量值
leal srcStr,%esi #裝源字符串地址至esi leal destStr,%edi #裝目的字符串地址到edi movl $srcSize,%ecx #裝源串長度至 cld#設置字符串從低到高
repz cmpsb #相等時一直比較 jne notEq #不相等時進行跳轉
pushl $hint1 call _printf addl $4,%esp jmp fileend notEq:
pushl $hint2 call _printf addl $4,%esp #_cdecl調用方式,調用者清棧
fileend: movl $1,%eax leave ret |
用gdb進行調試。輸出結果如下:
(gdb) print/x $esi $1 = 0x402000 #注:源字串開始地址 (gdb) print /x $edi $2 = 0x402028 #:目字串開始地址 (gdb) n 25 cld# (gdb) print $ecx $3 = 40 (gdb) n 27 repz cmpsb (gdb) n 28 jne notEq (gdb) print $ecx $4 = 34 #注:剩余沒有進行比較的字串數量 (gdb) n notEq () at cmpstest1.s:36 36 pushl $hint2 (gdb) n notEq () at cmpstest1.s:37 37 call _printf (gdb) n 兩個字符串不等 38 addl $4,%esp (gdb) n fileend () at cmpstest1.s:41 41 movl $1,%eax (gdb) n 42 leave (gdb) print /x $esi $5 = 0x402006 #注:0x402005是源串與目標串相等的位置,如果從0計位就是第5位不相等。0x402006是下一個開始的比較位 (gdb) print /x $edi $6 = 0x40202e |
3、cmps比較應用改進
上面那個比較字符程序,每次按一個字節進行比較,如果字符串的字符上萬個,可能會影響性能,同時intel提供的4字節指令(如果是64位,估計有8字節比較指令)沒有用。本次改進思路為先比較按4字節來,不足4字節的按一個一個字節來,這樣比較效率可以提高4倍。見改進版源碼:
# filename:cmpstest2 --比較兩個字符串是否相等,改進版, # 此比較存大缺陷,如果源串大於目標串或小於目標串可能會引起非法內存訪問 .section .data srcStr: .ascii "This A test programmer convert to upper\0" endSrc: .set srcSize,endSrc-srcStr #獲得源串的長度 destStr: .ascii "This A test programmer convert to upper\0" hint1: .string "兩個字符串相等\n" hint2: .string "兩個字符串不等\n" .section .text .global _start _start: nop pushl %ebp movl %esp,%ebp andl $-16,%esp #讓當前函數棧的地址為16的整數倍 subl $32,%esp #為當前留出32字節棧空間用來保存局部變量值
leal srcStr,%esi #裝源字符串地址至esi leal destStr,%edi #裝目的字符串地址到edi movl $srcSize,%ecx #裝源串長度至 shrl $2,%ecx #右移2位,相當於除以4 cld#設置字符串從低到高
repz cmpsl #先按4個節的長度進行比較 jne notEq #不相等時進行跳轉
movl $srcSize,%ecx andl $3,%ecx #求4的余數 repz cmpsb jne notEq
pushl $hint1 call _printf addl $4,%esp jmp fileend notEq:
pushl $hint2 call _printf addl $4,%esp #_cdecl調用方式,調用者清棧
fileend: movl $1,%eax leave ret |
4、字符串關系比較
通過cmps指令可以比較字符串不同或相等。可還可以比較字符串是大於還是小於。也就是把兩個字符串之間的4種關系全部辦到了(大於、小於、不等於、等於)。字符串的關系比較一般是按照字典順序(這兒指得是英文)。匯編語言怎樣處理中文字符串的比較,這個可以用c寫一個寬字符類型的程序,譯成匯編進行學習。
四、查找字符串
實際編程中,在字符串中查找是否存在某子字符串及返回他們在目標字符串的位置,此應用相當廣。我們可愛的intel為我們提供了查找機器指令。scas。
1、scas指令介紹。
scas指令以eax中的值為源操作數(相當於子串一部分),以edi中的地址值為目標值,進行比較。也就是用eax的值-(edi)。這可能會引起eflags的ZF、OF、CF等標志位值發生改變。當然利用這些標識位進行跳轉是我們匯編編程必不可少的環節。
指令 |
含義 |
scasb |
源操作數在al中,與edi指向的內存值進行對比。scasb執行完,edi依據df標志進行加1或減1 |
scasw |
源操作數在ax中,與edi指向的內存值進行對比。scasw執行完,edi依據df標志進行加2或減2 |
scasl |
源操作數在eax中,與edi指向的內存值進行對比。scasl執行完,edi依據df標志進行加4或減4 |
2、scas指令使用實例,在一個字符串中查找一個字符
在字符串中查找一個字符,如果存在就返回該字符在字符串中的首先出現的位置,如果查不到就返回-1。本次設計一個查找函數,傳入子字符串(一個字符)與母子符串,返回子字符串在母字符串開始的位置,如 果找不到,返回-1.下面列出源代碼:
strUnit.s此源文件包含一個查找函數:
.section .text .global _findStr .def _findStr ;.scl 2;.type 32 ;.endef /* int findStr(char * subStr,char * destStr); */ _findStr: pushl %ebp movl %esp,%ebp subl $96,%esp
pushl %edi pushl %esi pushl %ecx pushl 12(%ebp) call _strlen movl %eax,%ecx /*獲得源串長度除掉0的*/ movl %ecx,-4(%ebp) addl $4,%esp /*清棧*/
movl 8(%ebp),%esi /*源串地址入esi*/ movl 12(%ebp),%edi /*目串地址入edi */ /************************** leal 8(%ebp),%esi 。not ok會把棧地址傳給esi */ cld /*從低往高查*/ lodsb #裝載一個串到ax中 repnz scasb #找不到繼續找,直到找到為止 je find #找到進行跳轉 movl $-1,%eax jmp funend find: movl -4(%ebp),%eax subl %ecx,%eax #被減數在后面,不需要neg #neg %eax
funend: popl %ecx popl %esi popl %edi leave ret |
scastest1.s源文件,主要用來調用strUnit.s中的findStr函數。scastest1.s代碼如下:
# filename:scastest1.s --在一個字符串中查找一個字符,如果存在返回該字符的位置,如果不存在則返回-1, .section .data destStr: .ascii "This A test programmer convert to upper1\0" endSrc: .set strSize,endSrc-destStr #獲得目標串的長度 subStr: .string "1" hint1: .string "子串出現的位置是:%d\n" hint2: .string "子串:%s 在目標串中不存在\n" .section .text .global _start _start: nop pushl %ebp movl %esp,%ebp andl $-16,%esp #讓當前函數棧的地址為16的整數倍 subl $32,%esp #為當前留出32字節棧空間用來保存局部變量值
pushl $destStr pushl $subStr call _findStr add $8,%esp #清棧
cmpl $-1,%eax #%eax返回查找結果 jne finded #表示查找到了
pushl $subStr pushl $hint2 call _printf addl $8,%esp jmp fileend finded: pushl %eax pushl $hint1 call _printf addl $8,%esp #_cdecl調用方式,調用者清棧
fileend: movl $1,%eax leave ret .def _findStr ;.scl 2;.type 32; .endef |
對以上兩文件進行編與連接,指令如下:
as -o strUnit.o strUnit.s –gstabs as -o scastest1.o scastest1.s –gstabs ld -o scastest1.exe scastest1.o strUnit.o msvcrt.dll |
加上-gstabs開關為gdb調試方便,加入調試信息。
用gdb調試,現在載取一些相關的調試信息
24 call _findStr (gdb) print &destStr $10 = (<data variable, no debug info> *) 0x402000#注:調用之前打印下源串地址與目的串地址 (gdb) print &subStr $11 = (<data variable, no debug info> *) 0x402029 (gdb) s findStr () at strUnit.s:6 …… 19 movl 8(%ebp),%esi (gdb) n 20 movl 12(%ebp),%edi (gdb) n 24 cld (gdb) print /x $esi $13 = 0x402029 #子串地址已傳至esi (gdb) n 25 lodsb (gdb) print /x $edi $14 = 0x402000#注目的串地址已傳止edi (gdb) n 26 repnz scasb # (gdb) print $ecx $15 = 40 (gdb) n 27 je find # (gdb) n find () at strUnit.s:31 31 movl -4(%ebp),%eax (gdb) print $ecx $16 = 0#注:ecx執行完畢,ecx為0表示查找完成或在最后一個字串中查到了。 |
3、sasw與sasl的缺陷
因為sasw與sasl指令近照2個或4個字節進行查找。如果有一個字串"this is a beautiful girl "在當中查找eaut,用sasl方是查不到的。所以查找查符串指令還得要sasb這個指令
4、使用scas指令,在一個字符串中查找含有多個字符的子符串。
使用scas查找含多個子串的。示例如下:
.section .text .global _findSubStr .def _findSubStr;.scl 2;.type 32 ;.endef /* int findSubStr(char * subStr,char * destStr); 返回子字符串在在目的字符串出現的位置 -1表示查不到 */ _findSubStr: pushl %ebp movl %esp,%ebp subl $96,%esp pushl %edi pushl %esi pushl %ecx pushl %ebx
pushl 12(%ebp) call _strlen addl $4,%esp movl %eax,-4(%ebp) #裝入父串長度
pushl 8(%ebp) call _strlen addl $4,%esp movl %eax,-8(%ebp) #源串長度
movl -4(%ebp),%ecx cmpl %eax,%ecx jl notfind #如果父串小於子串,表示找不到 cld
movl $0,%ecx #母串計數置0 movl 12(%ebp),%edi#裝載母串地址
S0: movl $0,%ebx #子串計數器重置為0 movl 8(%ebp),%esi
S1: incl %ebx incl %ecx cmpl %ebx,-8(%ebp) je S3#如果相等,表示找到
lodsb #裝載源串 scasb #進行對比 /*incl 放在此會影響標志位放在上面*/ je S1 #相等進行下一個對比 jne S2
S2:#找不到結果 cmpl %ecx,-4(%ebp) jne S0 #沒有到結尾繼續查找 notfind: movl $-1,%eax jmp S4
S3:#表示查找到結果
subl %ebx, %ecx movl %ecx,%eax #獲得起始位置 incl %eax #因為是從0開始所以位置要加1
S4: popl %ebx popl %ecx popl %esi popl %edi
leave ret |
5、計算字符串的長度
五、字符串指令總結:
movs指令用字符串的傳送,可以實現字符串copy等功能。lods與stos指令可以實現給指給指定的內存賦值,實現內存值交換。cmps實現兩個內存相比較,判斷其內容是否相同,如對兩個數組、兩個結構成員進行對比,這個指令非常管用。scas指令實現寄存器值與內存的對比,通過遍歷可以實現在內存中查找某一個值。總而言之,字符串指令就是一個內存操作與管理指令,很實用。這些指令都能夠與rep前輟配合,實現ecx計算器自加。但intel指令還略有不足,反向時,像movs\cmps\lods\stos\scas這些指令還是還正向的方向取值,操作不方便。