汇编语言重点知识点总结
基础知识
汇编指令 : 计算机院唯一能识别的只有机器码,汇编指令与机器码一一对应
伪指令 : 没有对应的机器码 , 由编译器执行
其他符号 : +,-,*,/ . 没有对应的机器码,由编译器执行
CPU与内存如何交互
- 地址总线 : CPU通过地址总线告诉内存自己要访问哪个储存单元
- 地址总线的宽度决定了CPU的寻址能力
- 控制总线 : CUP通过控制总线告诉单元自己是要读还有要写
- 控制总线的宽度决定了CPU对其他器件的控制能力
- 数据总线 : 传送数据
- 数据总线决定了CUP与外界的数据传送能力
CPU如何控制外设 : CPU通过总线向接口卡发送命令,接口卡根据命令控制外设工作
寄存器与存储器
寄存器的功能
-
8086寄存器有4个段寄存器 : CS DS SS ES
-
寄存器的一般用途和专业用途
-
DS和[address]
-
储存字
- 在CPU中 , 16位寄存器存储一个字 , 高八位存储高位字节 , 低八位存储低位字节
- 在内存中字的低位字节存放在低地址单元 , 高位字节存储在高地址单元
-
DS和[adress]
- DS为数据段寄存器 , 存放数据的段地址
- mov bx,100h
mov ds,bx
//8086CPU自动取ds中的数据为内存单元的段地址,从而构造内存单元的物理地址
mov al,[0]
-
-
CS:IP控制程序执行流程
-
CS为代码段寄存器 , 存放指令段地址
-
IP为指令指针寄存器 , 存放指令的偏移地址. .
-
CS和IP共同指示了CPU要读取指令的物理地址
- 指令的执行过程
- 指令的执行过程
-
-
SS:IP提供堆栈栈顶单元地址
-
DS:BX(SI,DI)提供数据段内单元地址
-
SS:BP提供堆栈内单元地址
-
ES:BX(SI,DI)提供堆栈内单元地址
-
PSW程序状态字寄存器只能通过专用指令(lahf,sahf)和堆栈(pushf,popf)进行存取.
-
SS和SP
-
栈
- 8086CPU中可以将一段内存当做栈来用
- 入栈和出栈都是以字为单位进行的
-
SS和SP
- SS为段寄存器 , 存放栈顶的段地址 . SP存放栈顶的偏移地址
-
任意时刻 , SS:IP指向栈顶元素 , SS存放栈顶的段地址 , SP存放栈顶的偏移地址
-
POSH和POP的使用
-
//入栈的执行流程
push ax
1 SP=SP-2
2 向SS:SP指向的字单元中送入数据
//出栈的执行流程
pop ax
1 从SS:SP指向的字单元中读取数据
2 SP=SP+2 -
当栈为空的时候 , 没有栈顶元素 , SS:IP只能指向栈底部字单元的偏移地址+2处单元
-
-
栈顶超界问题
- 我们希望CPU有记录栈顶上限和栈底的寄存器 , 在我们PUSH和POP自动检测 , 防止超界 , 然而并没有
- 必须自己关注栈顶超界问题
-
POP和PUSH与MOV的区别
-
PUSH和POP都需要进行两步操作
- PUSH : 改变SP , 传送数据到SS:SP
- POP : 读取SS:SP处数据 , 改变SP
- PUSH和POP修改的只是SP , 栈顶变化范围0~FFFFH
-
MOV只需要一步操作
- 传送数据
-
数据的传送就是在寄存器与CPU之间进行 , 即CPU与内存之间
-
-
一段内存 , 可以既是代码段 , 又是数据段 , 还是栈段 , 也可以什么都不是 , 关键在于CPU中寄存器的位置 , 即CS , IP , SS , SP , DS的指向
-
寄存器分段管理
-
内存并没有真的被分段 , 段的划分来自CPU
-
段的起始地址必然是16的倍数
-
偏移地址为16位 , 16位地址的寻址能力为64KB , 所以段的最大长度为64KB
-
CPU可以用不同的段地址和偏移地址形成同一个物理地址
-
20位物理地址=段地址*10H=偏移地址
-
便于程序重定位
-
程序分段组织 : 一般由代码段 , 堆栈段 , 数据段 , 附加段组成不设置堆栈段时,使用系统内部的堆栈
堆栈
- 堆栈是一种先进后出的数据结构,数据的存取在栈顶进行,数据入栈使堆栈向地址减小的方向扩展
- 堆栈常用于保存子程序调用和中断响应时的断点以及暂存数据或中间计算结果
- 堆栈总是以字为单位存取
标志寄存器
- 子主题 1
0标志位ZF : 记录相关指令执行后结果是否为0 , 为0 , ZF=1 ; 非0ZF=0
奇偶标志PF : 记录相关指令执行后结果的所有bit位中1的个数是奇是偶 , 为奇 , PF=0, ; 为偶 , ZF=1
符号标志位SF : 记录有符号数运算接过是否为负(无符号数运算的时候SF的值没有意义) , 为负 SF=1 ; 为正 , SF=0
进位标志CF : 记录无符号数结果的最高位有效位向更高位的进位值(加法) , 或从更高位的减位值(减法)
溢出标志OF : 记录了有符号数运算结果是否发生了溢出 . 有,OF=1 ; 无 ,OF=0
ADC 指令
-
ADC AX , BX
- 带进位加法指令
- 功能 : (AX)=(AX)+(BX)+CF
-
设置CF为0 : SUB AX , AX
-
对于任意大的数进行加法运算
- /计算1EF000H+201000H/
//将计算分两步进行,先将低16位相加,然后将高16位和进位值相加。
mov ax, 001EH
mov bx, 0F000H
add bx, 1000H
adc ax, 0020H
- /计算1EF000H+201000H/
-
子主题 4
SBB指令
-
SBB AX , BX
- 带进位减法指令
- 功能 : (AX)=(AX)-(BX)-CF
-
对任意大数进行减法运算
- /计算003E1000H-00202000H/
mov bx, 1000H
mov ax, 003EH
sub bx, 2000H
sbb ax, 0020H
- /计算003E1000H-00202000H/
CMP指令
- CMP AX , BX 通过减法运算 , 影响表示寄存器 , 从而得知比较结果
-
CMP指令和条件转移指令搭配使用(相当于C语言中if判断)
- jcxz检测CX的值
方向进位标志DF :
- DF=0 , 每次操作后di , si递增
- DF=1 , 每次操作后di , si递减
串传送指令
-
MOVSB
- 将DS:SI指向的内存单元中的字节送入ES:DI中 , 然后根据标志寄存器DF的值 , 将SI或DI递增或递减
-
MOCSW
- 将ds:si指向的内
存字单元中的字送入es:di中,然后根据标志寄存器df位的值,将si和di递增2或递减2
- 将ds:si指向的内
-
rep movsb
- rep作用是根据CX的值重复执行后面的串传送指令
- 可以循环实现(CX)这个字符的传送
-
设置DF的值
- CLD DF=0
- STD DF=1
-
/将data段中的第一个字符串复制到它后面的空间中/
data segment
db ‘Welcome to masm!’
db 16 dup (0)
data ends
mov ax, data
mov ds, ax
mov si, 0
mov es, ax
mov di, 16
mov cx, 16
cld
rep movsb
/*
rep movsb相当于
s:movsb
loop s
*/
-
PUSH和POP命令
- pushf 是将标志寄存器里面的值压栈
- popf 从栈中弹出数据 , 送入标志寄存器
- pushf 和popf 是直接访问标志寄存器的一种方法
指令系统与寻址方式
指令系统
- 计算机提供给用户使用的机器指令集称为指令系统,大多数指令为双操作数指令。执行指令,后,一般源操作数不变,目的操作数被计算结果替代
- 机器指令由CPU执行,完成某种运算后者作指令系统,8086/8088指令系统中指令分位6类 : 数据传送 , 算术运算 , 逻辑运算 , 串操作, 控制转移和处理机机制
寻址方式
-
寻址方式确定执行指令时获得操作数地址的方法
-
分为与数据有关的寻址方式(7种)和与转移地址有关的寻址方式(4种)
-
与数据有关的寻址方式
- 立即数寻址方式 : 将常量赋值给寄存器或存储单元
- 直接寻址方式 : 存取单个变量
- 寄存器寻址方式 ; 访问寄存器的速度快于访问存储单元的速度
- 寄存器间接寻址方式 : 访问数组元素
- 变址寻址方式
- 变址基址寻址方式
- 相对基址变址寻址方式
- 与数据有关的寻址方式中 , 提供地址的寄存器只能是BX SI DI BP
-
与转移地址有关的寻址方式的一般用途
- 段内直接寻址 ; 段内直接转移或子程序调用
- 段内间接寻址 : 段内间接转移或子程序调用
- 段间直接寻址 : 段间直接转移或子程序调用
- 段间间接寻址 : 段间间接转移或字子程序调用
-
汇编程序和汇编语言
汇编程序 : 是将汇编语言源程序翻译成二进制代码程序的语言处理程序翻译的过程称为汇编
汇编语言
- 汇编语言使用指令助记符 , 各种标识变量 , 地址 , 过程等的标识符书写程序的语言 , 汇编语言指令与机器指令一一对应
- 伪指令 , 宏指令不是由CPU执行的指令 , 而是汇编程序在汇编期间处理的指令
- 伪指令指示汇编程序如何完成数据定义 , 存储空间分配 , 组织等工作
- 宏指令可简化程序并减少程序书写量
- 条件汇编伪指令的功能是确定是否汇编某段源程序,而不是实现程序分支 , 对未汇编的代码不产生相应的目标代码
- 指令的表达式在汇编期间计算,并且只能对常量或地址计算
第一个汇编程序
- /伪指令由编译器执行/
assume cs:codesg //assume伪指令,将代码段寄存器cs与段codesg关联起来,表示codesg是一个代码段
codesg segment //segment…ends伪指令,定义了一个段
//codesg,段名,也称作标号,此处指代一个段的段地址。
mov ax, 0123H
mov bx, 0456H
add ax, bx
add ax, ax
mov ax, 4c00H
int 21H //程序返回
codesg ends
end //end伪指令,表示汇编程序结束
程序的生命周期
- masm1 . asm 编译
- link1 . obj 链接
- 1 . exe 执行
DOS系统中.exe文件中程序被加载到内存的过程
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m3Wfikru-1650296260674)(assets/a355c8786a8db1bc09cd40875137bbbd768a7d046acff9675b2cbad11fdef055.png)]
程序的运行
-
在DOS中运行程序时 , 是command将程序加载到内存 , 程序运行结束返回到command中
-
在debug中将程序加载入内存 , 程序运行结束返回debug中
- 使用Q命令退出debug , 将返回到command中 . 因为debug是command加载运行的
- 在debug的时候 , 要是用P命令指向int 21h , 执行结束后 , 显示"program terminated normally" , 程序返回到debug中 , 表示程序正常结束 .
程序设计基础
分支程序设计
- 程序分支由条件转移指令或无条件转移指令实现
- 存放若干目的转移指令或跳转指令的跳转表常用于实现多路分支
- 条件转移指令只能实现偏移量为-128~~127字节范围的转移
- 无条件转移指令根据寻址方式可实现短转移(-128~~127字节范围内转移) , 段内转移 , 段间转移
循环程序设计 [BX]和LOOP指令
-
由循环控制指令或条件转移指令组织循环结构
-
内层循环结构必须完全包含在外层循环结构内 , 并不能发生从循环结构外向循环结构内转移
-
BX表示一个内存单元 , 段地址在DS中 , 偏移地址在寄存器BX中
-
LOOP指令格式
- LOOP标号
- 使用loop实现循环 , CX存放次数
-
CPU执行LOOP时
- (CX)=(CX)-1
- 判断CX的值 , 不为0则转至标号处执行 , 为0则向下执行
-
计算2^12
- assume cs:code
code segment
mov ax, 2
mov cx, 11 //cx控制循环次数
s: add ax, ax //s为标号,标号代表一个地址
loop s //可以理解为C语言中的do…while
mov ax,4c00h
int 21h
code ends
end
- assume cs:code
-
在汇编语言里 , 数据不能以字母开头
-
debug汇编程序中的两种写法(段前缀)
- //debug中
mov ax,0
mov bx,100h
mov ds,bx
mov al,[0]
mov al,[ax]
//汇编源程序中
mov ax,0
mov bx,100h
mov ds,bx
mov al,ds:[0] //如果是常量,就必须显式指明段地址所在段寄存器,ds:或者cs:等也叫段前缀
mov al,ds:[ax] //如果是寄存器,就不需要显式指明了。不过,建议还是加上段前缀,这样可以使得代码可读性强。
- //debug中
-
计算FFFF:0~FFFF:B单元中数据的和 , 结果存储在DX中
-
方案一 ; (DX)=(DX)+内存中的八位数据
- 显然八位和十六位的不能相加
- 可以采取八位转化为十六位在和十六位相加
-
方案二 : (DL)=(DL)+内存中的八位数据
- 不成立 , 可能超界
- 没有办法采取
- assume cs:code
code segment
mov ax, 0ffffh
mov ds, ax
mov bx, 0
mov dx, 0
mov cx, 12
s: mov al, [bx]
mov ah, 0
add dx, ax
inc bx
loop smov ax, 4c00h
int 21h
code ends
end -
一个包含多个段的汇编程序
-
/实现的功能:数据逆序存放/
assume cs:code,ds:data,ss:stack
//数据段
data segment
dw 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
data ends
//栈段
stack segment
dw 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
stack ends
//代码段
code segment
start: mov ax, stack //start,标号,为程序的入口地址
mov ss, ax
mov sp, 20hmov ax, data mov ds, ax mov bx, 0 mov cx, 8
s: push [bx]
add bx, 2
loop smov bx, 0 mov cx, 8
s0: pop [bx]
add bx, 2
loop s0mov ax, 4c00h int 21h
code ends
end start
/*
end的两大关键作用
1 通知编译器程序结束
2 通知编译器程序的入口在什么地方(隐含着在程序编译、链接之后,CS默认被设置为start)
*/
更灵活的定位内存地址方法
字符串如何存储以及大小写转换
-
字符串如何存储
- "f"的ASCII码值为66H , "O"为6FH , "R"为52H , K为4BH
- DB "FORK"等价于DB “66H , 6FH , 52H , 4BH” , 故内存地址由低到高存储为 66H 6FH 52H 4BH
-
将字符串小写字母转化为大写字母
- /*
如何区分大写字母与小写字母?
– 小写字母的ASCII码值比大写字母大20H
– 大写字母ASCII码值的二进制第5位为0(从右往左数),小写字母为一
/
/
如何实现小写字母转大写字母呢?
– 只需要将小写字母ASCII码值第5位由1变0,大写字母由0变0即可
– 使用and,or可以实现二进制的与,或。当然这里我们是用and al,11011111B
*/
assume cs:codesg,ds:datasg
- /*
datasg segment
db ‘BaSiC’
db ‘iNfOrMaTion’
datasg end
codesg segment
start: mov ax, datasg
mov ds, ax
mov bx, 0
mov cx, 5
s: mov al, [bx]
and al, 11011111B
mov [bx], al
inc bx
loop s
mov bx, 5
mov cx, 11
s0: mov al, [bx]
or al, 00100000B
mov [bx], al
inc bx
loop s0
mov ax, 4c00h
int 21h
codesg ends
end start
不同寻址方式
-
[idata]
- 使用一个常量表示地址 , 直接定位一个单元
-
[bx]
- 使用一个变量表示地址 , 间接定位一个单元
-
[bx+idata]
- 使用一个变量和一个常量表示地址
- 可以应用到一维数组
- /将第一个字符串转化为大写,第二个字符串转化为小写/
/*
– 在C语言中我们定义数组:char a[]=“hello”;
a存放的是字符h的地址,可以使用a[0]来访问字符h
– 在汇编语言中我们定义数组:db ‘BaSiC’
0存放的就是B的地址,可以使用0[0]来访问字符B
所以下面代码中的[bx]实际就是0[bx],[bx+5]实际就是5[bx]
*/
assume cs:codesg,ds:datasg
datasg segment
db ‘BaSiC’
db ‘MinIx’
datasg ends
codesg segment
start:
mov ax, datasg
mov ds, ax
mov bx, 0
mov cx, 5
s: mov al, [bx]
and al, 11011111b
mov [bx], al
mov al, [bx+5]
or al, 00100000b
mov [bx+5], al
inc bx
loop s
mov ax, 4c00h
int 21h
codesg ends
end start
-
[bx+si]
- 使用两个变量表示地址
- si和di不能被分为两个8位寄存器使用
- si和di与bx功能相近
-
[bx+si+idata]
- 使用两个变量一个常量表示地址
二重循环问题的处理
- /将每个单词改为大写字母/
/*
方案一
–四个一维数组,四次循环即可。
–显然这样代码量太大
方案二
–看成4*16的二维数组
–可行
/
/
实现二维数组的方法
–外层循环次数压栈
–进行内层循环
–内层循环结束
–外层循环次数出栈
*/
assume cs:codesg,ds:datasg,ss:stacksg
datasg segment
db 'ibm ’
db 'dec ’
db 'dos ’
db 'vax ’
datasg ends
stacksg segment
dw 0, 0, 0, 0, 0, 0, 0, 0
stacksg ends
codesg segment
start: mov ax, stacksg
mov ss, ax
mov sp, 16
mov ax, datasg
mov ds, ax
mov bx, 0
mov cx, 4 //外层循环控制数组个数
s0: push cx //外层循环次数压栈
mov si, 0
mov cx, 3 //内层循环控制每个数组的元素个数
s: mov al, [bx+si]
and al, 11011111b
mov [bx+si], al
inc si
loop s
add bx, 16 //切换到第二个一维数组的起始地址
pop cx //外层循环次数出栈
loop s0
mov ax,4c00H
int 21H
codesg ends
end start
子程序设计
- 子程序中要保护寄存器内容,并正确使用堆栈 , 成对执行PUSH和POP指令 , 保证执行RET指令时堆栈栈顶为返回地址
- 主程序可通过寄存器, 参数表 , 堆栈传递参数给子程序
数据处理的两个基本问题
处理的数据在什么地方
-
内存地址
- 只有BX , SI , DI , BP这四个寄存器可以用于内存寻址也就是说可以写在 [ ] 中
- 可以单独出现 , 也可以以以下四种组合出现 ,BX和SI , BX和DI , BP和SI , BP和DI , 当然可以有idata加持
- 只要 [ ] 中出现BP , 段地址默认在SS中
-
机器指令处理的数据在什么地方
-
汇编语言中数据位置的表达
-
立即数idata
- MOV AX , 1
- 1执行前在指令缓冲器里
-
寄存器
- MOV BX , AX
-
段地址和偏移地址
- MOV AX,[BX]
- 段地址默认在DS中
-
-
寻址方式
要处理的数据有多长
-
通过寄存器指名要处理的数据长度
- MOV AX , [0] 处理一个字数据
- MOV AL , [0] 处理一个字节数据
-
通过操作符指定
- mov byte ptr [0] , 1 处理一个字节数据
- mov word ptr [0] , 1 处理一个字数据
-
push和pop默认只进行字操作
DIV指令和MUL指令
div指令
- /利用除法指令计算100001/100/
//10001>65535,16位除法
mov dx, 1
mov ax, 86A1H
mov bx, 100
div bx
/利用除法指令计算1001/100/
//1001<65535,8位除法
mov ax, 1001
mov bl, 100
div b1
mul指令
-
被乘数与乘数
- 要么都是8位,要么都是16位
- 若都是八位 , 一个默认放在AL中
- 若都是十六位 , 一个默认放在AX中
-
乘积
- 若是八位乘法 , 默认存放在AX中
- 如是16位乘法 , 高位默认存放在DX中 , 低位默认存放在AX中
- /计算10010*/
//100和10小于255,8位乘法
mov al,100
mov bl,10
mul bl
/计算10010000*/
//100小于255,10000大于255,16位乘法
mov ax,100
mov bx,10000
mul bx
伪指令DD
- DB 定义字节型数据 , 8位
- DW定义字型数据 , 16位
- DD定义双字型数据 , 32位
DUP操作符
-
由编译器识别
-
进行数据重复
-
DB 3 DUP (0 , 1 , 2)
- 定义了九个字节
- 相当于db 0 , 1 , 2 , 0 , 1 , 2 , 0 , 1 , 2 ,
汇编语言与c语言
-----------------------修改结构体中的值------------------------
/C语言/
struct company
{
char cn[3];
char hn[9];
int pm;
int sr;
char cp[3];
};
int main()
{
struct company dec = {“DEC”, “Ken Olsen”, 137, 40, “PDP”};
int i;
dec.pm = 38;
dec.sr = dec.sr + 70;
i = 0;
dec.cp[i] = 'V';
i++;
dec.cp[i] = 'A';
i++;
dec.cp[i] = 'X';
return 0;
}
/汇编语言/
//建议参考上面的图片阅读,自己对比,理解C语言的底层实现
mov ax, seg
mov ds, ax
mov bx, 60h
mov word ptr [bx+0ch], 38
//mov word ptr [bx].0ch, 38 //dec.pm = 38
add word ptr [bx+0eh], 70
mov si, 0
mov byte ptr [bx+10h+si], ‘V’
//mov byte ptr [bx].10h[si], ‘V’ //dec.cp[i] = ‘V’;
inc si
mov byte ptr [bx+10h+si], ‘A’
inc si
mov byte ptr [bx+10h+si], ‘X’
转移指令
转移行为的分类
-
段内转移 , 只修改IP , JMP AX
- 短转移 , IP修改范围-128~127
- 近转移 , IP修改范围-32768~32767
-
段间转移 , 同时修改CS和IP , JMP 1000:0
转移指令的分类
- 无条件转移 JMP
- 条件转移
- 循环指令
- 过程
- 中断
offset操作符与nop指令
- offect由编译器识别 , 主要功能是取得标号的偏移地址
- nop指令 , 也叫空指令 , 什么都不做 , nop机器码占一个字节
JMP指令要给出的信息
- 转移的目的地址
- 转移的距离
根据位移进行转移的JMP指令
-
CPU根据位移进行跳转
- /汇编语言/
assume cs:codesg
codesg segment
start:mov ax,0
jmp short s
add ax,1
s:inc ax
codesg ends
end start
/机器码/
0BBD:0000 B80000 MOV AX,0000
0BBD:0003 EB03 JMP 0008
0BBD:0005 050100 ADD AX,0001
0BBD:0008 40 INC AX
/*
可以看出,不论idata是一个数据还是一个内存单元偏移地址,它都会对 应机器指令中给出。
但是,jmp 0008中的idata却没有在机器码中给出。
那CPU根据什么进行转移的呢?根据机器码,可以看出03其实是位移,CPU 是依据位移进行转移的。
*/
- /汇编语言/
-
CPU根据位移转移的意义
- 程序装在内存中的不同位置都可以正确执行
-
段内近转移 jmp near 标号
-
功能(IP)=(IP)+16位位移
-
16位位移=标号处的偏移地址-JMP指令下一条指令的偏移地址
-
-
段内短转移 jmp short 标号
- 功能(IP)=(IP)+8位位移
- 8位位移=标号处的偏移地址-JMP指令下一条指令的偏移地址
根据目的地址在指令中的jmp指令
-
CPU根据目的地址进行跳转
-
/汇编语言/
assume cs:codesg
codesg segment
start:mov ax,0
mov bx,0
jmp far ptr s
db 256 dup(0)
s:add ax,1
inc ax
codesg ends
end start
/机器码/
0BBD:0000 B80000 MOV AX,0000
0BBD:0003 B80000 MOV BX,0000
0BBD:0006 EA 0B01BD0B JMP 0BBD:010B
/*
可以看出,jmp far ptr s对应的机器码,包含转移的目的地址。
高地址是段地址,低地址是偏移地址
*/ -
段间转移 , 也叫远转移 JMP FOR PTR 标号
- (CS)=标号所在段的段地址 , (IP)=标号所在段的偏移地址
- far ptr 指明了指令用标号的段地址和偏移地址修改CS和IP
-
转移地址在寄存器中的jmp指令
- jmp ax 相当于 (IP)=AX
转移地址在内存中的JMP指令
- 段内转移 jmp word ptr 内存单元地址 == 内存单元里放着一个字 , 是转移的目的偏移地址
- 段间转移 jmp dword ptr 内存单元地址 == 内存单元里放着两个字 , 高地址转移的目的段地址 , 低地址是转换的目的偏移地址
条件转移指令 JCXZ
-
所有的条件转移指令都是短转移
-
JCXZ标号
- 若CX等于0 , jmp short 标号
- 若CX不等于0 , 程序向下执行 , 什么也不做
循环指令LOOP
-
所有的循环指令都是短转移
-
LOOP标号
- (CX)=(CX)-1
- 如果CX不等于0 , jmp short 标号 : 如果CX等于0 , 程序继续向下执行
过程 (程序的模块化开发)
-
ret 与 retf 指令
- ret指令用栈中数据 , 修改IP , 实现近转移 , 相当于pop ip
- retf用栈中数据 , 修改CS和IP , 实现远转移 , 相当于先pop ip 再pop cs
-
call指令
-
CPU执行call指令时分两步操作
- 相当于IP 或 CS和IP 压入栈
- 转移
-
CALL指令不能实现短转移
-
依据位移进行位移的call指令(call标号)
-
功能相当于
- push ip
- jmp near ptr 标号
-
-
转移地址在寄存器中的CALL指令 CALL AX
- PUSH IP
- JMP AX
-
转移地址在内存中的CALL指令
-
CALL WORD PTR 内存单元地址
- PUSH IP
- JMP DWORD PTR 内存单元地址
-
CALL DWORD PTR 内存单元地址
- PUSH CS
- PUSH IP
- JMP DWROD PTR 内存单元地址
-
-
call和ret的简单配合:(bx)=?
- //当然是(bx)=8
assume cs:code
code segment
start: mov ax,1
mov cx,3
call s //CPU缓冲寄存器存放call指令,IP指向mov bx,ax指令,执行call指令,IP压栈,然后将IP的值改变为标号s的偏移地址
mov bx,ax
mov ax,4c00h
int 21h
s: add ax,ax
loop s //loop循环结束,(ax)=8
ret //(IP)等于栈中元素,即语句mov bx,ax的偏移地址
code ends
end start - //当然是(bx)=8
-
call和ret配合实现高级语言中的函数调用案例
- /将字符串转化为大写/
//参数(返回值)可以使用寄存器传递,也可以使用栈来传递
assume cs:code
- /将字符串转化为大写/
-
data segment
db ‘conversation’
data ends
code segment
main:mov ax,data //主函数
mov ds,ax
mov si,0 //参数一,字符串首地址存放在ds:[0]中
mov cx,12 //参数二,字符串长度存放在cx中
call capital //函数调用
mov ax,4c00h
int 21h
capital:and byte ptr [si],11011111b //子函数
inc si
loop capital
ret //子函数返回
code ends
end main
内中断
CPU产生内中断的原因
- 除法错误 , 比如溢出
- 单步执行
- 执行into指令
- 执行int指令
CPU识别中断信息来源----中断类型码 , 是一个字节型数据,可以表示256种中断信息的来源
CPU找到中断处理程序入口地址
- CPU用八位中断类型码通过中断向量向量表找到对应的中断处理程序入口地址
- 中断向量表就是中断处理程序入口地址的列表 , 保存在内存指定位置处 , 中断处理程序也一直保存在内存中
- 在8086CPU中,内存0000:0000-0000:03FF的1024个单元存放着中断向量表。入口地址包括段地址和偏移地址,所以一个表项占两个字。高地址字段存放段地址,低地址字段存放偏移地址,共4*256=1024个单元
CPU的中断过程
- 取得中断类型码
- pushf
- TF=0, IF=0
- PUSH CS
- PUSH IP
- (IP)=(N4) , (CS)=(N4+2)
中断处理程序编写步骤
-
保存用到的寄存器
-
处理中断
-
恢复用到的寄存器
-
用iret指令返回
- 功能 : POP IP ;POP CS ;POP F;
- 相当于恢复现场
除法溢出
- /编程处理0号中断:当发生除法溢出时,在屏幕中间显示"overflow!",返回DOS系统/
/*
1 编写可以显示"overflow!"的中断处理程序:do0
2 将do0送入内存0000:0200处
3 将do0的入口地址0000:0200存储在中断向量表0号表项中
*/
assume cs:code
code segment
start:
//将do0送入内存0000:0200处
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
//将do0的入口地址0000:0200存储在中断向量表0号表项中
mov ax, 0
mov es, ax
mov word ptr es:[04], 200h
mov word ptr es:[04+2], 0
mov ax,4c00h
int 21h
do0: jmp short do0start
db “overflow!”
do0start:
//编写可以显示"overflow!"的中断处理程序:do0
mov ax, cs
mov ds, ax
mov si, 202h
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, 1
mov al, 02h
mov es:[di], al
add di, 1
loop s
mov ax, 4c00h
int 21h
do0end: nop
code ends
end start
单步中断
-
CPU在执行完一条指令后 , 若检测到TF为1 , 会产生单步中断
-
debug中的t命令
- debug提供了单步中断的处理程序 , 功能为显示所有寄存器功能后等待输入命令
- 在使用t命令执行命令时 , debug将TF设置为1 , 使得CPU在单步中断的方式下 , 在CPU执行完这条指令后就单步中断 , 执行单步中断的处理程序 , 所有寄存器内容显示在屏幕上 , 等待输入命令
-
CPU为避免在执行中断处理程序时发生单步中断 , 将TF设置为0
-
CPU为单步跟踪处理程序过程 , 提供了实现机制 , 提供单步中断功能
-
设置SS之后 , CPU不会响应中断 , 只有设置完SPCPU才会响应中断 , 所以要将设置ss和设置sp的指令连续存放
int指令 int n
- 功能 :引发中断过程
- /求23456^2*/
assume cs:code
code segment
start:
mov ax, 3456
int 7ch //计算(ax)的平方
add ax, ax
adc dx, dx
mov ax,4c00h
int 21h
code ends
end start
- /安装中断7ch的中断例程/
assume cs:code
code segment
start:
//将程序安装在0:200处
mov ax,cs
mov ds,ax
mov si,offset sqr
mov ax,0
mov es,ax
mov di,200h
mov cx,offset sqrend - offset sqr
cld
rep movsb
//将程序入口地址保存在7ch表项中
mov ax,0
mov es,ax
mov word ptr es:[7ch4], 200h
mov word ptr es:[7ch4+2], 0
mov ax,4c00h
int 21h
//计算平方
sqr:
mul ax //如果是16位乘法,高位默认存放在DX中,低位默认存放在AX中
iret //恢复现场
sqrend:nop
code ends
end start
- /将一个全是字母,以0结尾的字符串,转化为大写/
assume cs:code
data segment
db ‘conversation’,0 //0标记着字符串的结束
data ends
code segment
start: mov ax, data
mov ds, ax
mov si, 0
int 7ch //转化为大写
mov ax,4c00h
int 21h
code ends
end start
- /安装中断7ch的中断例程/
assume cs:code
code segment
start:
mov ax,cs
mov ds,ax
mov si,offset capital
mov ax,0
mov es,ax
mov di,200h
mov cx,offset capitalend - offset capital
cld
rep movsb
mov ax,0
mov es,ax
mov word ptr es:[7ch*4],200h
mov word ptr es:[7ch*4+2],0
mov ax,4c00h
int 21h
capital:
push cx
push si
change:
mov cl,[si]
mov ch,0
jcxz ok //判断是否为0
and byte ptr [si],11011111b
inc si
jmp short change
ok:
pop si
pop cx
iret
capitalend:nop
code ends
end start
实现LOOP指令功能
-
/在屏幕中间显示80个’!’/
assume cs:code
code segment
start:mov ax,0b800h
mov es,ax
mov di,160*12mov bx,offset s-offset se mov cx,80
s:mov byte ptr es:[di],’!’
add di,2 //一个字符在缓冲区占两个字节,分别存放字符的ASCII和属性
int 7ch
se:nopmov ax,4c00h
int 21h
code ends
end start
- /安装中断7ch的中断例程/
lp:push bp
mov bp,sp
dec cx
jcxz lpret
add [bp+2],bx
lpret:pop bp
iret
BIOS 和 DOS提供中断处理程序 , 用ah来传达内部子程序编号
-
int 21
- /设置光标位置功能/
mov ah,2 //表示调用第10h号中断例程的2号子程序
mov bh,0 //第0页
mov dh,5 //行号
mov dl,12 //列号
int 10h
/在光标位置显示字符功能/
mov ah,9
mov al,‘a’ //字符
mov bl,7 //颜色属性
mov bh,0 //第0页
mov cx,3 //字符重复个数
int 10h
- /设置光标位置功能/
-
显示缓冲区结构
- /bh中的页号介绍/或/显示缓冲区的结构/
–内存地址空间中,B8000H-BFFFFH共32KB的空间,为8025彩色字符模式的显示缓冲区,向这个地址空间写入数据,写入的内容会立即出现在显示器上。
–在8025彩色字符模式下,显示器可以显示25行,每行80个字符,每个字符可以由256种属性。
–一个字符在缓冲区占两个字节,分别存放字符的ASCII和属性(低位ASCII,高位属性)。一屏的内容在显示缓冲区共占4000个字节。
–显示缓冲区分为8页,每页4KB,约4000字节,显示器默认显示第0页。也就是B8000H-B8F9FH中的4000个字节。
/bl中的颜色属性介绍/或/属性字节的格式/
7 6 5 4 3 2 1 0
BL R G B I R G B
7为闪烁,4、5、6为背景,3为高亮,0、1 、2为前景
例如:红底高亮闪烁绿色的属性字节为11001010b
闪烁的效果必须在全屏DOS方式下才能看到。
- /bh中的页号介绍/或/显示缓冲区的结构/
-
dos 中 int 21h
-
mov ax,4c00h
int 21h
/真实含义/
mov ah,4ch //表示调用第21h号中断例程的4ch号子程序,功能为程序返回
mov al,0 //返回值
int 21h -
/在屏幕的5行12列显示字符串"Welcome to masm!"/
assume cs:code
-
data segment
db ‘Welcome to masm’,’$’
data ends
code segment
start:
//设置光标位置功能
mov ah, 2
mov bh, 0
mov dh, 5
mov dl, 12
int 10h
//显示字符串功能
mov ax, data
mov ds, ax
mov dx, 0
mov ah, 9
int 21h
//程序返回功能
mov ax, 4c00h
int 21h
code ends
end start
外中断
CPU通过端口与外设联系
- 外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口中
- CPU向外设的输出也不是直接送入外设,而是先送入端口,再由相关芯片送到外设
- CPU发送控制命令给相关芯片的端口,相关芯片再根据命令对外设进行控制
外中断源
-
可屏蔽中断
-
CPU可以不响应的外中断
-
当CPU检测到可屏蔽中断信息后,如果IF=1,则CPU执行完当前指令后响应中断;如果IF=0,则不响应中断。
-
可屏蔽中断属于外部中断,中断类型码由数据总线送入CPU;而内中断的中断类型码是在CPU内部产生的。
-
在进入中断处理程序后,禁止其他的可屏蔽中断。所以CPU中断过程中将IF置为0
-
设置IF的值
- sti设置IF=1
- sli设置IF=1
-
几乎所有由外设引发的外中断都是可屏蔽中断
-
-
不可屏蔽中断
- CPU必须响应的中断
- 当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应。
- 中断类型码固定为2
键盘的处理过程
-
键盘输入
-
按下一个键,键盘芯片产生一个扫描码,送入主板上相关接口芯片寄存器中,该寄存器端口地址60H
- 扫描码说明了按键在键盘中的位置
- 按键的扫描码叫通码;松键的扫描码叫断码
- 扫描码长度为一个字节,通码第7位为0,断码第7位为1
- 断码=通码+80H
-
松下一个键,键盘芯片产生一个扫描码,送入主板上相关接口芯片寄存器中,该寄存器端口地址60H
-
-
引发9号中断
- 输入到达60H,相关芯片向CPU发出中断类型码为9的可屏蔽中断。CPU检测到后,如果IF=1,则响应中断,执行int9中断例程(BIOS提供的)
-
执行int9中断例程
- 读出60H端口扫描码
- 如果是字符键的,将该码和它对应字符码送入内存中的BIOS键盘缓冲区。如果是控制键和切换键的,将其转变为状态字节写入内存中的存储状态字节的单元。
- 对键盘系统进行相关控制,比如向相关芯片发出应答信息
/在屏幕中间依次显示“a”~“z”,并可以让人看清。在显示的过程中,按下’Esc’键后,改变显示的颜色/
/实则为编写int9中断例程/
assume cs:code
stack segment
db 128 dup (0)
stack ends
data segment
dw 0,0
data ends
code segment
start:
mov ax,stack
mov ss,ax
mov sp,128
mov ax,data
mov ds,ax
mov ax,0
mov es,ax
//将原来的int 9中断例程入口地址保存在ds:[0]和ds[2]里
push es:[94]
pop ds:[0]
push es:[94+2]
pop ds:[2]
//在中断向量表中设置新的int 9中断例程入口地址
mov word ptr es:[94], offset int9
mov es:[94+2], cs
//屏幕中间依次显示字符a-z
mov ax, 0b800h
mov es, ax
mov ah, ‘a’
s:
mov es:[16012+402], ah
call delay //调用delay函数进行延时,使得每显示一个字母,可以让人看清
inc ah
cmp ah, ‘z’
jna s
//在中断向量表中设置为原来的int 9的中断例程入口地址(恢复操作)
mov ax,0
mov es,ax
push ds:[0]
pop es:[94]
push ds;[2]
pop es;[94+2]
//程序返回
mov ax,4c00h
int 21h
//延时函数
delay:
push ax
push dx
mov dx, 2000h
mov ax, 0
s1:
sub ax, 1
sbb dx, 0
cmp ax, 0
jne s1
cmp dx, 0
jne s1
pop dx
pop ax
ret
/新的int 9中断例程/
int9:
//将需要改变的寄存器压栈
push ax
push bx
push es
//从端口60H读入用户的键盘输入
in al, 60h
/模拟执行原int 9中断例程/
//标志寄存器入栈
pushf
//IF=0,TF=0
pushf
pop bx
and bh,11111100b
push bx
popf
//(IP)=((ds)16+0),(CS)=((ds)16+2)
call dword ptr ds:[0]
//看用户输入是否为esc
cmp al,1
jne int9ret
//如果是esc的话,属性值+1,从而改变颜色
mov ax,0b800h
mov es,ax
inc byte ptr es:[16012+402+1]
//寄存器出栈
int9ret:
pop es
pop bx
pop ax
iret
code ends
end start
Xmind 思维导图文件
https://download.csdn.net/download/THATMASTER/54792277
PNG 思维导图
https://download.csdn.net/download/THATMASTER/54799236
SVG 网页文件
https://download.csdn.net/download/THATMASTER/54800455