下面的笔记是根据鱼c工作室里的汇编教学视频里的总结的,方便复习
1: Debug的使用:
配置好DosBox后,打开运行,输入e:,回车(之前配置的)。然后就可以使用Debug了
2: 命令:
.R命令查看、改变cpu寄存器的内容
执行后,会看到寄存器的内容,然后输入r ax,就可以在下面输入值,ax就会是改变后的值
. D命令查看内存中的内容
. E命令改写内存中的内容(机器指令的格式)
//比如-e 1000:0 23 11 22 66就是将段地址为1000,偏移地址为0的数据写为23,1000:1为11,1000:2为22...
. U命令将内存中的机器指令翻译成汇编指令
. T命令执行一条机器指令
. A命令以汇编指令的格式在内存中写入一条机器指令
编写程序时,比如想将ffff赋值给ax,不可以mov ax,ffff,因为编译器不认识以字母开头的值,必须在前面加上0
汇编中可以用loop循环,cx保存循环的次数,每次循环,cx-1,cx为0时结束循环,比如88*99,可以
mov ax,0
mov cx,99s: add ax,88 loop s
5: 难道88*99真的要循环那么多次吗?于是乎,引入g命令来解决!也可以是p命令
比如执行时,查看一下cs和ip的值,用-u指令查看汇编指令的地址,然后直接-g 后面加上偏移地址
就可以直接跳转到该地址,相当于vs中在循环下面加个断点,然后跳到断点执行,就跳过了循环了
6:一段安全空间
并不是所有的内存空间都可以拿来用的,有些空间中可能有系统或其他程序的数据或代码,所以写入的话会引发错误
需要直接向内存写入内容时,就使用0:300-02ff这段空间
7:在8086CPU中,只有下面4个寄存器(bx,si,di,bp)可以用在”[]”中来进行内存单元的寻址
而且这四个寄存器可以单个出现,或者只能以四中组合出现:
bx和si,bx和di,bp和si,bp和di
* bp默认段地址在ss中
8:指令中要处理的数据是有长度的,比如c,c++里面int,char,double等等,汇编中通长有db(字节型),dw(字型),dd(双字型 )这几种类型,那么如何知道要处理的数据到底多长呢?
1)通过寄存器名指明:
mov ax,bx //字型
mov al,bl //字节型
2) 在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte
mov word ptr ds:[bx],1
mov byte ptr ds:[bx],2
注意,有些指令只能进行字单元操作或者字节操作,比如push只能对字单元操作,因为每次push前都会sp=sp-2
3)其他...
9:div(division)指令
注意:
1):除数,8为或16位,在寄存器或内存单元中
2):被除数,(默认)放在AX或AX和DX中
!!为什么是AX 或者 AX和DX 呢?
除数 被除数
8位 16位(AX)
16位 32位(AX+DX,AX存放低16位,DX存放高16位)
除数:8位 16位
商: AL AX
余数:AH DX
3): 指令格式
div reg //div加上一个寄存器
div 内存单元
10:伪指令dup
格式: db(或者dw,dd等) 重复的次数 (重复的数据)
实例:
# db 3 dup(0)
相当于db 0,0,0
# db 3 dup(0,1,2)
相当于db 0,1,2,0,1,2,0,1,2
dup是编译器区识别的,比如要定义容量为30个字节的栈段,可以
stack segment
db 30 dup(0)
stack ends
这样就很方便了
11:转移指令
# 8086cpu的转移指令分为以下几类:
* 无条件转移指令(如:jmp)
* 条件转移指令
* 循环指令(如:loop)
* 过程(相当于函数)
* 中断
# 操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址
a. 无条件转移:
# jmp为无条件转移,可以只改变IP,也可以同时修改CS和IP
jmp指令要提供两条信息:
* 转移的目的地址
* 转移的距离(段间距离,段内短转移,段内近转移)
//==============================段内转移====================================
1)jmp short 标号 (转到标号处执行指令)
这种格式的jmp指令实现的是段内短转移,它对IP的修改范围为-128-127,即向前转移最多 可以越过128个字节,向后转移最多可以越过127个字节
assume cs:codesg
codesg segment
start: mov ax,0
jmp short s
add ax,1 //此处ax+1未执行,由jmp直接跳过了,所以最终ax为1
s:inc ax
codesg ends
end start
实际上,指令"jmp short 标号"的功能为ip=ip+8位位移
* 8位位移="标号"处的地址->jmp指令后的第一个字节的地址
* short指令表明此处的位移为8位位移
* 8位位移的范围为-128-127,用补码表示
* 8位位移由编译程序在编译时算出
2) jmp near ptr 标号(和jmp short 标号类似,不过是段内近转移)
指令"jmp near ptr 标号"的功能为ip=ip+16位位移
* 16位位移="标号"处的地址->jmp指令后的第一个字节的地址
* near指令表明此处的位移为16位位移
* 16位位移的范围为-32769-32768,用补码表示
* 16位位移由编译程序在编译时算出
3)jmp 16位寄存器
功能:IP=16位寄存器
4)jmp word ptr 内存单元地址
功能:从内存单元地址处开始存放一个字,是转移的目的的偏移地址
//==============================段间转移=======================================
1)jmp far ptr 标号(实现的是段间转移,又称为远转移)
功能:
* cs=标号所在段的段地址
* ip=标号所在段中的偏移地址
2)jmp dword ptr 内存单元地址
功能:从内存单元地址处开始存放两个字,高地址处的字是转移目的的段地址,低地址处是 偏移地址
b.有条件转移
1)jcxz指令
jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移
而不是目的地址。对IP的修改范围都为-128-127
指令格式: jcxz 标号
如果cx=0,则转移到标号处执行,相当于(if(cx==0) jmp short 标号)
2)loop
loop为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,对ip的修改范围
为-128-127
指令格式:loop 标号
每次执行后cx=cx-1,如果cx!=0,则转移到标号处执行,与jcxz相反,相当于
if(--cx!=0)
jmp short 标号
12 call和ret指令
# call和ret指令都是转移指令,他们都修改IP,或同时修改CS和IP
1)ret指令用栈中的数据,修改IP的内容,从而实现近转移
Cpu执行ret指令时,进行下面两步操作
a: IP=ss*16+sp
b: sp=sp+2
//相当于pop IP
2) retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移
Cpu执行ret指令时,进行下面四步操作
a: IP=ss*16+sp
b: sp=sp+2
c: CS=ss*16+sp
d: sp=sp=2
//相当于pop IP
pop CS
3)call指令经常跟ret指令配合使用,因此cpu执行call指令,进行两步操作
a:将当前的IP或CS和IP压入栈中
b:转移(jmp)
# call指令不能实现短转移,除此之外,call指令实现转移的方法和jmp指令的原理相同
格式:* call 标号(将当前IP压入栈后,转移到标号处执行指令)
cpu执行此种格式的call指令时,进行如下操作:
sp=sp-2
ss*16+sp=ip
ip=ip+16位位移(“标号”处的地址减去call指令后的第一个字节的地址)
//相当于push IP
jmp near ptr 标号
* call far ptr 标号 (实现的是段间转移)
cpu执行此种格式的call指令时,进行如下操作:
sp=sp-2
ss*16+sp=cs
sp=sp-2
ss*16+sp=ip
cs=标号所在的段地址
ip=标号所在的偏移地址
//相当于push CS
push IP
jmp far ptr 标号
* call 16位寄存器
功能:
sp=sp-2
ss*16+sp=ip
ip=16位寄存器
相当于push IP
jmp 16位寄存器
# 转移地址在内存中的call指令(有两种格式)
* call word ptr 内存单元地址
相当于 push IP
jmp word ptr 内存单元地址
* call dword ptr 内存单元地址
相当于 push CS
push IP
jmp dword ptr 内存单元地址
#可以写一个具有一定功能的程序段,我们称其为子程序,在需要的时候,用call指令转去执行,可是执行完子程序之后,如何 让cpu继续向下执行呢?就是ret了,call指令后面的指定的地址被存储在栈中,所以可以在子程序到后面使用ret指令,用栈中 的数据设置IP的值从而转到call指令后面的代码处继续执行,这样就可以利用call和ret来实现子程序的机制
13:mul指令(乘法指令)
* 相乘的两个数,要么都是8位,要么都是16位
8位:AL中和8位寄存器或内存字节单元中,结果在AX中
16位:AX中和16位寄存器或内存字单元中,结果在DX(高位)和AX(低位)中
* 格式:
mul reg
mul 内存单元
int(): 描述性运算符,取商,比如int(11/3)=9
rem(): 描述性运算符,取余数,比如rem(11/3)=2
13:标志寄存器
* 8086cpu的flag寄存器的结构
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OP DF IF TF SF ZF AF PF CF
1):ZF标志 (Zero)
falg的第6位,零标志位。它记录相关指令执行后,结果为0,则ZF=1,否则为0
注意:在8086cpu的指令集中,有的指令的执行是影响标志位的,比如:add,sub,mul,div,inc,or,and等,它们大多
都是运算指令(进行逻辑或算数运算);而有的指令没有影响,比如:mov,push,pop等,大都是传送指令
2):PF标志 (Parity n.平价,价值对等; 同等,平等; 奇偶性; )
falg的第2位,奇偶标志位。记录指令执行后,结果的最低位字节中“1”的个数若为偶数,则PF=1,否则为0
3):SF标志 (sign)
falg的第7位,符号标志位。它记录执行指令后,若结果为负,SF=1,否则SF=0,所以说,SF是cpu对有符号数元算 结果的一种记录,它记录数据的正负,无符号数就不用管此标志了
4): CF标志
falg的第0位,进位标志位。主要用来反映运算符是否产生进位或借位,如果进位,CF=1,否则为0
5):OF标志
falg的第11位,溢出标志位。
* CF和OF到区别:
# CF是对无符号数运算有意义的标志位;
# 而OF是对有符号数运算有意义的标志位
6) : adc指令
带进位加法指令。它利用了CF位上记录的进位值。
格式: adc 操作对象1,操作对象2
功能: 操作对象1=操作对象1+操作对象2+CF
7):sbb指令
带借位减法指令,它利用了CF位上记录的借位值
格式:sbb 操作对象1,操作对象2
功能:操作对象1=操作对象1-操作对象2-CF
8):cmp指令
格式:cmp 操作对象1,操作对策2
功能:计算操作对象1-操作对象2但并不保存结果,仅仅根据计算结果对标志寄存器进行设置
* 根据无符号数的比较结果进行转移的条件转移指令,他们检测ZF,CF的值
* 根据有符号数的比较结果进行转移的条件转移指令,他们检测SF,OF和ZF的值
9):检测比较结果的条件转移指令
//=================检测比较结果的条件转移指令===========================
条件转移指令小结
指令 含义 检测的相关标志位
je 等于则转移 ZF=1 jump equal
jne 不等于则转移 ZF=0 jump not equal
jb 低于则转移 CF=1 jump below
jnb 不低于则转移 CF=0
ja 高于则转移 CF=0,ZF=0 jump above
jna 不高于则转移 CF=1或ZF=1
*上面这些都是检测无符号数的指令
10):DF标志和串传送指令
falg的第10位,方向标志位。
# 在串处理指令中,控制每次操作后si,di的增减
* DF=0时:每次操作后,si,di递增
* DF=1时,每次操作后,si,di递减
格式1:movsb
功能:(以字节为单位传送)
a. es*16+di=ds*16+si
b. 如果DF=0,则si=si+1
di=di+1
否则: si=si-1
di=di-1
movsb的功能是将ds:si指向的内存单元的字节送入es:di中,然后根据标志寄存器DF位的值,将si和di
递增或递减
格式2:movsw
功能:(以字为单位传送)
将ds:si指向的内存单元的字单元送入es:di中,然后根据标志寄存器DF位的值,将si和di
递增2或递减2
一般来说,movsb和movsw都和rep配合使用,格式:rep movsb
rep作用是根据cx的值,重复执行后面传传送指令,这rep movsb可以循环实现cx个字符的传送,那么有没有设置DF
的值的指令呢?下面两条指令就是:
cld指令:将标志寄存器的DF位设置为0
std指令:将标志寄存器的DF位设置为1
11):pushf和popf
pushf:将标志寄存器的值压栈
popf:从栈中弹出数据,送入标志寄存器
12):标志寄存器在Debug中的表示
14:内中断*
1):内中断的产生
根据中断源的不同,可以把中断分为硬件中断和软件中断两大类,而硬件中断又可以分为外部中断和内部中断两类 ,主要讲解硬件中断。外部中断(如:键盘中断,打印机中断等)是可以屏蔽的中断,内部中断(如:除数为0,突然断电 ,运算溢出等)不可以屏蔽。
软件中断其实并不是真正的中断,只是可被调用执行的一般程序以及Dos系统功能调用(INT 21h)等,都是软件中断
2):中断处理程序
中断产生后,要定位中断处理程序(中断类型码就是用来定位中断处理程序的),需要知道其段地址和偏移地址,但 是如何根据中断类型码得到中断处理程序的段地址和偏移地址呢?这就要引入“中断向量表”了。
3):中断向量表
cpu用8位中断类型码通过中断向量表找到相应的中断处理程序的入口地址,中断向量表在内存中存放,其中存放着
256个中断源所对应的中断处理程序的入口,在8086cpu中,中断向量表在内存0000:0000到0000:03ff处的1024个字 节,(一个物理地址由段地址和偏移地址组成,即2个字,4个字节,4*256=1024)
4): 中断过程
a.(从中断信息中)取得中断类型码N
b. 标志寄存器的值入栈(保护标志位)(pushf)
c.设置标志寄存器的第8位TF和第9位IF的值为0(这样做的目的后面再说)(TF=0,IF=0)
d.CS的内容入栈(push CS)
e.IP的内容入栈(push IP)
f.从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS
((IP)=(N*4),(CS)=(N*4+2))
5): 中断处理程序
常规的步骤:
a. 保存用到的寄存器
b. 处理中断
c. 恢复用到的寄存器
d. 用iret指令返回
* iret指令的功能用汇编语法描述为:
pop IP
pip CS
popf
6):单步中断
cpu在执行完一条指令之后,如果检测到标志寄存器的TF位为1,则产生单步中断,引发中断过程
15: int指令
1)int指令
int格式:int n //n为中断类型码,功能是引发中断过程
2)编写供应用程序调用的中断例程
3)对int,iret和栈的深入理解
4)BIOS和DOS所提供的中断例程
5)BIOS和DOS中断例程的安装过程
6)BIOS中断例程应用
int 10h中断例程是BIOS提供的中断例程,其中包含了多个和屏幕输出相关的子程序
BIOS和DOS提供的中断例程,都是用ah来传递内部子程序的编号
7)DOS中断例程应用
int 21h中断例程是DOS提供的中断例程,其中包含了DOS提供给程序员在编程时调用的子程序
之前一直是这么用的:
mov ah,4ch //程序返回
mov al,0 //返回值
int 21h
16:端口
cpu可以直接读写3个地方的数据(cpu内部寄存器,内存单元,端口)
1)端口的读写
* 对端口的读写只有两条(不能用mov,push,pop等内存读写指令),in和out,注意:在int和out指令中,只能用
ax或al来存放从端口中读取或写入的数据,访问8位端口用al,16位端口用ax
* 对0-255以内的端口进行读写:
int al,20h //从20h端口读入一个字节
out 20h,al //向20h断口写入一个字节
* 对256-65535的端口进行读写时,端口号放在dx中
mov dx,3f8h //将端口号3f8h送入dx
in al,dx //从3f8h端口读入一个字节
out dx,al //向3f8h端口写入一个字节
2)CMOS RAM芯片
pc机中有一个CMOS RAM芯片,其特征如下:
* 包含一个实时钟和一个有128个存储单元(存储的就是一个开机信息,该信息给BIOS主板)的RAM存储器。( 早期的计算机为64个字节)
* 该芯片主要靠电池供电,因此,关机后其内部的实时钟任可工作,RAM的信息不会丢失
* 128个字节的RAM中,内部实时钟占用0-0dh单元来保存时间信息,其余大部分单元用于保存系统配置信息 供系统启动时BIOS读取
* 该芯片内部有两个端口,端口地址为70h和71h,cpu通过这两个端口读写CMOS RAM
* 70h为地址端口,存放要访问的CMOS RAM单元的地址:71h为数据端口,存放从选定的CMOS RAM单元中读 取的数据,或者写入到其中的数据
3) shl和shr指令
shl和shr是逻辑移位指令,后面会用到移位指令,这里先讲一下
* shl:逻辑左移指令,功能:
a.将一个寄存器或内存单元中的数据向左移位
b.将最后移出的一位写入CF中
c.最低位用0补充
例如:
mov al,01001000b
shl al,1 ;将al中的数据左移一位
执行后,(al)=10010000b,CF=0(最高位移出的放入CF)
注意:如果移动位数大于1时,必须将移动位数放在cl中,比如:
mov al,01010001b
mov cl,3
shl al,cl
执行后,(al)=10001000b,这里移出的是前面010三位,所以最后移出的一位就是右边从0,所以CF=0、
* shr:自己体会,不用写了
4)CMOS RAM存储的时间信息
* 在CMOS RAM中,存放着当前时间:
秒: 00h
分: 02h
时: 04h
日: 07h
月: 08h
年: 09h
这6个信息的长度都为1个字节,这些数据以BCD码(会计用到比较多)的方式存放:
数码:0 1 2 3 4
BCD码:0000 0001 0010 0011 0100
数码:5 6 7 8 9
BCD码:0101 0010 0111 1000 1001
比如十进制数26,对应的BCD码为:0010 0010
所以,一个字节可以表示两个BCD码,CMOS RAM存储 时间信息的单元中,存储了两个BCD码表示的两位十 进制数,高4位的BCD码表示十位,低4位的BCD码表示个位
17:外中断
* 以前我们讨论的都是CPU对指令的执行。我们知道,CPUA在计算机系统中,除了能够执行指令,进行运算之外,还应该能够
对外部设备进行控制,接收它们的输入,向它们输出。也就是说,CPU除了有运算能力外,还要有I/O能力
1):接口芯片和端口
2):外中断信息
在PC系统中,外中断源一共有两类:
a. 可屏蔽中断
b. 不可屏蔽中断
* 可屏蔽中断时CPU可以不响应的中断,CPU是否响应可屏蔽中断,要看标志寄存器的IF位的设置,现在知道了之前
内中断将IF设置为0的原因了吧,原因就是在进入中断处理程序后,禁止其他的可屏蔽中断
* 8086cpu提供的设置IF的指令如下:
sti,用于设置IF=1
cli,用于设置IF=0
* 外中断中,不可屏蔽中断的中断类型码固定为2,所以中断过程中,不必像内中断那样取中断类型码
* 不可屏蔽中断的中断过程:
a. 标志寄存器入栈,IF=0,TF=0
b. CS,IP入栈
c. (IP)=(8),(CS)=(0AH) //国定的一个地址
3):pc机键盘出处理过程
a.键盘输入
b.引发9号中断
c.执行int9中断例程
* BIOS提供了int 9中断例程,用来进行基本的键盘输入处理
4):编写int9中断例程(对键盘所处理的一个中断)
5):安装新的int'9中断例程