3. 解开 64 位模式下 operand size 的迷惑


mik

在 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 位?为什么需要这样设计?


1. 什么类型指令的 default operand size 是 64 位

有两类指令的 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


1.1 Near branches 类指令

这类指令的操作数是 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 位

 

1.2 基于 rsp 的栈操作指令

这类指令将显式或隐式地对 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 位。


1.3 far branches 指令

然而对于远跳转指令来说,它的 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. operand size override 情况

上面 2 类指令的 default operand size 是 64 位,但是最终的 operand size 还是需要取决是否 operand size override

当对上述的指令进行 operand size override 时,情况会是怎样呢?下面看看几个例子


2.1 对 near branches 指令进行 operand size override

bits 64
    call next
    call word next
next:  

在上面的代码中,其中有一条指令将 operand size 设为 word,从而进行了 operand size override 操作,下面是 nasm 编译器的结果:

00000000  E804000000        call dword 0x9
00000005  66E80000          call word 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 位的。

 

2.2 对 rsp 相关类指令进行 operand size override

bits 64
    push 0x11223344
    push word 0x11223344

上面代码中同样将 32 位的 immediate 调整为 16 位,nasm 编译结果为:

00000000  6844332211      push 0x11223344
00000005  66684433        push 0x3344

和 2.1 中同样的结果,使用了 66H prefix 将 32 位的 immediate 调整为 16 位。 指令的 operand size 也变为 16 位。

实际上这里修改了 stack pointer 的宽度,由原来 64 位的 rsp 变成了 16 位的 sp,因此经过 operand size override,指令执行后将向栈压入 16 位的值,并且 rsp 加上 2

 


所有权限 mik 所有,转载请注明出处