欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

汇编总结(4)——字符串操作和位操作

程序员文章站 2022-07-14 19:41:33
...

字符串操作

首先明确什么是字符串:

字符串是字符的一个序列,对字符串的操作处理包括复制、比较和检索等,为了有效地处理字符串,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