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 size64 位的指令,它的指令编码中并不需要 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 位的。,一部分有 IbIz 两个版本,一部分只有其中一个属性。

这为指令的进行的操作是:

rip = rip + offset(immediate

这个 offset 就是指令中的 immediate 操作数,它是个 signed 值,这个 offset 无论是 Ib 属性还是 Iz 属性,它们都会符号扩展到 64 位,然后加上当前的 rip(指令指针),这类指令的 operand size 是 rip 宽度(64 位)

但是:对于 JMP 指令中 OpcodeFF /4 的情况有些例外,这个 Opcode 的操作数并不是基于 rip offset(immediate),rip = reg64rip = [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 依然是 3248far 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 所有,转载请注明出处