第七部分 立即数
立即数是Intel指令的中操作对象的一种(加上寄存器、内存共三种)。作为指令的一部分,立即数必须是直接可用的数字,这是立即数跟其他两种操作对象的主要区别(寄存器操作对象,CPU需要首先定位到寄存器,然后再读或写该寄存器中的对象。CPU指令的执行过程可粗分为:取指令,指令解码,取操作对象, ALU运算,存结果,取下一条指令……,立即数跟其他两种操作对象的区别体现在“取操作对象”这一步。立即数需要0个时钟周期,解码之后,立即数就可以使用了,立即立即,这就是立即的涵义所在;寄存器操作对象需要1个时钟周期;内存操作对象根据寻址方式的不同可能需要2-3或更多个时钟周期)。
7.1 立即数的解析
立即数的解析相对来很简单。立即数有一些很明显的特性:
(1) 立即数只有大小的分别。一般情况,我们也把改变EIP的指令中的常数作为立即数来处理。比如jmp rel8/16 或者 call 16:32……,这里相对偏移地址的大小与EIP/IP大小不一致的时候,一般需要进行符号位扩展。(前面曾经提到过指令的操作数大小一般必须相同,这点有特殊情况存在的,操作数大小扩展指令的操作对象就不一样,例如movcx, movzx, cwd, cdq等等指令)。
(2) 立即数只能做为源操作数而不能作为目的操作数。这点是很明确的,目的操作对象只能是寄存器或者内存。
立即数的解析只需要知道需要解析的立即数的大小就可以了,是几个字节读几个字节就行了。这里需要注意的一个重要的问题就是立即数需不需要进行符号为扩展的问题,或者更简单点来说,就是立即数是signed类型的还是unsigned的类型的。这就需要对指令的另外一个特殊的位进行解析了,那就是s位。
7.2 s位
先来看看下面的一组指令:
00401000 80C0 F8 add al, 0F8
00401003 81C0 78563412 add eax, 12345678
00401009 82C0 F8 add al, -8 实际为: add al, F8
0040100C 83C0 F8 add eax, -8 实际为: add eax, FFFFFFF8
看看80,81,82,83指令的定义:(括号中表示ModR/M中的Reg/Opcode辅助编码部分)
80(0) add r/m8, imm8
81(0) add r/m16/32/64, imm16/32
82(0) add r/m8, imm8
83(0) add r/m16/32/64, imm8
16/32位的立即数不存在扩展之分,这里主要的区别在8位的立即数。很明显,82中的立即数是signed类型的,而80的是unsigned类型的,83实际为add eax, FFFFFFF8(因为指令的操作对象必须大小一致),需要把8位的立即数进行符号位扩展成32位。我们看看他们的编码区别:
指令 编码 非立即数操作对象大小 立即数
----------------------------------------------
80 1000 00[0]0 8位 8位(unsigned)
81 1000 00[0]1 16/32位 16/32位
82 1000 00[1]0 8位 8位(signed)
83 1000 00[1]1 16/32位 8位(signed extern to 32)
这里我们首先能很明显地看到w位的存在,w位决定了内存编码的操作对象的