汇编语言

环境搭建

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

直接定址表

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号功能进行如下工作:

  1. 检测键盘缓冲区中是否有数据
  2. 没有则继续做第一步
  3. 读取缓冲区第一个字单元中的键盘输入
  4. 将读取的扫描码送入ah,ASCII码送入al
  5. 从键盘缓冲区中删除
    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中断例程对磁盘进行读写


  1. (ah)=功能号,2表示读扇区
    (al)=读取得扇区数
    (ch)=磁道号
    (cl)=扇区号
    (dh)=磁头号
    (dl)=驱动器号
    -软驱从0开始,0:软驱A,1:软驱B
    -硬盘从80h开始,80h:硬盘C, 81h:硬盘D
    es:bx 指向接收此扇区读入数据得内存区
    返回参数:
    操作成功, (ah)=0,(al)=读入得扇区数
    操作失败, (ah)=错误代码

  2. (ah)=3
    (al)=写入得扇区数
    (ch)=磁道号
    (cl)=扇区号
    (dh)=磁头号
    (dl)=驱动器号
    -软驱从0开始,0:软驱A,1:软驱B
    -硬盘从80h开始,80h:硬盘C, 81h:硬盘D
    es:bx 指向将写入磁盘得数据
    返回参数:
    操作成功: (ah)=0,(al)=写入得扇区数
    操作失败, (ah)=错误代码
posted @ 2020-06-12 23:04  feibilun  阅读(603)  评论(0编辑  收藏  举报