引言:
在8086CPU中,可以修改IP(Instruction Pointer ,指令指针寄存器)或同时修改CS(Code Segment,代码段寄存器)和IP的指令称为转移指令,更通俗的说,转移指令就是控制CPU执行内存中某处代码的指令。jmp 指令就属于转移指令中其中的一员,而且是无条件转移的。
为了更加清晰明了的解读汇编语言中的转移指令 jmp,我们还需要了解一下CPU执行指令的过程:
(1)从CS:IP指向的内存单元中读取指令,读取的指令放入CPU的指令缓冲区中;
(2)修改IP的值,(IP) = (IP) + 所读取到指令的指令的长度(以字节为单位),从而使IP指向下一条指令;
(3)执行指令缓冲区中的指令
(4)转到步骤(1),重复执行这个过程
1. 根据位移进行转移的 jmp 指令
根据位移进行转移的 jmp 指令是段内转移指令,即只修改IP的转移指令,位移是相对于当前 IP 的转移位移,根据转移的位移范围,分为段内短转移(jmp short 标号)和段内近转移(jmp near ptr 标号)
1.1 jmp short 标号
jmp short 标号是段内短转移指令,该汇编指令的功能是转到标号处执行指令,其对应的机器指令码由2个字节表示,低8位表示该指令是段内短转移指令,高8位表示位移,位移范围在 -128 ~ 127 之间,也就是向前转移时,最多越过128个字节,向后转移时最多越过127个字节。基本原理如下:
(1) short指明此处的位移为8位位移,8位位移用补码形式表示,位移范围在 -128 ~ 127 之间;
(2)(IP)=(IP)+8位位移(补码形式表示);
(3)8位位移由编译程序在编译时算出,8位位移 = 标号所在处的指令的首地址 - jmp指令所在处的下一条指令的首地址(因为根据文章开头所描述的CPU执行指令的过程可以知道,CPU执行存储在指令缓冲区中的 jmp 指令前,IP已经指向 jmp 指令的下一条指令)。
;jmp short 标号 基本原理;1. short指明此处的位移为8位位移,8位位移用补码形式表示,位移范围在 -128 ~ 127 之间;2. (IP)=(IP)+8位位移(补码形式表示);3. 8位位移由编译程序在编译时算出,8位位移 = 标号所在处的指令的首地址 - jmp指令所在处的下一条指令的首地址
assume cs:code
code segment
start:mov ax, 0 ;机器码:B80000,3字节jmp short s ;机器码:EB03,2字节add ax, 2 ;机器码:050200, 3字节
s: inc ax ;机器码:40h,1字节mov ax, 4c00h ;机器码:B8004C, 3字节int 21h ;机器码:CD21, 2字节
code ends
end start
注意到红长方形框出来的机器指令了吧,汇编指令:jmp short s 被编译器预编译后生成:JMP 0008(jmp s标号指令所在的首地址),编译最后生成的机器指令为:03EB,低8位 EBh 代表的是jmp的段内短转移指令(参考8086CPU的对应的指令编码表可知),高8位 03h 就是要转移的位移。
(1)标号所在处的指令的首地址 - jmp指令所在处的下一条指令的首地址 = 8位位移,
即 0008h - 0005h = 03h;
(2)明白:jmp short 标号 为什么叫段内短转移指令,转移的位移范围为:-128 ~ 127 了吧,因为该汇编指令对应的机器指令的低8位表示的就是段内短转移指令,而且只有一个字节(即8位)来表示位移信息,用补码的形式表示,能够表达的范围就是:-128 ~ 127。
补充知识点
原码:数据在计算机中的二进制表示形式,即用最高位存放符号,正数为0,负数为1。其余位表示值。例如,一个字节的十进制数,3 的原码:0000,0011。 一个字节的十进制数,-3 的原码:1000,0011。
反码: 正数的反码就是它的原码;
负数的反码是在其原码的基础上, 符号位不变,其余各个位取反。
例如,一个字节的十进制数,3 的反码:0000,0011。 一个字节的十进制数,-3 的反码:1111,1100。
补码:正数的补码就是它的原码;负数的补码 = 反码 + 1
例如,一个字节的十进制数,3 的补码:0000,0011。 一个字节的十进制数:-3 的补码:1111,1100 + 0000,0001 = 1111,1101。
1.2 jmp near ptr 标号
jmp near ptr 标号是段内近转移指令,该汇编指令的功能是转到标号处执行指令,其对应的机器指令码由3个字节表示,低8位表示该指令是段内近转移指令,高16位表示位移,位移范围在 -32768 ~ 32767 之间,也就是向前转移时,最多越过32768 个字节,向后转移时最多越过32767 个字节。其基本原理跟 jmp short 标号 的基本原理差不多:
(1) near ptr 指明此处的位移为16位位移,16位位移用补码形式表示,位移范围在 -32768 ~ 32767 之间;
(2)(IP)=(IP)+16位位移(补码形式表示);
(3)16位位移由编译程序在编译时算出,16位位移 = 标号所在处的指令的首地址 - jmp指令所在处的下一条指令的首地址(因为根据文章开头所描述的CPU执行指令的过程可以知道,CPU执行存储在指令缓冲区中的 jmp 指令前,IP已经指向 jmp 指令的下一条指令)。
;jmp near ptr 标号 基本原理;1. near ptr 指明此处的位移为16位位移,16位位移用补码形式表示,位移范围在 -32768 ~ 32767 之间;2. (IP)=(IP)+16位位移(补码形式表示);3. 16位位移由编译程序在编译时算出,16位位移 = 标号所在处的指令的首地址 - jmp指令所在处的下一条指令的首地址
assume cs:code
code segment
start:mov ax, 0 ;机器码:B80000,3字节jmp near ptr s ;编译器预编译后变成:jmp s标号所在处的地址;编译后生成的机器码有3个字节为:低8位表示段内近转移指令,高16位为转移位移dw 100 dup (0)
s: inc ax ;机器码:40h,1字节mov ax, 4c00h ;机器码:B8004C, 3字节int 21h ;机器码:CD21, 2字节
code ends
end start
从上面红色圈出的部分细节,我们可以知道,汇编指令:jmp near ptr s 被编译器预编译后生成:JMP 00CE(jmp s标号指令所在的首地址),编译最后生成的机器指令为:00C8E9,低8位 E9h 代表的是jmp的段内近转移指令(参考8086CPU的对应的指令编码表可知),高16位 00C8h 就是要转移的位移。
(1)标号所在处的指令的首地址 - jmp指令所在处的下一条指令的首地址 = 16位位移,
即 00CEh - 0006h = 00C8h;
(2)明白:jmp near ptr 标号 为什么叫段内近转移指令,转移的位移范围为:-32768 ~ 32767了吧,因为该汇编指令对应的机器指令的低8位表示的就是段内近转移指令,而且用2个字节(即16位)来表示位移信息,用补码的形式表示,能够表达的范围就是:-32768 ~ 32767。
可能好奇心比较强的你,会不会有个疑问,如果 jmp near ptr s 的转移范围在 -128 ~ 127 之间呢,答案是编译器会优化成段内短转移指令 jmp short s(2个字节),同时把多出来的一个高字节单元用 NOP 填充(因为jmp near ptr 标号其对应的机器指令码由3个字节表示,所以多出来的一个没用到的高字节单元用NOP指令填充)。注:NOP(no operation),汇编语言的字节填充指令,该指令占一个字节单元。
;jmp near ptr 标号 基本原理;1. near ptr 指明此处的位移为16位位移,16位位移用补码形式表示,位移范围在 -32768 ~ 32767 之间;2. (IP)=(IP)+16位位移(补码形式表示);3. 16位位移由编译程序在编译时算出,16位位移 = 标号所在处的指令的首地址 - jmp指令所在处的下一条指令的首地址
assume cs:code
code segment
start:mov ax, 0 ;机器码:B80000,3字节jmp near ptr s ;转移范围在 -128 ~ 127 之间,编译器会将该指令优化为:;jmp short s;NOPadd ax, 2 ;机器码:050200, 3字节
s: inc ax ;机器码:40h,1字节mov ax, 4c00h ;机器码:B8004C, 3字节int 21h ;机器码:CD21, 2字节
code ends
end start
(1)标号所在处的指令的首地址 - jmp指令所在处的下一条指令的首地址 = 8位位移,
即 0009h - 0005h(即NOP指令的首地址) = 0004h;
(2)该汇编指令经过编译器生成的机器指令为:04EBh 和 1个字节填充指令NOP,即 90h 组成。
2. 根据目的地址进行转移的 jmp 指令
2.1 jmp far ptr 标号
jmp far ptr 标号是段间转移指令(又称远转移指令),该汇编指令的功能是转到标号处执行指令,其对应的机器指令码由5个字节表示,低8位表示该指令是段间近转移指令,高16位表示段地址,中间16位表示偏移地址。其基本原理如下:
(1)far ptr 指明了该指令用标号的段地址和偏移地址修改CS和IP;
(2)(CS) = 标号所在段的段地址,(IP) = 标号在段中的偏移地址
assume cs:code
code segment
start:mov ax, 0jmp far ptr s ;转到标号s处执行指令dw 100 dup (0) ;定义100个dw类型数据,其值为0,占200个字节
s: inc axmov ax, 4c00hint 21h
code ends
end start
jmp far ptr s 被预编译成了 jmp 204D:00D0,最终生成的机器码为:204D00D0EA,低8位:EA是jmp的段间转移指令编码(参考8086CPU的对应的指令编码表可知),高16位:204D是标号s所在的段地址(CS = 204D),中间16位:00D0是标号S在所在段内的偏移地址(IP = 00D0)
现在有个疑问,如果 jmp far ptr s 的转移范围在 -128 ~ 127 之间呢,答案是编译器会优化成段内短转移指令 jmp short s(2个字节),同时把多出来的3个高字节单元用 NOP 填充(因为jmp far ptr 标号其对应的机器指令码由5个字节表示,所以多出来的3个没用到的高字节单元用NOP指令填充)。注:NOP(no operation),汇编语言的字节填充指令,该指令占一个字节单元。
assume cs:code
code segment
start:mov ax, 0jmp far ptr s ;转到标号s处执行指令add ax, 2
s: inc axmov ax, 4c00hint 21h
code ends
end start
(1)标号所在处的指令的首地址 - jmp指令所在处的下一条指令的首地址 = 8位位移,
即 000Bh - 0005h(即NOP指令的首地址) = 0006h;
(2)该汇编指令经过编译器生成的机器指令为:06EBh 和 3个字节填充指令NOP,即 90h 90h 90h 组成。
3.总结
1. jmp short 标号 是根据位移进行转移的段内短转移指令,只修改IP的值,其机器码有2个字节,低8位是用于表示jmp的段内短转移指令的指令编码,高8位是用补码形式表示的位移信息,转移的范围:-128 ~ 127
2. jmp near ptr 标号 是根据位移进行转移的段内近转移指令,只修改IP的值,其机器码有3个字节,低8位是用于表示jmp的段内近转移指令的指令编码,高16位是用补码形式表示的位移信息,转移的范围:-32768 ~ 32767
3. jmp far ptr 标号 是根据目的地址进行转移的段间转移指令(又称为远转移指令),可以同时修改CS和IP,其机器码有5个字节,低8位用于表示jmp的段间转移指令的指令编码,高16位用于表示转移的目的段地址CS,中间16位用于表示转移的偏移地址IP
所以,在实际开发过程中
1. 如果只段内转移(只修改IP)时
(1)相对于当前 IP 的转移位移在 -128 ~ 127 之间时,选择用段内短转移指令 jmp short 标号,CPU的执行效率是最高的。
(2)相对于当前 IP 的转移位移在 -32768 ~ 32767 之间时,选择用段内近转移指令 jmp near ptr 标号,CPU的执行效率是最高的。
2. 如果是段间转移(同时修改CS和IP)时,那就选择用段间转移指令(远转移)jmp far ptr 标号
参考文献
《汇编语言(第4版)》王爽