汇编语言(十)
第九章 转移指令的原理
我们8086cpu的转移行为有以下几类:
1 | 只修改IP,成为段内转移,比如:jamp ax |
对于IP的修改范围不同,段内转移又分为:短转移和近转移。
短转移的IP的修改范围为-128~127。
近转移IP的修改范围为-32768~32767。
操作符offset
功能为取得标号的偏移地址,比如下面的程序:
1 | assume cs:cosdeg |
下面看问题
所以只需要添加:
1 | mov ax,cs:[si]#先将s处的偏移地址放进ax |
jmp指令
给出两种信息:
1 | 转移的目的地址 |
依据位移进行转移的jmp指令
比如
1 | jmp short 标号#表示短转移,因为对IP修改的范围为-128~127,向后最多越过127个字节。 |
转移指令结束后,CS:IP应该指向标号处的指令
比如如下程序:
1 | assume cs:codesg |
我们看一下jmp指令执行的具体过程。举个例子
可以看到汇编指令中的立即数(idata)不论是一个数据还是一个内存单元的偏移地址,都会在机器指令中出现。
看来jmp 0008也就表示jmp short s,但是他的机器码却之后EB03,不包含转移的目的地址
那么我们再来看一个例子
看到本程序的机器码,对于执行jmp竟然也是EB03,说明cpu在执行jmp指令的时候不需要转移的目的地址。
我们可以具体看一下执行过程,就明白为什么了:
1 | 首先(CS)=0BBDH,(IP)=0006H,CS:IP指向EB03 |
其实EB03中告诉了CPU要转移的位移。也就是让IP向后移动3个字节。
而这个位移,是编译器根据汇编指令中的“标号”计算出来的,如图所示
这张图要好好回味,画得十分抽象!我来详细解释一下:也就是我们的CPU在执行指令缓冲期中的EBF7时,这时的cs:ip要提前向下读取,也就是已经指向了0009然而在EBF7处执行的指令是跳转到s也就是让IP指向0000,所以两者之间相差0-9=-9取它的补码也就是F7。
所以说“jmp short 标号”的功能为
1 | (IP)=(IP)+标号处的地址-jmp指令后的第一个字节的地址。 |
还有一种和jmp short 功能相似的指令:jmp near ptr 标号,其功能为:(IP)=(IP)+16位移
1 | (IP)=(IP)+标号处的地址-jmp指令后的第一个字节的地址。 |
转移的目的地址在指令中的jmp指令
1 | jmp far ptr 标号 实现的是段间转移,也就是“远转移” |
指明了指令用标号的段地址和偏移地址修改CS和IP。
看如下的程序:
注意,远转移和短转移可就不一样了,这个指令直接读取了目的地址并且直接执行跳转到目的地址,不用位移的形式了。如图所示:
当然后面那一大堆都是定义的dup(0)不用管他。
我们看到jmp far ptr s所对应的机器码
1 | EA0B01 BD0B在这里执行的数据也就是0B01 BD0B我们按照高低的顺序读,BD0B也就是新的段地址,而0B01也就是偏移地址。 |
转移地址在寄存器中的jmp指令
指令:jmp 16位reg
功能:(ip)=(16位reg)
这种指令很好理解,不再多说。
转移指令在内存中的jmp指令
第一种格式
1 | jmp word ptr 内存单元地址(段内转移) |
例如:
1 | mov ax,0123h |
或者
1 | mov ax,0123h |
第二种格式
1 | jmp dword ptr 内存单元地址(段间转移)#这里写了double字也就是两个字,高地址处的字是转移的目的段地址,低地址处的字是转移的偏移地址。 |
比如下面的程序
1 | mov ax,0123h |
执行之后(cs)=0(也就对应了ds:[2]处的一个字被改为0了),(ip)=0123h。所以cs:ip指向0000:0123h
再看下面的指令
1 | mov ax,0123h |
也就是[bx]处的两个内存单元被设置为0123h,高地址的两个内存单元被设置为0
执行后结果与上面相同。
我们来看一个例子:
1 | assume cs:code |
若要使程序中的jmp指令执行后,cs:ip指向程序的第一条指令,则data段中应该定义哪些数据
我们自习思考,结果应该如下:
1 | first_instruction dw offset start |
这里的offset start也就是显示出start的偏移地址
所以也就是定义了一个字,正好两个字节,读取为跳转的目的ip,非常巧妙!
再来看下一个程序
1 | assume cs:code |
补全程序jmp执行之后cs:ip指向程序第一条指令。
显然要读取ds的前四个字节也就是两个字作为cs:ip,显然先读取的是ip应该设置为offset start,后面两个字节的cs应该设置为直接写cs。
再来看最后一个练习
显然执行过后(cs)=0006, (ip)=00BE
jcxz指令
为条件转移指令,所有的条件转移指令都是短转移。用位移表示。
与short s的执行过程一样,都是偏移一个8位的位移。
jcxz指令相当于:
1 | if((cx)==0) jmp short 标号 |
来看一个例题:
利用jcxz指令,实现在内存2000H段中查找第一个值为0的字节。找到后,将它的偏移地址存储在dx中。
1 | assume cs:code |
看我如下示例(不一定正确,因为我并没有正确答案)
1 | s:mov cl,[bx] |
loop指令
循环指令,所有的转移都为短转移。在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围都为:-128~127
loop指令的功能相当于
1 | (cx)--; |
来看如下程序:利用loop指令,实现在内存2000h中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。(也就是换个指令玩玩)
1 | assume cs:code |
这里我给出
1 | jcxz ok#因为只要前面cl找到0,就可以利用jcxz直接跳出去,如果没找到0,也就不跳出去继续bx+1然后执行循环继续查找 |
根据位移进行转移的意义
我们总结一下根据位移进行转移的指令
1 | jmp short 标号 |
如果我们都不用位移而是用地址,标号的地址,那么就会对程序段在内存中的偏移地址有严格的限制!
编译器对转移位移超界的检测
比如下面的跳转超过了IP可以跳转的界限,就会报错!
以为jmp short s的转移范围是-128-127所以最多向后移动127个字节!
实验:分析一个很难的程序!是否能成功返回
1 | assume cs:codesg |