分析一个奇怪的程序
题目
分析下面的程序,在运行前思考:这个程序可以正确返回吗?
运行后再思考:为什么是这种结果?
通过这个程序加深对相关内容的理解。
1 | assume cs:code |
设想过程
首先我把编译后的结果放出来,方便下面理解
刚看到这个题目时所设想的程序执行过程为:
- 从start开始先执行
mov ax,0
- 继续执行 s里面的内容,这里把s2的地址传给了s
- 执行到s0的
jmp short s
,这时由于上一步将s2的地址传给了s,所以jmp short s
执行过后会执行s2的jmp short s1
- 下一步跳到s1,执行mov ax,0 int 21h 程序终止
但是实际debug的结果并非上述所示。下面我们一步一步看。
调试过程
程序开始时cs:ip在0005 mov ax,0
的位置
之后也执行了s里面的内容
至此已经执行了红框里面的指令
下一步执行 jmp short s
将会跳转到s的位置,也就是0008的位置
但是注意此时0008位置的指令已经变成了s2的 jmp short s1
,按理说下一步执行 jmp short s1
时会跳转到s1,也就是0018,但是执行jmp 0008
后的结果不是jmp 0018而是变成了jmp 0000
,这是为什么呢?
分析原因
我们看一下jmp 0008
执行完成之后程序的反编译的结果:
此时 0008 处的机器码为 EBF6 (jmp 0000)
还记得上面的图吗?
这张图里面0020处的机器码是EBF6(jmp 0018)。这就奇怪了, 为什么机器码明明都是EBF6,但为什么一个是jmp 0000
另一个是jmp 0018
呢?这就要说到jmp short指令了。
jmp指令
jmp为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。
jmp指令要给出两种信息:
- 转移的目的地址
- 转移的距离(段间转移、段内短转移、段内近转移)
不同的给出目的地址的方法,和不同的转移位置,对应有不同格式的jmp指令。
依据位移进行转移的jmp指令
jmp short 标号(转到标号处执行指令)
这种格式的jmp指令实现的是段内短转移,它对IP的修改范围为-128~127,也就是说,它向前转移时最多可以越过128个字节,向后转移可以最多越过127个字节。
我们再来看上面的jmp指令所对应的机器码,都是EB F6。这说明CPU在执行jmp指令时并不需要转移的目的地址。
那么CPU在执行 EB F6的时候是根据什么修改的IP,使其指向目标指令的呢?就是根据指令码中的F6。在jmp short 标号
指令所对应的机器码中,并不包含转移的目的地址,而包含的是转移的位移。这个位移是编译器根据汇编指令中的标号
计算出来的,具体计算方法如图所示。
实际上,jmp short 标号
的功能为:(IP)=(IP)+8位位移
,其中:
- 8位位移 = 标号处的地址 - jmp指令后的第一个字节的地址;
- short 指名此处的位移为8位位移;
- 8位位移的范围为 -128~127,用补码表示(第一位为1表示负数,第一位为0表示正数,正数的补码取反+1=原码,复数的补码取反+1=该数的绝对值);
- 8位位移由编译器在编译时算出;
还有一种和jmp short 标号
功能相似的指令格式,jmp near ptr 标号
,它实现的是段内近转移。
jmp near 标号
的功能为:(IP)=(IP)+16位位移
,其中:
- 16位位移 = 标号处的地址 - jmp指令后的第一个字节的地址;
- near 指名此处的位移为16位位移;
- 8位位移的范围为 -32768~32767,用补码表示;
- 8位位移由编译器在编译时算出;
回到之前的问题:此时可以算出0018H-0022H = -AH,用补码表示为F6。所以EB F6 表示向前转移10个字节。这就可以解释了为什么在0020时会jmp到0018,而在0008时jmp到0000。
注:此文部分摘抄自王爽 - 《汇编语言(第四版)》,若有侵权,可留言删除。