汇编语言
基础知识
汇编语言的组成
-
汇编指令
- 有对应机器码的助记符
-
伪指令
- 没有对应机器码,由编译器执行
-
其他符号
- 由编译器识别,没有对应机器码,如+、-、*、/等
存储器
-
内存
-
内存单元
- 8bit为一个内存单元(一个字节)
-
指令和数据的区别
CPU对存储器的读写
-
需要以下3类信息交互
- 内存单元的地址(地址信息)
- 器件的选择,读或写的指令(控制信息)
- 读或写的数据(数据信息)
-
·总线
-
在计算机中专门连接CPU和其他芯片的导线
-
地址总线
-
地址总线的宽度决定了CPU寻址能力
- 2^n
-
-
控制总线
- 控制总线的宽度决定了CPU对系统中其他器件的控制能力
-
数据总线
- 数据总线的宽度决定了CPU和外界的数据传送速度
-
-
硬件概述
-
主板
-
接口卡
- CPU通过总线向接口卡发出命令,接口卡根据CPU命令来控制外设
-
各类存储器芯片
-
随机存储器(RAM)
-
用于存放供CPU使用的绝大部分程序和数据
- 主板上的RAM
- 扩展插槽上的RAM
- 接口卡上的RAM
-
必须带电存储,关机后存储内容丢失
-
-
只读存储器(ROM)
- BIOS
- 只能读取不能写入
-
注意点
- CPU可以直接使用的信息在存储器(内存)中存放
寄存器
CPU组成
-
运算器
- 进行信息处理
-
控制器
- 控制各种器件进行工作
-
寄存器(CPU工作原理)
- 进行信息存储
-
内存总线
- CPU的总线叫内部总线,连接CPU的各个组成部分,在它们之间进行数据传送
通用寄存器
-
AX、BX、CX、DX
-
均可分为两个8为寄存器
- 例如:AX可分为ah和al
-
-
一个16位寄存器所能存储的数据的最大值为?
-
2^16-1
- 因为2^16 = 100000000,而寄存器只有16位,所以要-1
-
几条汇编指令
-
mov ax,2000H
- 把数据2000H放进寄存器AX中
-
add ax,bx
- 把bx中的数据加到ax中
物理地址
-
所有的内存单元构成的存储空间是一个一维的线性空间
- 每一个内存单元在这个空间中都有唯一的地址,我们将这个唯一的地址成为物理地址
8086CPU的地址加法器
-
8086CPU是16位CPU
-
运算器一次最多可以处理16位的数据,但它却有20位地址总线
- 所以我们需要一种特殊的方法来将两个16位地址合成一个20位地址
-
-
物理地址 = 段地址*16 + 偏移地址
- 段地址*16也就是将二进制存放的段地址左移4位
段
- 实际中内存并没有分段,这个分段来自CPU的逻辑内存地址(就是说我把你看成一段一段的,便于操作)
段寄存器
-
CS 代码段寄存器
- 存放指令的段地址
-
IP 指令指针寄存器
- 存放指令的偏移地址
-
任何时刻,CPU都会将CS:IP指向的内容作为指令指向
-
8086CPU的工作原理
- (1)先把CS、IP中的数据送入地址加法器
- (2)地址加法器运算物理地址(CS)*16 + (IP)
- (3)将运算出的物理地址传送进输入输出电路
- (4)输入输出电路将物理地址送上地址总线
- (5)从指定的物理内存开始存放的机器指令 通过数据总线送入CPU
- (6)将这些机器指令加载进指令缓冲器
- (7)IP自动增加读入的机器指令的长度(字节)
- (8)指令被加载进执行控制器,执行该指令
-
修改CS、IP的指令
-
转移指令 jmp
-
jmp 段地址:偏移地址
- 修改CS:IP
-
jmp 某一合法寄存器
- 修改IP
-
-
实验1
-
Debug
-
R
- 查看、改变CPU寄存器的内容
-
D
- 查看内存中的内容
-
E
- 改写内存中的内容
-
U
- 将内存中的机器码翻译成汇编指令
-
T
- 执行一条汇编指令
-
A
- 以汇编指令的格式在内存中写入一条机器指令
-
注意点!
-
寄存器位数的溢出(越界问题)
- 两个寄存器相加后不止16位的话,就只保留后16位在寄存器中
-
CPU可以用不同的段地址和偏移地址来形成同一个物理地址
-
偏移地址16位,其变换范围为0~FFFFH
-
在汇编中,一个数据不能用字母来开头,必须加个0
寄存器(内存访问)
内存中字的存储
-
字单元
-
存放一个字型数据的内存单元,有两个地址连续的内存单元构成
- 地址小的是低位,大的是高位
-
DS
-
用来存放要访问数据的段地址
-
mov ax,[0]
- 直接指定内存地址时,ds内存放的是段地址,偏移地址由mov传入
-
栈
-
核心
- 先进后出
-
SS:SP
- 任意时刻,SS:SP指向栈顶元素
-
栈操作
-
push
-
入栈
- (1)SP = SP-2
- (2)向SS:SP中送入数据
-
-
pop
-
出栈
- (1)从SS:SP指向的字单元中读取数据
- (2)SP = SP+2
-
-
-
最大容量
-
栈顶的变换范围是0~FFFFH
- 64KB
-
-
8086CPU只记录栈顶,栈空间的大小需要我们自己管理
段
-
数据段
- DS
-
代码段
- CS:IP
-
栈段
- SS:SP
第一个程序
源程序
- assume cs:codesg
codesg segment
mov ax,0123H
mov bx,0456H
add ax,bx
add ax,ax
mov ax,4c00H
int 21
codesg ends
end
编写
-
伪指令
-
XXX segment
…
XXX ends- 代码段在这中间
-
end
- 整个汇编程序结束的标记
-
assume
-
假设
-
假设某一段寄存器的程序和程序中的某一个代码段相关联
- 类似于备注?
-
-
-
标号
-
codesg
-
标号指代了一个地址
- CS:codesg
-
-
-
程序返回
-
概念
- 把CPU的控制权交还给使它得以运行的程序
-
代码
-
mov ax,4c00H
int 21H- 固定加入这两段代码就可以实现程序返回
-
-
编译
-
(1)运行masm
-
(2)输入asm文件名称
-
(3)输入列表文件名称
- 略
-
(4)输入交叉引用文件名称
- 略
-
简易方式
-
masm 文件路径;
- 有分号!
-
连接
-
(1)运行link
-
(2)创建obj文件
-
(3)连接映像文件名称
- 略
-
(4)连接库文件
- 略
-
简易方式
-
link 文件名;
- 有分号!
-
执行
-
masm 程序
-
谁将我们要执行的程序加载入了内存?
- command
-
程序结束后返回到哪里?
- command
[BX]和loop指令
寄存器
-
bx
-
[bx]
- 存放偏移地址
-
-
cx
- loop的计数器
-
dx
- 累加寄存器
loop
-
loop的本质是更改IP寄存器,实现循环
-
代码段
- s: add dx,ax
loop s
- s: add dx,ax
新debug指令
-
g 内存地址
- 跳转到指定内存地址开始执行
-
p
- 一次将loop循环执行完毕
新汇编指令
-
inc
-
类似 ++ 使目标中内容+1
- 可用于loop操作,类似其他语言里的 i
- inc bx
-
段前缀
-
用于显式地指明内存单元的段地址的
- ds:
- cs:
- ss:
- es:
安全的代码段
-
0:200~0:2ff
- 这段空间中一般没有其他数据
注意点
-
汇编源程序中,数据不能以字母开头
-
错误
- ffffH
-
正确
- 0ffffH
-
-
idata
- 代表常量,类似数学中的c
-
汇编源程序中的 [idata] 问题
-
错误
- mov al,[0]
-
正确
-
mov al,ds:[0]
- 用[idata]传地址,需要加上段地址寄存器
-
-
正确
-
mov al,[bx]
- 用寄存器传地址可以
-
-
包含多个段的程序
dw
- 定义数据(开辟内存空间)
分段
- 数据段
- 代码段
- 栈段
更灵活的定位内存地址的方法
位操作指令
-
and
- 两个都是1才是1,其他均为0
-
or
- 只要有一个是1就是1,都是0就是0
db和dw的区别
- dw只能定义一个字,小于或大于一个字的都用db
[bx+idata]
-
常用形式
- [200+bx]
- 200[bx]
- [bx].200
-
灵活运用
-
[bx+si]和[bx+di]
- ((ds)*16+(dx0)+(si))
-
[bx+si+idata]和[bx+di+idata]
- 一个意思
-
不同的寻址方式的灵活应用
- 循环可以嵌套
注意点
-
mov ax,[bx] => ax=[bx+1,bx]
- 也就是ax里的内容就是把目标内存地址的内容当作al,下一个内存地址中的内容当作ah
实验7.9
- assume cs:codesg,ds:datasg,ss:stack
datasg segment
db '1. display ’
db '2. brows ’
db '3. replace ’
db '4. modify ’
datasg ends
stack segment
dw 0,0,0,0,0,0,0,0
stack ends
codesg segment
start:mov ax,datasg
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,16
mov si,0
mov bx,0
mov cx,4
s0:push cx
mov cx,4
s:mov al,[bx+si+3]
and al,11011111B
mov [bx+si+3],al
inc si
loop s
mov si,0
add bx,16
pop cx
loop s0
mov ax,4c00H
int 21H
codesg ends
end start
数据处理的两个基本问题
问题
- 处理的数据在什么地方?
- 要处理的数据有多长?
[…]
- bx
- si
- di
- bp
特殊点
-
reg
- 寄存器
-
sreg
- 段寄存器
寻址方式
-
[idata]
-
[寄存器]
-
[寄存器+idata]
-
[寄存器+寄存器]
- [bx+si]
- [bx+di]
- [bp+si]
- [dp+di]
-
[寄存器+寄存器+idata]
处理数据的长度格式
-
byte ptr
- 修改一个单元(位)
-
word ptr
- 修改两个单元(字)
div(除法)
-
除数
- 8位
- 16位
-
被除数
-
除数为8位
-
被除数为16位
- 存在AX中
-
-
除数为16位
-
被除数为32位
- DX存高16位
- AX存低16位
-
-
-
结果
-
除数为8位
- al存商
- ah存余数
-
除数为16位
- AX存商
- DX存余数
-
伪指令dd
- 定义一个双字型数据
- 和db,dw一个道理
dup
- db 重复次数 dup (重复的字节型数据)
- dw 重复次数 dup (重复的字型数据)
- dd 重复次数 dup (重复的双字型数据)
转移指令的原理
定义
- 可以修改IP,或同时修改CS和IP的指令统称为转移指令
操作符offset
-
取得标号的偏移地址
- mov ax,offset 标号
无条件转移指令
-
jmp
-
依据位移来进行转移的jmp指令
-
jmp short 标号
-
段内短转移
-
8位位移
- -127~128
-
-
-
jmp near ptr 标号
-
段内近转移
-
16位位移
- -32768~32767
-
-
-
-
转移的目的地址在指令中的jmp指令
-
jmp far ptr 标号
-
段间转移(远转移)
- 例如:EA20001000
-
-
-
转移地址在寄存器中的jmp指令
-
jmp 16位reg
-
最早学的jmp指令
- 例如:jmp ax
-
-
-
转移地址在内存中的jmp指令
-
jmp word ptr 内存单元地址(段内转移)
- 把指定内存单元处的一个字作为转移的目的偏移地址
-
jmp dword ptr 内存单元地址(段间转移)
-
把指定内存单元处的两个字作为转移目的地址
- (CS) = (内存单元地址+2)
- (IP) = (内存单元地址)
-
-
-
计算位移的原理
-
简便算法:取jmp 标号 下一个指令的偏移地址与目标标号偏移地址进行运算
有条件转移指令
-
jcxz
-
等价于C语言的 if((cx)==0) jmp short 标号
- cx=0的时候才会执行的指令,cx!=0什么也不做
-
-
loop
-
(cx)–
if((cx)!=0) jmp short 标号- cx先减1,在判断
-
注意点
- 注意各种转移位移的越界问题!
- 在这里补码知识很重要!
- 转移标号后的机器码不变 实验8
CALL和RET指令
二者均是转移指令
ret和retf
-
ret
- pop IP
-
retf
- pop IP
pop CS
- pop IP
call
-
call指令的通用操作
- (1)将当前的IP或CS和IP压入栈中
- (2)转移
-
call的执行原理
- (1)先读入call指令,此时call指令的机器码在指令缓冲器中
- (2)这时候IP自动增加,指向call指令后的下一个字节
- (2)等IP增加完以后,指令缓冲器中的call机器码就会载入执行控制器,这时候push IP中的IP就指向的就是call指令的下一个字节了
-
call无法实现段内短转移
-
依据位移进行转移的call指令
-
call 标号
- 段内转移
- push IP
jmp near ptr 标号
-
-
转移的目的地址在指令中的call指令
-
call far ptr 标号
- 段间转移
- push CS
push IP
jmp far ptr 标号
-
-
转移地址在寄存器中的call指令
-
call 16位reg
- 段内转移
- push IP
jmp 16位reg
-
-
转移地址在内存中的call指令
-
call word ptr 内存单元地址
- push IP
jmp word ptr 标号
- push IP
-
call dword ptr 内存单元地址
- push CS
push IP
jmp dword ptr 标号
- push CS
-
call和ret配合使用
-
子程序
-
子程序框架
- assume cs:code
code segment
main: …
…
call sub1 ;调用子程序1
…
mov ax,4c00H
int 21H
sub1: …
…
call sub2
…
ret ;子程序返回
sub2: …
…
…
ret
code ends
end main
mul指令
-
乘法指令
-
注意点
- 两个相乘的数,位数必须相同(都是8位或都是16位)
-
乘数
-
8位
-
一个默认放在al中
-
另一个
- 8位reg
- 内存字节单元
-
-
16位
-
一个默认放在AX中
-
另一个
- 16位reg
- 内存字单元
-
-
-
结果
-
8位
- AX
-
16位
- 高位默认在DX,低位默认在AX
-
-
格式
- mul reg
mul 内存单元
- mul reg
-
例子
-
100*10
-
mov al,10
mov bl,100
mul bl- ax=1000(03E8H)
-
-
100*10000
-
mov ax,100
mov bx,10000
mul bx- (ax)=4240H
- (dx)=000FH
-
-
模块化程序设计
-
参数和结果传递的问题
- (1)参数N存储在什么地方?
- (2)计算得到的数值,存储在什么地方?
-
批量数据的传递
-
当参数过多的时候我们该怎么办?
- 将批量数据存放到内存中,再把它们所在内存空间的首地址放在寄存器中,传递给需要的子程序
-
例子
- 将data段中的字符串转化为大写
-
-
寄存器冲突的问题
实验10
-
显示字符串
- pp99.exe
-
解决除法溢出的问题
- pp999.exe
-
十进制转换
- pp9999.exe
- int(十六进制数据/10) + 30h = 对应的十进制ASCII码
标志寄存器
flag寄存器
- 与其他寄存器不同,标志寄存器是按位来起作用的
注意点
-
传送指令不影响标志寄存器
- mov、push、pop
ZF标志
-
flag的第6位
-
操作指令结果
-
为0
- ZF = 1
-
不为0
- ZF = 0
-
PF标志
-
flag的第2位
-
操作指令结果中所有 bit位中 1的个数
-
偶数
- PF = 1
-
非偶数
- PF = 0
-
SF标志
-
flag的第7位
-
操作指令执行后的结果
-
负
- SF = 1
-
非负
- SF = 0
-
CF标志
-
flag的第0位
-
在进行无符号数运算时,它记录了运算结果的最提高有效位向更高位的进位值,或从更高位的借位值
-
进位
- 98H + 98H
-
退位
-
97H - 98H
- 97H要向最高位退位变成197H才能进行运算,此时CF = 1
-
-
OF标志
-
flag的第11位
- 溢出标志位
-
是否发生了溢出
-
是
- OF = 1
-
否
- OF = 0
-
-
溢出是什么?
-
在进行有符号数运算时,如果结果超出了机器所能表示的范围就称为溢出
-
mov al,98
add al,99- al位8位寄存器,能表示的数的范围是 -128~127
- 而98 + 99 = 197,溢出
-
mov al 0F0h
add al,088h-
0F0h 为有符号数-16的补码
088h 为有符号数-120的补码- -16-120 = -136 al为8位寄存器,溢出
-
-
-
CF和OF的区别
- CF是对无符号数运算有意义的标志位
- OF是对有符号数运算有意义的标志位
-
再来恶补一下计算机码!
- 计算机码:计算机在实际存储数据的时候,采用的编码规则(二进制规则)
计算机码:源码、反码、补码
Ps:数值本身最左边一位是符号位,正数为0,负数为1;
- 计算机码:计算机在实际存储数据的时候,采用的编码规则(二进制规则)
源码:数据本身从十进制转换为二进制的结果(正数0,负数1)
反码:针对负数,符号位不变,其他位取反
补码:针对负数,反码+1
(正数的原码、反码、补码一致)
系统中存在两个0:+0和 -0
+0: 00000000
-0: 10000000 原码
取反:11111111
补码:00000000
- 取反是不改变符号的..!!!!
- 所以0F0h(1开头)是负数-16的补码,
而7Dh(0开头)却是个正数
adc指令
-
带进位加法指令
-
格式
-
adc 操作对象1,操作对象2
- adc ax,1
-
-
功能
-
操作对象1 = 操作对象1 + 操作对象2 + (CF)
- (ax) = (ax) + 1 +CF
-
-
相当于多加了个CF的add
-
我们为什么需要加上CF的值?
-
分布计算
-
add al,dl
adc ah,dh-
add al,dl计算后如果需要进位,CF就等1,这时候再用adc ah,dh来计算,就能求出精确的值
-
add128 128加法器
- assume cs:code
-
-
-
-
data segment
db
data ends
stack segment
db 16 dup (0)
stack ends
code segment
start: mov ax,data
mov ds,ax
mov ax,stack
mov ss,ax
mov sp,16
call add128
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 di
inc di
inc si
inc si
loop s
pop di
pop si
pop cx
pop ax
ret
code ends
end start
- 这里的4个inc 不能用 2个add来代替
- 因为add会影响CF、SF、OF
- 而inc只影响PF
sbb指令
-
带借位减法指令
-
格式
- sbb 操作对象1,操作对象2
-
功能
- 操作对象1 = 操作对象1 + 操作对象2 +(CF)
-
同adc
cmp指令
-
比较指令
- 就是用来比大小的!
-
格式
- cmp 操作对象1,操作对象2
-
功能
-
计算 操作对象1 - 操作对象2
- 做减法,但不保存结果,只改变标志寄存器的内容
-
-
各种比大小的组合
-
cmp ax,bx
-
ZF = 1
- (ax) = (bx)
-
ZF = 0
- (ax) != (bx)
-
CF = 1
- (ax) < (bx)
-
CF = 0
- (ax) >= (bx)
-
SF
-
因为存在溢出的问题,所以单个SF标志无法判断ax和bx的关系,所以需要配上of
-
SF = 1, OF = 0
- 结果正确,为负,ax < bx
-
SF = 0, OF = 0
- 结果正确 ,为正,ax > bx
-
SF = 1, OF = 1
- 结果不正确,大小关系应该相反,ax > bx
-
SF = 0, OF = 1
- 结果不正确,大小关系应该相反,ax < bx
-
-
-
检测比较结果的条件转移指令
-
根据条件修改IP的指令
-
因为cmp可以进行两种比较,所以根据cmp指令的比较结果进行转移的指令也分为两种
-
根据无符号数的比较结果
-
检测ZF、CF
-
je
-
等于则转移
-
ZF = 1
- equal
-
-
-
jne
-
不等于则转移
-
ZF = 0
- not equal
-
-
-
jb
-
低于则转移
-
CF = 1
- below
-
-
-
jnb
-
不低于则转移
-
CF = 0
- not below
-
-
-
ja
-
高于则转移
-
CF = 0 && ZF = 0
- above
-
-
-
jna
-
不高于则转移
-
CF = 1 || ZF = 1
- not above
-
-
-
-
-
根据有符号数的比较结果
- 检测SF、OF
-
DF标志和串传送指令
-
DF标志
- flag的第10位
-
串传送指令
-
原理
-
DF = 0
- 每次操作后si、di递增
-
DF = 1
- 每次操作后si、di递减
-
-
传送一个字节
-
格式
- 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
-
-
-
-
传送一个字
-
格式
- movsw
-
功能
-
(1)((es)*16 + (di)) = ((ds)*16 + (si))
-
(2)如果
-
DF = 0
- (si) = (si) + 2
- (di) = (di) + 2
-
DF = 1
- (si) = (si) - 2
- (di) = (di) - 2
-
-
-
-
与rep配合使用
-
rep movsb
-
用汇编语言描述就是:
- s: movsb
loop s
- s: movsb
-
rep 是根据 cx 的值重复执行后面的串传送指令
-
-
rep movsw
- 同理
-
-
-
如何设置DF?
-
cld指令
- DF = 0
-
std指令
- DF = 1
-
pushf和popf
- 专门处理标志寄存器flag
标志寄存器在Debug中的表示
-
就是r指令查看寄存器时,右下角那一排
-
标志
-
1
- 0
-
-
OF
-
OV
- NV
-
-
SF
-
NG
- PL
-
-
ZF
-
ZR
- NZ
-
-
PF
-
PE
- PO
-
-
CF
-
CY
- NC
-
-
DF
-
DN
- UP
-
-
注意点
- ASCII码转换不能用ax寄存器,要用al或ah,因为如果是16位寄存器的话,cf计算ASCII码不管怎么样都是0
实验11
- pp11.exe
内中断
中断信息
- 要求CPU马上进行某种处理,并向所要进行的该种处理提供了必备的参数的通知信息
内中断的产生
-
4大类
- 除法错误(比如div产生的除法溢出)
- 单步执行
- 执行 into 指令
- 执行 int 指令
-
中断源
- 产生中断信息的事件
-
相对应的中断源
-
除法错误
- 0
-
单步执行
- 1
-
执行 into 指令
- 4
-
int n
-
n
- n是一个字节型数据
-
-
中断处理程序
- 用来处理中断信息的程序
中断类型码
-
用来定位中断处理程序
- 定位程序就需要改变CS:IP的内容,所以我们需要:中断向量表
中断向量表
-
中断向量
- 中断处理程序的入口地址
-
中断处理程序入口地址的列表
- 在8086PC机中,其存放于0000:0000~0000:03FF,且位置不可更改,内容可更改
-
与中断类型码 N 的对应关系
-
偏移地址
-
4N
- 低地址
-
-
段地址
-
4N+2
- 高地址
-
-
中断过程
-
CPU完成以下工作的过程
- (1)(从中断信息中)得到中断类型码
- (2)找到中断向量
- (3)用其设置CS:IP
-
思考
- CPU执行完中断程序后,是不是该返回原来的执行点继续执行?
- 答案是必然的,所以我们需要更完整的中断过程
-
完整的中断过程
-
(1)(从中断信息中)得到中断类型码
-
(2)标志寄存器入栈(因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中)
-
(3)设置标志寄存器的第8位TF和第9位IF的值为0
- TF和IF后面才会学到
-
(4)push CS
-
(5)push IP
-
(6)从 4N、4N+2 中读取中断程序地址入口,并设置给CS:IP
-
-
代码表示
- (1)获取中断类型码N
- (2)pushf
- (3)TF = 0,IF = 0
- (4)push CS
- (5)push IP
- (6)(IP) = 4N,(CS) = 4N + 2
-
这是由CPU自动运行的,程序员无法操纵
中断处理程序和 iret 指令
-
中断处理程序的格式
- (1)保存用到的寄存器
- (2)处理中断
- (3)恢复用到的寄存器
- (4)用 iret 指令返回
-
iret 指令
-
功能
- pop IP
pop CS
popf
- pop IP
-
除法错误中断的处理
- 返回 Divide overflow
编写中断程序
-
我们这里用 0 号中断类型码来做例子
-
整理思路
-
我们需要做什么才能让取得 0 号中断类型码时运行我们编写的中断程序?
-
(1)要有中断处理程序
-
(2)中断处理程序得要放在内存中
- 我们一般放在0000:0200~0000:002ff这段安全空间中,这本是中断向量表的空间,但这后面一段一般是空的,并不会存储中断向量
-
(3)更改中断向量表中0号所对应的中断向量 指向 中断处理程序的地址入口
-
思路整理完毕,我们看框架
-
-
框架与流程
- assume cs:code
code segment
start: ;do0安装程序
;设置中断向量表
mov ax,4c00h
int 21h
do0: ;显示字符串"overflow!"
mov ax,4c00h
int 21h
code ends
end start
- 拆分过程
- (1)do0的安装
- (2)do0的编写
- (3)设置中断向量
-
总流程
-
(1)安装
-
思路
-
用 rep movsb 来实现内容的快速复制
-
设置es:di指向目标地址
-
设置ds:si指向源地址
-
设置cx为传输长度
-
mov cx,offset do0end - offset do0
- do0: 显示字符串"overflow!"
mov ax,4c00h
int 21h
- do0: 显示字符串"overflow!"
-
-
-
-
-
do0end: nop
- 我们可以让计算机确定程序长度!
- 用do0end的位移量 减去 do0的位移量
- 编译器是可以识别+、-、*、/的哦!!!!
- 设置传输方向为正
- cld
- DF = 0 正向
- 我们要正向的
- std
- DF = 1 逆向
- (2)编写中断处理程序
- 要注意"overflow!"这样的字符不能放在data段里,因为程序返回有可能会覆盖掉data段,所以我们需要将这些字符放在安全的空间里,那么我们就可以将其放进中断处理程序do0中,一起复制进0:200里
- (3)设置中断向量
- mov ax,0
mov es,ax
mov word ptr es:[04],200h
mov word ptr es:[04 + 2],0
单步中断
-
中断类型码
- 1
-
条件
- CPU执行完一条指令后,若TF = 1,则产生单步中断
-
中断过程
- (1)取得中断类型码1
- (2)将标志寄存器入栈,TF、IF入栈
- (3)CS、IP入栈
- (4)(IP) = 14,(CS) = 14 + 2
-
为什么要有单步中断???
-
Debug 中的 t 功能就是单步中断的最好应用
- 执行 t 指令时,TF = 1,经过中断过程后,开始执行中断处理程序,之后返回
- 得要让TF = 0
- 明白为什么要把标志寄存器入栈了吧!
因为我们需要让TF = 0!不然就是个死循环!
-
实现单步跟踪
-
响应中断的特殊情况
-
SS:SP的设置
- SS:SP的设置必须连续,不然CPU中断以后就会使SS:SP指向错误的栈顶
实验12
-
qs2.exe
- 中断处理程序
int指令
int n
-
n 就是中断类型码
- 一般情况下,系统将一些有一定功能的子程序,一中断处理程序的方式提供给应用程序调用。
我们在编程的时候,可以用int指令调用这些子程序。
- 一般情况下,系统将一些有一定功能的子程序,一中断处理程序的方式提供给应用程序调用。
中断例程
- 中断处理程序的简称
编写基于 int 指令的中断例程
-
int 和 iret 的配合使用
-
类似于 call 和 ret 的配合使用
-
iret 的功能相当于
- pop IP
pop CS
popf
- pop IP
-
基础案例
-
用int 7ch实现 loop 指令
- do0: push bp
mov bp,sp ;让bp指向栈顶
dec cx ;cx–
jcxz lpret
mov [bp + 2],bx ;让栈顶的后一个字变成标号s的偏移地址
- do0: push bp
-
-
ipret: pop bp
iret
BIOS和DOS所提供的中断例程
-
BIOS
- 主板ROM中存放的一套程序
-
BIOS的内容
- (1)硬件系统的检测和初始化程序
- (2)外部中断和内部中断的中断例程
- (3)用于对硬件设备进行I/O操作的中断例程
- (4)其他和硬件系统相关的中断例程
-
DOS
- 和硬件设备相关的DOS中断例程中,一般都调用了BIOS的中断例程
BIOS和DOS中断例程的安装过程
BIOS中断例程的应用
-
闪烁字符
-
qs4.exe
-
放置光标(10h 2号)
- mov ah,2 ;放置光标
mov bh,0 ;第0页
mov dh,5 ;dh放行数
mov dl,12 ;dl放列数
int 10h
- mov ah,2 ;放置光标
-
在光标位置重复字符(10h 9号)
- mov ah,9 ;在光标位置显示字符
mov al,‘a’ ;字符
mov bl,02h ;颜色属性
mov bh,0 ;第0页
mov cx,3 ;字符重复个数
int 10h
- mov ah,9 ;在光标位置显示字符
-
mov ax,4c00h
int 21h
的真正含义
-
调用了第 21h 号中断例程中的 4ch 号子程序
- mov ah,4ch ;程序返回
mov al,0 ;返回值
int 21h
- mov ah,4ch ;程序返回
DOS中断例程应用
-
mov ah,9
功能号9,显示字符串- qs5.exe
-
ds:dx 指向字符串 ;要显示的字符串需用"$"作为结束符
mov ah,9
int 21h
编写、应用中断例程
-
(1)qs6.exe
-
(2)qs7.exe
-
整套安装中断程序的模板
- qss.asm
-
(3)qs8.exe
端口
概念
- 与CPU通过总线相连的芯片上可供CPU读写的寄存器
CPU可以直接读写
- (1)CPU内部寄存器
- (2)内存单元
- (3)端口
端口的读写
-
只能用 in 和 out,不能用其他的!
-
in
-
读
-
in al,60h
- 从60h号端口读入一个字节到 al 中
-
-
-
out
-
写
-
mov al,2
out 70h,al- 向70h号端口写入 al 中的内容2
-
-
CMOS RAM芯片
-
端口
-
70h
- 存放要访问的CMOS RAM单元的地址
-
71h
- 存放从选定的CMOS RAM单元中读取的数据
-
shl 和 shr 指令
-
二者均为 逻辑移位指令
-
shl
-
逻辑左移指令
-
功能
-
(1)将一个寄存器或者内存单元中的数据向左移位
(2)将最后移出的一位以为写入CF中
(3)最低位用0补充 -
效果相当于
- X = X*2
-
-
注意点
- 如果移动位数大于 1 时,必须将移动位数放在cl中
-
-
shr
-
逻辑右移指令
-
功能
-
(1)将一个寄存器或者内存单元中的数据向右移位
(2)将最后移出的一位以为写入CF中
(3)最搞位用0补充 -
效果相当于
- X = X/2
-
-
注意点
- 如果移动位数大于 1 时,必须将移动位数放在cl中
-
-
子主题 4
CMOS RAM中存储的时间信息
-
CMOS RAM中存放着当前时间
-
内存单元为:
- 秒:0
- 分:2
- 时:4
- 日:7
- 月:8
- 年:9
-
这些数据都是用BCD码来存放的(类似十六进制)
-
4位二进制数 表示一个十进制数
-
0100 0010
- 42
-
-
-
将BCO码转换为十进制数据显示在屏幕上的方法
-
让ah等于高4位,
al等于低4位,
需要用到逻辑移位指令- mov al,8
our 70h,al
in al,71h ;获取了月份
- mov al,8
-
mov ah,al ;al中存放着月份
mov cl,4
shr ah,cl ;ah中为月份的十位数码值
and al,00001111b ;al中为月份的个位数码值
-
实验14
-
qs9.exe
- 完美完成!!!!!!
-
外中断
CPU通过端口和外部设备联系
可屏蔽中断
-
CPU可以不响应的外中断
-
两种情况
-
IF = 1
- CPU在执行完当前指令后响应中断,引发中断过程
-
IF = 0
- CPU不响应可屏蔽中断
-
-
设置 IF
-
sti
- 设置 IF = 1
-
cli
- 设置 IF = 0
-
不可屏蔽中断
-
当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程
-
中断过程
- 对于8086CPU,不可屏蔽中断码固定为2
- (1)标志寄存器入栈,IF = 0,TF = 0
- (2)CS,IP入栈
- (3)(IP) = (8),(CS) = (0AH)
PC机键盘的处理过程
-
1、键盘输入
-
(1)按下按键时
- 开关接通,芯片产生一个扫描码,扫描码被送入主板上的相关接口芯片的寄存器中,该寄存器的端口地址为60h
- 通码
-
(2)松开按键时
- 也产生一个扫描码,这个扫描码说明了松开的键在键盘上的位置,同在60h端口
- 断码
-
断码 = 通码 + 80h
-
-
2、引发9号中断
- 如果IF = 1就引发中断过程,转去执行 int9 中断例程
-
3、执行 int9 中断例程
-
(1)读出60h端口中的扫描码
-
(2)扫描码类型识别
-
字符键
-
将该扫描码和其对应的ASCII码送入内存中的BIOS键盘缓冲区
-
BIOS键盘缓冲区
-
BIOS用于存放 int 9 中断例程所接收的键盘输入的内存区
-
高位字节
- 扫描码
-
低位字节
- 字符码(ASCII码)
-
-
-
控制键
-
将其转变为状态字节,写入内存中存储状态字节的单元
-
状态字节
- 用二进制位记录控制键和切换键状态的字节
- 具体信息看书p275
-
-
-
(3)对键盘系统进行相关控制
-
-
键盘输入的处理过程总结
- (1)键盘产生扫描码
- (2)扫描码送入 60h 端口
- (3)引发9号中断
- (4)CPU执行 int 9 中断例程处理键盘输入
编写 int 9 中断例程
-
在屏幕中间一次显示’a’-‘z’,并可以让人看清 。在显示的过程中,按下Esc键后,改变显示颜色
-
功能拆分
-
(1)依次显示’a’-‘z’
- 简单的写入
-
(2)让人看清
- 用多次循环延迟一小段时间
-
(3)按下ESC改变颜色
-
需要我们编写 int 9 中断例程
-
功能
-
(1)从60h端口中读出键盘的输入
- in al,60h
-
(2)调用BIOS的 int 9 中断例程,处理其他硬件细节
-
由于 int 9 中存放着一些处理硬件细节的机器码,所以我们编写的 int 9 中断例程还是得要调用 int 9 中断例程
-
我们写的就是int 9,在这里如何调用 int 9?
-
模拟 int 指令的功能
-
功能
- (1)获取中断类型码n
(2)标志寄存器入栈
(3)IF = 0,TF = 0
(4)CS、IP入栈
(5)(IP) = (n4) CS = (n4 + 2)
- (1)获取中断类型码n
-
模拟功能
-
因为我们知道中断类型码,所以我们不需要获取
-
功能可以模拟成
-
-
-
-
-
-
-
-