mov指令(传送指令)
PS:
(1) 3、4中的[0],0为内存单元的偏移地址,段地址自动从DS寄存器中获取
(2) mov al,[0]传送的是字节型数据(8位),而mov ax,[0](16位)传送的则是字型数据,由寄存器位数决定
(3) mov用在寄存器之间传送数据的指令长度是2个字节,用在寄存器和立即数之间是3个字节,用在寄存器和内存单元之间是2个字节。
add指令、sub指令(加法指令、减法指令)
和mov一样 都有两个操作对象,都有以下几种形式:
push指令、pop指令(入栈指令、出栈指令)
格式如下:
push 寄存器(包括段寄存器) 将一个寄存器中的数据入栈
pop 寄存器(包括段寄存器) 将栈顶元素出栈放到一个寄存器中
push 内存单元 将一个内存单元处的字入栈
pop 内存单元 将栈顶元素出栈放到一个内存单元中
PS:
(1) 8086的入栈出栈操作都是以字为单位的,对于8086而言,pop先读内存后SP+2,push先写内存后SP-2
(2) pop 寄存器 是将栈顶元素放到寄存器中,并不是将栈中与寄存器对应的值出栈,初学者容易误解。
(3) push和pop指令访问的内存单元地址由SS:SP给出
(4) 8086无法保证我们对栈操作是数据不会越界
(5) 栈的实质是我们自己约定的一段内存空间,计算机只记录栈顶,栈空间的大小我们要自己管理
and指令、or指令(逻辑与、逻辑或)
and指令:逻辑与指令,按位进行与运算
or指令:逻辑或指令,按位进行或运算
div指令(除法指令)
(1)除数:有8位和16位,在一个reg(寄存器)或内存单元中
(2)被除数:若除数为8位,则被除数为16位,存放在AX中;若除数为16位,则被除数为32位,存放在AX和DX中,AX中存放低16位,DX中存放高16位
(3)结果:若除数为8位,则商存放在AL中,余数存放在AH中;若除数为16位,则商存放在AX中,余数存放在DX中。
mul指令(乘法指令)
(1)两个相乘的数:要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存单元中;如果是16位,一个默认放在AX中,另一个放在16位reg或内存单元中;
(2)结果:如果是8位乘法,结果默认放在AX中;如果是16位乘法,结果默认放在AX和DX中,高16位在DX中,低16位在AX中。
除法指令举例:
div byte ptr es:[0]
含义:
(al)=(ax)/(es*16+0)的商
(ah)=(ax)/(es*16+0)的余数
div word ptr es:[0]
含义:
(ax)=((dx)*10000H+(ax))/((es)*16+0)的商
(bx)=((dx)*10000H+(ax))/((es)*16+0)的余数
shl指令、shr指令(移位指令)
shl指令
逻辑左移指令,功能是将一个寄存器或内存单元中的数据向左移位,然后将最后移出的一位写入标志寄存器的CF位,低位用0补充。示例:
mov al,11001100B
shl al,1
左移一位,移位后(al)=10011000,CF=1
mov al,11001100B
mov cl,3
shl al,cl
左移3位,移位后(al)=01100000,CF=0
shr指令
逻辑右移指令,功能是将一个寄存器或内存单元中的数据向右移位,然后将最后移出的一位写入标志寄存器的CF位,高位用0补充。
PS:如果移动位数大于1,必须将移动位数放在cl中
转移指令
可以修改IP,或同时修改CS和IP的指令统称转移指令。概括地将,转移指令就是可以控制CPU执行内存中某处代码的指令。 转移指令分为以下几类:
这些转移指令的前提条件可能不同,但转移的基本原理是相同的,以无条件转移指令jmp来理解CPU执行转移指令的基本原理。首先要知道转移指令分为段间转移和段内转移,段内转移又分短转移和近转移。
段间转移指令执行结果同时修改CS和IP,查看段间转移指令对应的机器指令可以看到指令中包含4字节的目标地址;
段内转移指令执行结果只修改IP,其中短转移转移的距离范围为-128~127,近转移转移的距离范围为-32768~32767;
无条件转移指令jmp指令:
有条件转移指令jcxz指令:
指令格式: jcxz 标号 (如果(cx)=0,则转移到标号处;否则继续执行下一条指令)
PS:所有的有条件转移指令都是短转移,在对应的机器指令中包含转移的位移(转移距离),而不是目标地址
循环指令loop指令:
格式为: loop 标号 标号用来标记循环执行的指令
cs中保存了循环的次数,执行loop指令前要先执行对cx的赋值指令。执行loop指令时,要执行两步操作:(1)(cx)=(cx)-1 (2)判断(cx)的值,若(cx)=0,则往下指令;若(cx)不为0,则执行标号标记的指令。
PS:所有的循环指令都是短转移,在对应的机器指令中包含转移的位移(转移距离),而不是目标地址
call和ret指令
ret指令用栈中的数据,修改IP的内容,从而实现近转移。执行ret指令,相当于执行pop IP;
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。执行retf指令,相当于先后执行pop IP;pop CS
call指令相当于在执行jmp指令之前先将当前的IP内容push到栈中保存,转移的原理和jmp一样。
call指令格式:
call指令和ref指令的配合使用
配合使用call指令和ref指令可以用来实现子程序的实际。格式为:
main: ...
(主程序)
...
call sub ;调用子程序sub
...
...
mov ax,4c00h
mov int 21h
sub: ...
(子程序)
...
ref ;子程序返回
通用(数据)寄存器
AX:累加寄存器,也称之为累加器
BX:基地址寄存器
CX:计数器寄存器
DX:数据寄存器
PS:
(1) 这四个寄存器都由2个8位寄存器组成,例如AX由8位的AH和AL组成
(2) 程序段的长度保存在CX中
段寄存器
CS:存放要访问的指令的段地址
DS:存放要访问的数据的段地址
SS:存放栈顶的段地址
ES:附加段寄存器
指针寄存器
IP:存放要访问的指令的偏移地址
SP:存放栈顶的偏移地址
BP 基指针寄存器
变址寄存器
SI:源变址寄存器
DI:目的变址寄存器
PS:
(1) 不能直接像段寄存器送入数据,可以从内存单元或其他合法寄存器中送入;另外两个内存单元之间也不能直接传送数据
(2) 在8086CPU中,只有BX、SI、DI、BP这4个寄存器可以用在[…]中来进行内存单元的寻址,如mov ax,[bx]是合法的,mov ax,[cx]是不合法的;但是这4个寄存器多个组合的话只能以4种组合方式一起出现:bx和si bx和di bp和si bp和di
(3) 只要在[…]中使用寄存器bp,而指令中没有显示地给出段地址(即以段前缀的方式给出),那么段地址默认保存在ss中,而不在ds中
标志寄存器flag
flag寄存器是按位起作用的,每一位都有专门的意义,记录特定的信息。
标志位介绍
ZF标志位:
零标志位,记录相关指令执行后,其结果是否为0。如果结果是0,那么ZF=1;如果结果不为0,那么ZF=0;
PF标志位:
奇偶标志位,记录相关指令执行后,及结果的所有bit位中1的个数是否为偶数。如果1的个数为偶数,PF=1;如果为奇数,PF=1;
SF标志位:
,符号记录相关指令执行后,其结果是否为负。如果结果为负,SF=0;如果非负,SF=1;
CF标志位:
进位标志位,在进行无符号数运算的时候,它记录了运算结果的做高有效位向更高位的进位值,或从更高位的借位值;
OF标志位:
溢出标志位,在进行有符号数运算的时候,它记录了运算结果是否发生溢出。若结果溢出,OF=1;若不溢出,OF=0;
DF标志位:
方向标志位,在串传送指令(见下面)中,控制每次操作后si、di的增减。fd=0,每次操作后si、di递增;df=1,每次操作后si、di递减。
标志位相关的指令
adc指令:
带进位加法指令,利用了CF位上记录的进位值。比如指令 adc ax,bx 实现的功能是:(ax)=(ax)+(bx)+CF
adc指令:
带借位减法指令,利用了CF位上记录的借位值。比如指令 sbb ax,bx 实现的功能是:(ax)=(ax)-(bx)-CF
cmp指令:
比较指令,其功能相当于减法指令,只是不保存结果,但是对标志寄存器产生影响。比如指令 cmp ax,ax 做(ax)-(ax)的运算,结果为0,但是不在ax中保存,仅影响flag的相关标志位。指令执行后,zf=1,pf=0,sf=0,cf=0,of=0。对于无符号数和有符号数,cmp指令影响的标志位不同。
检测比较结果的条件转移指令:
根据标志寄存器中相关标志位的值判断是否进行转移。这里以无符号数的比较结果为例进行说明(有符号数比较结果又是另一套判断标准和指令),例如je指令,若zf=1,则发生转移。其他无符号数比较结果的条件转移指令指令包括:
指令 含义 检测的相关标志位 je 等于则转移 zf=1 jne 不等于则转移 zf=0 jb 小于则转移 cf=1 jnb 不小于则转移 cf=0 ja 大于则转移 cf=0且zf=0 jna 不大于则转移 cf=1或zf=1
实际上这些条件转移指令的判断条件是根据相关标志位判断是否转移的,而之所以称之为检测结果的条件转移指令,是因为它配合cmp指令使用可以检测比较结果。例如:
cmp al,10h
je do ;若ax等于10h,则执行do。相当于C语言的语法:if((al)=10h) do;
串传送指令:
movsb指令 相当于执行下面几步操作:
((es)\*16+(di))=((ds\*16)+(si))
如果df=0 则 (si)=(si)+1 (di)=(di)+1
如果df=1 则 (si)=(si)-1 (di)=(di)-1
movsw指令 每次传送一个字的数据,即上面±1变成±2
movsb和movsw指令通常配合reg使用,格式为 rep movsb rep的作用是根据cx的值,重复执行后面的传送指令,实现了(cx)个字符的传送,相当于执行:
s:movsb
loop s
cld指令和std指令:
cld:设置df=0,正向传送
std:设置df=1,负向传送
pushf指令和popf指令:
pushf:将标志寄存器的值压栈
popf:从栈中取出数据送到标志寄存器中
Debug是Dos、Windows都提供的实模式(8086方式)程序的调试工具,可以用来查看CPU各种寄存器的内容、内存的情况和在机器码级跟踪程序的运行。
和汇编密切相关的几个Debug命令:
汇编程序从编辑到执行的全过程:
编辑–产生.asm源程序文件–编译–产生.obj目标文件–连接–产生.exe可执行文件–加载在内存中运行
一条指令的执行过程:
(1) CPU将CS:IP指向的指令取到指令缓冲寄存器中
(2) IP+当前指令长度,指向下一条指令
(3) 执行指令
Debug和汇编编译器masm对指令的不同处理
[idata]的不同处理
用idata表示一个常量,指令中若出现[idata],Debug对idata的解释是一个偏移地址,而汇编编译器对idata的解释只是一个常量。以指令mov al,[0]为例:
在Debug中,该指令的作用是(ax)=((ds)*16+0)
对于汇编源程序,该指令和mov al,0是一样的,作用是(ax)=0
因此在汇编源程序中,要想把[idata]解释成偏移地址,不能简单地写[idata],而要在[idata]前加上对应段寄存器的名称。以 mov al,[0] 为例,应写成 mov al,ds:[0] 这里个段寄存器名称(ds)被称为段前缀。
另一种方法则是现将常量保存到一个通用寄存器中,例如bx,然后mov ax,[bx]。
立即数的不同处理
以mov ax 1001为例,Debug对1001的识别是16进制,而编译器将其识别成10进制,因此在源程序中16进制的1001要表示成1001H;另外编译器不能识别字母开头的立即数,因此对已b123H,在源程序中要写成0b123H。
标号
assume cs:code
code segment
a dw 1,2,3,4,5,6,7,8
b dd 0
start: mov si,0
mov cx,8
s: mov ax,a[si]
add a[16],ax
adc a[18],0
add si,2
loop s
mov ax,4c00h
int 21h
code ends
end start
上面这段代码中,code、start、s、a、b都是标号,但是code、start、s和a、b有很大的区别。前者标号仅仅表示了内存单元的地址,后者不仅仅表示了内存单元的地址,还表示了内存单元的长度,后者不需要加冒号(段名是特殊的标号,属于前者,也不需要加冒号)。这种包含单元长度的标号,可以使我们以简洁的形式访问内存中的数据,将这种标号成为数据标号。
mov ax,code ;将code段的段地址传送给ax
mov ax,offset s ;j将s的偏移地址传送给ax
mov ax,seg s ;将s的段地址传送给ax
mov ax,a[si] ;相当于mov ax,cs:0[si]
mov dword ptr b ;相当于mov dword ptr cs:16
如果想在代码段中直接用数据标号访问数据,则需要用伪指令assume将标号所在的段和一个段寄存器联系起来。
start标号
用于标记程序的入口地址。若不使用该标记,则程序从最先分配的空间开始执行。
操作符offset
用于获取标号的偏移地址,例如:
mov ax,offset start 相当于 mov ax,0
因为start是代码段的标号,它所标记的指令是代码段的第一条指令,偏移地址为0。
操作符seg
用于获取标号的段地址
数据定义
db定义字节型数据 占8位
dw定义字型数据 占16位
dd定义双字型数据 占32位
dup:配合dw、db、dd进行数据的重复定义,如
db 3 dup (0) 表示定义了3个字节型的0,等同于 db 0,0,0
db 3 dup (‘abc’,’ABC’) 等同于 db ‘abcABCabcABCabcABC’
dup格式如下:
db 重复的次数 dup (重复的字节型数据)
dw 重复的次数 dup (重复的字型数据)
dd 重复的次数 dup (重复的双字型数据)
dw、db、dd、dup都是伪指令,是由编译器识别处理的
内存分配
程序加载后分配空间是以16个字节为单位的,如果不足16个字节也分配16个字节
X ptr
计算机如何确定指定要处理的数据有多长?如果指令中有对寄存器的操作,则可以根据寄存器类型确定,如ax表明要处理的数据类型为2个字节(8086),al或ah则表明要处理的数据长度为1字节。
而在没有寄存器参与的内存单元的访问指令中,必须要显示地指明操作的数据长度,否则CPU无法得知所要访问的内存单元是子单元还是字节单元。用word ptr表明操作的内存单元为子单元,数据位16位;用byte ptr表明操作的内存单元为字节单元,数据位8位。例:
mov word ptr ds:[0] 1
inc byte ptr [bx]
add word ptr [bx],2
PS:
(1) 这样可以直接操作内存单元中的数据,而不需要经过寄存器中转
(2) push、pop指令是例外,例如push [0]并不需要显示指明操作的数据长度,入栈出栈指令只进行字操作
内存中数据的存数
低位在前,高位在后。例如将双字型的16存储到内存中,内存单元从上到下存储的二进制数一次为 10 00 00 00
段
(1) 在程序中,段名就相当于一个标号,它代表了该段的段地址。程序中对段名的引用,将被编译器处理为一个表示段地址的数值。因此 mov ds,data 是不合法的(data为数据段段名)。
内中断,即CPU内部的中断信息,当CPU内部有下面的情况发生的时候,将产生响应的中断信息:
(1) 除法错误,比如,执行div指令产生的除法溢出
(2) 单步执行
(3) 执行into指令
(4) 执行int指令
涉及到的相关概念如下:
中断源:产生中断信息的来源,例如上面4种中断源
中断类型码:用来标识中断源,例如上面4种中断源的中断码依次为 0、1、4、n;中断码是一个字节型数据,可以表示256种中断信息的来源
中断处理程序:用来处理中断信息的程序,又称中断例程
中断向量:中断处理程序的入口地址,一个中断向量占4个连续的内存单元
中断向量表:中断处理程序入口地址的列表,一段连续的内存单元。8086机的内存单元0000:0000到0000:03FF指定用来作为中断向量表存放中断向量,共1024个内存单元,可存放256个中断向量
中断过程:
CPU在收到中断信息后,如果处理该中断信息,就完成一个由硬件自动执行的中断过程(程序员无法改变这个过程中所要做的工作)。中断过程的主要任务就是用中断类型码在中断向量表中找到中断服务程序的入口地址,设置CS和IP。因为中断服务程序执行完成后,CPU还要回过头来继续执行被中断的程序,所以要在设置CS、IP之前,先将它们的值保存起来(CPU将它们保存到栈中)。在中断过程中还要做的一个工作是设置标志寄存器TF、IF位为0,因为TF一旦为1,CPU就要执行单步中断(下面介绍)。另外在执行完中断服务程序后,需要回复到进入中断程序之前的CPU现场,所以应该在修改标记寄存器之前,先将标记寄存器的值入栈。
中断过程如下:
(1)取得中断类型码N
(2)pushf
(3)TF=0 IF=0
(4)push CS
(5)push IP
(6)(IP)=(N\*4),(CS)=(N\*4+2)
iret指令
iret指令通常和硬件自动完成的中断过程配合使用,前面已经知道在中断过程中,标志寄存器、CS、IP依次入栈。那么在中断服务程序处理结束之前,就要依次出栈IP、CS、标志寄存器以恢复现场。iret指令的作用就相当于:
pop IP
pop CS
popf
单步中断:
我们在Debug的时候,使用t命令,可以执行一条指令。然而思考一个问题,既然只执行一条指令,那么屏幕上各个寄存器的值是怎么显示出来的呢?显示肯定也是需要指令来控制的啊?显然,这里不只执行了一个指令。在使用t命令之后,CPU产生了一个内中断,对应的中断服务程序的功能就是显示各个寄存器的值。这个中断就是单步中断,中断类型码为1。
int指令
前面讲过产生内中断的其中一种方式是执行int指令,该指令格式为
int n
n为中断类型码,因此int指令的功能就是产生指定中断类型码的中断。
BIOS和DOS中的中断例程:
我们可以自行编写中断例程,将它们放到安装程序中,然后运行安装程序,将它们安装到指定的内存区。此后,别的应用程序才能调用。
BIOS而DOS中的中断例程是如何安装到内存的?过程如下:
BIOS和DOS所提供的中断例程中包含了许多子程序,这些子程序实现了程序员在编程的时候经常需要用到的功能。程序员在编程的时候,可以用int指令直接调用BIOS和DOS提供的中断例程,来完成某些工作。
一般来说,一个供程序员调用的中断例程中往往包含多个子程序,中断例程内部用传递进来的参数决定执行哪一个子程序。BIOS和DOS提供的中断例程,都用ah来传递内部子程序的编号。以程序返回代码为例:
mov ax,4c00h
int 21h
调用21号中断例程的4ch号子程序,功能为程序返回。DOS为程序员提供了许多可以调用的子程序,都包含在int 21h中断例程中,具体可以查阅相关书籍。
在PC机系统中,和CPU通过总线项链的芯片除了各种存储器外,还有各种接口芯片,这些芯片作为CPU和接口设备或者接口卡(如网卡、显卡)之间的连接枢纽。在这些芯片中,都有一组可以由CPU读写的寄存器,CPU将这些寄存器当做端口,对它们进行统一编址,从而建立了一个统一的端口地址空间。每一个端口在地址空间中都有一个地址,CPU在访问端口的时候通过端口地址来定端口,从而读写端口中的数据。在PC系统中,CPU最多可以定位64KB个不同的端口,即端口的地址范围为0~65535
对端口的读写不能用mov、push、pop等内存读写指令,只能用in和out进行读和写。在in和out指令中,只能使用al或ax来存放从端口中读出的数据或要发送到端口中的数据。访问8位端口用al,访问16位端口用ax。例如:
in al,20h ;从20h号端口中读一个字节
out 20h,al ;向20h号端口写一个字节
如果要访问的端口的地址为256~65535,则需要把端口号放在dx中。例如:
mov dx,3f8h
in al,dx ;从3f8h号端口读一个字节
out dx,al ;向3f8h号端口写一个字节
前面介绍过内中断,中断信息来自CPU内部。当中断信息来自CPU外部,即以外设作为中断源时,产生的中断信息称为外中断信息。因此外中断就是由外部设备引发的CPU中断。 当CPU外部有需要处理的事情发生的时候,比如说,外设的输入到达,相关芯片将向CPU发出相应的中断信息。CPU在执行完当前指令后,可以检测到发送过来的中断信息,引发中断过程,处理外设的输入。
可屏蔽中断
可屏蔽中断是CPU可以不响应的中断。CPU是否响应可屏蔽中断,要看标志寄存器的IF位的设置。当CPU检测到可屏蔽中断时,如果IF=1,则CPU在执行完当前指令后响应中断,引发中断过程;如果IF=0,则不响应可屏蔽中断。可屏蔽中断的中断类型码由CPU从端口中获取,例如对于基本的键盘输入,BOIS提供了9号中断例程;另外还提供了16号中断例程来读取键盘缓冲区,提供13号中断例程来对磁盘进行读写。获取到中断类型码之后其余的中断过程和内中断一致。
不可屏蔽中断
不可屏蔽中断是CPU必须响应的外中断。当CPU检测到不可屏蔽中断信息时,则在执行完当前指令后,立即响应,引发中断过程。不可屏蔽中断的中断类型码固定为2。
几乎所有由外设引发的外中断,都是可屏蔽中断。不可屏蔽中断是在系统中有必须要处理的紧急情况发生时用来通知CPU的中断信息。
实模式,又叫实地址模式,CPU完全按照8086的实际寻址方法访问从00000h–FFFFFh(1MB大小)的地址范围的内存,在这种模式下,CPU只能做单任务运行;寻址公式为:物理地址=左移4位的段地址+偏移地址,即:物理地址是由16位的段地址和16位的段内偏移地址组成的。
实模式是CPU启动的时候的模式 这时候就相当于一个速度超快的8086 不能使用多线程 不能实现权限分级 还不能访问20位以上地址线,也就是说只能访问1M内存(!!!)
又叫内存保护模式,寻址采用32位段和偏移量,最大寻址空间4GB,在这种模式下,系统运行于多任务,设计这种模式的原因和好处是:保护模式增加了寻址空间,增加了对多任务的支持,增加了段页式寻址机制的内存管理(分段机制使得段具有访问权限和特权级,各应用程序和操作系统的代码和核心是被保护的,这也是多任务支持的实现关键和保护这个名字的由来)。寻址过程为:物理地址=由段地址查询全局描述符表中给出的段基址+偏移地址,即:物理地址由影像寄存器中的基址加上16位或者32位的偏移组成。
操作系统接管CPU后,会使CPU进入保护模式。这时候可以发挥80x86的所有威力,包括权限分级.内存分页.等等等等各种功能
虚拟8086模式是运行在保护模式中的实模式,为了在32位保护模式下执行纯16位程序。它不是一个真正的CPU模式,还属于保护模式。在windows系统中调出command.com就是从保护模式切换到虚拟8086模式,个人认为cmd不是虚拟8086模式。
在Windows/DOS操作系统中Command是16位命令行的操作界面,可以模拟实现DOS下的大部分功能。
在windows2000后开始被cmd.exe替代,但由于16位程序需要Command其实仍被保留在系统中。
在windows2003后开始被cmd.exe进一步替代,即使运行16位程序也会在运行后被强制返回cmd.exe。由于64位CPU对16位程序兼容性等原因,在64位系统中(例如win7的64位版)Command正式退役不再能正常使用。
区分command和cmd的简单方法:
一版情况下,窗口右侧带有滚动条的是cmd.exe,没有的则是command。
个人理解:command.com是16位操作系统(比如DOS)的shell。在32位系统的windows 2000中被cmd.exe替代,但是为了兼容16位程序,command.com在windows 2000中被保留。到了windows 2003,command.com被cmd.exe进一步代替,在command中运行完16位程序后(dos程序)会被强制返回cmd。到了64位,CPU无法再兼容16位程序,command正式退役,因此在64位系统中无法再运行command。在没有command的系统中,要运行16位程序则要借助第三方DOS模拟程序,比如dosbox。
操作系统是由多个功能模块组成的庞大、复杂的软件系统。任何通用的操作系统,都要提供一个称为shell(外壳)的程序,通常又被称为命令行工具或命令解释器,用户(操作人员)可以使用这个程序来操作计算机系统进行工作。
例如:DOS的shell是command,windows的shell则是cmd.exe(emd.exe类似于DOS操作系统,但它只是一个32位的程序,在64位windows系统中也可以打开);mac osx的shell则是Bash。