汇编语言
环境搭建
https://fishc.com.cn/forum.php?mod=viewthread&tid=156177&highlight=MASM
在dosbox运行,首先需要mount c 文件所在文件夹
源程序文件夹中需要 LINK.EXE, MASM.EXE, ML.EXE, DOSXNT.EXE
段寄存器
8086 CPU有四个段寄存器:
CS, DS, SS, ES
段寄存器用来提供段地址
CS和IP
CS: 代码段寄存器
IP: 指令指针寄存器
修改CS,IP: jmp 段地址:偏移地址
仅修改IP的内容:
jmp 某一合法寄存器
jmp ax 用ax中的值修改IP
下面的3条指令执行后,cpu几次修改IP?都是在什么时候?最后IP中的值是多少?
mov ax,bx
sub ax,ax
jmp ax
答:一共修改四次
第一次:读取mov ax,bx之后
第二次:读取sub ax,ax之后
第三次:读取jmp ax之后
第四次:执行jmp ax修改IP
最后IP的值为0000H,因为最后ax中的值为0000H,所以IP中的值也为0000H
debug
-r: 查看寄存器内容
-r ax :修改ax的内容
-t : 执行指令
-d :查看内存中内容
-u: 查看内存中内容,将机器指令翻译成汇编指令
-e : 改写内存中内容(机器指令)
-a: 以汇编指令的格式在内存中写入一条机器指令
DS和[address]
DS寄存器通常用来存放要访问的数据的段地址
若要读区10000H单元内容到寄存器:
mov bx,1000H
mov ds,bx
mov al, [0]
不可以直接 mov ds,1000H
8086cpu 不支持直接将数据送入段寄存器
将al中数据送入内存单元10000H:
mov bx, 1000H
mov ds, bx
mov [0], al
8086cpu有16根数据线,一次性可以传一个字
mov
mov 段寄存器,寄存器 是可以的
但是 mov 寄存器, 段寄存器 可以吗? 答案是可以。
栈
段寄存器SS: 存放栈顶的段地址
寄存器SP:存放栈顶的偏移地址
任意时刻,SS:SP指向栈顶元素
mov ax, 1000H
mov ss,ax // 不能直接mov数据到段寄存器
mov sp,0010H
push ax
push bx
push ds
第一个程序!
assume cs:abc
abc segment
mov ax,2
add ax,ax
add ax,ax
mov ax, 4c00H
int 21H
abc ends
end
.asm源代码文件,masm后.obj, link后.exe
可以加一个入口,然后debug,debug.exe要放在文件夹中。
assume cs:codesg
codesg segment
start: mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00H
int 21H
codesg ends
end start
shell中输入 debug 2.exe 开始单步调试。int 21H 要用p执行,其他用t。
debug将程序从可执行文件加载入内存后,cx中存放的是程序的长度。
程序加载后,ds中存放着程序所在内存区的段地址,偏移地址为0,则程序所在内存区的地址为 ds:0 。这个内存区的前256个字节存放PSP,dos用来和程序进行通信。
ds和cs相差10H,物理地址相差100H,256B
[bx]
若源程序中这样写
mov ax,[0]
编译器会认为是 mov ax,0
所以要这样:
mov ax,[bx]
bx中放偏移地址。
在debug中可以 mov ax,[0]
loop
执行loop时
- (cx)=(cx)-1
- 判断cx中的值,不为0则转至标号处执行,为0则向下执行。
通常用loop实现循环,cx中存放循环次数。
mov cx,11
s: add ax,ax
loop s
以上代码共执行11次add。
注意:在汇编源程序中数据不能以字母开头!
所以 mov ax,0ffffh
debug时用g命令可以直接跳到想要跳到的ip处。 -g 000B
也可以在ip在loop那行时执行p命令。
一段安全的空间
在一般的PC机中,DOS方式下,DOS和其他合法程序一般都不会用0:200h-0:2ffh的256个字节的空间,所以我们使用这段空间是安全的。
把ffff:0-ffff:b中的数据放入0020:0-0020:b中:
assume cs:code
code segment
mov ax,0ffffH
mov ds,ax
mov ax,0020H
mov es,ax
mov bx,0
mov cx,12
s: mov dl,[bx]
mov es:[bx], dl
inc bx
loop s
mov ax,4c00H
int 21H
code ends
end
使用es存目标段地址,避免在循环中重复改变ds
包含多个段的程序
在代码段中使用数据
dw: define word
assume cs:code
code segment
dw 1h, 2h, 3h, 4h
mov bx,0
mov ax,0
mov cx, 4
s: add ax, cs:[bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end
1h 在cs:0处,依次放4个字
这样的话cpu会把这些字转换成指令,而这些并不是指令,cpu会误读指令,我们可以在第一条需要执行的代码前加start(或其他任意,只要和end后对应):
assume cs:code
code segment
dw 1h, 2h, 3h, 4h
start: mov bx,0
mov ax,0
mov cx, 4
s: add ax, cs:[bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end start
end除了通知编译器程序结束外,还可以通知编译器程序的入口在什么地方。
在代码段中使用栈
利用栈,将程序中定义的数据逆序存放
assume cs:codesg
codesg segment
dw 0123h,0456h,8976h,0987h,4576h,0345h,0984h,7678h
dw 0,0,0,0,0,0,0,0 ;用dw定义8个字型数据,程序加载后将取得8个字的内存空间,后面把这些空间当作栈来使用。
start: mov ax,cs
mov ss,ax
mov sp,32
mov bx,0
mov cx,8
s: push cs:[bx]
add bx,2
loop s
mov bx,0
mov cx,8
s1: pop cs:[bx]
add bx,2
loop s1
mov ax,4c00h
int 21h
codesg ends
end start
将数据,代码,栈放入不同的段
assume cs:codesg, ds:data, ss:stack
data segment
dw 0123h,0456h,8976h,0987h,4576h,0345h,0984h,7678h
data ends
stack segment
dw 0,0,0,0,0,0,0,0
stack ends
codesg segment
start: mov ax,stack
mov ss,ax
mov sp, 16
mov ax,data
mov ds,ax
mov cx,8
mov bx,0
s: push [bx]
add bx,2
loop s
mov cx,8
mov bx,0
s1: pop [bx]
add bx,2
loop s1
mov ax,4c00h
int 21h
codesg ends
end start
不可以 mov ds,data
对于如下定义的段
name segment
...
name ends
如果段中的数据占N个字节,程序加载后,该段实际占有的空间为16*(N/6 +1)
用push指令将a段中的前8个字型数据逆序存放到b段中:
assume cs:code
a segment
dw 1,2,3,4,5,6,7,8,9,0ah,0bh,0ch,0dh,0eh,0fh,0ffh
a ends
b segment
dw 0,0,0,0,0,0,0,0
b ends
code segment
start:
mov ax,a
mov ds,ax
mov ax,b
mov ss,ax
mov sp,16
mov bx,0
mov cx,8
s:
push [bx]
add bx,2
loop s
mov ax,4c00h
int 21h
code ends
end start
更灵活的定位内存地址
大小写转换:
小写字母的ASCII码比对应的大写字母大32,即100000B.大写->小写,or al,00100000B; 小写->大写, and al,11011111B
assume cs:code, ds:data
data segment
db 'BaSiC'
db 'iNfOrMaTiOn'
data ends
code segment
start:
mov ax,data
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[bx]
and al,11011111B
mov [bx],al ;小写to大写
inc bx
loop s
mov bx,5
mov cx,11
s1: mov al,[bx]
or al,00100000B
mov [bx],al ;大写to小写
inc bx
loop s1
mov ax,4c00H
int 21H
code ends
end start
[bx+idata]作为偏移地址
mov ax,[bx+5]
mov ax,[5+bx]
mov ax,5[bx]
mov ax,[bx].5
assume cs:code, ds:data
data segment
db 'BaSiC' ;转换为大写
db 'iNfOr' ;转换为小写
data ends
code segment
start:
mov ax,data
mov ds,ax
mov bx,0
mov cx,5
s: mov al,[0+bx]
and al,11011111b
mov [0+bx],al
mov al,[5+bx]
or al,00100000b
mov [5+bx],al
inc bx
loop s
mov ax,4c00H
int 21H
code ends
end start
SI和DI
SI和DI不能分成两个8位寄存器使用。
mov si,0
mov ax,[si]
mov di,0
mov ax,[di]
mov di,0
mov ax,[di+123]
assume cs:code, ds:data
data segment
db 'welcome to masm!'
db '................'
data ends
code segment
start:
mov ax,data
mov ds,ax
mov si,0
mov cx,16
s: mov al,[si]
mov [16+si],al
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
[bx+si], [bx+di]
[bx+si+idata], [bx+di+idata]
练习:
- 将data中每个单词变为大写
assume cs:codesg, ds:datasg
datasg segment
db 'abc '
db 'def '
db 'ghi '
db 'gkl '
datasg ends
codesg segment
start:
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s: mov dx,cx
mov cx,3
mov di,0
s1: mov al,[bx+di]
and al,11011111b
mov [bx+di],al
inc di
loop s1
add bx,16
mov cx,dx
loop s
mov ax,4c00h
int 21h
codesg ends
end start
但是用dx暂存cx不合适,dx有可能会用到。
使用栈:
assume cs:codesg, ds:datasg, ss:stack
datasg segment
db 'abc '
db 'def '
db 'ghi '
db 'gkl '
datasg ends
stack segment
dw 0,0,0,0,0,0,0,0
stack ends
codesg segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s: push cx
mov cx,3
mov di,0
s1: mov al,[bx+di]
and al,11011111b
mov [bx+di],al
inc di
loop s1
add bx,16
pop cx
loop s
mov ax,4c00h
int 21h
codesg ends
end start
- 将单词的前四个字母变大写
assume cs:codesg, ds:datasg, ss:stack
datasg segment
db '1. abcdisplay '
db '2. defbrows '
db '3. ghikkkkk '
db '4. gkluiui '
datasg ends
stack segment
dw 0,0,0,0,0,0,0,0
stack ends
codesg segment
start:
mov ax,stack
mov ss,ax
mov sp,16
mov ax,datasg
mov ds,ax
mov bx,0
mov cx,4
s: push cx
mov cx,4
mov si,0
s1: mov al,3[bx+si]
and al,11011111b
mov 3[bx+si],al
inc si
loop s1
add bx,16
pop cx
loop s
mov ax,4c00h
int 21h
codesg ends
end start
数据处理的两个基本问题
bx,si,di,bp
只能以四种组合出现:[bx+si],[bx+di],[bp+si],[bp+di]
错误用法:[bx+bp],[si+di]
只要在[]中使用bp,而指令中没有显性的给出短地址,段地址就默认在ss中
可以显性的给出存放段地址的寄存器:
mov ax,ds:[bp]
mov ax,es:[bx]
mov ax,ss:[bx+si]
mov ax,cs:[bx+si+8]
寻址方式:
- 直接寻址:
mov ax,[2]
- 寄存器间接寻址:
mov ax,[bx]
- 寄存器相对寻址:
mov ax,[bx+2]
- 基址变址寻址:
mov ax,[bx+si]
- 相对基址变址寻址:
mov ax,[bx+si+2]
在没有寄存器参与的内存单元访问指令中,用word ptr或byte ptr显性的指明访问的内存单元的长度。
mov word ptr ds:[0],1
inc word ptr [bx]
inc word ptr ds:[0]
add word ptr [bx],2
若是字节型,byte ptr
push只push字型数据
push ds:[1]
div指令
- 除数8位,被除数16位(AX),商(al),余数(ah)
- 除数16位,被除数32位(dx+ax),商(ax),余数(dx)
div bype ptr ds:[0]
(al)=(ax)/((ds)16+0)的商,(ah)=余数
div word ptr ds:[0]
(ax)=[(dx)10000h+(ax)]/((ds)*16+0) 的商 ,(dx)=余数
计算 100001/100
100001=186a1h
mov dx,1h
mov ax,86a1h
mov bx,100
div bx
执行完成后,(ax)=03e8h(100), (dx)=1
计算 1001/100
mov ax,1001
mov bl,100
div bl
执行后,(al)=0ah, (ah)=1
伪指令 dd
用来定义double word, 32位
assume cs:code, ds:data
data segment
dd 100001
dw 100
dw 0
data ends
code segment
start: mov ax,data
mov ds,ax
mov ax,ds:[0]
mov dx,ds:[2]
mov bx,ds:[4]
div bx
mov ds:[6],ax
mov ax,4c00h
int 21h
code ends
end start
伪指令 dup
和db,dw,dd配合使用,进行数据的重复。
- db 3 dup (0)
定义了3个字节,值都是0 - db 3 dup (0,1,2)
相当于 db 0,1,2,0,1,2,0,1,2 - db 3 dup ('abc','ABC')
相当于 db 'abcABCabcABCabcABC' - 定义200字节大小的栈:
stack segment
db 200 dup (0)
stack ends
实验: 把table填上数据
assume cs:code,ds:data,es:table
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;以上是表示21年的字符串 4 * 21 = 84
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;以上是表示21年公司总收入的dword型数据 4 * 21 = 84
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;以上是表示21年公司雇员人数的21个word型数据 2 * 21 = 42
data ends
table segment
db 21 dup ('year summ ne ?? ') ; 'year summ ne ?? ' 刚好16个字节
table ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,table
mov es,ax
mov bx,0 ;年和总收入的偏移
mov si,0 ; 人数的偏移
mov di,0 ; 结果的行的偏移
mov cx,21
s: mov ax,ds:[0+bx] ;填年份
mov es:[di],ax
mov ax,ds:[2+bx]
mov es:[di+2],ax
mov ax,ds:[84+bx] ;填总收入
mov es:[di+5],ax
mov ax,ds:[86+bx]
mov es:[di+7],ax
mov ax,ds:[168+si] ;填人数
mov es:[di+10],ax
mov ax,ds:[84+bx] ;计算平均收入
mov dx,ds:[86+bx]
div word ptr ds:[168+si]
mov es:[di+13],ax ; 填平均收入
add bx,4 ;双字型移4位
add si,2 ; 字型移2位
add di,16
loop s
mov ax,4c00h
int 21h
code ends
end start
转移指令
操作符offset
是由编译器处理的符号,用来取得标号的偏移地址。
assume cs:code
code segment
start: mov ax, offset start ;相当于 mov ax,0
s: mov ax, offset s ; 相当于 mov ax,3
code ends
end start
把s处的第一条指令复制到s0处:
assume cs:code
code segment
s: mov ax,bx ; 2 bytes
mov si,offset s
mov di, offset s0
mov ax,cs:[si]
mov cs:[di],ax
s0: nop ; 1 byte
nop
code ends
end
jmp指令
无条件跳转
- jmp short 标号
转到标号处执行指令
这种格式的jmp指令实现段内短转移,对ip的修改范围为 -128~127
assume cs:code
code segment
start: mov ax,0
jmp short s
inc ax
add ax,ax
s: inc ax
mov ax,4c00h
int 21h
code ends
end
jmp对应的机器码 EB03, 读取指令 EB03 进入指令缓冲器后,ip变为 0005, 执行 EB03, IP变为 0008,成功跳转。
EB后面的8位位移由编译程序在编译时算出,因为是8位,所以范围为 -128-127
- jmp near ptr 标号
16位的偏移。
位移范围: -32768~32767 - jmp far ptr 标号
段间转移
far ptr指明了标号的段地址和偏移地址,修改cs:ip
assume cs:code
code segment
start: mov ax,0
mov bx,0
jmp far ptr s
db 256 dup (0)
s: add ax,1
inc ax
mov ax,4c00h
int 21h
code ends
end start
注意到jmp指令对应的机器码,和cs:ip对应
- jmp 16位寄存器
寄存器中放的是ip的值。 - 转移地址在内存中
- jmp word ptr 内存单元地址
内存单元中存放的字是转移目的的偏移地址。
跳转到ip 0123h处mov ax,0123h mov ds:[0],ax jmp word ptr ds:[0]
- jmp dword ptr 内存单元地址
(cs)=(内存单元地址+2)
(ip)=(内存单元地址)
(cs)=0, (ip)=0123hmov ax,0123h mov ds:[0],ax mov word ptr ds:[2],0 jmp dword ptr ds:[0]
- jmp word ptr 内存单元地址
jcxz 指令
有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对ip的修改范围都为 -128~127
格式: jcxz 标号
当(cx)=0时,(IP)=(IP)+8位位移
当(cx)!=0时,程序向下执行
loop指令
短转移,在对应的机器码中包含转移的位移,而不是目的地址。
对ip的修改范围 -128~127
根据位移进行转移的意义
jmp short 标号
jmp near ptr 标号
jcxz 标号
loop 标号
在他们的机器码中不包含转移的目的地址,包含的是到目的地址的位移距离。
转移范围有限,超出编译时编译器将报错。
assume cs:code
code segment
start: jmp short s
db 128 dup(0)
s: mov ax,0ffffh
code ends
end start
注意: jmp 2000:1000这样的转移指令实在debug中使用的,汇编编译器并不认识。
实验8:
assume cs:codesg
codesg segment
mov ax,4c00h
int 21h
start:
mov ax,0
s:
nop ;nop指令占一个字节
nop
mov di,offset s
mov si,offset s2
mov ax,cs:[si] ; (ax)=F6EB, F6h=-10,即往回跳10个字节, 0022->0018
mov cs:[di],ax ; s处将存 EBF6 ,即0008处存EBF6
s0:
jmp short s ;跳到s后执行EBF6,往上跳10个跳到了0000
s1:
mov ax,0
int 21h
mov ax,0
s2:
jmp short s1
nop
codesg ends
end start
实验9
assume cs:code, ds:data, ss:stack
data segment
db 'welcome to masm!'
db 02h,24h,71h
data ends
stack segment
db 20 dup (0)
stack ends
code segment
start: mov ax,data
mov ds,ax ;源段
mov ax,0B800h
mov es,ax ;目标段
mov ax,stack
mov ss,ax
mov sp,20
mov bx,1820 ;目标显示的偏移,每次加160
mov si,0 ;颜色格式,每次加1
mov cx,3
s: mov ah,ds:[16+si] ;颜色
push cx
push si
mov si,0 ;源字符串偏移,每次加1
mov di,0 ;目标偏移位置,每次加2
mov cx,16
s1: mov al,ds:[si]
mov es:[bx+di],al
mov es:1[bx+di],ah
inc si
add di,2
loop s1
pop si
pop cx
inc si
add bx,160
loop s
mov ax,4c00h
int 21h
code ends
end start
Call和Ret指令
call和ret都是转移指令,他们都修改ip,或者同时修改cs和IP
ret和retf
ret指令用栈中的数据,修改ip的内容,从而实现近转移。
cpu执行ret指令时,进行下面两步操作:
- (ip)=((ss)*16+(sp))
- (sp)=(sp)+2
retf 指令用栈中的数据修改cs和ip的内容,从而实现远转移。 - (ip)=((ss)*16+(sp))
- (sp)=(sp)+2
- (cs)=((ss)*16+(sp))
- (sp)=(sp)+2
call
cpu执行call指令,进行两步操作:
- 将当前ip或者cs和ip入栈
- 转移(jmp)
call 不能实现短转移,除此之外,call实现转移的方法和jmp指令的原理相同。
- call 标号
将当前的ip压栈后,转到标号处执行指令。
cpu操作步骤:
- (sp)=(sp)-2
- ((ss)*16+(sp))=(IP)
- (ip)=(ip)+16位位移
- call far ptr 标号
段间转移
cpu执行此操作时的步骤:
- (sp)=(sp)-2
- (cs)入栈
- (sp)=(sp)-2
- (ip)入栈
- 跳到标号处
-
call 16位寄存器
功能:
ip入栈,跳到ip为寄存器内容处 -
转移地址在内存中
-
call word ptr 内存单元地址
段内
先push ip再跳到内存中存着的ip -
call dword ptr 内存单元地址
先push cs, push ip, 再跳
内存中第一个字存着要跳的ip,下一个字存着要跳的cs
mov sp,10h
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0]
执行后,(cs)=0, (ip)=0123h,(sp)=0ch
call和ret配合使用
mul 指令
mul reg
mul 内存单元
用al或者ax中的数据×,都是8位或者都是16位。
8位的两个数结果存在ax, 16位的两个数结果 高位存在DX,低位存在AX
计算100*10:
mov al,100
mov bl,10
mul bl
结果 (ax)=1000
计算100*10000:
mov ax,100
mov bx,10000
mul bx
结果 (dx)=000fh, (ax)=4240h
参数和结果的传递
编程:计算data段中第一组数据的3次方,结果存在后面一组dword中
assume cs:code, ds:data
data segment
dw 1,2,3,4,5,6,7,8
dd 0,0,0,0,0,0,0,0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov di,0
mov cx,8
s: mov ax,ds:[si]
call cube
mov ds:[16+di],ax
mov ds:[16+di+2],dx
add si,2
add di,4
loop s
mov ax,4c00h
int 21h
cube: mov bx,ax
mul bx
mul bx
ret
code ends
end start
批量数据的传递
把批量数据放到内存中,然后把他们所在内存空间的首地址放到寄存器。
《汇编语言第三版》实验10-1
assume cs:code
data segment
db 'Welcome to masm!',0
data ends
code segment
start: mov dh,8
mov dl,3
mov cl,2
mov ax,data
mov ds,ax
mov si,0
call show_str
mov ax,4c00h
int 21h
show_str: mov al,160
mul dh
mov di,ax
add dl,dl
mov dh,0
add di,dx
mov ax,0B800h
mov es,ax
mov dl,cl ;dl存颜色信息
mov ch,0
s: mov cl,[si]
jcxz ok
mov es:[di],cl
mov es:[di+1],dl
inc si
add di,2
jmp short s
ok: ret
code ends
end start
实验10-2
assume cs:code, ds:data
data segment
dw 0,0,0,0,0 ;分别存被除数的低16位,高16位,除数; 公式右半部分商,余数
data ends
code segment
start: mov ax,data
mov ds,ax
mov ax,4240h
mov dx,000fh
mov cx,0ah
mov ds:[0],ax
mov ds:[2],dx
mov ds:[4],cx
call divdw
mov cx,4c00h
int 21h
divdw: mov ax,ds:[2]
mov dx,0
div cx ; rem(H/N) 结果在dx
mov ax,ds:[0]
div cx ;公式右半部分结果,商在ax,余数在dx
mov ds:[6],ax
mov ds:[8],dx
mov ax,ds:[2]
mov dx,0
div cx ; int(H/N) 结果在ax
mov dx,ax
mov ax,ds:[6]
mov cx,ds:[8]
ret
code ends
end start
改进:
assume cs:code
stack segment
dw 0,0,0,0,0
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,10
mov ax,4240h
mov dx,000fh
mov cx,0ah
call divdw
mov cx,4c00h
int 21h
divdw: push ax
mov ax,dx
mov dx,0
div cx ;int(H/N)--ax, rem(H/N)--dx
mov bx,ax ;bx: int(H/N)
pop ax
div cx ; [res(H/N)*65536+L]/N
mov cx,dx
mov dx,bx
ret
code ends
end start
实验10-3
assume cs:code
displaycontent segment
db 10 dup (0)
displaycontent ends
tmp segment ;用于逆序
db 10 dup (0)
tmp ends
code segment
start: mov ax,12666
mov bx,tmp
mov es,bx
mov bp,0
mov bx,displaycontent
mov ds,bx
mov si,0
call dtoc ;转换到10进制的字符并存到displaycontent中
mov dh,8 ;开始准备显示displaycontent中的内容
mov dl,3
mov cl,2
mov si,0
call show_str
mov ax,4c00h
int 21h
dtoc: mov dx,0
mov cx,10
div cx ; ax:商,dx:余数
add dx,30h
mov es:[bp],dl
mov cx,ax
jcxz dtocok
inc bp
jmp short dtoc
dtocok:
mov al,es:[bp]
mov ds:[si],al
mov cx,bp
jcxz ok_1
inc si
dec bp
jmp short dtocok
ok_1: ret
show_str: mov al,160
mul dh
mov di,ax
add dl,dl
mov dh,0
add di,dx
mov ax,0B800h
mov es,ax
mov dl,cl ;dl存颜色信息
mov ch,0
s: mov cl,[si]
jcxz ok
mov es:[di],cl
mov es:[di+1],dl
inc si
add di,2
jmp short s
ok: ret
code ends
end start
课程设计1
assume cs:code,ds:data,es:table
data segment
db '1975','1976','1977','1978','1979','1980','1981','1982','1983'
db '1984','1985','1986','1987','1988','1989','1990','1991','1992'
db '1993','1994','1995'
;以上是表示21年的字符串 4 * 21 = 84
dd 16,22,382,1356,2390,8000,16000,24486,50065,97479,140417,197514
dd 345980,590827,803530,1183000,1843000,2759000,3753000,4649000,5937000
;以上是表示21年公司总收入的dword型数据 4 * 21 = 84
dw 3,7,9,13,28,38,130,220,476,778,1001,1442,2258,2793,4037,5635,8226
dw 11542,14430,15257,17800
;以上是表示21年公司雇员人数的21个word型数据 2 * 21 = 42
data ends
table segment
db 21 dup ('year summ ne ?? ') ; 'year summ ne ?? ' 刚好16个字节
table ends
displaycontent segment
db 10 dup (0)
displaycontent ends
tmp segment ;用于逆序
db 10 dup (0)
tmp ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,table
mov es,ax
mov bx,0 ;年和总收入的偏移
mov si,0 ; 人数的偏移
mov di,0 ; 结果的行的偏移
mov cx,21
s: mov ax,ds:[0+bx] ;填年份
mov es:[di],ax
mov ax,ds:[2+bx]
mov es:[di+2],ax
mov ax,ds:[84+bx] ;填总收入
mov es:[di+5],ax
mov ax,ds:[86+bx]
mov es:[di+7],ax
mov ax,ds:[168+si] ;填人数
mov es:[di+10],ax
mov ax,ds:[84+bx] ;计算平均收入
mov dx,ds:[86+bx]
div word ptr ds:[168+si]
mov es:[di+13],ax ; 填平均收入
add bx,4 ;双字型移4位
add si,2 ; 字型移2位
add di,16
loop s
mov ax,table
mov ds,ax ;ds: table段地址
mov ax,0B800h
mov es,ax ;es:显存段地址
mov bx,0 ;table的行首偏移地址
mov si,160 ;显存的行首地址
mov cx,21
s4: push cx
mov cl,02h
mov al,ds:[bx]
mov es:[si],al
mov es:[si+1],cl
mov al,ds:[bx+1]
mov es:[si+2],al
mov es:[si+3],cl
mov al,ds:[bx+2]
mov es:[si+4],al
mov es:[si+5],cl
mov al,ds:[bx+3]
mov es:[si+6],al
mov es:[si+7],cl
; 第二列:总工资
mov ax,ds:[bx+5]
mov dx,ds:[bx+7]
call ddtoc
mov di,si
add di,20
call showstr
; 第三列:人数
mov ax,ds:[bx+10]
mov dx,0
call ddtoc
mov di,si
add di,50
call showstr
; 第四列:平均工资
mov ax,ds:[bx+13]
mov dx,0
call ddtoc
mov di,si
add di,80
call showstr
pop cx
add bx,16
add si,160
loop s4
mov ax,4c00h
int 21h
;需要传入 di, es
; 需要用到寄存器: ds, bx , cx
showstr:
push ds
mov ax,displaycontent
mov ds,ax ;ds:displaycontent段地址
push bx
mov bx,0 ; bx: displaycontent的偏移
ls: mov cl,ds:[bx]
mov ch,0
jcxz showstr_ok
mov es:[di],cl
mov cl,02h
mov es:[di+1],cl
add di,2
inc bx
jmp short ls
showstr_ok: pop bx
pop ds
ret
;一个子程序,负责把dword (dx+ax)转变成字符串存到displaycontent
; 此程序需要传入dx,ax
; 需要用到寄存器: bx, es, ds, si, di, cx
ddtoc:
push bx
push ds
push es
push si
mov bx,displaycontent
mov es,bx ;es: displaycontent段地址
mov bx,tmp
mov ds,bx ; ds:tmp段地址
mov si,0 ;暂存的tmp的偏移地址
mov di,0 ; 结果displaycontent的偏移地址
s0: mov cx,10
call divdw
add cx,30h
mov ds:[si],cl
mov cx,dx
jcxz s1
inc si
jmp short s0
s1: mov cx,ax
jcxz s2
inc si
jmp short s0
s2: mov bl,ds:[si]
mov es:[di],bl
mov cx,si
jcxz s3
dec si
inc di
jmp short s2
s3: inc di
mov es:[di],0 ;结束标记
pop si
pop es
pop ds
pop bx
ret
; 32位/16位,(dx+ax)/cx, 结果余数cx,商dx+cx
; 需要传入 dx,ax,cx
; 需要用到寄存器: bx
divdw: push ax
mov ax,dx
mov dx,0
div cx ;int(H/N)--ax, rem(H/N)--dx
mov bx,ax ;bx: int(H/N)
pop ax
div cx ; [res(H/N)*65536+L]/N
mov cx,dx
mov dx,bx
ret
code ends
end start
标志寄存器
6--ZF
结果为0,ZF=1
不为0,ZF=0
PF
奇偶标志位,结果的所有的二进制位中1的个数
为偶数,PF=1
为奇数,PF=0
7--SF
结果为负,SF=1
为正,SF=0
当我们将数据当作有符号数,可以通过SF得知结果的正负;
如果当作无符号数,则SF无意义。
add,sub,mul,div,inc,or,and等运算指令影响标志寄存器; mov,push,pop等传送指令对标志寄存器无影响
0--CF
进位标志位
无符号数运算产生进位 CF=1
如果两数相减,产生借位,CF也为1.
OF
有符号数运算超过机器所能表示的范围产生溢出。
例如8位al寄存器范围,如果是有符号数, -128~127
溢出针对有符号数,进位针对无符号数!
adc指令
adc 操作对象1 操作对象2
功能: 操作对象1+操作对象2+CF
mov al,98h
add al,98h
adc ah,0
执行后 (ah)=1
mov ax,2
mov bx,1
sub bx,ax ;借位,CF=1
adc ax,1
执行后 (ax)=4
编程计算 1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中:
assume cs:code
code segment
start: mov bx,0f000h
mov ax,1eh
add bx,1000h
adc ax,20h
mov ax,4c00h
int 21h
code ends
end start
adc指令也可能改变CF!
编程计算 1EF0001000h + 2010001EF0H
结果放在ax+bx+cx中
assume cs:code
code segment
start: mov cx,1000h
mov bx,0f000h
mov ax,1eh
add cx,1ef0h
adc bx,1000h
adc ax,20h
mov ax,4c00h
int 21h
code ends
end start
计算两个128位数据的和:
assume cs:code, ds:data
data segment
db 88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h,88h
db 88h,88h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h,11h
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov di,16
mov cx,8
sub ax,ax
call add128
mov ax,4c00h
int 21h
add128: mov ax,[si]
adc ax,[di]
mov [si],ax
inc si ;必须inc,不可以add,inc不影响CF
inc si
inc di
inc di
loop add128
ret
code ends
end start
sbb 借位减法指令
sbb ax,bx
(ax)=(ax)-(bx)-CF
计算 003E1000H - 00202000H
mov bx, 1000H
mov ax, 003EH
sub bx,2000H
sbb ax,0020H
cmp指令
只对标志寄存器产生影响,不保存结果。
cmp ax,ax
做(ax)-(ax),结果0,并不在ax中保存,只影响flag相关各位。
- 比较无符号数
ZF=1 说明相等
CF=1 说明前面小于后面 - 比较有符号数
ZF=1, 两数相等
SF=1, 前面<后面 ???
不可以,可能溢出, 比如 (ah)=22h, (bh)=0a0h, cmp ah,bh 产生溢出
要考虑溢出!!!
需要同时考虑OF和SF- SF=1,OF=0 前面<后面
- SF=1,OF=1 前面>后面
- SF=0,OF=1 前面<后面
- SF=0,OF=0 前面>=后面
检测比较结果的条件转移指令
无符号:
je 检测的是ZF的值,不管je前面是什么指令,只要cpu执行je指令时ZF=1,则转移。
其他指令同理。
计算8的个数:
assume cs:code, ds:data
data segment
db 8,11,8,1,8,5,8,38
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov ax,0
mov cx,8
s: mov bl,[si]
cmp bl,8
jne skip
inc ax
skip: inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
小于8:
assume cs:code, ds:data
data segment
db 8,11,8,1,8,5,8,38
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov ax,0
mov cx,8
s: mov bl,[si]
cmp bl,8
jnb skip
inc ax
skip: inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
DF标志和串传送指令
DF:方向标志位
在串处理指令中,控制每次操作后si,di的增减
- DF=0: 每次操作后si,di递增
- DF=1: 每次操作后si,di递减
- movsb
以字节单位传送
((es)x16+(di))=((ds)x16+(si))
- DF=0: 每次操作后si,di递增
- DF=1: 每次操作后si,di递减
- movsw
传送一个字
一般来说movsb,movsw都和rep配合使用,格式:rep movsb
rep的作用是根据cx的值重复执行后面的串传送指令
改变DF的值: - cld指令:DF设置为0
- std指令:DF设置为1
把前面的字符串复制到后面:
assume cs:code, ds:data
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
code segment
start: mov ax,data
mov ds,ax
mov es,ax
mov si,0
mov di,16
mov cx,16
cld
rep movsb
mov ax,4c00h
int 21h
code ends
end start
把F000H段中最后16个字节数据存到data段中:
assume cs:code, ds:data
data segment
db 16 dup (0)
data ends
code segment
start: mov ax,0F000h
mov ds,ax
mov ax,data
mov es,ax
mov si,0ffffh
mov di,15
mov cx,16
std
rep movsb
mov ax,4c00h
int 21h
code ends
end start
pushf和popf
pushf: 将标志寄存器的值入栈
popf: 从栈中弹出数据,送入标志寄存器
实验11:将data中数据的小写字母转为大写
assume cs:codesg
datasg segment
db "Beginner's All-purpose Symbolic Instruction Code.",0
datasg ends
codesg segment
start: mov ax, datasg
mov ds, ax
mov si, 0
call letterc
mov ax, 4c00H
int 21H
letterc: mov cl,[si]
mov ch,0 d 076a:0d
jcxz done
cmp cl,97
jb skip
cmp cl,122
ja skip
and cl, 11011111B
mov [si],cl
skip: inc si
jmp short letterc
done: ret
codesg ends
end start
内中断
在中断过程中,寄存器的入栈顺序是标志寄存器,CS,IP,而iret的出栈顺序为IP,CS,标志寄存器,刚好对应。
中断处理程序的cs和ip存放在0000:0000--0000:03FF 中,即中断向量表。
一般,0000:0200--0000:02FF的256字节是空的,是一段安全的内存空间。
ip在低地址,cs在高地址
N号中断的处理程序的ip在4N,cs在4N+2
一个中断处理程序占4个字节,前两个字节是ip,后两个是cs
编程处理0号中断:
- 在内存0:0200处安装do0的代码,将0号中断处理程序的入口地址设置为0:0200,即0000:0000为0200,0000:0002为0
- do0的代码不在程序执行时执行,只在发生除法溢出时执行
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset do0
mov ax,0
mov es,ax
mov di,0200h
mov cx,offset do0end - offset do0 ;计算do0程序段的长度
cld
rep movsb
mov ax,0
mov ds,ax
mov dx,0200h
mov ds:[0],dx ;0号中断处理程序的ip
mov dx,0
mov ds:[2],dx ; cs
mov ax,4c00h
int 21h
do0: jmp short do0start ;此程序将要放到内存0:0200处
db "Not good, 0# INT!",0
do0start: mov ax,0
mov ds,ax
mov ax,0B800h
mov es,ax
mov si,0202h
mov di,7*160+20
mov ch,0
s: mov cl,[si]
jcxz done
mov es:[di],cl
mov cl,02h
mov es:[di+1],cl
inc si
add di,2
jmp short s
done: mov ax,4c00h
int 21h
do0end: nop
code ends
end start
单步中断
CPU在执行完一条指令后,如果监测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。
单步中断的中断类型码是1
引发的中断过程如下:
- 取得中断类型码1;
- 标志寄存器入栈,TF,IF设为0
- CS,IP入栈
- (IP)=(4), (cs)=(6)
如果TF=1,则执行一条指令后cpu就要去执行1号中断处理程序。
debug提供了单步中断的中断处理程序,功能为显示所有寄存器的内容后等待输入命令。
在debug中,使用t命令执行指令时,debug将TF设置为1,使得cpu工作于单步中断方式下,则在cpu执行完这条指令后便引发单步中断,执行单步中断的中断处理程序。
总之,当TF=1时,cpu在执行完一条指令后引发单步中断,转去执行中断处理程序。执行完中断处理程序后,返回原来的位置继续。
int指令
cpu执行 int n 指令,相当于引发一个n号中断的中断过程,过程如下:
- 取中断类型码
- 标志寄存器入栈,IF=0,TF=0
- CS,IP入栈
- (ip)=(4n),(cs)=(4n+2)
例1
编写int 7ch 的中断处理函数并安装
在中断例程的最后,使用iret指令
iret指令的功能为:
pop ip
pop cs
popf
中断程序及安装:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset sqr
mov ax,0
mov es,ax
mov di,200h
mov cx, offset sqrend - offset sqr
cld
rep movsb
mov ax,0
mov ds,ax
mov ax,200h
mov ds:[7ch*4],ax ;修改7ch中断的中断处理函数的ip
mov ax,0
mov ds:[7ch*4+2],ax
mov ax,4c00h
int 21h
sqr: mul ax
iret
sqrend: nop
code ends
end start
测试程序:
assume cs:code
code segment
start: mov ax,3456
int 7ch
add ax,ax
adc dx,dx
mov ax,4c00h
int 21h
code ends
end start
例2
转换字符串为大写
中断例程及安装:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset sqr
mov ax,0
mov es,ax
mov di,200h
mov cx, offset sqrend - offset sqr
cld
rep movsb
mov ax,0
mov ds,ax
mov ax,200h
mov ds:[7ch*4],ax ;修改7ch中断的中断处理函数的ip
mov ax,0
mov ds:[7ch*4+2],ax
mov ax,4c00h
int 21h
sqr: mov cl,ds:[si]
mov ch,0
jcxz done
and cl,11011111b
mov ds:[si],cl
inc si
jmp short sqr
done: iret
sqrend: nop
code ends
end start
用到中断的程序:
assume cs:code
data segment
db 'conversation',0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
int 7ch
mov ax,4c00h
int 21h
code ends
end start
例3
用int 7ch实现loop的功能
中断程序:
assume cs:code
code segment
start: mov ax,cs
mov ds,ax
mov si,offset lp
mov ax,0
mov es,ax
mov di,200h
mov cx, offset lpend - offset lp
cld
rep movsb
mov ax,0
mov ds,ax
mov ax,200h
mov ds:[7ch*4],ax ;修改7ch中断的中断处理函数的ip
mov ax,0
mov ds:[7ch*4+2],ax
mov ax,4c00h
int 21h
lp: push bp
mov bp,sp
dec cx
jcxz done
add ss:[bp+2],bx
done: pop bp
iret
lpend: nop
code ends
end start
测试程序:
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov di,160*10
mov bx,offset s - offset se
mov cx,80
s: mov byte ptr es:[di],'!'
add di,2
int 7ch ; int时,压栈的ip是se的位置,此ip+bx可以得到s的位置
se: nop
mov ax,4c00h
int 21h
code ends
end start
int 10h
mov ah,2 ; 2号子程序:放置光标
mov bh,0 ; 第0页
mov dh,5 ; dh中放行号
mov dl,12 ; dl中放列号
int 10h
(ah)=2 表示调用第10h号中断例程的2号子程序,功能为设置光标位置
mov ah,9
mov al,'a' ;字符
mov bl,7 ; 颜色属性
mov bh,0 ; 第0页
mov cx,3 ; 重复个数
int 10h
9号子程序,功能为在光标位置显示字符。
int 21h
ds:dx 指向字符串 ;要显示的字符串需要用$作为结束符
mov ah,9
int 21 h
在光标位置显示字符串,可以提供要显示的字符串的地址作为参数。
实验13_1
中断程序及安装:
assume cs:code
code segment
start:
mov ax,cs
mov ds,ax
mov si,offset intcode
mov ax,0
mov es,ax
mov di,0200h
mov cx, offset intcodeend - offset intcode
cld
rep movsb
mov ax,0
mov ds,ax
mov word ptr ds:[7ch*4],0200h
mov word ptr ds:[7ch*4+2],0
mov ax,4c00h
int 21h
intcode: mov al,160
mul dh
mov di,ax
add dl,dl
mov dh,0
add di,dx
mov ax,0B800h
mov es,ax
mov dl,cl
mov ch,0
s: mov cl,ds:[si]
jcxz done
mov es:[di],cl
mov es:[di+1],dl
inc si
add di,2
jmp short s
done: iret
intcodeend: nop
code ends
end start
测试中断的程序:
assume cs:code
data segment
db "welcome to masm!",0
data ends
code segment
start: mov dh,10
mov dl,10
mov cl,2
mov ax,data
mov ds,ax
mov si,0
int 7ch
mov ax,4c00h
int 21h
code ends
end start
实验13_2
中断程序及安装:
assume cs:code
code segment
start:
mov ax,cs
mov ds,ax
mov si,offset intcode
mov ax,0
mov es,ax
mov di,0200h
mov cx, offset intcodeend - offset intcode
cld
rep movsb
mov ax,0
mov ds,ax
mov word ptr ds:[7ch*4],0200h
mov word ptr ds:[7ch*4+2],0
mov ax,4c00h
int 21h
intcode: dec cx
jcxz done
mov bp,sp
add ss:[bp],bx
done: iret
intcodeend: nop
code ends
end start
测试程序:
assume cs:code
code segment
start: mov ax,0b800h
mov es,ax
mov di,160*12
mov bx, offset s - offset se
mov cx,80
s: mov byte ptr es:[di],'!'
add di,2
int 7ch
se: nop
mov ax,4c00h
int 21h
code ends
end start
实验13_3
assume cs:code
code segment
s1: db 'Good, better, best,', '$'
s2: db 'Never let it rest,', '$'
s3: db 'Till good is better,', '$'
s4: db 'And better,best.', '$'
s: dw offset s1, offset s2, offset s3, offset s4
row: db 2, 4, 6, 8
start: mov ax,cs
mov ds,ax
mov bx,offset s
mov si, offset row
mov cx,4
ok:mov bh,0 ; 第0页
mov dh,[si] ; dh中放行号
mov dl,0 ; dl中放列号
mov ah,2 ;2号子程序:放置光标
int 10h
mov dx,[bx] ;ds:dx 指向字符串 要显示的字符串需要用$作为结束符
mov ah,9 ;在光标位置显示字符串
int 21h
inc si
add bx,2
loop ok
mov ax,4c00h
int 21h
code ends
end start
端口
端口的读写
读写指令只有两条: in 和 out
in al,60h
从60h号端口读入一个字节
out 21h, al
向21h端口写al
注意,只能用al或者ax
- 对0-255端口进行读写
in al,20h
out 20h,al
- 对256-65535号端口进行读写,端口号放在dx中
mov dx,3f8h
in al,dx
out dx,al
CMOS RAM芯片
包含128个存储单元
该芯片靠电池供电,断电后仍可继续工作,信息不丢失。
时钟占用0-0dh单元,其余大部分单元保存系统配置信息,供系统启动时BIOS程序读取。
该芯片有两个端口,70h和71h,cpu通过这两个端口读写CMOS RAM
70h为地址端口,存放要访问的CMOS RAM单元的地址;71h为数据端口,存放从选定的CMOS RAM单元中读取的数据,或者要写入的数据。
读2号单元:
- 将2送入端口70h
- 从71h读出2号单元的内容
编程读取CMOS RAM 2号存储单元内容:
assume cs:code
code segment
start: mov al,2
out 70h, al
in al,71h
mov ax,4c00h
int 21h
code ends
end start
向 2号存储单元写入0:
assume cs:code
code segment
start: mov al,2
out 70h, al
mov al,0
out 71h,al
mov ax,4c00h
int 21h
code ends
end start
shr, shl
shl 逻辑左移,将一个寄存器或者内存单元中的数据向左移位,最后移出的一位写入CF。
mov al,01001000b
shl al,1
执行后 (al)=10010000B, CF=0
如果移位位数大于1,必须将位数放在cl中。
比如:
mov al,01010001B
mov cl,3
shl al,cl
执行后 (al)=10001000b, CF=0
计算 (ax)=(ax)*10
assume cs:code
code segment
start: mov bx,ax
shl ax,1
mov cl,3
shl bx,cl
add ax,bx
mov ax,4c00h
int 21h
code ends
end start
CMOS RAM 中存储的时间信息
秒: 00h
分: 02H
时: 04H
日: 07H
月: 08H
年: 09H
存的数据以BCD码格式,00010001 表示 11
实验14
显示当前日期和时间
assume cs:code
data segment
db 09h,08h,07h,04h,02h,00h
db '// :: '
data ends
code segment
start: mov ax,0b800H
mov es, ax
mov di, 160*10+20
mov ax,data
mov ds,ax
mov si,0
mov dl,02h
mov cx,6
s: push cx
mov al,ds:[si]
out 70h,al
in al,71h
call show
mov al, ds:[6+si]
mov byte ptr es:[di],al
mov es:[di+1],dl
add di,2
inc si
pop cx
loop s
mov ax,4c00h
int 21h
show: mov ah,al
mov cl,4
shr ah,cl
add ah,30h
mov es:[di],ah
mov es:[di+1],dl
add di,2
mov ah,al
shl ah,cl
shr ah,cl
add ah,30h
mov es:[di], ah
mov es:[di+1],dl
add di,2
ret
code ends
end start
外中断
外中断源有两类:可屏蔽中断和不可屏蔽中断
可屏蔽中断是cpu可以不响应的外中断。cpu是否响应可屏蔽中断,要看标志寄存器IF位的设置。
当cpu检测到可屏蔽中断信息时,如果IF=1,则cpu响应中断,引发中断过程; 若IF=0, 则不响应可屏蔽中断。
可屏蔽中断信息来自于cpu外部,中断类型码是通过数据总线送入cpu的。
内中断的中断类型码是cpu内部产生的。
中断过程中将IF设置为0的原因:在进入中断处理程序后,禁止其他的可屏蔽中断。
如果在中断处理程序中需要处理可屏蔽中断,可以用指令将IF设为1.
sti: 设置IF=1
cli: 设置IF=0
不可屏蔽中断是cpu必须响应的外中断。当cpu检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。
8086cpu不可屏蔽中断的中断类型码固定为2,中断过程中不需要取中断类型码。
不可屏蔽中断中断过程:
- 标志寄存器入栈, IF=0, TF=0
- CS,IP入栈
- (ip)=8, (cs)=10
键盘
键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关进行扫面。按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60h。松开按下的键时,也产生一个扫描码,扫描码说明了松开的键在键盘上的位置。松开按键时产生的扫描码也被送入60h端口中。
按下一个键时产生的扫描码称为通码,松开一个键产生的扫描码称为断码。
扫描码长度为一个字节,通码的第七位为0,断码的第七位为1. 断码=通码+80h
BIOS提供了int 9中断例程,用于进行基本的键盘输入处理,主要工作如下:
- 读出60h端口中的扫描码
- 如果是字符键的扫描码,将该扫描码和对应的字符码(ASCII码)送入内存中的BIOS键盘缓冲区。
键盘的处理过程:
键盘的输入到达60h端口时,相关芯片向cpu发出中断类型码为9的可屏蔽中断信息
cpu检测到中断信息后,如果IF=1,则响应中断,去执行int 9中断例程
如果是控制键(ctrl)和切换键(caps),则将其转变为状态字节,写入内存中存储状态字节的单元
BIOS键盘缓冲区是系统启动后,BIOS用于存放int 9中断例程所接收的键盘输入的内存区
该内存区可以存储15个键盘输入,一个键盘输入用一个字单元存放,高位字节存放扫描码,低位字节存放字符码
0040:17 单元存储键盘状态字节,记录了键盘控制键和切换键的状态,
编写int 9例程,每次按下esc, 改变显示字符颜色:
assume cs:code
stack segment
db 128 dup(0)
stack ends
data segment
dw 0,0
data ends
code segment
start: mov ax,stack
mov ss, ax
mov sp, 128
mov ax,data
mov ds,ax ; ds: data
mov ax,0
mov es, ax ; es:0
push es:[9*4]
pop ds:[0]
push es:[9*4+2]
pop ds:[2]
mov word ptr es:[9*4], offset int9
mov ax,cs
mov es:[9*4+2], ax
mov ax,0b800h
mov es,ax ;es: 0b800h
mov ah,'a'
mov al,02h
mov es:[160*10+21],al
s: mov es:[160*10+20],ah
call delay
inc ah
cmp ah,'z'
jna s
mov ax,0
mov es, ax ; es: 0
push ds:[0]
pop es:[9*4]
push ds:[2]
pop es:[9*4+2]
mov ax,4c00h
int 21h
delay:
push ax
push dx
mov dx,10
mov ax,0
s1: sub ax,1
sbb dx,0
cmp ax,0
jne s1
cmp dx,0
jne s1
pop dx
pop ax
ret
int9: push es
push ax
push bx
in al,60h
pushf ; 标志寄存器入栈
pushf
pop bx
and bh, 11111100b ; IF,TF 置为0
push bx
popf
call dword ptr ds:[0] ; 调用原来的int 9程序,最后会pop IP, pop CS, pop 标志寄存器
cmp al,1
jne int9ret
mov ax,0b800h
mov es,ax ; es: 0b800h
inc byte ptr es:[160*10+20+1]
int9ret: pop bx
pop ax
pop es
iret
code ends
end start
安装int 9例程,实现按下f1键,改变dos窗口颜色
assume cs:code
stack segment
db 128 dup (0)
stack ends
code segment
start: mov ax, 0
mov es, ax ; es:0
mov di,0204h
mov ax, cs
mov ds, ax ; ds: cs
mov si, offset int9
mov cx, offset int9end - offset int9
cld
rep movsb
mov ax,stack
mov ss,ax
mov sp,128
push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[202h]
mov ax, 204h
mov es:[9*4], ax
mov ax,0
mov es:[9*4+2], ax
mov ax,4c00h
int 21h
int9: push ax
push ds
push cx
push bx
in al,60h
pushf
call dword ptr cs:[200h]
cmp al,3bh
jne int9ret
mov ax,0b800h
mov es,ax
mov bx,1
mov cx,2000
s: inc byte ptr es:[bx]
add bx,2
loop s
int9ret: pop bx
pop cx
pop ds
pop ax
iret
int9end: nop
code ends
end start
实验15 安装新的int9 中断例程
assume cs:code
stack segment
db 128 dup (0)
stack ends
code segment
start: mov ax, 0
mov es, ax ; es:0
mov di,0204h
mov ax, cs
mov ds, ax ; ds: cs
mov si, offset int9
mov cx, offset int9end - offset int9
cld
rep movsb
mov ax,stack
mov ss,ax
mov sp,128
push es:[9*4]
pop es:[200h]
push es:[9*4+2]
pop es:[202h]
mov ax, 204h
mov es:[9*4], ax
mov ax,0
mov es:[9*4+2], ax
mov ax,4c00h
int 21h
int9: push ax
push es
push cx
push bx
in al,60h
pushf
call dword ptr cs:[200h]
cmp al,9eh
jne int9ret
mov ax,0b800h
mov es,ax
mov bx,0
mov cx,2000
s: mov byte ptr es:[bx],'A'
add bx,2
loop s
int9ret: pop bx
pop cx
pop es
pop ax
iret
int9end: nop
code ends
end start
直接定址表
a db 1,2,3,4,5,6,7,8
b dw 0
start: mov al,a[si] ;相当于 mov al,cs:0[si]
mov al, a[3] ;相当于 mov al,cs:0[3]
mov ax,b ; mov ax,cs:[8]
mov b,2 ; mov word ptr cs:[8],2
inc b ; inc word ptr cs:[8]
将a处的8个数据相加结果存到b处的双字中:
assume cs:code
code segment
a dw 1,2,3,4,5,6,7,8
b dd 0
start: mov si,0
mov cx,8
s:mov ax, a[si]
add a[16],ax
adc a[18],0
add si,2
loop s
mov ax,4c00h
int 21h
code ends
end start
若将数据放在数据段,则必须assume
assume cs:code, ds:data
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
data ends
code segment
start: mov ax,data
mov ds,ax
mov si,0
mov cx,8
s:mov al, a[si]
mov ah,0
add b,ax
inc si
loop s
mov ax,4c00h
int 21h
code ends
end start
如果想在代码段中直接用数据标号访问数据,则需要用伪指令assume将标号所在的段和一个段寄存器联系起来。否则编译器在编译时无法确定标号的段地址在哪儿一个寄存器中。
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dd a,b
data ends
c处存储的两个双子为标号a的偏移地址和段地址,标号b的偏移地址和短地址。
相当于
data segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw offset a, seg a, offset b, seg b
data ends
应用:以16进制形式显示数字
assume cs:code, ds:data
data segment
table db '0123456789ABCDEF'
data ends
code segment
start: mov ax,data
mov ds,ax
mov ax,0b800h
mov es,ax
mov di,160*10+40
mov al,0eh
mov ah,al
shr ah,1
shr ah,1
shr ah,1
shr ah,1
mov bl,ah
mov bh,0
mov ah,table[bx]
mov es:[di], ah
mov ah,02h
mov es:[di+1],ah
and al,00001111b
mov bl,al
mov bh,0
mov ah,table[bx]
mov es:[di+2],ah
mov ah,02h
mov es:[di+3],ah
mov ax,4c00h
int 21h
code ends
end start
另一个应用,计算sin
assume cs:code
code segment
start: mov al,120
call showsin
mov ax,4c00h
int 21h
showsin:
jmp short show
table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180
ag0 db '0',0
ag30 db '0.5',0
ag60 db '0.866',0
ag90 db '1',0
ag120 db '0.866',0
ag150 db '0.5',0
ag180 db '0',0
show: mov bx,0b800h
mov es,bx
mov di, 160*10+20
mov ah,0
mov bl,30
div bl
add al,al
mov bl,al
mov bh,0
mov bx, table[bx]
s: mov al,cs:[bx]
cmp al,0
je showend
mov es:[di],al
mov al,02h
mov es:[di+1],al
inc bx
add di,2
jmp short s
showend:
ret
code ends
end start
程序入口地址的直接定址表
assume cs:code
code segment
start: mov ah,3 ;ah: 程序号
mov al,02h ; al: 参数 (前景色,背景色)
call setscreen
mov ax,4c00h
int 21h
setscreen: jmp short set
table dw sub1,sub2,sub3,sub4 ; sub1: 清屏, sub2:设置前景色, sub3:设置背景色, sub4:向上滚动一行
set:
push bx
cmp ah,3
ja sret
mov bl,ah
mov bh,0
add bx,bx
call word ptr table[bx]
sret: pop bx
ret
sub1: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,0
mov cx,2000
sub1s: mov byte ptr es:[bx],' '
add bx,2
loop sub1s
pop es
pop cx
pop bx
ret
sub2: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov bx,1
mov cx,2000
sub2s: and byte ptr es:[bx], 11111000b
or es:[bx],al
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
sub3: push bx
push cx
push es
mov bx,0b800h
mov es,bx
mov cl,4
shl al,cl
mov bx,1
mov cx,2000
sub3s: and byte ptr es:[bx], 10001111b
or es:[bx],al
add bx,2
loop sub2s
pop es
pop cx
pop bx
ret
sub4: push bx
push cx
push es
push ds
push si
push di
mov bx,0b800h
mov ds,bx
mov es,bx
mov si,160
mov di,0
mov cx,24
sub4s: push cx
mov cx,160
cld
rep movsb
pop cx
loop sub4s
mov cx,80
mov si,24*160
sub4s1:
mov byte ptr es:[si], ' '
add si,2
loop sub4s1
pop di
pop si
pop ds
pop es
pop cx
pop bx
ret
code ends
end start
使用BIOS进行键盘输入和磁盘读写
键盘输入将引发9号中断,BIOS提供了int 9中断例程。
cpu在9号中断发生后,执行int 9中断例程, 从60h端口读出扫描码,并将其转换为相应的ASCII码或者状态信息(如shift),存储在内存的指定空间(键盘缓冲区或状态字节)中。
一般的键盘输入,在cpu执行完int9 中断例程后,都放到了键盘缓冲区中。
键盘缓冲区有16个字单元,可以存储15个按键的扫面码和对应的ASCII码。
int 16h 中断例程中包含的一个最重要的功能是从键盘缓冲区中读取一个键盘输入,该功能的编号为0.
下面的指令从键盘缓冲区中读取一个键盘输入,并将其从缓冲区中删除:
mov ah,0
int 16h
结果:(ah)=扫描码,(al)=ASCII码
int 16h中断例程的0号功能进行如下工作:
- 检测键盘缓冲区中是否有数据
- 没有则继续做第一步
- 读取缓冲区第一个字单元中的键盘输入
- 将读取的扫描码送入ah,ASCII码送入al
- 从键盘缓冲区中删除
BIOS的int 9中断例程和int 16h中断例程是一对相互配合的程序,int 9中断例程向键盘缓冲区中写入,int 16h中断例程从键盘缓冲区中读出。
他们写入和读出的时机不同,int 9中断例程在有键按下的时候向键盘缓冲区中写入数据,int 16h在应用程序对其进行调用的时候将数据从键盘缓冲区中读出。
实例:接收键盘输入,若是r,显示字体红色;g:。。。
assume cs:code
code segment
start: mov ah,0
int 16h
cmp al, 'r'
je red
cmp al, 'g'
je green
cmp al, 'b'
je blue
red: mov ah, 00000100b
jmp short s
green: mov ah,00000010b
jmp short s
blue: mov ah,00000001b
s: mov bx, 0b800h
mov es,bx
mov bx,1
mov cx,2000
s1: and byte ptr es:[bx], 11111000b
or es:[bx], ah
add bx,2
loop s1
mov ax,4c00h
int 21h
code ends
end start
实例:输入并显示字符串
assume cs:code
display segment
db 80 dup (0)
display ends
code segment
top dw 0 ; top 记录display中下一个要插入字符的位置
start: mov ax,display
mov ds, ax
getstrs:
mov ah,0
int 16h
cmp al,20h
jb nochar
call pushchar
jmp getstrs
nochar: cmp ah,0eh ;退格键
je backspace
cmp ah,1ch ; Enter键
je enters
jmp getstrs
backspace:
cmp top,0
je empty
dec top
mov bx,top
mov al,0
mov ds:[bx],al
call showstr
empty: jmp getstrs
enters: call showstr
mov ax,4c00h
int 21h
pushchar: mov bx,top
inc top
mov ds:[bx],al
call showstr
ret
showstr:
push ax
mov ax, 0b800h
mov es,ax
mov di, 10*160+20
mov si,0
s: mov al, ds:[si]
cmp al,0
je showends
mov es:[di],al
mov al,02h
mov es:[di+1],al
add di,2
inc si
jmp short s
showends:
mov ax,11*160
cmp di,ax
je showret
mov byte ptr es:[di], ' ' ; 字符串之后全部显示‘ ’
add di,2
jmp short showends
showret: pop ax
ret
code ends
end start
应用int 13h中断例程对磁盘进行读写
- 读
(ah)=功能号,2表示读扇区
(al)=读取得扇区数
(ch)=磁道号
(cl)=扇区号
(dh)=磁头号
(dl)=驱动器号
-软驱从0开始,0:软驱A,1:软驱B
-硬盘从80h开始,80h:硬盘C, 81h:硬盘D
es:bx 指向接收此扇区读入数据得内存区
返回参数:
操作成功, (ah)=0,(al)=读入得扇区数
操作失败, (ah)=错误代码 - 写
(ah)=3
(al)=写入得扇区数
(ch)=磁道号
(cl)=扇区号
(dh)=磁头号
(dl)=驱动器号
-软驱从0开始,0:软驱A,1:软驱B
-硬盘从80h开始,80h:硬盘C, 81h:硬盘D
es:bx 指向将写入磁盘得数据
返回参数:
操作成功: (ah)=0,(al)=写入得扇区数
操作失败, (ah)=错误代码