在 64 位模式下(CS.L = 1 & CS.D = 0)情况稍有些特殊,大部分指令的 default operand size 都是 32 位的,但是有一部分指令的 default operand size 是 64 位的。可是 default address size 是 64 位的。
这里主要探讨什么类型的指令是 64 位的?为什么在 64 位下,default operand size 却是 32 位?为什么需要这样设计?
有两类指令的 default operand size 是 64 位的,它们是基于:
第 1 类是基于 rip(instruction pointer)的分支转移指令(near branches 类指令),这类指令的 immediate 操作数是基于 rip 的偏移量。第 2 类是基于 rsp(stack pointer)的栈操作指令,它们是 push/pop 类指令。
对于 defalut operand size 为 64 位的指令,它的指令编码中并不需要 REX.W = 1
这类指令的操作数是 immediate,这个 immediate 是基于 rip 的偏移量,分支指令中的 immediate 操作数包含 Ib 与 Iz
关于 immediate 有关的描述,请参见:http://www.mouseos.com/x64/doc9.html 以及: http://www.mouseos.com/x64/puzzle02.html
指令名 |
Opcode 码 |
immediate 属性 |
immediate size |
描述 |
call |
E8 |
Jz |
16, 32 |
基于 rip 偏移量的 call 指令 |
jmp |
EB |
Jb |
8 |
基于 rip 偏移量的 jmp 指令 |
E9 |
Jz |
16, 32 |
||
FF /4 |
Ev |
16, 64 |
||
jcc |
70 ~ 7F |
Jb |
8 |
条件 jmp 指令 |
0F 80 ~ 0F 8F |
Jz |
16, 32 |
||
jrcx |
E3 |
Jb |
8 |
jmp 指令使用 cx/ecx/rcx 为条件 |
loop |
E0, E1, E2 |
Jb |
8 |
条件循环指令 |
上面这类基于 rip 的 near branches 指令的 default operand size 是 64 位的。,一部分有 Ib 和 Iz 两个版本,一部分只有其中一个属性。
这为指令的进行的操作是:
rip = rip + offset(immediate) |
这个 offset 就是指令中的 immediate 操作数,它是个 signed 值,这个 offset 无论是 Ib 属性还是 Iz 属性,它们都会符号扩展到 64 位,然后加上当前的 rip(指令指针),这类指令的 operand size 是 rip 宽度(64 位)
但是:对于 JMP 指令中 Opcode 为 FF /4 的情况有些例外,这个 Opcode 的操作数并不是基于 rip 的 offset(immediate),rip = reg64 或 rip = [mem64],它的 deafult operand size 同样是 64 位的。它的 operand size 只有 16 和 64 位
这类指令将显式或隐式地对 stack 进行相关的操作,它们包括:
指令名 |
Opcode 码 |
Operand 属性 |
operand size |
描述 |
call |
FF /2
|
Ev |
16, 64 |
rip = reg64 或 rip = [memory] |
E8 |
Jz |
16, 32 |
rip = rip + offset |
|
push |
FF /6 |
Ev |
16,64 |
|
50 ~ 57 |
reg16/reg64 |
16, 64 |
||
68 |
Iz |
16, 32 |
||
6A |
Ib |
8 |
||
pop |
8F /0 |
Ev |
16, 64 |
|
58 ~ 5F |
reg16/reg64 |
16, 64 |
||
enter |
c8 |
Iw, Ib |
16, 8 |
|
leave |
c9 |
--- |
--- |
|
ret |
c2 |
Iw |
16 |
|
c3 |
--- |
--- |
这类指令的 default operand size 也是 64 位,可以被 override 为 16 位。
然而对于远跳转指令来说,它的 default operand size 依然是 32 或 48 位 far pointer(16:16 或 16:32)
在 64 位模式下有效的 far branches 指令有:
指令名 |
Opcode 码 |
Operand 属性 |
operand size |
描述 |
jmp |
FF /5
|
Mp |
16, 32 |
far pointer 从 [mem] 获得 |
call |
FF /3 |
Mp |
16, 32 |
far pointer 从 [mem] 获得 |
这类指令的 operand size 是 32 位(16:16)或者 48 位(16:32)
上面 2 类指令的 default operand size 是 64 位,但是最终的 operand size 还是需要取决是否 operand size override
当对上述的指令进行 operand size override 时,情况会是怎样呢?下面看看几个例子
bits 64 |
在上面的代码中,其中有一条指令将 operand size 设为 word,从而进行了 operand size override 操作,下面是 nasm 编译器的结果:
00000000 E804000000 call dword 0x9 |
可以看出,在未 override 情况下,指令的 immediate 是 dword(32 位),而使用了 66H prefix 进行 override 后,指令的 immediate 变成了 word(16 位)
经过 operand size override 后指令的 immediate 变成了 16 位。
指令的 immediae 被调整为 16 位,指令的最终 operand size 根据 override 而也变成了 16 位 |
rip 将被截断为 16 位,致使指令最终的 operand size 是 16 位的。
bits 64 |
上面代码中同样将 32 位的 immediate 调整为 16 位,nasm 编译结果为:
00000000 6844332211 push 0x11223344 |
和 2.1 中同样的结果,使用了 66H prefix 将 32 位的 immediate 调整为 16 位。 指令的 operand size 也变为 16 位。
实际上这里修改了 stack pointer 的宽度,由原来 64 位的 rsp 变成了 16 位的 sp,因此经过 operand size override,指令执行后将向栈压入 16 位的值,并且 rsp 加上 2