汇编语言
物理地址
CPU访问内存单元是要给出内存单元的地址。
8086的寻址能力和cpu的寻址能力不匹配,前者20位后者只有16位。
8086CPU的解决办法:
物理地址=段地址*16+偏移地址(乘16相当于把段地址左移一位)
一个物理地址不能决定段地址和偏移地址
用两个16位的地址相加得到一个20位的物理地址
用分段的方式管理内存
内存并没有分段,段的划分来自于CPU
一个内存段的起始地址(基础地址)为段地址的十六倍
- 一个段的起始地址一定是十六的倍数
- 编译地址为十六位,十六位地址的寻址能力为64k,所以一个段的最长大小为64k
eg:数据在21F60H内存单元中,段地址是2000H,说法:
- 数据存在内存2000:1FH单元中
- 数据存在2000H段中的1F60H单元中
四个段寄存器:
CS代码寄存器
DS数据寄存器
SS栈段寄存器
ES附加寄存器
debug的使用
借debug观察计算机内部的情况
1.启动debug:
2.R-查看寄存器内容:R 寄存器名, 再进行改变
3.D-查看内存中的内容
D 列出预设地址内存处的128个字节的内容
D 段地址:偏移地址
4.E-改变内存中的内容:
E 段地址:偏移地址 数据1 数据2 数据3……
E 段地址:偏移地址 原数据.新数据 原数据.新数据……
5.U命令间隔你存中的机器指令翻译成汇编指令
- E 段地址:偏移地址 -写入地址(写入的全是对应的机器码)
- D 地址-查看
- U 地址-查看代码
6 . A-以汇编指令的格式写入命令:A 地址
7 .T-执行机器指令:CS:IP 处的指令(单步执行)
8 . q退出
CS:IP与代码段
CS:代码寄存器
IP:指令指针寄存器(只有CPU自己能够进行修改,不能用mov)
1.从所指向的内存单元读取指令,读取的指令进入指令缓冲器
2.IP=IP+所读取指令的长度,从而指向下一条指令
3.执行指令,转到步骤1,重复过程
先-r看一哈cs和ip的指向哪里,再修改值,向相应的单元写入数据,再A,再U查看,也可以用D
jmp指令
修改CS,IP 的方法:
1.rcs,rip进行修改,但是不现实,debug是调试手段
2.转移指令jmp:
同时修改cs,ip:jmp 段地址:偏移地址
仅修改ip的内容:jmp 某一合法寄存器(jmp ax=mov ip ax但是后者不能用)
jmp可以用于循环
内存中字的存储
对于8086CPU,16位作为一个字,16位的字在内存中需要两个连续字节存储:
低位字节存放在低地址单元,高位字节存放在高地址单元
eg:4B20H存放在0,1两个单元。20存放在0单元,4B放在1单元
0地址单元中存放的字节数据是20H
0地址字单元存放的字型数据是4E20H
读的时候先读高地址单元再读低地址单元
用DS和[address]实现字的传送
CPU从内存单元中读取数据:
用DS寄存器存放要访问数据的段地址
偏移地址用[……]的形式直接给出
eg:mov bx,1000h
mov ds,bx
mov al,[0]
以上代码段的意思是将1000:0处的数据读到al中
eg:mov bx,1000h
mov ds,bx
mov [0],al
以上代码段的意思是将al中的数据写到1000:0中
不支持将数据直接送入段寄存器,要先送入基础寄存器
每次读两个单元,如果为[0],则读的实际数据是[1] [0]
DS与数据段
将哪段内存当作数据段,段地址如何定,在编程时安排
用DS存放数据段的段地址
用相关指令访问数据的具体单元,单元地址有[address]给出
eg:累加数据段中前3个单元的数据:add al,[0] add al,[1] add al,[2]
累加数据段中前3个字型数据:add ax,[0] add ax,[2] add ax,[4]
栈及栈操作的实现
栈是一种只能在一端进行插入或者删除的数据结构
入栈push:
push ax 将ax中的数据送入栈中
出栈pop:
pop ax 将栈顶的数据送入ax
都是以字为单位进行的操作,栈越往上内存地址越小
CPU如何知道一段内存被当作栈使用?
SS是栈段寄存器:存放栈顶的段地址
SP是栈顶指针寄存器:存放栈顶的偏移地址
任何时刻,SS:SP指向栈顶元素
栈的操作
push ax:
SP=SP-2
将ax中的内容送入SS:SP指向的内存单元,指向新栈顶SS: SP
pop ax:
将SS :SP指向的内存单元处的数据送入ax中
SP=SP+2
栈顶超界问题
栈顶超界是危险的,在执行push和pop指令都可能出现
CPU不保证栈顶不会超界
段的总结
物理地址=段地址*16+偏移地址
数据段:
段地址放在DS中,与[address]搭配使用,用mov,add,sub等访问时将内存单元中的内容当作数据来访问
代码段:
CS和IP用于指向要寻找的代码段
栈段:
SS和SP,采用push和pop指令,其中SP的加减取决于指令,需要进行栈段的操作是就用这个
三个段地址可以一样
用汇编语言写的源程序
汇编程序——编译器——机器码——计算机执行
汇编代码中的汇编指令称为伪指令
段定义:
一个汇编程序是由多个段组成的,这些段被用来存放代码,数据,或者当作栈空间来使用
一个有意义的汇编程序至少要有一个段,这个段用来存放代码
定义程序的段:每个段都需要段名
eg:段名 segment——段的开始
段名 ends——段的结束
end:
不是ends ,若程序结尾处不加end,编译器在编译程序时不知道程序在何处结束
assume:
假设某一段寄存器和程序中某一个用segment……ends定义的相关段相关联——assume cs:codesg指的是CS寄存器和codesg关联,将定义的codesg当作程序的代码段使用
汇编程序.asm——可执行程序.exe:
1.在debug中直接写入指令:适用于功能简单,短小精悍
2.单独写源程序文件后再进行编译:适用于编写大程序,需要包括汇编指令还需要伪指令,由段构成
如何写一个程序:
编程求2^3:
定义一个段
实现处理任务
指出程序在何处结束
短语寄存器的关联
加上程序返回时的代码
1 |
|
程序可能发现的错误:
语法错误:编译时被编译器发现
逻辑错误
由源程序到程序运行
编辑源程序:
写好asm文件
编译:
masm xx.asm,得到目标文件xx.obj,或者直接masm,再进行写文件名
连接:
link (obj的名字)
no stack segment可以不管
用debug跟踪程序运行
用法:debug xx.exe
程序加载后,DS中存放着程序所在内存区的段地址,这个内存区的偏移地址为0,则程序所在的内存区的地址为DS:0
这个内存区的前256个字节村PSP(段前缀),DOS用来和程序进行通信,256字节后的空间存放处是程序,CS的值为DS+10H
程序加载后CX中存放代码的长度
-t单步执行
-p类似t命令,但遇到子程序或者中断时,直接执行然后显示结果
[…] (…)
[]——在汇编语法中表示一个内存单元,根据ax和al判断操作单位是字还是字节:[bx]=((ds)*16+(bx))
()——在学习中为了方便做出的约定,表示一个内存单元或寄存器的内容:(ax)=0010H
idata——表示常量
inc :加一指令
loop指令
功能:循环(计数型循环)
CPU执行loop指令是要进行的操作
(cx)=(cx)-1
判断CX中的值:不为0则转至表好处执行程序,如果为0则向下执行
要求: cx中要提前存放循环次数,因为(cx)影响这loop指令执行的结果,定义一个标号
1 |
|
用loop实现循环的三个要点:
1.在cx中存放循环次数
2.用标号指定循环开始的位置
3.在标号和loop指令的中间要写上循环执行的程序段(循环体)
1 |
|
1 |
|
段前缀的使用
为了防止在程序段中的mov ax,[0]这种类似的形式在编译时被编译成mov ax ,00被当成了数据而不是偏移地址
对策:在[idata]前显式的加上寄存器:mov ax,ds:[bx]
访问连续的内存单元:loop和[bx]联手
问题:计算ffff:0~ffff:b字节单元中的数据的和,结果储存在dx中?
对策:取出8位数据,加到16位寄存器
mov al,ds:[addr]
mov ah,0
add dx,ax
1 |
|
在代码中使用数据
问题:
在程序中直接写地址是危险的
对策:
在程序的段中存放数据,运行时由操作系统分配空间
段的类别:数据段,代码段,栈段
各种段中均可以有数据
可以在单个段中安置,也可以将数据,代码,栈放入不同的段中
1 |
|
1 |
|
在代码中使用栈
问题:利用栈,将程序中定义的数据逆序存放
对策:依次将八个子单元的数据入栈,在出栈,实现逆序。可以定义空来取得空
1 |
|
将数据,代码,栈放入不同段
特点:三种都在一个段
功能:应用于要处理的数据很少,用到的占空间也小,加上没有多长的代码
对策:遇到很大的放入不同段
1 |
|
处理字符问题
汇编程序中,用’……’的方式指明数据是字符串,转化为ASCII码
大写字母的ASCII比小写的小20H
1 |
|
[bx+idata]寻址方式
[bx+idata]表示一个内存单元,它的偏移地址为(bx)+idata
mov ax,[bx+200]的含义:
将一个内存单元送入ax
这个内存单元长度为两个字节,存放一个字
内存单元的段地址在ds内,偏移地址为200加上bx中的数值
数学化的描述为:(ax)=((ds)*16+200+(bx))
[bx+200]还能表示为[200+bx],[bx].200,200[bx]
1 |
|
变址寄存器SI和DI
si和di是和bx功能相近的寄存器,也能[si+idata],[di+idata]
[bx+si] [bx+di]也可以用来指定地址
[bx+si]表示一个内存单元:偏移地址为bx的数值加上si的数值
bx叫做基址,si叫做变址
[bx+si+idata] [bx+di+idata]也可以用来指定地址
[bx+si+200]的其他写法:[bx+200+si] ,[bx].200[si] ,[bx] [sx].200,200[bx] [si]数值跟在寄存器后面要加点
寻址方式总结
[idata]直接寻址
[bx]寄存器寻址
[bx+idata]寄存器相对寻址
[bx+si]基址变址寻址
[bx+si+idata]相对基址变址寻址
用于内存寻址的寄存器用法
只有idata,bx,bp,si,di可以用于内存单元寻址,可以往方括号[]里面放
[bx+bp] [si+di]不能这样用,是错误的
bp默认的是SS段,bx默认的是DS段,如果指定了段前缀就用指定的段
数据的位置和长度
1.数据的位置
idata:称为立即数,数据包含在指令中
寄存器:要处理的数据在寄存器中,给出相应的寄存器名字
内存:段地址SA和偏移地址EA
2.数据的长度
字:word操作,16位,ax
字节:byte操作,8位,al
未知ax或者al:用word ptr或者byte ptr指明(eg:mov word ptr ds:[0],1)
div除法指令
使用div做除法的时候:被除数默认放在AX和DX和AX中,除数放在寄存器或者内存单元中
除数为8位内存或者寄存器则AL放商,AH放余数,除数位16位寄存器或者内存,则AX放商,DX放余数
eg:div bx 被除数:(DX*10000H+AX) 除数:(BX) 商:AX 余数:DX
提前在默认的寄存器中设置好被除数,且默认寄存器不做别的用处,可以把其中的数据装在内存单元或者压栈
dup的功能和用法
功能:dup和db,dw,dd等数据定义伪指令配合使用,用来进行数据的重复
eg:
db 3 dup(0) 定义了三个字节,它们的值都是0 相当于db 0,0,0
db 3 dup(0,1,2) 定义了九个字节,由0,1,2重复构成
db 3 dup(‘abc’,’ABC’) 定义了18个字节,构成‘abcABCabcABCabcABC’,一个字符一个字节
1 |
|
操作符offset
用offset获得标号的偏移地址
eg:mov si,offset s
jmp无条件转移
无条件转移,可以只修改IP,也可以修改CS,IP
段间转移(远转移):jmp 2000:0,源程序不能这样,只能在debug中使用
段内短转移:jmp short 标号,八位的位移量是指标号处的地址与jmp后一条指令地址的差,差要在-128~127之间
段内近转移:jmp near ptr 标号,十六位的转移量与上面差不多,但是差值的范围更大
远转移:far ptr指明了跳转到的目的地址,包含了标号的段地址和偏移地址
寄存器:jmp ax,IP=(AX)十六位
内存:段内:jmp word ptr 地址两个字节
段间:jmp dword ptr 地址,段地址在高位,偏移地址在低位四个字节
jcxz指令
格式:jcxz 标号
功能:如果(cx)=0,则转移到目的标号处执行
当(cx)!=0时,则顺序执行
所有有条件的转移均为短转移
call指令和ret指令
调用子程序:call
返回:ret
call:
字面意思:调用子程序
实质:流程转移,与jmp相似
格式:call 标号
操作:
将当前IP或者CS和IP压入栈中
转移到标号处执行指令
16位位移量
call far ptr 可以实现段间转移
相当于压栈和jmp的结合体
ret:
功能:用栈中的数据,修改IP的内容,实现金砖一,相当于pop IP
没有call也可以用ret,实际上转移到出栈的数据所代表的位置
retf:可以实现远转移
乘法mul指令
八位乘法:被乘数放在AL中,结果放在AX中
十六位乘法:被乘数放在AX中,结果高十六位放在DX,第十六位放在AX
模块化程序设计
1 |
|
1 |
|
还可以用栈(栈顶sp放ip)和内存单元传递参数
标志寄存器
标志寄存器是按位起作用的,每一位都有专门的含义
8086的1,3,5,12,13,14,15位不具有任何意义
标志寄存器的作用:
用来储存相关指令的某些执行结果
用来为CPU执行相关指令提供行为依据
用来控制CPU的相关工作方式
直接访问标志寄存器的方法:
pushf 将标志寄存器的值压入栈
popf 从栈中弹出数据,送入标志寄存器
ZF-零标志(zero flag)
ZF=1,表示结果是0
ZF=0,表示结果不是0
PF-奇偶标志(parity flag)
PF=1,1的个数是偶数
PF=0,1的个数是奇数
SF-符号标志(sign flag)
SF=1,结果为负
SF=0,结果为正
CF-进位标志(carry flag)
CF=1,有进位
CF=0,无进位
OF-溢出标志(overflow flag)
OF=1,有溢出
OF=0,无溢出
adc是带进位加法指令
利用了CF位上是否有进位
cmp和条件转移指令
格式:cmp 操作对象1,操作对象2
功能:计算操作对象1-操作对象2
应用:通过cmp指令执行后相关标志位的值,可以看出比较的结果、
1 |
|
条件转移指令中各符号的意思:
j-jump e-equal n-not b-below a-above l-less g-greater s-sign c-carry p-parity o-overflow z-zero
1 |
|
DF标志及串传送指令
DF=0,每次操作后si,di递增,否则递减。递增和递减的大小取决于传送的单位
movsb:以字节单位传送
movsw以字单位传送
rep指令
功能:根据cx的值,重复执行后面的指令
用法: rep movsb=s:movsb
loop s
1 |
|
移位指令
逻辑左移SHL
逻辑右移SHR
循环左移ROL
循环右移ROR
算数左移SAL
算术右移SAR
带进位循环左移RCL
带进位循环右移RCR
RO-rotate,C-carry
移位指令+OPR+CNT OPR代表操作数,CNT表述移动数位
用法:左移乘以2,右移除以二
1 |
|
操作显存数据
屏幕上的数据=显存内的数据
显存空间:128kRAM 其中B8000H-BFFFFH共32k空间是80*25彩色字符模式第0页的显示缓冲区(80列25行)
每一行80个字,160个字节
低位字节放ASCII,高位字节放属性字节
1 |
|
描述内存单元的标号
代码段中的数据也可以用标号
在code段使用标号但不使用冒号,使用字还是字节看后面,不加冒号的为数据标号,加冒号的为地址标号
数据标号同时描述内存地址和单元长度
地址标号只能在代码段使用
seg的作用是取段地址
1 |
|
直接定址表
问题:以十六进制的形式在屏幕中间娴熟给定的byte型数据
分析:把一个byte的高四位和第四位分开,显示对应的数码字符
利用表,在两个数据集合之间建立一种映射关系,用查表的方法根据给出的数据得到其在另一集合中的数据
ah放功能号,al放属性号
中断及其处理
内中断:CPU内部发生的事件引起的中断
外中断:外部设备发生的事情引起的中断
8086的内中断:
除法错误:div指令产生除法溢出;中断码0
单步执行:中断类型码1
执行into指令:中断类型码4
执行int指令:执行int n指令,立即数为中断类型码
1 |
|
中断处理程序
CPU接收到中断信息执行中断处理程序
中断信息和其处理程序的入口地址之间有某种联系
中断向量表可以由中断类型码查表得到中断处理程序
高位放CS,低位放IP
中断过程
中断过程由CPU的硬件自动完成
IP=4N,CS=4N+2
设置中断向量表
把程序的入口地址,写道中断向量表对应的n号表项中
1 |
|
单步中断
TF(陷阱标志)标志为1,让CPU处在单步中断方式下
IF(中断标志)标志为1,允许CPU相应可屏蔽中断请求,IF=0关闭中断
中断过程:
取得中断类型码N
标志寄存器入栈,TF,IF置为0
CS,IP入栈
IP=4N CS=4N+2
中断不响应的情况
ss:sp联合指向栈顶,对他们的涉资连续完成,不进行单步执行
由int指令引起的中断
中断过程与上方一致
BIOS基本输入输出系统
1 |
|
端口的读写
in从端口读取数据和out输出数据
设备控制寄存器61h
端口对应除CPU以外的外部设备
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!