串操作类指令

  • 串操作指令是8086指令系统中比较独特的一类指令,采用比较特殊的数据串寻址方式,在操作主存连续区域的数据时,特别好用、因而常用
    • 重要掌握:MOVS STOS LODS CMPS SCAS REP
    • 一般了解:REPZ/REPE REPNZ/REPNE

串数据类型

  • 串操作指令的操作数是主存中连续存放的数据串(String)——即在连续的主存区域中,字节或字的序列
  • 串操作指令的操作对象是以字(W)为单位的字串,或是以字节(B)为单位的字节串

串存储STOS(store string)

  • 把AL或AX数据传送至目的地址

    image-20240809192232735

串读取LODS(load string)

  • 把指定主存单元的数据传送给AL或AX

    image-20240809192520963

串比较CMPS(compare string)

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

    image-20240809192554192

串扫描SCAS(scan string)

  • 将AL/AX减去至目的操作数,以便设置标志,进而比较AL/AX与操作数之间的关系

    image-20240809192719403

重复前缀指令(repeat)

  • 串操作指令执行一次,仅对数据串中的一个字节或字量进行操作。但是串操作指令前,都可以加一个重复前缀,实现串操作的重复执行。重复次数隐含在CX寄存器中
  • 重复前缀分2类,3条指令:
    • 配合不影响标志的MOVSSTOS(和LODS)指令的REP前缀
    • 配合影响标志的CMPSSCAS指令的REPZREPNZ前缀

REP重复前缀指令

1
2
REP		;每执行一次串指令,CX减一
;直到CX=0,重复执行结束
  • REP前缀可以理解为:当数据串没有结束(CX不等于0),则继续传送

REPZ重复前缀指令

1
2
3
REPZ	;每执行一次串指令,CX减1
;并判断ZF是否为0
;只要CX=0或ZF=0,重复执行结束
  • REPZ/REPE前缀可以理解为:当数据串没有结束(CX不等于0),并且串相等(ZF=1),则继续比较

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
MyStack segment stack
db 256 dup(?)
MyStack ends

MyData segment
MY_MSG1 db "Hello World1!",0dh,0ah,'$'
MY_MSG2 db 20 dup(?)
MY_MSG3 db "Hall",0
MyData ends

MyCode segment

MY_ADD proc stdcall uses bx cx, p1:word, p2:word
local @n1:word
local @n2:word
local @n3[3]:byte

mov bx,p1
mov cx,p2
mov @n1,2
mov @n2,3

;mov ax,p1
;add ax,p2
ret
MY_ADD endp

MAIN:

mov ax,MyData
mov ds,ax
mov es,ax

invoke MY_ADD,1,2

;伪指令 宏汇编编译器
.if ax!=1
mov ax,bx
.else
mov bx,bx
.endif

.while ax<10
mov cx,cx
.endw

;串操作指令 10
;and ax,1
;jz

test ax,1 ;ax&1==>flag
cmp ax,0 ;4
test ax,ax ;3 if(n==0)

mov cx,10
LOOP1:
mov ax,ax
;dec cx ;1
;jnz LOOP1 ;3
LOOP LOOP1 ;6

;方向标志位
;std
mov ax,10
cld
mov si,offset MY_MSG1 ;ds:[si]
mov di,offset MY_MSG2 ;es:[di]
mov cx,ax
shr cx,1 ;正数
rep movsw
mov cx,ax
and cx,1 ;正数 取最低位
rep movsb

;memset al,[di]
cld
mov di,offset MY_MSG2
mov al,01
mov cx,10
rep stosb

;mov si,offset MY_MSG2 ;memchr "hello"
;mov cx,5
;cmp al,'e'
;lodsb

cld
mov si,offset MY_MSG1
mov di,offset MY_MSG3
mov cx,4
;repnz CMPSB ;memcmp ZR==0
repz cmpsb

;strlen
cld
mov al,0
mov di,offset MY_MSG3
mov cx,20
;repnz CMPSB ;memcmp ZR==0
repnz scasb

mov ax,4c00h
int 21h

MyCode ends

end MAIN

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:过程调用期间保存 BXCX 寄存器的内容,并在过程结束时恢复它们。
    • p1:word, p2:word:定义两个参数 p1p2,都是 word 类型。
1
2
3
local @n1:word
local @n2:word
local @n3[3]:byte
  • local @n1:word:声明一个局部变量 @n1,类型为 word
  • local @n2:word:声明一个局部变量 @n2,类型为 word
  • local @n3[3]:byte:声明一个局部变量 @n3,类型为 byte 数组,长度为3字节。
1
2
3
4
mov bx,p1
mov cx,p2
mov @n1,2
mov @n2,3
  • mov bx,p1:将参数 p1 的值加载到 BX 寄存器中。
  • mov cx,p2:将参数 p2 的值加载到 CX 寄存器中。
  • mov @n1,2:将值 2 存入局部变量 @n1 中。
  • mov @n2,3:将值 3 存入局部变量 @n2 中。
1
2
    ret
MY_ADD endp
  • ret:返回调用程序,结束过程。
  • MY_ADD endp:结束 MY_ADD 过程的定义。

调用宏指令

1
invoke MY_ADD,1,2
  • invoke MY_ADD,1,2: 调用 MY_ADD 宏,传入参数 12

条件分支和循环

1
2
3
4
5
.if ax!=1
mov ax,bx
.else
mov bx,bx
.endif
  • .if ax!=1: 如果 AX 不等于 1,执行接下来的代码。
  • mov ax,bx: 如果条件成立,执行 mov ax,bx,将 BX 的值赋给 AX
  • .else: 否则执行 .else 分支的代码。
  • mov bx,bx: 这行代码实际上什么也不做(无操作)。
  • .endif: 结束条件分支。
1
2
3
.while ax<10
mov cx,cx
.endw
  • .while ax<10: 当 AX 小于 10 时,进入循环体。
  • mov cx,cx: 这行代码实际上什么也不做(无操作)。
  • .endw: 结束 while 循环。

测试与比较指令

1
2
3
test ax,1   ;ax&1==>flag
cmp ax,0 ;4
test ax,ax ;3 if(n==0)
  • test ax,1: 对 AX 进行按位与操作,检查最低位是否为1,设置标志位(影响后续条件跳转)。
  • cmp ax,0: 比较 AX0 的值。
  • test ax,ax: 对 AX 自身进行按位与操作,常用于检查 AX 是否为0。

循环指令

1
2
3
4
    mov cx,10
LOOP1:
mov ax,ax
LOOP LOOP1
  • mov cx,10: 将循环计数器 CX 设为 10
  • LOOP1:: 定义一个标签 LOOP1,作为循环起始点。
  • LOOP LOOP1: 将 CX 减1,如果 CX 不为0,则跳转到 LOOP1 标签,继续循环。

字符串操作指令

1
2
3
4
5
6
7
8
9
10
mov ax,10
cld
mov si,offset MY_MSG1 ;ds:[si]
mov di,offset MY_MSG2 ;es:[di]
mov cx,ax
shr cx,1 ;正数
rep movsw
mov cx,ax
and cx,1 ;正数 取最低位
rep movsb
  • 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
2
3
4
5
cld
mov di,offset MY_MSG2
mov al,01
mov cx,10
rep stosb
  • 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
2
3
4
5
cld
mov si,offset MY_MSG1
mov di,offset MY_MSG3
mov cx,4
repz cmpsb
  • 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 指令,比较 SIDI 所指向的字节,直到找到不相等的字节或 CX 为0。

strlen 操作

1
2
3
4
5
cld
mov al,0
mov di,offset MY_MSG3
mov cx,20
repnz scasb
  • 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,表示字符串结束)的字节。

运行结果

伪指令被翻译成汇编代码

image-20240809214335457

复制10个字节后的结果

image-20240809214838740

将MY_MSG2中前10个字节置为01

image-20240809214928737

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

image-20240809215633222

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

image-20240809215320391