从现在开始进行第十章;
1. Call和ret 指令都是转移指令,他们都可以修改IP或同时修改CS和IP。他们经常被共同被用来实现子程序的设计。
2. Ret指令用栈中的数据,来修改IP的内容,来实现近转移。 相当于pop ip
Retf指令用栈中的数据来同时修改CS和IP,来实现远转移。 相当于pop ip pop cs
从retf指令我们也可以知道,在栈中目的IP也是在栈的低字单位,cs也是在栈中的高字。
Ret 和retf指令的使用格式都是独立使用的。相当于C语言中的“零目运算符”。
3.执行call指令产生两步操作:一 将当前的CS IP 入栈 二 转移
注意:call指令不能实现短转移,除此之外,call的原理与jmp的原理相同。
以下我们将通过给定目的指令地址的不同来讲解call的几种格式。
3. Call+标号 型
Call的近转移 (机器码中给出位移)
格式:Call 标号 用汇编来解释 :push ip jmp near ptr 标号
Call的远转移(机器码中给出转移目的地址)
格式:Call far ptr 标号 用汇编语言来解释:push cs push ip jmp far ptr 标号
4.Call+数值存在于媒介中 型
Call指令 在十六位寄存器中存放转移地址(此时是段内转移)
格式:call (16位寄存器) 汇编来解释:push ip jmp 16位寄存器
Call指令 在内存单元中存放转移地址
格式 : call word ptr 内存单元地址 push ip jmp word ptr 内存单元地址
Call dword ptr 内存单元地址 push cs push ip jmp dword ptr 内存单元地址
4. P197 198等等讲述了具有调用子程序功能的例题和通用框架,从此我们也知道了为什么从栈中取转移指令地址的这样的指令符号是ret:ret意为return 和call正好是调用和返回的一对。
5. 子程序的经典格式: 标号: 指令
Ret
6. Mul乘法指令 对于乘法我们有如下规则:
一.格式一致。只存在这两种情况
8位*8位 默认两个乘数一个在AL,另一个在8位寄存器或内存单元中
16位*16位 默认 一个在AX,另一个在16位寄存器或字型内存单元中
二.结果存在哪儿?
8位*8位 结果存于AX中 16位*16位 结果高位存于DX中低位存于AX
格式: mul reg mul 内存单元
7. 有了call 和ret我们就可以通过调用子程序进行模块化的程序设计。我们以后在写某项特殊功能的子程序时,应该养成注释的好习惯注释信息应该包括功能、参数、结果的说明,汇编语言用分号 ; 来做注释符号。
8. 为了防止寄存器冲突的出现,我们在进行子程序编写的时候经常使用栈来进行暂存寄存器(如CX)中的值。调用完子程序后再通过出栈来恢复寄存器中的值。参照P205我们以后在编写子程序的时候就按照这样的一个框架。P205是一个典型的循环程序。
9. 标志寄存器的三个作用:用来记录某些程序的结果;用来作为某程序执行的依据;用来决定CPU不同的工作模式。
8086的寄存器为16位,其存储的信息成为程序状态字(psw).
10. Zf 位于标志寄存器的第6位。它标记相关指令执行后,结果是否为0,是0 zf=1
非0 Zf=0
11.8086的指令集中有些指令是影响标志位(大多数运算指令)而有些指令是不影响标志位的(如传送指令)。
12.Flag的第2位是PF,奇偶标志位。用来标记某些指令执行后其结果的二进制各位中1的个数是否为偶数,是则pf=1 否则 pf=0
13.Flag的第7位SF,用来表明结果的符号是否为负,是则sf=1 否则sf=0。对于add指令无论进行的是有符号数的相加,还是无符号数的相加。都会影响sf。但是我们只有在进行有符号数的相加的时候,SF的数值才有意义。
14.Flag的第0位是CF,进位标志位。一般情况下,他表示在进行无符号数运算时,最高有效位向假想更高位所进位的数值或者想减时的借位值。
15.Flag的第11位OF,溢出标志位。如果溢出OF=1,不溢出OF=0。
什么是溢出:在进行符号数运算的时候,如果结果超出了机器所能表达的范围。则称此种情况为溢出。
16.Adc指令:带进位加法指令 格式:adc 操作数1,操作数2
功能:操作数1=操作数1+操作数2+CF
关于带进位加法指令加上CF这样做的意义:见P220 执行adc指令的时候加上CF值的含义,是由adc前面的指令决定的。
由此可以知道加法的原理:一 低位相加 二 高位相加再加上低位相加产生的进位CF值。
所以我们可以知道带进位加法指令adc的功能是:对任意大的数进行加法运算。
17.Sbb指令:带借位减法指令。指令格式:sbb 操作数1,操作数2
功能:操作数1-操作数2-CF值(借位值) 如sbb ax,bx (ax)=(ax)-(bx)-CF
18.Cmp是比较指令,cmp相当于减法指令,但是不保存结果。格式:cmp 草1,草2
功能:草1-草2 不保存结果,仅根据结果对标志寄存器进行设置。这样的对标志位的各位的影响,影响了是否奇偶、是否为零、是否为负、是否进位、是否溢出等各个位。
19.同add sub指令一样,CPU在执行cmp指令的时候,也包含两种含义:一是无符号数的运算,一种是有符号数的运算。对于前者,很容易得到各个标志位的值很容易根据标志位判断两个操作数之间的关系。对于后者因为存在溢出问题,如果溢出则SF的值与实际结果的正负相反。所以需要知道OF和SF两个值结合来确定实际结果的正负。详情见P223。224 。225。
20.关于条件转移指令:如果满足一定的条件,则转移修改IP,否则什么也不做。例如jcxz
所有的条件转移指令变化范围都是-128——127
除jcxz外,还有很多条件转移指令而且大多数都是检测标志位的指令。而标志位又是cmp影响的,所以,这些条件转移指令经常和cmp结合使用,就像call和ret结合使用一样。
因为cmp可以进行两种比较一是无符号一是有符号,所以,根据检测结果也有两类条件转移指令。
21.DF位是flag位的第十位,方向标志位。在串操作指令中,控制每次操作后si di的增减。
Df=0 每次操作后si di 递增 df=1 每次操作后si di 递减 df就是标志是否递减的一个量。
22.一个串传送指令:movsb 功能相当于下面几个操作:
(1)((es)*16+(di))=((ds)*16+(si))
(2) df=0 (si)=(si)+1 (di)=(di)+1
Df=1 (si)=(si)-1 (di)=(di)-1
Movsb是传送一个字节,我们还可以传送一个字 movsw 那他就类似这样的指令
Mov es:di word ptr ds:si 实际上8086并不支持这样的指令。后若df=0 si=si+2 di=di+2
23.既然是串传送,那么传送的单元就是一个字符串。而movsb movsw都是传送的一个字节或字,所以,上述两个指令都是串传送的一部分,另一部分就是rep我们常用的格式就是rep movsb用汇编来解释其功能就是: s : movsb loop s
可见rep的作用就是根据CX中的值来重复执行后面的串传送指令。
Rep就可以实现(cx)个字节(字)的传送。
24.CPU 提供了相应的指令来对DF位, cld指令:将DF置0 std指令:将DF置1
这两条指令都是单独使用的,属于算是 零目操作符。
25.我们知道push 和pop是压栈和入栈指令,目的地点是一般的寄存器。同样,向标志寄存器中压和入栈,用指令:pushf popf
Debug中对标志寄存器中各个位的表示。见P234
26关于中断:是CPU在执行当前指令后,接受来自CPU外部或内部产生的一种信息。CPU必须立即对此种信息进行处理。所以中断可以分为内部中断和外部中断。
27.对于内部中断,肯定是CPU内部发生了什么事情,而后便产生中断信息,(用来表示中断来源的一个字节我们称之为中断类型码也称中断源。所以我们可以知道可以表示有256种中断信息。)这些事情是:
(1)除法错误。例如div指令出现溢出。 中断类型码 0
(2)单步执行 中断类型码 1
(3)执行Int0指令 中断类型码4
(4)执行int指令 格式为int n 其中n位字节型立即数,是提供给CPU的中断类型码
28.CPU在收到中断信息时,应转去执行该中断信息的处理程序。所以中断信息中应该包含中断信息的处理程序的地址信息。所以CPU的设计者应该能在中断信息和处理程序的入口地址之间建立某种联系。
8086CPU用中断类型码通过中断向量表找到相应的处理程序的入口地址。
29.什么是中断向量表?是中断向量的列表。 什么又是中断向量呢?就是中断处理程序的入口地址。
中断向量表在内存中保存,其中放着256个中断源所对应的处理程序的入口地址。通过中断类型码我们就可以找到相应的处理程序入口地址。在这之前我们还有一个首要问题要解决:怎样找到中断向量表。
对于8086来说,指定中断向量表放在地址0处。从0:0 到0:03E8这1000个内存单元中。由于一个表项需要包含程序入口地址的段地址和偏移地址,所以,一个表项占2个字节。高地址存放段地址,低地址存放偏移地址。
30.中断处理程序的编写与一般子程序的编写类似,以下是一般过程:
(1)保存用到的寄存器(2)处理中断(3)恢复用到的寄存器(4)用iret指令返回。
关于iret指令,用汇编语句来解释它:pop ip pop cs popf
由此,我们看到中断的时候,先将标志寄存器入栈,再将cs ip入栈。
31.8086虽然支持256个中断,但是实际上,系统要处理的中断事件远没有256个,所以,许多中断向量表是空的。中断向量表空间是受操作系统严格保护的一段内存区域,所以其他程序不会对其使用,所以我们在编写一些比较简短的中断程序的时候可以使用其部分空白的区域。如:0:0200 到 0:0300 我们前面也是用过的。
32.P234的后面三句话,揭示了一个共同点:想某一段内存中的内容成为具有特殊功能的部分。我们需要将其的地址信息加以特殊设置。将其赋值与特殊的位置。
所以,要想某一段内存内容成为中断程序,就应该将其地址赋予中断向量表中的特定位置。
33.总之,编写中断程序需要做:编写中断程序、安装中断程序。安装包括将程序送至0:200 到0:300处和上面32条所说的地址设置在中断向量表中。
34.安装具体步骤:先说设置中断向量表,这一步容易,只是将程序的段地址和偏移地址传送到指定位置即可。再说将程序代码传送到指定内存区域,这一步我们可以用rep movsb指令。首先我们需要确定这些信息作为传送的先决条件:源地址、目的地址、传送方向、代码长度。前三者可以通过传送和cld容易得到。
那我们怎样确定一小段代码的长度呢?我们用编译器来计算它。编译器可以识别一些运算符号如: mov ax,8-4 相当于mov ax,4 所以,如果我们假设do0是中断类型码是0的中断处理程序我们就可以用指令mov cx,offset do0end – offset do0。(具体例P246)
35.下面我们编写中断处理程序:do0程序的主要作用就是显示字符串,要想显示字符串就是将字符串写入显存中。关于详细情况参照P246以后的三页。
36.关于单步中断:当CPU执行完一条指令后,检测若TF=1则产生单步中断。引发中断过程。具体发生的过程:
(一)、取得中断类型码1
(二)、标志寄存器入栈,TF、IF置零
(三)、CS、IP入栈
(四)、(ip)=(1*4) (cs)=(1*4+2)
37.当涉及到联系紧密的指令时,CPU可以不对中断响应。比如传送到ss寄存器后,即使TF=1,也不会响应中断。否则在没有继续改变SP值的情况下,一旦中断将会产生错误。
38.Int 指令时一个和普遍的中断指令。我们可以在程序中调用任何一个中断处理程序。
一般情况下,系统将一些具有特殊功能的子程序作为中断程序供其他程序调用。以后,我们称这些程序为中断例程。
39.对调用的深入理解:int引发中断后,会进入int中断例程。当前的标志寄存器,cs ip都要入栈,此时入栈的分别是调用程序的段地址和int 后一条指令的偏移地址。
40.对于bios和dos中的中断程序,很多都是有关于硬件的IO中断程序。Dos的中断其实就是操作系统提供给程序员们的子程序的资源,它有很多也是调用了bois中断例程。
我们只需要知道一点:bois是固化在rom中的,是一直存在于内存中的,所以,当用到bois例程时,只需要将对应的例程地址设置在对应的中断向量表中即可。
41.但是当dos启动后,除完成其他工作外,还需要将其提供的中断例程转入内存并且设置中断向量表。
42.Bois中断例程应用举例:int 10h是bois 提供的中断例程,其中包含了多个和屏幕输出相关的子程序。
从这里我们多说一句,一个供程序员调用的中断例程中包含了多个子程序,中断例程用传递来的参数来确定调用哪个子程序。对于bois 和dos中断例程都是用ah来传递内部子程序中的编号。
例如ah=2为用来设置光标的位置,ah=9h为在光标位置显示字符。
43.关于通过调用中断例程来实现内存空间中的彩色显示缓冲区,查看P260
44.Dos中断例程应用:int 21h就是dos提供的调用子程序。同样也是通过设置ah值来调用不同的子程序。