汇编语言

环境搭建

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时

  1. (cx)=(cx)-1
  2. 判断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]


练习:

  1. 将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 
  1. 将单词的前四个字母变大写
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]


寻址方式:

  1. 直接寻址: mov ax,[2]
  2. 寄存器间接寻址: mov ax,[bx]
  3. 寄存器相对寻址: mov ax,[bx+2]
  4. 基址变址寻址: mov ax,[bx+si]
  5. 相对基址变址寻址: 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指令

  1. 除数8位,被除数16位(AX),商(al),余数(ah)
  2. 除数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 内存单元地址
      内存单元中存放的字是转移目的的偏移地址。
      mov ax,0123h 
      mov ds:[0],ax 
      jmp word ptr ds:[0]
      
      跳转到ip 0123h处
    • jmp dword ptr 内存单元地址
      (cs)=(内存单元地址+2)
      (ip)=(内存单元地址)
      mov ax,0123h 
      mov ds:[0],ax 
      mov word ptr ds:[2],0 
      jmp dword ptr ds:[0]
      
      (cs)=0, (ip)=0123h

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指令时,进行下面两步操作:

  1. (ip)=((ss)*16+(sp))
  2. (sp)=(sp)+2


    retf 指令用栈中的数据修改cs和ip的内容,从而实现远转移。
  3. (ip)=((ss)*16+(sp))
  4. (sp)=(sp)+2
  5. (cs)=((ss)*16+(sp))
  6. (sp)=(sp)+2

call

cpu执行call指令,进行两步操作:

  1. 将当前ip或者cs和ip入栈
  2. 转移(jmp)

call 不能实现短转移,除此之外,call实现转移的方法和jmp指令的原理相同。

  • call 标号
    将当前的ip压栈后,转到标号处执行指令。
    cpu操作步骤:
  1. (sp)=(sp)-2
  2. ((ss)*16+(sp))=(IP)
  3. (ip)=(ip)+16位位移
  • call far ptr 标号
    段间转移
    cpu执行此操作时的步骤:
  1. (sp)=(sp)-2
  2. (cs)入栈
  3. (sp)=(sp)-2
  4. (ip)入栈
  5. 跳到标号处
  • call 16位寄存器
    功能:
    ip入栈,跳到ip为寄存器内容处

  • 转移地址在内存中

  1. call word ptr 内存单元地址
    段内
    先push ip再跳到内存中存着的ip

  2. 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相关各位。

  1. 比较无符号数
    ZF=1 说明相等
    CF=1 说明前面小于后面
  2. 比较有符号数
    ZF=1, 两数相等
    SF=1, 前面<后面 ???
    不可以,可能溢出, 比如 (ah)=22h, (bh)=0a0h, cmp ah,bh 产生溢出
    要考虑溢出!!!
    需要同时考虑OF和SF
    1. SF=1,OF=0  前面<后面
    2. SF=1,OF=1   前面>后面
    3. SF=0,OF=1   前面<后面
    4. 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递减
  1. movsb
    以字节单位传送
    ((es)x16+(di))=((ds)x16+(si))
  • DF=0: 每次操作后si,di递增
  • DF=1: 每次操作后si,di递减
  1. movsw
    传送一个字
    一般来说movsb,movsw都和rep配合使用,格式:rep movsb
    rep的作用是根据cx的值重复执行后面的串传送指令
    改变DF的值:
  2. cld指令:DF设置为0
  3. 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号中断:

  1. 在内存0:0200处安装do0的代码,将0号中断处理程序的入口地址设置为0:0200,即0000:0000为0200,0000:0002为0
  2. 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. 取得中断类型码1;
  2. 标志寄存器入栈,TF,IF设为0
  3. CS,IP入栈
  4. (IP)=(4), (cs)=(6)
    如果TF=1,则执行一条指令后cpu就要去执行1号中断处理程序。
    debug提供了单步中断的中断处理程序,功能为显示所有寄存器的内容后等待输入命令。
    在debug中,使用t命令执行指令时,debug将TF设置为1,使得cpu工作于单步中断方式下,则在cpu执行完这条指令后便引发单步中断,执行单步中断的中断处理程序。
    总之,当TF=1时,cpu在执行完一条指令后引发单步中断,转去执行中断处理程序。执行完中断处理程序后,返回原来的位置继续。

int指令

cpu执行 int n 指令,相当于引发一个n号中断的中断过程,过程如下:

  1. 取中断类型码
  2. 标志寄存器入栈,IF=0,TF=0
  3. CS,IP入栈
  4. (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号单元:

  1. 将2送入端口70h
  2. 从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,中断过程中不需要取中断类型码。
不可屏蔽中断中断过程:

  1. 标志寄存器入栈, IF=0, TF=0
  2. CS,IP入栈
  3. (ip)=8, (cs)=10

键盘

键盘上的每一个键相当于一个开关,键盘中有一个芯片对键盘上的每一个键的开关进行扫面。按下一个键时,开关接通,该芯片就产生一个扫描码,扫描码说明了按下的键在键盘上的位置。扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60h。松开按下的键时,也产生一个扫描码,扫描码说明了松开的键在键盘上的位置。松开按键时产生的扫描码也被送入60h端口中。
按下一个键时产生的扫描码称为通码,松开一个键产生的扫描码称为断码。
扫描码长度为一个字节,通码的第七位为0,断码的第七位为1. 断码=通码+80h

BIOS提供了int 9中断例程,用于进行基本的键盘输入处理,主要工作如下:

  1. 读出60h端口中的扫描码
  2. 如果是字符键的扫描码,将该扫描码和对应的字符码(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
原文地址:https://www.cnblogs.com/FEIIEF/p/13111042.html