/*******8086CPU执行指令的步骤********/
极重要:
1、从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器
2、(IP)=(IP)+所读取指令的长度,从而指向下一条指令
3、执行指令,转到第一步,重复这三步
(注意:CPU是先指向要执行指令的下一条指令再开始执行指令)
/******offset:获取标记地址******/
获得伪代码中的标记地址后,我们可以在这些地址中任意跳跃,非常方便,那么怎么获得这种内存地址呢?下面的例子一看就明白:
assume cs:codeseg codeseg segment start:mov ax,offset start s:mov bx,offset s codeseg ends end startoffset 【标号】指令就可以获取到标号处的地址,上面的例子中,ax寄存器存放了start的首地址,bx存放了s的首地址
/******jmp指令*********/
一种无条件转移指令
(1)根据位移地址转移:短转移
1、jmp short 【标号】---->转移到标号处执行指令---->对IP修改范围为-128~127
等价功能:(IP)=(IP)+8位位移(也就是说CPU只需要知道指令转移的偏移位置即可)
对应的机器码:EB【偏移量】(偏移量指的是偏移的字节数,从开始的CPU执行指令的步骤来看,jmp应该跳到位置是:jmp下一条指令的首地址+偏移的字节数)
2、jmp near 【标号】---->功能同上---->对IP的修改范围为-32768~32767
等价功能:(IP)=(IP)+16位位移(16位的偏移量就说明为什么是-32768~32767)
(注意:上面两种转移都只是向CPU提供了偏移量)
(2)根据目的地址转移:远转移
jmp far ptr 【标号】---->实现段间转移,称为远转移---->修改的是CS和IP
对应机器码:EA【IP值】【CS值】
(3)根据寄存器转移
jmp 16位 寄存器---->通过修改IP值来转移指令
有点儿类似于mov IP,ax。当然,mov指令是不能直接修改IP寄存器的,这儿只是利于理解
(4)根据内存数据转移
1、jmp word ptr 内存单元地址---->从内存单元地址处取一个字的数据来实现段内转移---->修改IP
2、jmp dword ptr 内存单元地址---->从内存单元地址处取两个字的数据来实现段间转移---->高地址的字修改CS,低地址的字修改IP
/******jcxz:条件转移*********/
jcxz是要满足一定条件才转移的,所有的条件的转移指令都是短转移
jcxz 【标号】---->(cx)=0的时候就转移到标号处,(cx)不等于0的时候什么都不做,有点类似于C语言中的if语句
/********loop:循环指令***********/
所有的循环指令都是短转移
loop 【标号】---->每次执行loop之前,(cx)=(cx)-1,(cx)不等于0就跳到标号处
/***********汇编中的子程序原理*************/
(1)ret、retf 转移指令详解
1、ret---->根据栈顶元素来修改IP,实现近转移
CPU执行ret时的步骤:(IP)=(ss)*16+(SP)---->(SP)=(SP)+2
等价于:pop IP
2、retf---->根据栈顶元素来修改CS和IP,实现远转移
CPU执行retf时的步骤:(IP)=(SS)*16+(SP)---->(SP)=(SP)+2---->(CS)=(SS)*16+(SP)---->(SP)=(SP)+2
等价于:pop IP---->pop CS
(2)call指令:
1、概括:CPU执行call指令时:将当前IP或CS和IP压入栈中,转移
2、根据位移地址转移
call 【标号】---->等价于:1.push IP 2.jmp near ptr 【标号】---->根据前面的jmp知识可知转移位移为-32678~32767
3、根据目的地址转移
call far ptr 【标号】---->等价于:1.push CS 2.push IP 3.jmp far ptr 【标号】---->很明显这是一个段间转移
4、根据寄存器转移
call 16位寄存器---->等价于:1.push IP 2.jmp 16位寄存器
5、根据内存数据转移
call word ptr 内存单元地址---->等价于:1.push IP 2.jmp word ptr 内存单元地址
call dword ptr 内存单元地址---->等价于:1.push CS 2.push IP 3.jmp dword ptr 内存单元地址
(3)模块化设计-----双剑合璧:call、ret
我们可以利用这样的模式来实现一种类似于C语言中的函数调用:
call s
...
s:
...(s段的内容,即子程序实现的功能)
ret
理解这种用法我们就要需要通过CPU执行指令的过程来看:
(子程序原理,极重要)
1、CPU读取call s指令,作为将要执行的指令
2、然后IP指向call s的下一条指令
3、这个时候执行call s指令,将IP值入栈,其实就相当于保存了call s指令的下一条指令的首地址
4、转移到s段开始执行,直到ret处,IP值可以暂时不考虑了,因为ret会相当于pop IP指令,那么此时IP就是在执行call s的时候压入的IP值,即call s指令的下一条指令的首地址
5、很正常的可以想到,此时CPU会从call s的下一条指令开始执行,这就相当于到了s段执行完了子程序功能后又回到上一个地址继续执行指令
从这个模块化设计中我们可以了解到C语言中函数调用的实现,以及那些比较难理解的过程活动记录入栈之类的问题都可以很容易想到了。