汇编原理自我总结(三)
第十一章 标志寄存器
在8086CPU中,是一种特殊的寄存器,叫做flag。作用如下:
- 储存相关指令的某些执行结果;
- 用来为CPU执行某些指令提供依据;
- 用来控制CPU的相关工作方式;
flag是按位起作用的,每一位都具有专门的含义,记录特定的信息。
11.1 ZF标志
ZF是flag的第6位,用来指定相关执行指令后,其结果是否为0。如果为0,则为真,设1,反之设0。1表示逻辑真,0表示逻辑假。如:
mov ax,1
sub ax,1 ;此时,ZF为真,设1
mov ax,2
sub ax,1 ;(ax) != 0 ,ZF为假,设0
在指令集中,有些指令是影响标志寄存器的,如:
add, sub,mul,div,inc,and,or ;大多是运算指令
mov ,pop, push ;不影响,大多是传送指令
11.2 PF标志
PF是flag的第2位,用来标识相关指令执行后,其结果中的bit位中的1是否为偶数。如果是偶数,pf=1,反之,若为奇数,pf = 0。如:
mov al,1
add al,10 ;al = 00001011 pf=0
mov al,1
add al,2 ;al = 00000011 pf=1
sub al,3 ;al = 00000000 pf=1
11.3 SF标志
SF是flag的第7位,用来标识相关指令执行后,其结果是否为负。若为负,sf = 1,不为负,sf = 0。但是,在计算机中,同一数值可以解释为负数或正数,这就意味着同一信息包含两种不同的结果。SF正是对运算结果的记录,它记录数据的正负。如果我们数据当作有符号数来运算,可以通过它来得知数据结果。如:
mov al,10000001B
add al,1 ;al = 10000010B, sf=1 =>-126(补码)
mov al,10000001B
add al,01111111B ;al = 0 sf=0 表示若进行无符号运算,结果为正
11.4 CF标志
CF是flag的第一位,进位标志位,一般情况下,在进行无符号数运算的时候,记录了最高有效位向更高位的进位值,或从更高位的借位值。
当两个数进行运算时,有可能当前的位数不能够保存,就将进位值记录在CF上。
mov al,98H
add al,al ;(al)=30H, cf=1 进位
add al,al ;(al)=60H, cf=0
mov al,97H
sub al,98H ;(al)=ffH, cf=1 借位
sub al,al ;(al)=0,cf = 0
11.5 OF标志
flag的第11位,溢出标志位,进行有符号数运算的时候,有时会发生溢出,如:-89+(-98) = -187 ,而8位的有符号表示范围是-128~127,不能保存完整数值,所以发生溢出而造成结果的错误。如果发生溢出,OF=1。OF是对于有符号数来说的,而CF是对于无符号数说的,两者没有任何关系。
mov al,98
add al,99 ;于无符号数,cf=0.于有符号数,of=1
mov al,0f0H
add al,80H ;cf = 1,of = 1
11.6 adc指令
带位加法指令。利用了CF中的进位值。
adc object1,object2
objiect1 = object+object2+cf
将两个较大的数据相加,如超过16位的的数据相加,可以分为低16位相加,然后将高16位和进位值相加,这样就完成了大数据的加法,由于每次相加都会将进位放在进位标志中,所以此种方法可以将任意大的数据相加。如:
;1ef000H+201000H
mov ax,001eH
mov bx,0f000H
add ax,1000H; 低16位相加 设置cf
adc bx,0020H ;高16位相加 利用cf
;1ef0001000h + 2010001ef0H
mov ax,001eH
mov bx,0f000H
mov cx,1000H
add cx,1ef0H
adc bx,1000H
adc cx,0020H
编写一个子程序,将两个128位数据进行相加:
;add128
;将两个128位数据进行相加
;参数:ds:di 指向第一个数的存储空间,8个字单元,由低到高依次存储128位数据的由低到高的各个字 ds:di指向第二个数。
add128:
push ax
push cx
push si
push di
sub ax,ax ;将cf设为0
mov cx,8
s:
mov ax,[si]
adc ax,[di]
mov [si],ax
inc si
inc si
inc di ;不能用add si,2 这样会影响cf的值
inc di
loop s
pop di
pop si
pop cx
pop ax
11.7 sbb指令
带位减法指令,设计思想和adc一致,用法也一样。
sbb object1,object2
object1 = object1-object2-cf
;003e1000H-00202000H
mov ax,1000H
mov bx,003e
sub ax,2000H
sbb bx,0020H
11.8 cmp指令
cmp是比较指令,相当于不存储结果的减法指令,但是对标志寄存器产生影响,其它指令通过标志寄存器来得知比较结果。如:
mov ax,2
cmp ax,2 ;zf=1, pf=1 , sf=0,cf=0,of=0
mov ax,8
mov ax,5 ;zf=0, pf=1, sf=0,cf=0,pf=0
比较寄存器得知结果:
mov ax,bx
ax == bx ;zf=1
ax != bx ;zf=0
ax < bx ;产生借位,cf=1
ax >= bx ;不产生借位 , cf=0
ax > bx ;不借位,结果也不为0 ;cf=0, zf=0
ax<=bx ;可能有借位,也可能为0,cf=1, zf=1
所以,可以通过标志寄存器来得知比较的结果:
无符号数的比较:
cmp ax,bx
zf = 1 => ax == bx
zf = 0 => ax!=bxcf=1 => ax < bx
cf=0 => ax >= bx
cf = 0且zf =0 =>ax>bx
cf=1或zf=1 => ax<=bx
有符号数的比较:
总结不清楚,所以直接贴图:
mov ah,al
zf = 1 => ah == al
zf = 0 => ah != al
sf = 1, of=0 => ah<al
sf = 0,of = 0 => ah>al
sf =1,of = 1 => ah>al
sf = 0, of = 1 =>ah<al
11.9 检测比较结果的条件转移指令
所有的条件转移指令都是短转移【-128~127】。
大多数条件转移指令都根据标志寄存器的某些位来决定是否修改IP,是其中受cmp指令影响的那些位,根据比较结果设置的。这些条件转移指令通常和cmp指令配合使用。
无符号数的比较与转移指令
je 等于则转移 zf=1
jne 不等于则转移 zf=0
jb 低于则转移 cf=1
jnb 不低于则转移( >=) cf=0
ja 高于则转移 cf=0且zf=0
jna 不高于则转移( <=) cf=1或zf=1
j => jump e => equal n => not a => above b => below
编程:如果al == ah,al = al+al,否则,ah = al+ah
com al,ah
je s
add al,al
jmp short ok
s:
add ah,al
ok:...
编程:统计ds:di中的8个数据中大于10H的数的个数:
assume cs:code
data segment
db 20H, 2H,30H,25H,6H,34H,23H,7H
data ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,0
mov di,0
mov cx,8
s:
cmp byte ptr [di],20H
jna next
inc ax
next:inc di
loop s
code ends
end start
编写指令计算在内存F000:0的32个字节数据中,在[32~128]的数据的个数:
assume cs:code
code segment
start:
mov ax,0f000H
mov ds,ax
mov bx,0
mov dx,0
mov cx,32
s:
mov al,[bx]
cmp al,32
jb s0
cmp al,128
ja s0
inc dx
s0: inc bx
loop s
code ends
end start
11.10 DF标志和串传送指令
DF标志是flag的第10位,用来指定串传送的方向,即将di和si递增或是递减。
串传送指令:movsb,将ds:di中的内容传送到es:si中,单位是字节,次数由cx决定。
和rep指令配合使用
在传送之前,程序需要知道方向是如何的,这样才能从DF中获得信息,决定是递增还是递减。
cld:将df置0 => inc di, inc si
std:将df置1 => dec di,dec si
assume cs:code
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,10H ;源地址:es:si data:0目的地址:ds:di data:10H
mov cx,16 ;长度
cld ;方向
rep movsb
code ends
end start
将内存f000:0段中最后16个字符复制到data段中:
assume cs:code
data segment
dw 16 dup (0)
data ends
code segment
start:
mov ax,data
mov ds,ax
mov di,000fH
mov ax,0f000H
mov es,ax
mov di,0ffffH
std
mov cx,16
rep movsb
code ends
end start
11.11 pushf 和 popf
将标志寄存器的值压栈,和将标志寄存器的值出栈。
mov ax,0
push ax
popf
mov ax,0fff0h
add ax,0010h
pushf
pop ax
and al,11000101B
and ah,00001000B
将执行add ax,0010h指令后标志寄存器的值压栈,之后出栈后就是ax的值,再进行两个逻辑与运算。
11.12 标志寄存器在Debug中的表示
实验十一
编写一个子程序,将包含任意字符,以0结尾的字符串中的小写字母转化为大写字母。
;letterc
;将以0结尾的字符串中的小写字母转化为大写字母。
;ds:si指向字符串的首地址
assume cs:code
data segment
db "Beginner's All-purpose Symbolic Instruction Code.",0
data ends
code segment
begin:
mov ax,data
mov ds,ax
mov si,0
call letterc
mov ax,4c00h
int 21h
letterc:
push ax
s:
mov al,[si]
cmp al,0
je end
cmp 97
jb s0
cmp 123
ja s0
sub al,20H
mov [si],al
s0: inc si
loop s
end:
pop ax
ret
code ends
end begin
第十二章 内中断
中断是指CPU在接收到来自外部或由内部产生的信息时,停止执行下一条指令,转而去处理这个信息。
12.1 内中断的产生
中断信息在什么情况下发生呢?8086CPU有以下4中内中断:
- 除法错误,如产生溢出
- 单步执行
- 执行into 指令
- 执行int 指令
要分别处理各种中断,就要区别各种中断。每种中断都有中断码来区分,所以中断信息中包含了终端码。中断码是一个字节,因此可以区分256中中断。
- 除法错误: 0
- 单步执行: 2
- 执行into指令: 4
- 执行int指令,指令格式为:int n,n是字节型立即数,提供给CPU的中断类型码。
12.2 中断处理程序
收到中断信息后,要对中断信息进行处理,而处理是由CPU执行中断处理程序来对信息做出反应的。中断处理程序由自己编写,一般不同的中断要编写不同的处理程序。既然要执行程序,就必须要让CS:IP指向该程序的第一条指令,就必须要知道程序的入口,即段地址:偏移地址。而中断码的设计正是用来解决此种问题。
12.3 中断向量表
中断向量是指中断处理程序的入口地址,那么中断向量表就是存放中断处理程序入口的列表。这样,就可以通过中断码来查找其相应的中断程序入口地址。但先决条件是找到中断向量表,中断向量存放在内存的固定位置。8086CPU中,它在内存0000:0000~0000:03ff的1024个内存单元中。一个表项存放一个入口地址,大小为两个字,高地址字存放段地址,低地址字存放偏移地址。
12.4 中断过程
中断过程是指取得终端码,通过中断向量表取得程序入口地址,设置CS:IP的过程,这一过程由硬件完成。由于执行完中断程序后,还要回到原来指令处执行,需要保存一些值,具体过程如下:
-
取得中断码N
-
pushf 将标志寄存器入栈,因为后面有修改
-
将标志寄存器中IF与TF置0
-
push CS
-
push IP
-
(IP) = (N*4) (CS) = (N*4+2)
最后一步完成后,CPU开始执行中断程序指令。
12.5 中断处理程序和iret指令
由于随时都可能处理中断信息,所以中断处理程序必须一直都在其内存位置,编写中断处理程序与编写子程序相差不多:
- 将用到的寄存器入栈
- 处理中断信息
- 恢复寄存器
- iret
iret指令与中断过程配合使用,先后将IP,CS,标志寄存器出栈。
12.6 除法错误中断的处理
执行下列指令:
mov ax,1000h
mov bh,01
div bh
将会产生除法错误,从而出现中断。但各个系统的中断处理程序不同。
12.7 编程处理0号中断
重新编写0号中断处理程序,功能是在屏幕中间出现“overflow”,之后返回系统。
要做的事有以下几项:
- 编写中断处理,显示“overflow”:do0;
- 将do0这段程序放在内存空间:0:200~0:2ff处(这段空间系统不会用,且256字节足够);
- 将中断向量表中对应的0号表项内容设为中断程序的入口地址:0:0200H;
12.8 安装
assume cs:code
code segment
start:
mov ax,cs ;源地址
mov es,ax
mov si,offset do0
mov ax,0
mov ds,ax
mov di,200H ;目的地址
mov cx,offset do0end-offset do0;取得处理中断代码段长度
cld
rep movsb
;设置中断向量表
mov ax,4c00h
int 21h
do0:
;显示字符串
do0end:nop
code ends
end start
12.9 do0
assume cs:code
data segment
db 'overflow!'
data ends
code segment
start:
mov ax,cs ;源地址
mov es,ax
mov si,offset do0
mov ax,0
mov ds,ax
mov di,200H ;目的地址
mov cx,offset do0end-offset do0;取得处理中断代码段长度
cld
rep movsb
;设置中断向量表
mov ax,4c00h
int 21h
do0:
;显示字符串
mov ax,data
mov ds,ax
mov si,0 ;指向字符串
mov ax,0b800h
mov es,ax
mov di,12*160+36*2 ;
mov cx,9
s: mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end:nop
code ends
end start
以上的程序看似没有问题,但是不能正常工作。原因是数据被放在data段,但是程序安装完成后,内存空间要被释放,用作他途,又很大可能会被覆盖。所以要将数据放在处理中断程序当中,如下:
assume cs:code
code segment
start:
mov ax,cs ;源地址
mov ds,ax
mov si,offset do0
mov ax,0
mov es,ax
mov di,200H ;目的地址
mov cx,offset do0end-offset do0;取得处理中断代码段长度
cld
rep movsb
;设置中断向量表
mov ax,0
mov es,ax
mov word ptr es:[0*4],0200h
mov word ptr es:[0*4+2],0
mov ax,4c00h
int 21h
do0:
jmp short do0start ;2字节
db 'overflow!'
;显示字符串
do0start:
mov ax,cs ;数据在程序段中,开头0:202H处,把cs写成了cx,我哭了
mov ds,ax
mov si,202h
mov ax,0b800h
mov es,ax
mov di,160*12+32*2
mov cx,9
s:
mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end:nop
code ends
end start
12.10 设置中断向量
mov ax,0
mov es,ax
mov word ptr es:[0*4],0200h
mov word ptr es:[0*4+2],0
12.11 单步中断
单步中断的中断码是1。当CPU检测到标志寄存器的TF = 1时,就触发了单步中断。Debug 正是利用了这一中断,其实单步中断就是为实现单步追踪程序而设计的机制。但是,单步中断程序本身也是程序指令,那中断的指令引发单步中断,之后的中断又引起新的中断,如此循环。将标志寄存器·入栈,之后再将TF置0,就解决了这个问题。所以步骤如下:
- 取得中断码1
- 标志寄存器入栈
- TF=0,IF=0
- cs,ip入栈
- (ip) =(1*4),(cs)=(1*4+2)
12.12 响应中断的特殊情况
有些情况下,即便是发生中断,也不会得到响应,例如:
mov ax,stack
mov ss,ax
mov sp,10h
其中,执行完mov ss,ax后,紧接着就执行mov sp,10h。这是因为如果发生中断,就只设置了ss,而却要将标志寄存器,cs ,ip等入栈,没有设置栈顶而导致错误。
实验十二
编写0号中断的处理程序,使其在屏幕中间显示“divide error!”;
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
cld
rep movsb
;设置中断向量表
mov ax,0
mov es,ax
mov word ptr es:[0*4],0200h
mov word ptr es:[0*4+2],0
mov ax,4c00h
int 21h
;do0
do0:
jmp short do0start
db 'divide error!'
do0start:
;function
mov ax,cs
mov ds,ax
mov si,202h
mov ax,0b800h
mov es,ax
mov di,160*12+33*2
mov cx,13
s: mov al,[si]
mov es:[di],al
inc si
add di,2
loop s
mov ax,4c00h
int 21h
do0end: nop
code ends
end start
同上一程序没有差别。