汇编总结(4)——字符串操作和位操作
字符串操作
首先明确什么是字符串:
字符串是字符的一个序列,对字符串的操作处理包括复制、比较和检索等,为了有效地处理字符串,IA-32系列处理器有专门处理字符串的指令,称之为字符串操作指令,简称为串操作指令。
字符串操作指令
主要有五种常见的串操作指令:
- 串装入指令
- 串存储指令
- 串传送指令
- 串扫描指令
- 串比较指令
对于以上的五种常见操作指令,都对应三种字符尺寸:
- 字节(8位)
- 字(16位)
- 双字(32位)
串操作指令说明:
- 源串 DS:ESI
- 目的串 ES:EDI
如果只有一个数据段,或者说源串与目的串在同一个数据段,那么可以简单地认为,ESI指向源串,EDI指向目的串。
可以简单记忆位源串:Start(SI),目的串:End(DI)
串操作指令执行时,会自动调整作为指针使用的寄存器ESI和EDI的值,使之指向下一个字符,每次调整的尺寸与字符串中字符的尺寸一致。
字符串的操作方向一般是由低地址向高地址,但是也可以由高地址向低地址,这是由标志DF决定的:
- DF为0(复位):由低到高,按递增方式调整
- DF为1(置位):由高到低,按递减方式调整
而对于DF的调整,有如下两个指令;
- CLD 清DF,DF=0(clear D)
- STD 置DF,DF=1(set D)
字符串装入指令(LOAD String)
三种使用格式:
LOADSB 装入字节(Byte),执行后ESI变化1
LOADSW 装入字(Word),执行后ESI变化2
LOADD 装入双字(Double Word),执行后ESI变化4
字符串装入指令的作用是把ESI指向的字符串中的一个字符装到累加器AL、AX、EAX中,分别对应字节、字、双字,然后根据字符尺寸及方向标志DF的值调整ESI的位置。
字符串存储指令(Store String)
三种使用格式:
STOSB 存储字节(Byte)
STOSW 存储字(Word),执行后ESI变化2
STOSD 存储双字(Double Word),执行后ESI变化4
字符串装入指令的作用是把累加器AL、AX、EAX中的字符存到EDI指向的字符串中,分别对应字节、字、双字的存储,然后根据字符尺寸及方向标志DF的值调整ESI的位置。
字符串传送指令(Moving String)
三种使用格式:
MOVSB **;**字节传送
MOVSW **;**字传送
MOVSD **;**双字传送
作用:把ESI指向的字节/字/双字传送到EDI指向的存储单元中,然后根据操作大小及方向标志DF调整ESI和EDI的值,类似如下两句:
LOADSB
STOSB
但是并不会影响AL/AX/EAX的值。
字符串扫描指令(Scan String)
三种格式:
SCASB **;**串字节扫描
SCASW **;**串字扫描
SCASD **;**串双字扫描
把累加器AL/AX/EAX中的内容和由寄存器EDI所指向的一个字节/字/双字的目标数据采用相减的方式比较,相减结果反映到各状态标志,但不影响两个操作数,然后根据字符大小和方向标志DF调整EDI的值。
Demo
判断一个字符是否是十六进制字符:
char string[] ="0123456789ABCDEFabcdef";
char varch= '%'; //用于保存其他方式输入的字符
int flag; /反映是否为十六进制数符号
_asm {
MOV AL, varch ;把要判断的字符送AL
MOV ECX, 22 ;合计22个十六进制数符号
LEA EDI, string
NEXT:
SCASB ;
LOOPNZ NEXT ;没有找遍,且没有找到,继续找
JNZ NOT_FOUND ;没有找到
FOUND: ;找到,字符是十六进制数符号
MOV flag, 1
JMP SHORT OVER
NOT_FOUND: ;字符不是十六进制数符号
MOV flag, 0
OVER:
}
printf("flag=%d\n", flag); //显示为flag=0
return 0;
核心算法思想是将呆判断字符放在AL中,将EDI指向十六进制标准字符串的首地址,然后使用SCASB,如果执行SCASB后ZF标志位不为0,就说明待判断字符和十六进制标准字符串中的当前字符不等,然后进入下一个循环,注意此时EDI已经自动指向了十六进制标准字符串的下一位。如果此时ZF为0了,会进入JNZ NOT_FOUND 这句,但是由于相等时ZF等于0,所以不会执行JNZ,会执行其下一句FOUND。
这就是主要的逻辑。
字符串比较指令(CoMPare String)
三种格式:
CMPS****B **;**串字节比较
CMPS****W **;**串字比较
CMPS****D **;**串双字比较
把寄存器ESI指向的一个字节/字/双字与EDI所指向的一个字节/字/双字采用相减的方式比较,结果反映到各有关标志中,但不影响两个操作数,然后根据字符尺寸和方向标志DF的值调整ESI和EDI的值。
重复操作前缀
串操作指令每次只能处理一个字符,为了进一步提高效率,IA-32系列CPU提供重复操作前缀,重复操作前缀加在字符串操作指令之前,起到重复执行其后面一条字符串操作指令的作用。
主要设计三个:
- REP
- REPZ/REPE
- REPNZ/REPNE
REP
REP每一次先判断寄存器ECX是否为0,如果为0就结束对其后字符操作指令的重复,否则ECX减1,然后重复其后的串操作指令,所以当ECX值等于0时就不执行其后的串操作指令。
它与LOOP指令差不多,但是LOOP指令时先给ECX减1再判断ECX是否为0,而REP是先判读是否为0,不是0的话再减1,二者的共性是操作过程中给ECX减1不影响标志位。
REP只要用在MOVS和STOS之前。
Demo
以下两段代码等价:
使用LOOP:
MOV ECX,8;
MOVSD;
DEC ECX;
LOOP ECX;
使用REP:
MOV ECX,8;
REP MOVSD;
REPE/REPZ
REPE和REPZ是一个前缀的两个助记符,说白了他们两功能一样:
重复前,先判断ECX是否为0,每重复一次,ECX值减1(不影响标志位),一直重复到ECX为0或者串操作指令使ZF为0为止,只有ZF为1(相等)的时候才有可能继续重复。
主要用在CMPS和SCAS之前。
Demo
跳过字符串前面的空格符:
MOV EDI,string;
MOV ECX,-1;
MOV AL,20H;//空格符
REPE SCASB;//SCASB会让当前EDI所指的值和AL(空格)比较,当相等时ZF为0,结束REPE的条件
DEC EDI;//上面多调整了一次EDI的值,这里减1
REPNE/REPNZ
每次重复前先判断ECX是否为0,不为0时进行重复,每重复一次ECX值减1,一直到ECX为0或者ZF为1,只有当不相等(ZF为0)时才有可能重复。
REPNE/REPNZ主要用在SCAS之前。
Demo
求字符串长度:
XOR AL, AL ;AL= 0(字符串结束标记值)
MOV ECX, -1 ;假设字符串足够长(0FFFFFFFFH)
REPNZ SCASB ;寻找字符串结束标记
NOT ECX
DEC ECX ;至此ECX含字符串长度
位操作
无论在表示、存储或者处理时,位(bit)是计算机系统中最基本的单位,为了提高位操作的效率,IA-32系列处理器提供专门的位操作指令,所谓的位操作指令,就是以位(bit)为操作单位的指令。
位测试及设置指令组
位测试及设置指令组含有如下4条指令:
- BT 位测试指令(Bit test)
- BTC 位测试并取反指令(Bit test and complement)
- BTR 位测试并复位指令(Bit test and reset)
- BTS 位测试并置位指令(bit test and set)
一般格式:
BT OPRD1,OPRD2
BTC OPRD1,OPRD2
BTR OPRD1,OPRD2
BTS OPRD1,OPRD2
操作数OPRD1指定位串,可以是16位或32位通用寄存器,可以是16位或32位存储单元地址。
操作数OPRD2指定位号,可以是操作数OPRD1尺寸相同的通用寄存器,也可以是8位立即数。
如果操作数OPRD1是32位(16位)寄存器,那么被测位串也就限于32位(16位),实际被测位号将是操作数OPRD2取32(或16)的余数。
具体解释:
- BT:把被测试位的值送到进位标志CF
- BTC:把被测试位送到进位标志CF,并且把被测试位取反
- BTR:把被测试位送到进位标志CF,并且把被测试位复位(清0)
- BTS:把被测试位送到进位标志CF,并且把被测试位置位(置1)
需要注意的是:
如果给出被测位串的操作数OPRD1是32位(或16位)存储单元的地址,那么意味着被测的位串在存储器中。存储器中的被测位串可以足够长,可以是多个32位(或16位)假设由OPRD1给出的存储单元有效地址是EA,由OPRD2给出的位号是BitOffset。
在OPRD1是32位存储单元的情况下:
实测存储单元 = EA + (4 ∗ (BitOffset DIV 32))//4 ∗ (BitOffset DIV 32)表示第BitOffset DIV 32个存储单元,因为一个32位存储单元4个字节,所以乘4,下面的乘2是同样的道理
存储单元中的实测位号 = BitOffset MOD 32
在OPRD1是16位存储单元的情况下:
实测存储单元 = EA + (2 ∗ (BitOffset DIV 16))
存储单元中的实测位号 = BitOffset MOD 16
在这种情况下,由于操作数OPRD2可以是有符号整数值,所以当OPRD2是32位时,可访问(-2G)至(2G-1)范围内的位串;当OPRD2为16位时,可访问(-32K)至(32K-1)范围内的位串,注意这里的正负是相对于EA为0点而言的。
位扫描指令组
包含两条指令:
- BSF:顺向位扫描(bit scan forward)
- BSR:逆向位扫描(bit scan reverse)
一般格式:
BSF OPRD1,OPRD2
BSR OPRD1,OPRD2
操作数OPRD1是16或32位通用寄存器,操作数OPRD2可以是16位或32位通用寄存器或者存储单元;但操作数OPRD1和OPRD2的位数(长度)必须相同。
功能如下:
- 顺向位扫描指令BSF,从右向左(位0至位15或位31)扫描字或双字操作数OPRD2中第一个含“1”的位,并把扫描到的第一个含“1”的位的位号送操作数OPRD1。
- 逆向位扫描指令BSR,从左向右(位15或位31至位0)扫描字或双字操作数OPRD2中第一个含“1”的位,并把扫描到的第一个含“1”的位的位号送操作数OPRD1。
- 如果字或双字操作数OPRD2等于0,那么零标志ZF被置1,操作数OPRD1的值不确定;否则零标志ZF被清0。
Demo
MOV EBX, 12345678H//1在第28位
BSR EAX, EBX ;ZF=0, EAX=1CH=12+16=28
BSF DX, AX ;AX=CH=1100B,ZF=0, DX=2=10B
BSF CX, DX ;ZF=0, CX=1
条件设置字节指令
一般格式:
SETcc OPRD
符号cc是代表各种条件的缩写,是指令助记符的一部分;操作数OPRD只能是8位寄存器或者字节存储单元,用于存放设置结果。当条件满足时,那么将目的操作数OPRD设置成1,否则设置成0。这里的条件与条件转移指令中的条件一样。
应用场景:一般在使用条件转移指令且涉及到对1的操作时考虑使用条件设置指令代替条件转移指令,比如:
设r1、r2、r3都代表通用寄存器,要计算r3=(r1 的值 >=r2的值)?1:0:
使用条件转移:
CMP r1, r2
MOV r3, 1
Jae NEXT
MOV r3, 0
NEXT:
使用条件设置字节:
XOR r3, r3
CMP r1, r2
SETae r3
上一篇: 字符串4:表示数值的字符串
下一篇: Swift4:字符串操作,数组操作
推荐阅读