如果想要某一位变为0,利用0与其进行与运算
如果想要某一位变为1,利用1与其进行或运算
1111 and 1011 => 1011
0000 or 0100 => 0100
- 将大写+20h可转换为小写, 将小写-20h可转换为大写
- 利用and或or运算,更改第五位(索引从0开始), 要获得大写改为0, 要获得小写则改为1
- [bx+idata]
- 在8086CPU中,能够用于偏移地址的寄存器只有 bx, si, di, bp
- 在表示偏移地址的组合中,只有bx和si, bx和di, bp和si, bp和di这四种组合
- 如果使用了bp而又没有显式的给出段地址,那么段地址就是ss, 如
mov ax, [bp] <=> mov ax, ss:[bp]
mov ax, [bp+idata] <=> mov ax, ss:[bp+idata]
- 直接寻址
- 寄存器间接寻址
- 寄存器相对寻址
- 基址变址寻址
- 相对基址变址寻址
- 如果使用到具体寄存器,则操作数据的大小等同于寄存器
- 没有寄存器的情况下可使用X ptr表示,X可为word,byte
- 其他,如push操作数据的大小只能是word
mov word ptr ds:[0], 1
mov byte ptr ds:[0], 1
; div reg / div [...]
div ax
div byte ptr ds:[0]
div是一个除法指令,除法指令执行我们需要知道的信息有
- 除数的大小(8/16位)
- 被除数的最大值
- 被除数在哪
- 商被保存在哪
- 余数被保存在哪
- 被除数比除数大一倍, 若除数是8位,被除数则为16位
除数的大小 | 被除数 | 商 | 余数 |
---|---|---|---|
8位 | AX | AL | AH |
16位 | DX(高),AX(低) | AX | DX |
使用div计算的时候需要注意,不能让商溢出, 8位除数商最大值为255, 16位除数商最大值65535 对应的被除数最大值为65279(FEFF 8位), 4294901759(FFFE FFFF 16位)
程序示例
除数为8位: 8_div_8.asm
除数为16位: 8_div_16.asm
div byte ptr [0]
(al) = (ax) / ((ds)*16 + 0)的商
(ah) = (ax) / ((ds)*16 + 0)的余数
div word ptr [0]
(ax) = (ds)*10000h + (ax) / ((ds) * 16 + 0)的商
(dx) = (ds)*10000h + (ax) / ((ds) * 16 + 0)的余数
db: 定义字节数据, 每个占一个字节
dw: 定义字数据, 每个占两个字节
dd: 定义双字数据, 每个占四个字节
配合db,dw,dd使用,定义连续n个相同的数据
db 重复次数 dup (重复的字节型数据)
dw 重复次数 dup (重复的字型数据)
dd 重复次数 dup (重复的双字型数据)
- 只要是段内转移,并不包含真实地址,只是通过偏移量跳转
- 所有的有条件转移指令都是短转移,对IP的修改范围-128~127
- 所有的循环指令都是短转移, 对IP的修改范围为-128~127
- 位移距离使用补码表示
- jmp 无条件跳转
- jcxz cx为零则跳转
- loop cx不等于0则跳转
- je 等于则跳转 (zf=1)
- jne 不等于则跳转 (zf=0)
- jb 低于则跳转 (cf=1)
- jnb 不低于则跳转 (cf=0)
- ja 高于则跳转 (cf=0且zf=0)
- jna 不高于则跳转 (cf=1或zf=1)
## 短转移
不包含目的地地址, 只是获得转移的位移,只修改IP, 范围-128~127
可以修改IP或同时修改CS和IP的指令统称为转移指令 8086CPU的转移指令分为以下几类
- 无条件转移(如jmp)
- 条件转移
- 循环 (loop)
- 过程
- 中断
offset的作用是获取标号的偏移地址
start: mov ax, offset s ; 获取标号s的偏移地址,送入ax
end: mov ax, offset end ; 获取标号end的偏移地址,送入ax
- 从CS:IP指向的单元读取指令到指令缓冲器
- IP = IP+指令的长度
- 执行指令, 回到第一步重复这个过程
修改CS:IP或IP使程序跳转都某处开始执行代码
位移 = 标号位置 - jmp指令的下一条指令的位置
; 几条转移指令
jmp 标号
jmp short 标号 ; 8位位移 (范围-128~127)
jmp near ptr 标号 ; 此处的位移是16位 (范围-32768~32767)
jmp far ptr 标号 ; 实现段间转移, (不存在转移范围只能是 -32768~32767的问题)
jmp 寄存器 ; IP = 寄存器的值
jmp word ptr 内存单元地址 ; 段内转移
jmp dword ptr 内存单元地址 ; 段间转移, 高位存储段地址, 低位存储偏移地址
jcxz 标号 ; jmp if cx is zero的缩写(我猜),用C表示就是 if (0==cx) jmp short 标号
在内存地址中B8000~BFFFF,共32kib的空间为80x25(列x行)彩色字符模式的显示缓冲区,在这里写入数据将会显示出来
每个字符占2个字节,分别是字符的ASCII码(低位), 字符的属性(高位)
字符的属性有: 前景色,背景色,闪烁,高亮, (闪烁的效果必须在DOS全屏的方式才能看到?)
闪烁 | 背景 | 高亮 | 前景 |
---|---|---|---|
BL | R G B | I | R G B |
7 | 6 5 4 | 3 | 2 1 0 |
- ret => pop ip
- retf => pop ip, pop cs
- call不能实现短转移
- 子程序中使用了寄存器,可能主程序也使用了,从而造成寄存器冲突
ret指令利用栈实现修改IP的值,从而实现近转移
retf利用栈实现修改CS和IP的值,从而实现远转移
; 执行ret的时候
(ip) = ((ss)*16+ (sp))
(sp) = (sp)+2
; 相当于汇编指令
pop ip
; 执行retf的时候
(ip) = ((ss)*16 + (sp))
(sp) = (sp)+2
(cs) = ((ss)*16 + (sp))
(sp) = (sp)+2
; 相当于汇编指令
pop ip
pop cs
执行call会将下一条指令的IP或CS:IP入栈,然后跳转
需要关注call后的参数,会导致IP或CS:IP入栈
- call 标号
- callfar ptr 标号
- call 16位寄存器
- call word ptr 内存单元地址
- call dword ptr 内存单元地址
- 将call指令的下一条指令压栈
- 程序跳转到标号处执行
用汇编解释
push ip
jmp near ptr 标号
- 将call指令的下一条指令的CS压栈
- 将call指令的下一条指令的IP压栈
- 跳转到标号处执行
用汇编解释
push cs
push ip
jmp far ptr 标号
和除法指令相同,乘法指令需要用到ax和dx,同时2个数需要同时是8位或16位
若是8位,则一个数存放在al,另一个数存放在8位寄存器或内存单元中, 结果存于ax
若是16位,则一个数存放在ax,另一个数存放在16位寄存器或内存单元中,结果存于ax(低位)和dx(高位)中
; 格式如下
; mul 寄存器
; mul 内存单元
mul byte ptr ds:[0]
mul word ptr ds:[0]
mul bl
mul bx
本章讲了关于标志寄存器的相关内容
标志寄存器位置
|15|14|13|12|11|10|09|08|07|06|05|04|03|02|01|00|
| | | | |of|df|if|tf|sf|zf| |af| |pf| |cf|
debug中标志位的值
|flag| =1 | =0 |
| of | OV | NV |
| of | NG | PL |
| of | ZR | NZ |
| of | PE | PO |
| of | CY | NC |
| of | DN | UP |
- 影响标志寄存器的大多是运算指令,如add,sub,div
zf是零标志位,当操作的结果为0时,zf位的值为1
在8086CPU中,有的指令会影响标志寄存器,如add,sub,mul,div,inc,or,and
有的指令对标志寄存器没有影响,如mov,push,pop
pf是奇偶标志位,它记录了相关指令执行后,如果其而2进制值中的1的个数为偶数,则pf=1
sf是符号标志位,它记录了相关指令执行后,如果其结果为负数,则sf=1
cf是进位标志位,当进行无符号运算过程中产生了进位或者借位,则cf=1
在进行有符号运算产生溢出,of=1
df是方向标志位,可在串处理指令中操作di,si的增减
; 相关指令参考
cld ; df=0,正向传送
std ; df=1,逆向传送
rep movsb ;每次传送byte, di,si步长=1, 用汇编描述就是 => 1.mov ds:[si], byte ptr es:[di] 2. inc si 3. inc di
; rep movsb等价于 (描述用语)
s:
mov [si], byte ptr [di]
inc di
inc si
loop s
rep movsw ;每次传送word, di,si步长=2, 用汇编描述就是 => 1.mov ds:[si], byte ptr es:[di] 2. add si,2 3. add di,2
; rep movsw等价于 (描述用语)
s:
mov [si], word ptr [di]
add si, 2
add di, 2
loop s
加法指令,和add不同的是会加上cf的值, adc ax, bx => ax = ax + bx + cf
减法指令,和sub不同的是会减去cf的值, sbb ax, bx => ax = ax - bx - cf
cmp指令通过减法运算(不保存结果),影响标志寄存器的值,通过标志寄存器的值可得出相关对象的关系
如cmp ax, bx (无符号比较)
- if ax=bx, zf=1
- if ax!=bx, zf=0
- if ax<bx, cf=1
- if ax<=bx, cf=1 || zf=1
- if ax>=bx, cf=0
- if ax>bx, cf=0 && zf=0
如cmp ax, bx (有符号比较)
- if ax=bx, zf=1
- if ax>bx, sf=1 && of=1
- if ax<bx, (sf=1 && of=0) || (sf=0 && of=1)
- if ax>=bx, sf=0 && of=0 关于上面的详细证明查看书本的p222~p225
pushf和popf是针对标志寄存器的入栈和出栈操作
; pushf和popf让访问标志寄存器中的数据提供了可能
pushf
pop ax ; 将标志寄存器的值送入ax
- 什么是中断
- 什么是中断向量表
- 中断过程和iret指令
- 中断的4种情况
- 单步中断
- 响应中断的特殊情况
CPU在执行程序的过程中,接收到的一种特殊请求,会暂停当前的操作,转而处理该特殊请求,完成后再继续执行,我们称之为中断
CPU接收的中断信息必须包含一个字节的中断类型码,
- 除法错误(0)
- 单步执行(1)
- 执行into指令(4)
- 执行int指令 (int n)
中断向量表即是记录了中断处理程序地址的列表,其位于内存地址0:0~0:3FF中
一个表项的大小是4个字节,高位存储短地址,低位存储偏移地址
CPU通过中断类型码找到中断向量,并设置CS和IP,整个过程便是中断过程,由CPU自动完成
中断过程:
- 从中断信息获取中断类型码
- 标志寄存器入栈
- 设置标志寄存器TF,IF = 0
- CS入栈
- IP入栈
- CS:IP指向中断处理程序, IP=中断类型码4, CS=中断类型码4+2
用汇编描述就是
- 获取类型码
- pushf
- TF=0, IF=0
- push cs
- push ip
- ip = 中断类型码4, cs=中断类型码4+2
从某种角度上看, 中断和子程序的整个过程都是类似的
iret和ret比较像,仅仅多了个popf,用汇编描述:
pop ip
pop cs
popf
当CPU执行完一条指令后发现TF=1,则产生单步中断(类型码=1)
也因为如此,所以中断过程需要将设置TF=0, 防止CPU无限执行1号中断操作
在一般情况下,CPU在执行完一条指令,如果发生中断,会马上执行中断
但在有些情况下,即便是发生了中断,CPU也不响应,我们可以测试下面的代码
mov ax, 1
mov ss, ax
int 21h ; cpu不响应
int 21h ; cpu不响应
mov ax, 4c00h ; 当执行mov ss, ax后这条指令就被接着执行
int 21h
修改ss不响应中断的原因是,当修改ss的时候,如果处理中断,那么需要将标志寄存器,cs,ip入栈,这样造成了指向了错误的栈顶位置,导致错误
因此我们需要将对ss和sp的修改放在一起连续执行
int指令就是一个用来触发任意中断号的一个指令, 如int 21h就是触发21h号中断
要点:注意使用bx保留偏移位移,如何理解转移位移
add_1(需要跳转到的地址) - add_2(下一指令的地址) = add_3 (转移位移)
而在中断过程中已经将下一指令地址压栈(位置在sp+2)
因此我们只要将 (add_3+add_2)即可得到add_1的地址,将此地址修改到ss:[sp+2] (栈中保存IP的位置)
然后在中断例程执行iret后,程序便跳转到了add_1的地址处
cpu可以直接读取3个地方的数据
- cpu内部的寄存器
- 内存单元
- 端口(此端口非操作系统上的端口)
8086CPU可定位端口的范围为0~65535
针对端口的指令有2个, in和out
; 访问内存
mov ax, ds:[8]
; 1. cpu通过地址线将地址信息8发出
; 2. cpu通过控制线发出内存读命令,选中存储器,并通知要从中读取数据
; 3. 存储器将8号单元的数据通过数据线送入cpu
; 访问端口
in al, 60h
; 1. cpu通过地址线将地址信息60h发出
; 2. cpu通过控制线发出端口读命令,选中端口所在的芯片,并通知要从中读取数据
; 3. 端口所在的芯片将数据送入cpu
in和out只能使用ax和al进行对端口的读写, 8位时用al,16位时用ax
; 对0~255端口进行读写时
in al, 20h ; 从20h端口读一个字节
out 20h, al ; 往20h端口写入一个字节
; 对256~65535端口进行读写时, 端口号放在dx
mov dx, 300h ; 读取300h端口
in al, dx ; 从300h端口读一个字节数据
out dx, al ; 从300h端口吸入一个字节
shl和shr是逻辑移位指令,它们的功能是将数据左移或右移,用0填充空位,最后移出的移位存入CF
中
; shl 寄存器, 移动位数
mov al, 0ffh ; al= 1111 1111
shl al, 1 ; 左移移位, al = 1111 1110
mov cl, 2 ; 当移位大于1时,需要通过cl保存移动位数
shl al, cl ; 左移2位, al = 1111 1000
; shr 寄存器, 移动位数
mov al, 0ffh ; al= 1111 1111
shr al, 1 ; 右移移位, al = 0111 1111
mov cl, 2 ; 当移位大于1时,需要通过cl保存移动位数
shr al, cl ; 右移2位, al = 0001 1111
目前我们只要知道CMOS RAM存储着系统的时间及系统信息即可
我们可以通过对地址端口(70h)和数据端口(71h)的访问获取到系统时间
而获取到的数据是BCD码, BCD码就是十进制数,那么我们将BCD码+30h即可得到改数字的ASCII码
CMOS RAM中的数据存放地址
0:秒 2:分 4:时 7:日 8:月 9:年
- 可屏蔽中断
- 不可屏蔽中断
可屏蔽中断和不可屏蔽中断都属于外部中断,是由外部中断源引起的;但它们也有区别:可屏蔽中断是通过CPU的INTR引脚引入,当
中断标志IF=1时允许中断,当IF=0时禁止中断,不可屏蔽中断是由NMI引脚引入,不受IF标志的影响。
不可屏蔽中断源一旦提出请求,CPU必须无条件响应,而对可屏蔽中断源的请求,CPU可以响应,也可以不响应。
CPU一般设置两根中断请求输入线:可屏蔽中断请求INTR(Interrupt Require)和不可屏蔽中断请求NMI(NonMaskable Interrupt)。
对于可屏蔽中断,除了受本身的屏蔽位控制外,还都要受一个总的控制,即CPU标志寄存器中的中断允许标志位IF(Iinterrupt Flag)的控制,
IF位为1,可以得到CPU的响应,否则,得不到响应。IF位可以由用户控制,指令STI或Turbo c的Enable()函数,将IF位置1(开中断),
指令CLI或Turbo_c 的Disable()函数,将IF位清0(关中断)。
典型的非屏蔽中断源的例子是电源掉电,一旦出现,必须立即无条件地响应,否则进行其他任何工作都是没有意义的。
典型的可屏蔽中断源的例子是打印机中断,CPU对打印机中断请求的响应可以快一些,也可以慢一些,因为让打印机等待儿是完全可以的。
对于软中断,它不受IF位的影响,所以属于非屏蔽中断范畴。
处理过程:
- 如果IF=0,不响应可屏蔽中断
- 取中断类型码n
- 标志寄存器入栈,设置IF=0,TF=0
- CS,IP入栈
- IP=n4, CS=n4+2
对于8086CPU,不可屏蔽中断的类型码一定是2,因此在中断过程中不需要去中断类型码
处理过程:
- 标志寄存器入栈,IF=0, TF=0
- CS,IP入栈
- IP=8, CS=0ah
BIOS提供了int9中断来进行键盘输入输出处理
BIOS键盘缓冲区是BIOS用于存放int9中断例程所接收的键盘输入的内存区,该内存区可存储15个键盘输入,一个键盘输入用一个字单元存储,高位存放扫描码,低位存放字符码
0040:17单元存储键盘状态字节
- 0: 右shift状态 1表示按下
- 1: 左shift状态 1表示按下
- 2: Ctrl状态 1表示按下
- 3: Alt状态 1表示按下
- 4: ScrollLock状态 1表示Scroll指示灯亮
- 5: NumLock状态 1表示小键盘输入的是数字
- 6: CapsLock状态 1表示输入的是大写
- 7: Insert状态 1表示处于删除态
不要忘了调用原来的int9中断处理键盘输入,要不然在下一次按键的时候并不会触发到新int9中断程序,具体细节不明也不细究
我们可以使用标号[偏移地址]表示某个单元的数据,且大小已经确定了的,下面是代码示例
assume cs:code
code segment
a db 1,2,3,4,5,6,7,8
b dw 0
c dw offset a, offset b
d dw offset a, seg a, offset b, seg b ; 获取标号a, b的段地址和偏移地址
start:
mov ax, b ; => mov ax, cs:[8]
inc b ; => inc word ptr cs:[8]
inc a ; => inc byte ptr cs:[0]
mov al, a[si] ; => mov al, cs:[si]
;mov ax, a[si] ; => 错误, a[si]是的大小是byte
code ends
end start
一个寄存器通常是16位,如: al(8),ah(8) => ax(16)
al, ah的存值范围 0-255 => 1个字节 => 8个二进制数 => 2个16进制数
ax的存值范围 0-65535 => 2个字节 => 16个二进制数 => 4个16进制数
用16进制表示则是
al的范围是0-ff
ax的范围是0-ffff