汇编语言期末笔记


计科期末考试出题

  1. 读程序题
  2. 程序填空题
  3. 写程序题:计算
  4. 写程序题:循环分支
  5. 写程序题:中断

第3章 80x86的指令系统和寻址方式

80x86的寻址方式


80x86的指令系统

数据传送指令(p47)

通用数据传送指令

mov
  1. mov mem/reg1,mem/reg2
    先略。。。直接看书
push进栈指令
1
push src

(sp) ← (sp)-2
((sp)+1,(sp)) ← (src)

格式

push reg
push mem
push data8086不行
push segreg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
push ax      ; 推送 AX 寄存器
push bx ; 推送 BX 寄存器
push cx ; 推送 CX 寄存器
push dx ; 推送 DX 寄存器
push sp ; 推送 SP 寄存器
push bp ; 推送 BP 寄存器
push si ; 推送 SI 寄存器
push di ; 推送 DI 寄存器

push word ptr [address] ; 推送内存地址中的值

push cs ; 推送 CS 段寄存器
push ds ; 推送 DS 段寄存器
push es ; 推送 ES 段寄存器
push ss ; 推送 SS 段寄存器
pop出栈指令

注意要画栈图

1
pop dst

(dst) ← ((sp)+1,(sp))
(sp) ← (sp)+2

pop reg
pop mem
pop data8086不行
pop segreg

累加器专用传送指令

这些指令不影响标志位

in
1
2
3
4
in al,port ;字节, (al) ← (port)
in ax,port ;字, (ax) ← (port+1,port)
in al,dx ;字节, (al) ← ((dx))
in ax,dx ;字, (ax) ← ((dx)+1,(dx))
out
1
2
3
4
out port,al ;字节, port ← (al)
out port,ax ;字, (port+1,port) ← (ax)
out dx,al ;字节, ((dx)) ← (al)
out dx,ax ;字, ((dx)+1,(dx)) ← (ax)
xlat换码指令

al=[bx+al]

地址传送指令

lea有效地址送寄存器
1
lea reg,sec ;(reg)<-src

异同:lea bx,listmov bx,offset list, mov更快

lds, les, lfs, lgs, lss指针送寄存器和?s

p56, 实验暂时不用

标志寄存器传送指令

p57, 实验暂时不用

类型转换指令

cbw

将al的内容符号扩展到ah,形成ax的字。
若(AL)的最高有效位为0,则(AH)= 00H
若(AL)的最高有效位为1,则(AH)= 0FFH

cwd/cwde

将ax的内容符号扩展到dx,形成dx:ax中的双字。
若(AX)的最高有效位为0,则(DX) = 0000H
若(AX)的最高有效位为1,则(DX) = 0FFFFH


算术指令

加法指令

add加法
adc带进位加法
inc 加1指令

减法指令

sub减法
sbb带借位减法
dec减1
neg求补

按位求反+1

cmp比较

前操作数-后操作数,但是不保存结果,只设置条件标志位

乘法指令

目的操作数必须是累加器

mul无符号数乘法
1
mul src

(ax)<-(al)(src) ;字节操作数
(dx,ax)<-(ax)
(src) ;字操作数

imul带符号数乘法

同上,但是是带符号数

除法指令

div无符号数除法

div src

字节操作:
(ax)/(src)=(al)……(ah)
字操作:
(dx,ax)/(src)=(ax)……(dx)
注意进制转换的代码

idiv带符号数除法

同上

十进制调整指令

有压缩的BCD码调整指令:
DAA 加法的十进制调整指令
DAS 减法的十进制调整指令

非压缩的BCD码调整指令:
AAA 加法的ASCII调整指令
AAS 减法的ASCII调整指令
AAM 乘法的ASCII调整指令
AAD 除法的ASCII调整指令

逻辑指令

逻辑运算指令

and 逻辑与指令
or 逻辑或指令
not 逻辑非指令
xor 异或指令

要使操作数的某些位变反,使用xor,那些位设为1

test 测试指令

做and运算,但是不保存结果,只置条件码
要测试操作数的某位是都为1,可以先将操作数取反,再用test测试。

移位指令(p73)

e.g. shl opr,cnt
在8086中,cnt可以是cl或者1

shl 逻辑左移指令

无符号数乘法

sal 算数左移指令

有符号数乘法

shr 逻辑右移指令

无符号数除法

sal 算数右移指令

有符号数除法

rol 循环左移指令
ror 循环右移指令
rcl 带进位循环左移指令
rcr 带进位循环右移指令

串处理指令

这里要写一下字符串代码

与rep相配合工作的movs, stos, lods, ins, outs

rep重复串操作

重复串操作直到计数器Count Reg的内容为0位为止
操作:

1
2
3
while Count_Reg != 0:
Count_Reg-=1
执行后面的串指令
movs 串传送指令

格式

1
2
3
movs dst,src
movsb ;字节
movsw ;字

第一种格式要在操作数里标名是字还是字节操作。
movs es:byte ptr[di],ds:[si]

相当于strcpy
movs可以把由源变址寄存器指向的数据段中的一个字(或字节),传到目的变址寄存器指向的附加段中的一个字(或字节)去,同时根据方向标志以及数据格式(字or字节),对源变址寄存器和目的变址寄存器进行修改。

执行操作:

  1. ((DI)) ← ((SI))
  2. 字节操作:(SI)←(SI)±1, (DI)←(DI)±1
    字操作: (SI)←(SI)±2, (DI)←(DI)±2
    方向标志DF=0时用 +,DF=1时用 - 。

执行REP MOVS之前,应先做好:

  1. 源串首地址(末地址)→ SI
  2. 目的串首地址(末地址)→ DI
  3. 串长度 → CX
  4. 建立方向标志(CLD使DF=0,STD使DF=1)

方向标志

  • cld: df=0, 地址自动增量
  • std: df=1, 地址自动减量
stos 存入串指令

格式

1
2
3
stos dst
stosb ;字节
stosw ;字

相当于memset
将al、ax的内容存入由目的变址寄存器指向的附加段的某单元中,并根据……
初始化某一个缓冲区时很有用

执行操作:

  1. 字节操作:((DI))←(AL), (DI)←(DI)±1
  2. 字操作:((DI))←(AX), (DI)←(DI)±2

例:把附加段中的5个字节缓冲区置为20H

1
2
3
4
5
lea  di, mess2
mov al, 20H
mov cx, 5
cld
rep stosb

lods 从串取指令

格式

1
2
3
lods src
lodsb ;字节
lodsw ;字

LODS指令一般不与REP联用
源串必须在数据段中,目的串必须在附加段中,
但源串允许使用段跨越前缀来修改。
不影响条件标志位

执行操作:
字节操作:(AL)←((SI)),(SI)←(SI)±1
字操作:(AX)←((SI)),(SI)←(SI)±2

ins 串输入

格式

1
2
3
ins dst,dx
insb ;字节
insw ;字

执行操作:
字节操作:(DI)←((DX)), (DI)←(DI)±1
字操作:(DI+1,DI)←((DX)), (DI)←(DI)±2

outs 串输出

格式

1
2
3
outs dx,src
outsb ;字节
outsw ;字

执行操作:
字节操作:((DX)) ← (SI), (SI)←(SI)±1
字操作:((DX)) ←(SI+1,SI), (SI)←(SI)±2

repe/repz,repne/repnz,cmps,scas

repe/repz 为相等/为零时重复串操作
1
2
3
while Count_Reg!=0 and zf!=0:
Count_Reg-=1
执行其后的串指令
repne/repnz 为不相等/不为零时重复串操作
1
2
3
while Count_Reg!=0 and zf!=1:
Count_Reg-=1
执行其后的串指令
cmps 串比较指令

格式

1
2
3
cmps src,dst
cmpsb ;字节
cmpsw ;字

相当于strcmp

执行操作:
(1) ((SI)) - ((DI))
根据比较结果设置条件标志位:相等 ZF=1
(2) 字节操作:(SI)←(SI)±1, (DI)←(DI)±1
字操作:(SI)←(SI)±2, (DI)←(DI)±2

scas串扫描指令

格式

1
2
3
scas dst
scasb ;字节
scasw ;字

执行操作:
字节操作:(AL) - ((DI)), (DI)←(DI)±1
字操作:(AX) - ((DI)), (DI)←(DI)±2

控制转移指令

后面再看

无条件转移指令

jmp

条件转移指令(p89)

jz/je
jnz
js
jns
jo
jno
jp/jpe
jnp/jpo
jb/jnae
jnb/jae/jnc

循环指令

loop

Count_Reg!=0

loopz/loope

Count_Reg!=0 && zf=1

loopnz/loopne

Count_Reg!=0 && zf=0

子程序

call
ret

第8章 输入输出程序设计

(面向考试复习)

程序直接控制I/O方式(p283)

I/O指令(p284)

in和out

in 完成I/O -> CPU的信息传送
out完成CPU -> I/O的信息传送

0~0ffh,即0~255端口可以这样写:

1
2
3
4
5
6
;port是端口地址,一个8位的立即数
in al,port ;(al)<-(port)
in ax,port ;(ax)<-(port+1,port)

out port,al ;(port)<-(al)
out port,ax ;(port+1,port)<-(ax)

0~0ffffh,即0~255端口可以这样写(当端口>0ffh时,必须这样写!)

1
2
3
4
5
6
;dx里存了端口地址
in al,dx ;(al)<-((dx))
in ax,dx ;(ax)<-((dx)+1,(dx))

out dx,al ;((dx))<-(al)
out dx,ax ;((dx)+1,(dx))<-(ax)

注意端口是否带“h”,例如126h是大于十进制的255的,就得用dx

用法示例

  1. 把一个字,从端口地址28和29,传到存储器的字单元data_word中

    1
    2
    in  ax,28h
    mov data_word,ax
  2. 检测端口地址27h的第2位是否为1,如果1就是转到错误处理
    注意这里的第几位是从右到左从0数起
    因为test就是只改变flags的and,所以用来查1

    1
    2
    3
    in   al,27h
    test al,00000100b
    jnz error
  3. 将端口地址为126h的命令寄存器,第7位置1

    1
    2
    3
    4
    mov dx,126h
    in al,dx
    or al,80h ;80h=10000000b
    out dx,al

中断传送方式

8086的中断分类(p290)

中断标志位if

设置中断允许位(if=1)sti set interupt flag
清除中断允许位(if=0)cli clear interupt flag

中断屏蔽寄存器(端口21h)

7 6 5 4 3 2 1 0
打印机 软盘 硬盘 串行通信口1 串行通信口2 保留 键盘 定时器

中断屏蔽寄存器(端口20h)

7 6 5 4 3 2 1 0
R SL EOI 0 0 L2 L1 L0

扩充(实验书p92):

键盘的输入寄存器(PA,端口60h)

PB位7 7 6 5 4 3 2 1 0
PB7=1 磁盘驱动器数 显示器类型 系统板上RAM数 未用 非磁盘系统
PB7=0 键盘扫描码

控制寄存器(PB,端口61h)

7 PA选源,1=键盘应答
6 0=禁止键盘时钟
5 0=允许由扩充槽的错误信号
4 0=允许ram
3 0=启开盒式带马达
2 PC0~3选源
1 扬声器脉冲门
0 定时器2与门

1ch中断一秒钟约产生18次中断,具体使用可以看2324期末卷最后一题

中断向量表(p293)

功能 预置 执行 返回
设置中断向量 ah=25h
al=中断类型号
ds:dx=中断向量
int 21h
取中断向量 ah=35h
al=中断类型号
int 21h es:bx=中断向量

中断操作:

  1. 取中断类型号
  2. 计算中断向量地址
  3. 取中断向量,偏移地址送IP,段地址送CS
  4. 转入中断处理程序
  5. 中断返回到int的下一条指令

使用dos功能调用存取中断向量,模板

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
;保存原来的中断向量
mov al,n ;中断类型号
mov ah,35h ;取中断向量到es:bx
int 21h
push es
push bx
;设置新的中断向量
push ds
mov ax,seg myfunc
mov ds,ax
mov dx,offset myfunc
mov al,n
mov al,25h
int 21h
pop ds
;重新设置原来的中断向量
pop dx ;原来的bx
pop ds ;原来的es
mov al,n
mov ah,25h
int 21h
ret
;...
myfunc proc near
;...
iret
myfunc endp
;...

p300的程序可能会被改成考试题

p301响铃程序

只有铃响:

1
2
3
4
5
6
7
8
9
        mov    dx, 100h
in al, 61h
and al, 11111100b
sound: xor al, 2
out 61h, al
mov cx, 140h
wait1: loop wait1
dec dx
jne sound

完整程序:

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
datarea segment
count dw 1
msg db 'The bell is ringing',0dh,0ah,'$'
datarea ends
prognam segment
main proc far
assume cs:prognam,ds:datarea,es:datarea
start:
push ds
sub ax,ax
push ax
mov ax,datarea
mov ds,ax
mov es,ax

;保存旧的中断向量
mov al,1ch ;1ch中断
mov ah,35h ;取中断向量,保存在es:bx里
int 21h
push bx
push es

;设置新的中断向量
push ds
mov dx,offset ring ;ring的偏移地址
mov ax,seg ring ;ring的段地址
mov ds,ax ;ds:dx=段地址:偏移地址
mov al,1ch ;1ch中断
mov ah,25h ;设置中断向量
int 21h
pop ds

;将21h端口的第0位清零,即打开定时器
in al,21h
and al,11111110b
out 21h,al
sti ;设置中断标志,允许中断。

;循环20000*30000次,因为次数太多一个寄存器装不下
mov di,20000
delay:
mov si,30000
delay1:
dec si
jnz delay1
dec di
jnz delay

;重新设置回旧的中断向量
pop dx
pop ds
mov al,1ch
mov ah,25h
int 21h
ret
main endp
ring proc near
push ds
push ax
push cx
push dx

mov ax,datarea
mov ds,ax
sti

dec count
jnz exit
;打印消息'The bell is ringing'
mov dx,offset msg
mov ah,09h
int 21h

mov dx,100 ;打开关闭的次数
in al,61h
and al,0fch ;61h端口的第0、1位清0,分别是定时器2和扬声器脉冲门
sound:
xor al,02 ;61h端口的第1位转置,扬声器脉冲门
out 61h,al

;等待,因为cpu比端口快的多
mov cx,1400h
wait1:
loop wait1

dec dx
jne sound
mov count,182 ;10s的铃响delay
exit:
cli
pop dx
pop cx
pop ax
pop ds
iret
ring endp
prognam ends
end start

堆栈情况

alt text
堆栈段(SS)没有变化,始终保持为 076B。
堆栈指针(SP)在每次执行PUSH指令后减少2,这是因为每次PUSH操作都会将一个寄存器的值(2 字节)压入堆栈。

  1. 一开始,sp初始化为0
  2. push ds后,sp=0000h-2=0fffeh,压入ds=075Ch
  3. sub ax,ax后,ax=0
  4. push ax后,sp=0fffeh-2=0fffch,压入ax=0

附录 DOS系统功能调用(常用)

AH 功能 调用参数 返回参数
00 程序终止 CS=程序段前缀PSP
01 键盘输入并回显 AL=输入字符
02 显示输出 DL=输出字符
09 显示字符串 DS:DX=串地址 字符串以’$’结尾
0A 键盘输入到缓冲区 DS:DX=缓冲区首址 (DS:DX)=缓冲区最大字符数 (DS:DX+1)=实际输入的字符数
25 设置中断向量 DS:DX=中断向量
AL=中断类型号
35 取中断向量 AL=中断类型号 ES:BX=中断向量

常用debug(实验书p186)

  • -d: 显示内存单元内容
  • -r: 寄存器和标志位状态
  • -g: 运行
  • -t: 单步运行
  • -u: 反汇编
  • -q: 退出
标志名 置位 复位
溢出Overflow 是OV 否NV
方向Direction 减量DN 增量UP
中断Interrupt 允许EI 屏蔽DI
符号Sign 负NG 正PL
零Zero 是ZR 否NZ
辅助进位Auxiliary Carry 是AC 否NA
奇偶Parity 偶PE 奇PO
进位Carry 是CY 否NC

实验代码

成绩排名

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
datarea segment
grade dw 88,75,95,63,98,78,87,73,90,60
rank dw 10 dup(?)
datarea ends
prognam segment
main proc far
assume cs:prognam,ds:datarea,es:datarea
start:
push ds
sub ax,ax
push ax
mov ax,datarea
mov ds,ax
mov es,ax
call sort
call output
ret
main endp
sort proc
mov si,0
mov cx,10
outer_loop:
push cx
mov cx,10
mov dx,1
mov ax,grade[si]
mov di,offset grade
inner_loop:
cmp ax,[di]
jge no_add
inc dx
no_add:
add di,2
loop inner_loop
pop cx
mov rank[si],dx
add si,2
loop outer_loop
ret
sort endp
output proc
mov cx,10
mov si,0
t1:
push cx
mov ax,rank[si]
call print_dec
mov dl,' '
call putchar
pop cx
add si,2
loop t1
ret
output endp
putchar proc
mov ah,2
int 21h
ret
putchar endp
print_dec proc
;ax/?=al...ah, result:al,ah
mov bl,10
div bl
mov bx,ax
mov dl,bl
add dl,'0'
call putchar
mov dl,bh
add dl,'0'
call putchar
ret
print_dec endp
prognam ends
end start

ASCII码表

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
datarea segment
datarea ends
prognam segment
main proc far
assume ds:datarea,cs:prognam,es:datarea
start:
push ds
sub ax,ax
push ax
mov ax,datarea
mov ds,ax
mov es,ax

mov dx,10h
mov bx,0
rotate:
mov ah,2
int 21h
inc bx
inc dx
push dx
cmp bx,16
mov dl,' '
mov ah,2
int 21h
jl continue
xor bx,bx
mov dl,0dh
mov ah,2
int 21h
mov dl,0ah
mov ah,2
int 21h
continue:
pop dx
cmp dx,100h
jl rotate
ret
main endp
prognam ends
end start

反转字符串

我流版

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
datarea segment
limit db 30
len db ?
str db 30 dup('$')
datarea ends
prognam segment
main proc far
assume cs:prognam,ds:datarea,es:datarea
start:
push ds
sub ax,ax
push ax
mov ax,datarea
mov ds,ax
mov es,ax

;输入字符串
lea dx,limit
mov ah,0ah
int 21h
;回车换行
mov dl,0dh
mov ah,2
int 21h
mov dl,0ah
mov ah,2
int 21h
;di置为字符串长度减一,cx计数器置为字符串长度除以2
mov si,0
mov bl,len
xor bh,bh
mov di,bx
dec di
mov cx,1
;除以2
shr bx,cl
mov cx,bx
rotate:
mov al,str[si]
mov bl,str[di]
mov str[si],bl
mov str[di],al
inc si
dec di
loop rotate
lea dx,str
mov ah,9
int 21h
ret
main endp
prognam ends
end start

ppt递归版

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
datarea segment
str db "123456",'$'
datarea ends
prognam segment
main proc far
assume cs:prognam,ds:datarea,es:datarea
start:
push ds
sub ax,ax
push ax
mov ax,datarea
mov ds,ax
mov es,ax

mov bx, offset str
push bx
call revers
pop bx
mov dl, [bx]
mov ah, 2
int 21h

ret
main endp
revers proc near
push bp
mov bp, sp
push ax
push bx
push dx
mov bx, [bp+4]
mov al, [bx]
cmp al, '$'
je return
re_call:
inc bx
push bx
call revers
pop bx
mov dl, [bx]
mov ah, 2
int 21h
return:
pop dx
pop bx
pop ax
pop bp
ret
revers endp

prognam ends
end start

查找子串位置,十进制、十六进制输出

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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
datarea segment
sentence_label label byte
sentence_max db 50
sentence_len db ?
sentence db 50 dup('$')
key_label label byte
key_max db 30
key_len db ?
key db 30 dup('$')

cmd1 db 'Input a sentence:','$'
cmd2 db 'Input a keyword:','$'
no_match_str db 'No match!',0dh,0ah,'$'
match_str db "The location:",'$'
crlf_str db 0dh,0ah,'$'

datarea ends
prognam segment
main proc far
assume cs:prognam,ds:datarea,es:datarea
start:
push ds
sub ax,ax
push ax
mov ax,datarea
mov ds,ax
mov es,ax
loops:
lea dx,cmd1
call cout
call cin_sentence
call crlf
cmp sentence[0],3 ;比较ctrl+c
je exit
lea dx,cmd2
call cout
call cin_key
call crlf
call compare
jmp loops
exit:
ret
main endp
cin proc
mov ah,0ah
int 21h
ret
cin endp
cin_sentence proc
lea dx,sentence_label
call cin
ret
cin_sentence endp
cin_key proc
lea dx,key_label
call cin
ret
cin_key endp
cout proc
mov ah,09h
int 21h
ret
cout endp
crlf proc
lea dx,crlf_str
call cout
ret
crlf endp
compare proc
;cx=sentence_len-key_len+1
xor ch,ch
xor dh,dh
xor bx,bx
mov cl,sentence_len
mov dl,sentence_len
sub cx,dx
inc cx
rotate:
lea si,sentence[bx]
lea di,key
mov cl,key_len
xor ch,ch
cld
rep cmpsb
jz match
inc bx
loop rotate
no_match:
lea dx,no_match_str
call cout
ret
match:
lea dx,match_str
call cout
call print_dec
call crlf

lea dx,match_str
call cout
call print_hex
call crlf

ret
compare endp
;打印十进制数字
print_dec proc
;ax/?=al...ah
push bx
mov ax,bx
xor cx,cx
div10:
mov bl,10
div bl
push ax
inc cx
xor ah,ah
cmp al,0
jne div10
print10:
pop dx
mov dl,dh
add dl,30h
mov ah,2
int 21h
loop print10
pop bx
ret
print_dec endp
;打印十六进制数字
print_hex proc
push bx
xor ax,ax
div16:
mov dx,0fh
and dx,bx
push dx
inc ax
mov cl,4
shr bx,cl
cmp bx,0
jne div16
mov cx,ax
print16:
pop dx
add dx,'0'
cmp dx,'9'
jle not_letter
add dx,'A'-'0'-10
not_letter:
mov ah,2
int 21h
loop print16
pop bx
ret
print_hex endp
prognam ends
end start