汇编总结(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)

需要注意的是:

如果给出被测位串的操作数OPRD132位(或16位)存储单元的地址,那么意味着被测的位串在存储器中。存储器中的被测位串可以足够长,可以是多个32位(或16位)假设由OPRD1给出的存储单元有效地址是EA,由OPRD2给出的位号是BitOffset

​ 在OPRD132位存储单元的情况下:

​ 实测存储单元 = EA (4(BitOffset DIV 32))//4 ∗ (BitOffset DIV 32)表示第BitOffset DIV 32个存储单元,因为一个32位存储单元4个字节,所以乘4,下面的乘2是同样的道理

​ 存储单元中的实测位号 = BitOffset MOD 32

​ 在OPRD116位存储单元的情况下:

​ 实测存储单元 = 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

你可能感兴趣的:(汇编)