指令的 operand size


了解指令的 operand size(操作数大小)细节,需要掌握下面几个知识点:

1. 指令的 default operand size

在 x86/x64 指令集体系上,指令的 default operand size 来自于 CS.D 以及 CS.L 标志位。这个 CS.D 标志位是什么呢? 

CS.D 标志位是:

    code segment descriptor 的 D 标志位,它用来表示:code segment default operand size 实际上,它还用来指示 default address size 是多少

    当 CS.D = 1 时:default operand size 是 32 位,default address size 也是 32 位。
    当 CS.D = 0 时:default operand size 是 16 位,default address size 也是 16 位。

processor 在执行代码之前,会将 code segment descriptor 所有信息加载到 CS 寄存器(当然这需要通过 各方面 check),包括 code segment descriptor 的所有标志位,其中就有 D 标志位,因此,CS.D 就是 code segment descriptor D 标志位。

我们常说的 16 位模式 下就是指在 CS.D = 0(D 标志位被清为 0)的情况下,而 32 位模式 是指在 CS.D = 1(code segment descritor 的 D 标志位被设为 1)的情况下。


1.1 64 位模式下的 default operand size

x64 体系的 64 位模式 需要满足两个条件:

新增的标志位 CS.L = 1 用来指示 long 模式的 64 位模式,而 CS.L = 0 指示 long 模式的 compatibility 模式

在 64 位下必须 CS.D = 0 指示: default operand size 32 位,default address size64 位。


1.2 compatibility 模式下的 default operand size

CS.L = 0 表明 processor 运行在 compatibility 模式下,那么 default operand size 将分两种情况,这和 x86 体系的 protected 模式一样的:

CS.L = 0 & CS.D = 1 或 0 表示的 default operand sizelegacy 模式是一样的。


1.3 表1 default operand size & default address size

模式
CS.L 标志
CS.D 标志
default operand size
default address size
legacy 模式
protected 模式
不存在
CS.D = 1
32
32
real 模式
CS.D = 0
16
16
long 模式
compatibility 模式
CS.L = 0
CS.D = 1
32
32
CS.D = 0
16
16
64 位模式
CS.L = 1
CS.D = 1(保留未用)
保留未用
保留未用
CS.D = 0
32(大部分指令)
64

long 模式的 compatibility 子模式和 legacy 模式的情况是完全一致的。在 64 位模式下,default address size 是 64 位,大部分指令的 default operand size 却是 32 位,有一部分指令是 64 位的。

 

2. 指令的 effective operand size

上面所描述的只是 default operand size,但这并不代表指令的 operand size 就是 default operand size,还要视乎有没有 operand size override 行为,这个 operand size 如何 override 是根据 effective operand size 进行的。

effective operand size 是 x86/x64 指令体系所能表达的 operand size

从上面的“表1 default operand size & default address size ”中可以看出,effective operand szie 可以是:

它们都是 x86/x64 指令体系中可以 接受的 operand size,它们由 CS.DCS.L 来决定,同时,受 effective operand size 影响的两类 operand 属性:

z 属性表示:当 effective operand size 是 16 位时, operand size 是 16 位。当 effective operand size 是 32 位时,operand size 是 32 位。但是当 effective operand size 是 64 位时,z 属性所表达的 operand 还是 32 位的。z 属性所描述的 operand 它不具备 64 位的 size

v 属性表示:当 effective operand size 是 64 位时,operand size 是 64 位,v 属性描述的 operand 具有 64 位的 size,这一点和 z 属性是不同的,其它是一样的。

 

3. operand size override 行为

operand size override 就是改变指令的 operand size 行为,在可接受的 effective operand size 范围内改变指令的 default operand size

当没有 operand size override 行为时,default operand size 就是指令的 operand size,关于 operand size override 更详的描述,详见: http://www.mouseos.com/x64/doc3.html#041

经过 operand size override 后,指令的 operand size 就是被改写后的 size

 

4. 指令的 operand size

形成指令的最终的 operand size 由 default operand size 和 operand size override 决定:


4.1 以 default operand size 构造指令的 operand size

绝大部分情形下,指令不需要进行 operand size override,因此大部分情形下,operand size 就是 default operand size

看看下面的例子:

    bits 32
   
;-------------------------------------------------------
; long kstrcmp(const char *s1, const char *s2);
; input:         edi: s1     esi: s2
; return value:    1: equal      0: not equal
;-------------------------------------------------------
kstrcmp:
    xor eax, eax
kstrcmp_loop:   
    movzx ebx, byte [edi]
    movzx edx, byte [esi]
    cmp ebx, edx
    jnz kstrcmp_done
    test ebx, ebx
    jz kstrcmp_eql
    inc edi
    inc esi
    jmp kstrcmp_loop
kstrcmp_eql:
    inc eax
kstrcmp_done:
    ret

上面的代码中,使用 bits 32 指示代码编译为 32 位的代码。 那它们的 default operand size 是 32 位,以这条指令为例:

    xor eax, eax

它的 opcode 描述是:

XOR Gv, Ev

在当前的 32 位下,它的 default operand size 是 32 位,这条指令并不需要进行 operand size override 因此它的指令 operand size 是 32 位。

再来看看另一条指令:movzx ebx, byte [edi] 它的 opcode 描述是:

MOVZX Gv, Eb

那么它的 operand size 到底是多少呢? 在 32 位下,它的目标操作数是 32 位,而源操作数是 8 位。这条指令的 operand size 是由 Gv 决定

在没有 operand size override 的情况下,指令的 operand size 由 default operand size 决定的,它的 operand size 是 32 位(由 default operand size 决定),因此这条指令的是 operand size 是 32 位。


4.2 以 operand size override 的 size 为 operand size

在 operand size override 的情况下,指令的最终 operand size 就是 override 的 size,看看以下面的代码:

bits 16
    mov eax, ebx

bits 16 指示编译器要生成 16 位的代码(即:default operand size 为 16 位),然而指令中使用了 32 位的寄存器,nasm 会指令产生 operand size override 码,如下:

00000000  6689D8            mov eax,ebx

生成的机器编码中使用了 66H prefix 进行 operand size override,指令的 operand size 变成了 32 位。

再来看一段 64 位下的代码:

bits 64
    mov rax, rbx

bits 64 指示 nasm 生成 64 位代码,但是它的 default operand size 却是 32 位的,指令使用了 64 位的寄存器,因此 nasm 会生产使用扩展技术的指令编码,如下:

00000000 4889D8            mov rax,rbx

这个 48HREX prefix 用于进行 64 位扩展访问。REX.W = 1 表明使用了 64 位的 operand size

实际上使用 REX prefix 也相当于进行了 operand size override 指令最终的 operand size 由 32 位变成了 64 位。

 

5. 对 operand size override 更深入思考

在 x64 体系上,operand size override 有一些微妙的变化,有 2 类指令的 default operand size 是 64 位的:near branches 指令和依赖于 rsp 的相关指令。

bits 64
    call word next
next:  

上面这条指令进行了 operand size override 操作。它产生的效果有:

它的指令编码是:66 E8 00 00 offset 由 32 位变成了 16 位,而指令的最终 operand size 也变成 16 位,如下所示:

  1. rip = rip + offset16 --- 这个 offset16 由 32 位变为 16 位
  2. rip = rip & 0xFFFF --- rip 被截断成 16 位,最终的 rip 是被截断 rip 的低 16 位

5.1 operand size override 下的 sign-extended

由 operand size override 引发了另一个问题:sign-extended,符号扩展行为将被改变。由于 operand size override 后,immediate 与指令的 operand size 变得一致了,因此不会存在符号扩展行为 ,看一看下面这个机器编码:

66 e8 fc ff

这个机器编码对应的指令是:call .+0xfffc

它使用了 66H prefix 进行 operand size override, immediate 操作数是 16 位,指令的 operand size 也是 16 位的,这就不会产生 sign-extended 行为。

假设当前的 rip = 0x13f7a1038,那么:

经过截断后 rip 最终的值为:0x0000000000001034, 这个值不是 sign-extended 后的结果。

 


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