ASM16 25 串操作类指令
串操作类指令
- 串操作指令是8086指令系统中比较独特的一类指令,采用比较特殊的数据串寻址方式,在操作主存连续区域的数据时,特别好用、因而常用
- 重要掌握:
MOVSSTOSLODSCMPSSCASREP - 一般了解:
REPZ/REPEREPNZ/REPNE
- 重要掌握:
串数据类型
- 串操作指令的操作数是主存中连续存放的数据串(String)——即在连续的主存区域中,字节或字的序列
- 串操作指令的操作对象是以字(W)为单位的字串,或是以字节(B)为单位的字节串
串存储STOS(store string)
把AL或AX数据传送至目的地址

串读取LODS(load string)
把指定主存单元的数据传送给AL或AX

串比较CMPS(compare string)
将主存中的源操作数减去至目的操作数,以便设置标志,进而比较两操作数之间的关系

串扫描SCAS(scan string)
将AL/AX减去至目的操作数,以便设置标志,进而比较AL/AX与操作数之间的关系

重复前缀指令(repeat)
- 串操作指令执行一次,仅对数据串中的一个字节或字量进行操作。但是串操作指令前,都可以加一个重复前缀,实现串操作的重复执行。重复次数隐含在CX寄存器中
- 重复前缀分2类,3条指令:
- 配合不影响标志的
MOVS、STOS(和LODS)指令的REP前缀 - 配合影响标志的
CMPS和SCAS指令的REPZ和REPNZ前缀
- 配合不影响标志的
REP重复前缀指令
1 | REP ;每执行一次串指令,CX减一 |
- REP前缀可以理解为:当数据串没有结束(CX不等于0),则继续传送
REPZ重复前缀指令
1 | REPZ ;每执行一次串指令,CX减1 |
REPZ/REPE前缀可以理解为:当数据串没有结束(CX不等于0),并且串相等(ZF=1),则继续比较
代码分析
1 | MyStack segment stack |
MY_ADD
1 | MY_ADD proc stdcall uses bx cx, p1:word, p2:word |
MY_ADD proc stdcall uses bx cx, p1:word, p2:word:- 定义一个过程(函数)
MY_ADD,使用stdcall调用约定。 uses bx cx:过程调用期间保存BX和CX寄存器的内容,并在过程结束时恢复它们。p1:word, p2:word:定义两个参数p1和p2,都是word类型。
- 定义一个过程(函数)
1 | local @n1:word |
local @n1:word:声明一个局部变量@n1,类型为word。local @n2:word:声明一个局部变量@n2,类型为word。local @n3[3]:byte:声明一个局部变量@n3,类型为byte数组,长度为3字节。
1 | mov bx,p1 |
mov bx,p1:将参数p1的值加载到BX寄存器中。mov cx,p2:将参数p2的值加载到CX寄存器中。mov @n1,2:将值2存入局部变量@n1中。mov @n2,3:将值3存入局部变量@n2中。
1 | ret |
ret:返回调用程序,结束过程。MY_ADD endp:结束MY_ADD过程的定义。
调用宏指令
1 | invoke MY_ADD,1,2 |
invoke MY_ADD,1,2: 调用MY_ADD宏,传入参数1和2。
条件分支和循环
1 | .if ax!=1 |
.if ax!=1: 如果AX不等于1,执行接下来的代码。mov ax,bx: 如果条件成立,执行mov ax,bx,将BX的值赋给AX。.else: 否则执行.else分支的代码。mov bx,bx: 这行代码实际上什么也不做(无操作)。.endif: 结束条件分支。
1 | .while ax<10 |
.while ax<10: 当AX小于10时,进入循环体。mov cx,cx: 这行代码实际上什么也不做(无操作)。.endw: 结束while循环。
测试与比较指令
1 | test ax,1 ;ax&1==>flag |
test ax,1: 对AX进行按位与操作,检查最低位是否为1,设置标志位(影响后续条件跳转)。cmp ax,0: 比较AX和0的值。test ax,ax: 对AX自身进行按位与操作,常用于检查AX是否为0。
循环指令
1 | mov cx,10 |
mov cx,10: 将循环计数器CX设为10。LOOP1:: 定义一个标签LOOP1,作为循环起始点。LOOP LOOP1: 将CX减1,如果CX不为0,则跳转到LOOP1标签,继续循环。
字符串操作指令
1 | mov ax,10 |
mov ax,10: 将AX设置为10。cld: 清除方向标志,使后续字符串操作从低地址向高地址方向进行。在 x86 架构中,方向标志位(DF)决定了处理器在处理字符串操作时的方向:
- DF = 0:从低地址到高地址(递增),也就是内存地址递增的方向。这是字符串操作的默认方向。
- DF = 1:从高地址到低地址(递减),内存地址会递减。
某些情况下,之前的代码可能会设置 DF 为 1(使用
STD指令)。如果在此情况下直接使用字符串操作指令,数据将按从高到低的顺序处理。而对于大多数情况下,我们希望数据从低到高顺序操作,因此需要在使用字符串操作指令之前确保 DF 被清除(设置为 0)。mov si,offset MY_MSG1: 将MY_MSG1的偏移地址加载到SI中,指向数据段中的字符串。mov di,offset MY_MSG2: 将MY_MSG2的偏移地址加载到DI中,指向附加段中的字符串。mov cx,ax: 将AX的值加载到CX中,作为循环计数器。shr cx,1: 将CX右移一位,除以2,准备进行字的复制。(除2取商)rep movsw: 重复执行MOVSW指令,将SI所指向的数据段中的字(2字节)复制到DI所指向的附加段。mov cx,ax: 再次将AX的值加载到CX中。and cx,1: 对CX进行按位与操作,保留最低位。(除2取余)rep movsb: 重复执行MOVSB指令,将剩余的字节复制到附加段。
memset 操作
1 | cld |
cld: 清除方向标志。mov di,offset MY_MSG2: 将MY_MSG2的偏移地址加载到DI中。mov al,01: 将01加载到AL中。mov cx,10: 将CX设置为10。rep stosb: 重复执行STOSB指令,将AL中的值(01)存储到DI所指向的附加段内的10个字节中。
memcmp 操作
1 | cld |
cld: 清除方向标志。mov si,offset MY_MSG1: 将MY_MSG1的偏移地址加载到SI中。mov di,offset MY_MSG3: 将MY_MSG3的偏移地址加载到DI中。mov cx,4: 将CX设置为4,指定要比较的字节数。repz cmpsb: 重复执行CMPSB指令,比较SI和DI所指向的字节,直到找到不相等的字节或CX为0。
strlen 操作
1 | cld |
cld: 清除方向标志。mov al,0: 将AL设置为0。mov di,offset MY_MSG3: 将MY_MSG3的偏移地址加载到DI中。mov cx,20: 将CX设置为20,指定最大搜索范围。repnz scasb: 重复执行SCASB指令,查找DI所指向的字符串中第一个等于AL(即0,表示字符串结束)的字节。
运行结果
伪指令被翻译成汇编代码

复制10个字节后的结果

将MY_MSG2中前10个字节置为01

Hell和Hall在第2位不同,故循环在第2次结束后退出

20次循环在第6次循环开始前退出,说明第5次循环检测到0,说明长度为4
