首页 > 代码库 > x86汇编之十(使用字符串)

x86汇编之十(使用字符串)

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这些指令还是还正向的方向取值,操作不方便。

 

 

x86汇编之十(使用字符串)