《汇编语言》学习笔记(第一章、第二章)
《汇编语言》学习笔记(第三章、第四章)
《汇编语言》学习笔记(第五章、第六章)
《汇编语言》学习笔记(第七章、第八章)
《汇编语言》学习笔记(第九章、第十章)
《汇编语言》学习笔记(第十一章、第十二章)
《汇编语言》学习笔记(第十三章、第十四章)
《汇编语言》学习笔记(第十五章、第十六章)
文章目录
第11章、标志寄存器
CPU内部的寄存器中,有一种特殊的寄存器(对于不同的处理机,个数和结构都可能不同)具有以下3种作用。
(1)用来存储相关指令的某些执行结果;
(2)用来为CPU执行相关指令提供行为依据;
(3)用来控制CPU的相关工作方式。
这种特殊的寄存器在8086CPU中,被称为标志寄存器(flag)。
8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW-Program Status Word)
flag寄存器是按位起作用的,它的每一位都有专门的含义,记录特定的信息。
在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and等,它们大都是运算指令(进行逻辑或算术运算);有的指令的执行对标志寄存器没有影响,比如,mov、push、pop等,它们大都是传送指令
11.1 标志寄存器
1、零标志位 (ZF)
零标志位(Zero Flag)。它记录相关指令执行后,其结果是否为0。
如果结果为0,那么zf = 1(表示结果是0);如果结果不为0,那么zf = 0。
mov ax, 1
sub ax, 1 ;执行后,结果为0,则zf = 1
mov ax, 2
sub ax, 1 ;执行后,结果不为0,则zf = 0
2、奇偶标志位 (PF)
奇偶标志位(Parity Flag)。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。
如果1的个数为偶数,pf = 1,如果为奇数,那么pf = 0。
mov al, 1
add al, 10 ;执行后,结果为00001011B,其中有3(奇数)个1,则pf = 0;
mov al, 1
or al, 2 ;执行后,结果为00000011B,其中有2(偶数)个1,则pf = 1;
3、符号标志位(SF)
符号标志位(Symbol Flag)。它记录相关指令执行后,其结果是否为负。
如果结果为负,sf = 1;如果非负,sf = 0。
计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看成是无符号数。
00000001B
,可以看作为无符号数1,或有符号数+1;
10000001B
,可以看作为无符号数129,也可以看作有符号数-127。
对于同一个二进制数据,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算
CPU在执行add等指令的时候,就包含了两种含义:可以将add指令进行的运算当作无符号数的运算,也可以将add指令进行的运算当作有符号数的运算
SF标志,就是CPU对有符号数运算结果的一种记录,它记录数据的正负。在我们将数据当作有符号数来运算的时候,可以通过它来得知结果的正负。如果我们将数据当作无符号数来运算,SF的值则没有意义,虽然相关的指令影响了它的值
mov al, 10000001B
add al, 1 ;执行后,结果为10000010B,sf = 1,表示:如果指令进行的是有符号数运算,那么结果为负;
12
mov al, 10000001B
add al, 01111111B ;执行后,结果为0,sf = 0,表示:如果指令进行的是有符号数运算,那么结果为
正
4、进位标志位(CF)
进位标志位(Carry Flag)。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值
mov al, 98H
add al, al ;执行后:(al)=30H,CF=1,CF记录了从最高有效位向更高位的进位值
add al, al ;执行后:(al)=60H,CF=0,CF记录了从最高有效位向更高位的进位值
mov al, 97H
sub al, 98H ;执行后: (al)=FFH, CF=1, CF记录了向更高位的借位值
sub al, al ;执行后: (al)=0, CF=0,CF记录了向更高位的借位值
5、溢出标志位(OF)
溢出标志位(Overflow Flag)。一般情况下,OF记录了有符号数运算的结果是否发生了溢出。
如果发生溢出,OF = 1;如果没有,OF = 0。
CF和OF的区别:CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位
CPU在执行add等指令的时候,就包含了两种含义:无符号数运算和有符号数运算。
- 对于无符号数运算,CPU用CF位来记录是否产生了进位;
- 对于有符号数运算,CPU用OF位来记录是否产生了溢出,当然,还要用SF位来记录结果的符号。
mov al, 98
add al, 99 ;执行后将产生溢出。因为进行的"有符号数"运算是:(al)=(al)+ 99 = 98 + 99=197 = C5H 为-59的补码
;而结果197超出了机器所能表示的8位有符号数的范围:-128-127。
;add 指令执行后:无符号运算没有进位CF=0,有符号运算溢出OF=1
;当取出的数据C5H按无符号解析C5H = 197, 当按有符号解析通过SP得知数据为负,即C5H为-59补码存储,
mov al,0F0H ;F0H,为有符号数-16的补码
add al,088H ;88H,为有符号数-120的补码
;执行后,将产生溢出。因为add al, 088H进行的有符号数运算结果是:(al)= -136
;而结果-136超出了机器所能表示的8位有符号数的范围:-128-127。
;add 指令执行后:无符号运算有进位CF=1,有符号运算溢出OF=1
有符号和无符号只是数学上的意义的不同,对于计算机底层没有任何区别,都是二进制数。不管是有符号数还是无符号数,在底层中统一按照二进制数运算,算完了之后才给它们按照不同的映射转换成有符号数或无符号数。
对于两个7位的二进制数(从0开始算),如果第7位向更高位进位,则发生了无符号数溢出。如果第6位向第7位进位,则发生了有符号数溢出。
对于两个十进制数运算,超过了表达范围就是溢出。
11.6 adc指令
adc是带进位加法指令,它利用了CF位上记录的进位值。
指令格式:adc 操作对象1, 操作对象2
功能:操作对象1 = 操作对象1 + 操作对象2 + CF
mov ax, 2
mov bx, 1
sub bx, ax ;无符号运算借位CF=1,有符号运算OF = 0
adc ax, 1 ;执行后,(ax)= 4。adc执行时,相当于计算:(ax)+1+CF = 2+1+1 = 4。
;计算1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中。
;将计算分两步进行,先将低16位相加,然后将高16位和进位值相加。
mov ax, 001EH
mov bx, 0F000H
add bx, 1000H
adc ax, 0020H
11.7 sbb指令
sbb是带借位减法指令,它利用了CF位上记录的借位值。
指令格式:sbb 操作对象1, 操作对象2
功能:操作对象1 = 操作对象1 - 操作对象2 - CF
;计算 003E1000H - 00202000H,结果放在ax,bx中,程序如下:
mov bx, 1000H
mov ax, 003EH
sub bx, 2000H
sbb ax, 0020H
11.8 cmp指令
cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。cmp指令执行后,将对标志寄存器产生影响。
其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。
cmp指令格式:cmp 操作对象1,操作对象2
例如:
指令cmp ax, ax
,做(ax)-(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。
指令执行后:zf=1,pf=1,sf=0,cf=0,of=0。
CPU在执行cmp指令的时候,也包含两种含义:进行无符号数运算和进行有符号数运算。
cmp ax, bx | 无符号比较时 |
---|---|
(ax) = (bx) | zf = 1 |
(ax) ≠ (bx) | zf = 0 |
(ax) < (bx) | cf = 1 |
(ax) ≥ (bx) | cf = 0 |
(ax) > (bx) | cf = 0 且 zf = 0 |
(ax) ≤ (bx) | cf = 1 且 zf = 1 |
上面的表格可以正推也可以逆推
如果用cmp来进行有符号数比较时
SF只能记录实际结果的正负,发生溢出的时候,实际结果的正负不能说明逻辑上真正结果的正负。
但是逻辑上的结果的正负,才是cmp指令所求的真正结果,所以我们在考察SF的同时考察OF,就可以得知逻辑上真正结果的正负,同时就知道比较的结果。
mov ah, 08AH ; -Not(8A-1) = -118 即当成有符号数时为-118
mov bh, 070H ; 有符号数时最高位为0为正数, 70H = 112
cmp ah, bh ;(ah)-(bh)实际得到的结果是1AH
; 在逻辑上,运算所应该得到的结果是:(-118)- 112 = -230
; sf记录实际结果的正负,所以sf=0
12345
cmp ah, bh
(1)如果sf=1,而of=0 。 of=0说明没有溢出,逻辑上真正结果的正负=实际结果的正负; sf=1,实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)(2)如果sf=1,而of=1: of=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负; sf=1,实际结果为负。
实际结果为负,而又有溢出,这说明是由于溢出导致了实际结果为负,,如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正。 这样,sf=1,of=1,说明了(ah)>(bh)。(3)如果sf=0,而of=1。of=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;sf=0,实际结果非负。而of=1说明有溢出,则结果非0,所以,实际结果为正。
实际结果为正,而又有溢出,这说明是由于溢出导致了实际结果非负,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负。这样,sf=0,of=1,说明了(ah)<(bh)。
(4)如果sf=0,而of=0
of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;sf=0,实际结果非负,所以逻辑上真正的结果非负,所以(ah)≥(bh)。
11.9 检测比较结果的条件转移指令
可以根据某种条件,决定是否修改IP的指令
jcxz它可以检测cx中的数值,如果(cx)=0,就修改IP,否则什么也不做。
所有条件转移指令的转移位移都是[-128,127]。
这些条件转移指令通常和cmp配合使用,它们检测标志寄存器的相关标志位,而这些标志位就是被cmp影响的那些,根据检测的结果来决定是否修改IP。
因为cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种:
- 根据无符号数的比较结果进行转移的条件转移指令(它们检测zf、cf的值)
- 根据有符号数的比较结果进行转移的条件转移指令(它们检测sf、of和zf的值)。
下面是根据无符号数的比较结果进行转移的条件转移指令(它们检测zf、cf的值)
指令 | 含义 | 检测的相关标志位 |
---|---|---|
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,b:below,a:above,n:not
这些指令检测的标志位都是cmp指令进行无符号数比较的时候,记录比较结果的标志位。如果在这些指令前面使用了cmp,实际上就是间接地检测cmp的比较结果。
比如je指令,虽然它的逻辑含义是“相等则转移”,但它进行的操作是zf=1是则转移。“相等则转移”这种逻辑含义,是通过和cmp指令配合使用来体现的,因为是cmp指令为“zf=1”赋予了“两数相等”的含义。
至于究竟在je之前使不使用cmp指令,在于我们的安排。je 检测的是zf位置,不管je前面是什么指令,只要CPU执行je指令时,zf=1,那么就会发生转移。
data segment
db 8,11,8,1,8,5,63,38
data ends
;编程,统计data段中数值为8的字节的个数,用ax保存统计结果。
mov ax, data
mov ds, ax
mov bx, 0 ;ds:bx指向第一个字节
mov ax, 0 ;初始化累加器mov cx,8
s:
cmp byte ptr [bx], 8 ;和8进行比较
jne next ;如果不相等转到next,继续循环
inc ax ;如果相等就将计数值加1
next:
inc bx
loop s ;程序执行后:(ax)=3
11.10 DF标志和串传送指令
方向标志位。在串处理指令中,控制每次操作后si、di的增减。
- df = 0每次操作后si、di递增;
- df = 1每次操作后si、di递减。
格式:movsb
功能:将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将si和di递增或递减
格式:movsw
功能:将ds:si指向的内存字单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2。
格式:rep movsb
movsb和movsw进行的是串传送操作中的一个步骤,一般来说,movsb和movsw都和rep配合使用,
功能:rep的作用是根据cx的值,重复执行后面的串传送指令
8086CPU提供下面两条指令对df位进行设置。
cld
指令:将标志寄存器的df位置0std
指令:将标志寄存器的df位置1
;将data段中的第一个字符串复制到它后面的空间中。
data segment
db 'Welcome to masm!'
db 16 dup (0)
data ends
mov ax, data
mov ds, ax
mov si, 0 ;ds:si 指向data:0
mov es, ax
mov di, 16 ;es:di指向data:0010
mov cx, 16 ;(cx)=16,rep循环16次
cld ;设置df=0,正向传送
rep movsb
11.11 pushf和popf
pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中
pushf和popf,为直接访问标志寄存器提供了一种方法。
11.12 标志寄存器在Debug中的表示
第12章、内中断
任何一个通用的CPU,都具备一种能力,可以在执行完当前正在执行的指令之后,检测到从CPU外部发送过来的或内部产生的一种特殊信息,并且可以立即对所接收到的信息进行处理。这种特殊的信息,我们可以称其为:中断信息。
中断的意思是指,CPU不再接着(刚执行完的指令)向下执行,而是转去处理这个特殊信息。
中断信息可以来自CPU的内部和外部(内中断,外中断)
12.1 内中断的产生
内中断:当CPU的内部有需要处理的事情发生的时候,将产生中断信息,引发中断过程。这种中断信息来自CPU的内部
8086CPU的内中断(下面四种情况将产生中断信息)
- 除法错误,比如,执行div指令产生的除法溢出;
- 单步执行;
- 执行 into指令;
- 执行 int指令。
中断信息中包含中断类型码,中断类型码为一个字节型数据,可以表示256种中断信息的来源(中断源)
上述的4种中断源,在8086CPU中的中断类型码如下。
- 除法错误:0
- 单步执行:1
- 执行into指令:4
- 执行int指令,该指令的格式为int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。
12.2 中断处理程序
用来处理中断信息的程序被称为中断处理程序。
根据CPU的设计,中断类型码的作用就是用来定位中断处理程序。比如CPU根据中断类型码4,就可以找到4号中断的处理程序
12.3 中断向量表
中断向量就是中断处理程序的入口地址。中断向量表就是中断处理程序入口地址的列表
CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址
12.4 中断过程
中断过程的主要任务就是用中断类型码在中断向量表中找到中断处理程序的入口地址,设置CS和IP。这个工作是由CPU的硬件自动完成的(程序员无法改变这个过程中所要做的工作)。
下面是8086CPU在收到中断信息后,所引发的中断过程。简要描述如下:
- 从中断信息中取得中断类型码N;
- pushf(标志寄存器的值入栈,因为在中断过程中要修改标志寄存器的值,所以将其保存在栈中)
- TF=0,IF=0 (设置标志寄存器的第8位TF和第9位IF的值为0)
- TF设置为0的原因:CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。为了避免CPU在执行中断处理程序时发生单步中断,在进入中断处理程序之前将TF设置为0。
- IF设置为0的原因:当CPU检测到可屏蔽中断信息时,如果IF=1,则CPU在执行完当前指令后响应中断,引发中断过程;如果IF=0,则不响应可屏蔽中断。为了在进入中断处理程序后,禁止其他的可屏蔽中断,在进入中断处理程序之前将IF设置为0。
- push CS , push IP(CS、IP的内容入栈)
- (IP)=(N * 4),(CS)=(N * 4 + 2)
硬件在完成中断过程后,CS:IP将指向中断处理程序的入口,CPU开始执行中断处理程序。
12.5 中断处理程序和iret指令
CPU随时都可能执行中断处理程序,中断处理程序必须一直存储在内存某段空间之中
而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。
中断处理程序的常规编写步骤:
- 保存用到的寄存器(中断过程中保存的是CS、IP、Flag寄存器,中断过程是由CPU硬件自动完成的。而中断处理程序是程序员编写的,这里保存的寄存器是中断处理程序中可能会用到的程序)
- 处理中断;
- 恢复用到的寄存器;
- 用
iret
指令返回。(恢复中断过程中保存的CS、IP、flag寄存器)
iret 指令描述为:pop IP
pop CS
popf
iret指令执行后,CPU回到执行中断处理程序前的执行点继续执行程序
12.6 除法错误中断的处理
mov ax, 1000h
mov bh, 1
div bh ;除法溢出错误
1、当CPU执行div bh时,发生了除法溢出错误,产生0号中断信息,从而引发中断过程,
2、CPU执行0号中断处理程序
3、系统中的0号中断处理程序的功能:显示提示信息“Divide overflow”后,返回到操作系统中。
编程实验
编程:编写0号中断处理程序do0,当发生除法溢出时,在屏幕中间显示“overflow!”,返回DOS。
1、0000:0200至0000:02FF的256个字节的空间所对应的中断向量表项都是空的,可以将中断处理程序do0传送到内存0000:0200处。
2、中断处理程序do0放到0000:0200
,再将其地址登记在中断向量表对应表项
- 0号表项的地址
0:0
。0:0
字单元存放偏移地址,0:2
字单元存放段地址 - 将do0的段地址0存放在
0000:0002
字单元中,将偏移地址200H存放在0000:0000
字单元
assume cs:code
code segment
start:
mov ax, cs
mov ds, ax
mov si, offset do0 ;设置ds:si指向源地址
mov ax, 0
mov es, ax
mov di, 200h ;设置es:di指向目的地址0000:0200
mov cx, offset do0end - offset do0 ;设置cx为传输长度 编译时给出do0部分代码长度
cld ;设置传输方向为正
rep movsb ;将do0的代码送入0:200处
mov ax, 0 ;设置中断向量表
mov es, ax
mov word ptr es:[0*4], 200h
mov word ptr es:[0*4+2], 0
mov ax,4c00h
int 21h
;do0程序的主要任务是显示字符串
do0: jmp short do0 start
db "overflow!"
do0start:
mov ax, cs
mov ds, ax
mov si, 202h ;设置ds:si指向字符串
mov ax, 0b800h
mov es, ax
mov di, 12*160+36*2 ;设置es:di指向显存空间的中间位置
mov cx, 9 ;设置cx为字符串长度
s: mov al, [si]
mov es:[di], al
inc si
add di, 1
mov al, 02h ;设置颜色
mov es:[di], al
add di, 1
loop s
mov ax, 4c00h
int 21h
do0end: nop
code ends
end start
12.11 单步中断
CPU在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程。单步中断的中断类型码为1
Debug是如何利用CPU所提供的单步中断的功能进行调试?如使用t命令查看寄存器状态
Debug提供了单步中断的中断处理程序,功能为显示所有寄存器中的内容后等待输入命令
在使用t命令执行指令时,Debug将TF设置为1,在CPU执行完这条指令后就引发单步中断,执行单步中断的中断处理程序,所有寄存器中的内容被显示在屏幕上,并且等待输入命令。
在进入中断处理程序之前,设置TF=0。从而避免CPU在执行中断处理程序的时候发生单步中断
12.12 响应单步中断的特殊情况
在执行完向ss寄存器传送数据的指令后,即便是发生中断,CPU也不会响应。主要原因是,ss:sp 联合指向栈顶,而对它们的设置应该连续完成。如果在执行完设置ss的指令后,CPU响应中断,引发中断过程,要在栈中压入标志寄存器、CS和IP的值。而ss改变,sp并未改变,ss:sp指向的不是正确的栈顶,将引起错误。所以CPU在执行完设置ss的指令后,不响应中断。我们应该利用这个特性,将设置ss和sp的指令连续存放,使得设置sp的指令紧接着设置ss的指令执行,而在此之间,CPU不会引发中断过程。