ASM16

串操作指令

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

串数据类型

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

image-20241003175153146

串存储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

REP重复前缀指令

1
2
REP		;每执行一次串指令,CX减一
;直到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:
    
    1
    2
    3
    4
    5
    6
    7



    - 定义一个过程(函数)`MY_ADD`,使用 `stdcall` 调用约定。
    - `uses bx cx`:过程调用期间保存 `BX` 和 `CX` 寄存器的内容,并在过程结束时恢复它们。
    - `p1:word, p2:word`:定义两个参数 `p1` 和 `p2`,都是 `word` 类型。

    local @n1:word local @n2:word local @n3[3]:byte
    1
    2
    3
    4
    5

    - `local @n1:word`:声明一个局部变量 `@n1`,类型为 `word`。
    - `local @n2:word`:声明一个局部变量 `@n2`,类型为 `word`。
    - `local @n3[3]:byte`:声明一个局部变量 `@n3`,类型为 `byte` 数组,长度为3字节。

    mov bx,p1 mov cx,p2 mov @n1,2 mov @n2,3
    1
    2
    3
    4
    5
    6
    7
    8

    - `mov bx,p1`:将参数 `p1` 的值加载到 `BX` 寄存器中。
    - `mov cx,p2`:将参数 `p2` 的值加载到 `CX` 寄存器中。
    - `mov @n1,2`:将值 `2` 存入局部变量 `@n1` 中。
    - `mov @n2,3`:将值 `3` 存入局部变量 `@n2` 中。

    #### 调用宏指令

    invoke MY_ADD,1,2
    1
    2
    3
    4
    5

    - `invoke MY_ADD,1,2`: 调用 `MY_ADD` 宏,传入参数 `1` 和 `2`。

    #### 条件分支和循环

    .if ax!=1 mov ax,bx .else mov bx,bx .endif
    1
    2
    3
    4
    5
    6
    7

    - `.if ax!=1`: 如果 `AX` 不等于 `1`,执行接下来的代码。
    - `mov ax,bx`: 如果条件成立,执行 `mov ax,bx`,将 `BX` 的值赋给 `AX`。
    - `.else`: 否则执行 `.else` 分支的代码。
    - `mov bx,bx`: 这行代码实际上什么也不做(无操作)。
    - `.endif`: 结束条件分支。

    .while ax<10 mov cx,cx .endw
    1
    2
    3
    4
    5
    6
    7

    - `.while ax<10`: 当 `AX` 小于 `10` 时,进入循环体。
    - `mov cx,cx`: 这行代码实际上什么也不做(无操作)。
    - `.endw`: 结束 `while` 循环。

    #### 测试与比较指令

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

    - `test ax,1`: 对 `AX` 进行按位与操作,检查最低位是否为1,设置标志位(影响后续条件跳转)。
    - `cmp ax,0`: 比较 `AX` 和 `0` 的值。
    - `test ax,ax`: 对 `AX` 自身进行按位与操作,常用于检查 `AX` 是否为0。

    #### 循环指令

    mov cx,10 LOOP1: mov ax,ax LOOP LOOP1
    1
    2
    3
    4
    5
    6
    7

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

    #### 字符串操作指令

    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
    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

    - `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 操作

    cld mov di,offset MY_MSG2 mov al,01 mov cx,10 rep stosb
    1
    2
    3
    4
    5
    6
    7
    8
    9

    - `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 操作

    cld mov si,offset MY_MSG1 mov di,offset MY_MSG3 mov cx,4 repz cmpsb
    1
    2
    3
    4
    5
    6
    7
    8
    9

    - `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 操作

    cld mov al,0 mov di,offset MY_MSG3 mov cx,20 repnz scasb

- `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](../image/image-20240809214335457.png)

复制10个字节后

![image-20240809214838740](../image/image-20240809214838740.png)

将MY_MSG2中前10个字节置为01

![image-20240809214928737](../image/image-20240809214928737.png)

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

![image-20240809215633222](../image/image-20240809215633222.png)

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

![image-20240809215320391](../image/image-20240809215320391.png)