8. AVX 指令的 assembly 与 disassembly


assembly 是将汇编语句译为机器指令编码,disassembly 是 assembly 的逆操作,将机器指令编码译为汇编语句形式,这里将探讨下 AVX 指令的 assembly 与 disassembly 操作。

8.1 assembly

AVX 指令可用于 protected mode 和 long mode(包括:64-bit mode 和 compatibility mode),不能使用在 real mode 和 V86 mode,否则会产生 #UD 异常。可是依然能够在 16-bit proected mode 下使用。16-bit protected mode 下它的 default operand-size 和 default address-size 是 16-bit 的。

8.1.1 32-bit 与 16-bit 环境下的 assembly

32-bit 下的 assembly 与 16-bit 下的 assembly,如果指令中含有内存操作数,它们的行为是不同的,其它情况下是一样的。

vblendvps xmm1, xmm2, [eax], xmm4

这条指令在 32-bit 下译为:

在 16-bit 下仅加上了 address size override prefix:67H

67  C4 E3 69 4A 08 40
--  -----------------
|           |
|           |
+-----------+------------------> 16-bit:  address-size override
            |
            |
            +------------------> 32-bit:  encodes

和通用指令的 effective operand size 不同,AVX 指令的 operand size 是固定的,effective operand size 只有一种,依赖于具体的指令。

vcvtsi2sd xmm1, xmm2, eax

这条指令在 32-bit 和 16-bit 下是一样的,operand3 是通用寄存器 eax 在 16-bit 并不需要加 operand size override prefix,相反加了 66H 会产生 #UD 异常

C5 EB 2A C8
-----------
    |
    +-------------------------> 32-bit 和 16-bit 下的 encodes

8.1.1.1 32-bit 和 16-bit 环境的寄存器

表1:xmm/ymm 寄存器

VEX.R
ModRM.reg
VEX.vvvv
寄存器
有效
VEX.B
ModRM.r/m
1
000
1111
xmm0/ymm0
16/32/64-bit 有效
001
1110
xmm1/ymm1
010
1101
xmm2/ymm2
011
1100
xmm3/ymm3
100
1011
xmm4/ymm4
101
1010
xmm5/ymm5
110
1001
xmm6/ymm6
111
1000
xmm7/ymm7
0
000
0111
xmm8/ymm8
64-bit 有效
001
0110
xmm9/ymm9
010
0101
xmm10/ymm10
011
0100
xmm11/ymm11
100
0011
xmm12/ymm12
101
0010
xmm13/ymm13
110
0001
xmm14/ymm14
111
0000
xmm15/ymm15

从这个表看出,在 16-bit 和 32-bit 只有高半寄存器可以使用, VEX.R 和 VEX.B 必须为 1,这是反相编码设计的结果。

表2:通用寄存器表

VEX.R
ModRM.reg
寄存器
有效
寄存器
有效
VEX.X
SIB.index
VEX.B
ModRM.r/m,SIB.base
1
000
eax
16/32/64-bit

有效
rax
64-bit

有效
001
ecx
rcx
010
edx
rdx
011
ebx
rbx
100
esp
rsp
101
ebp
rbp
110
esi
rsi
111
edi
rdi
0
000
r8
64-bit 有效
001
r9
010
r10
011
r11
100
r12
101
r13
110
r14
111
r15

在 16-bit 和 32-bit 模式下只有 32 位的通用寄存器可用,64 位的寄存器仅在 64-bit 下可用。 VEX.RXB 都须为 1

8.1.2 64-bit 下的 assembly

实际上 64-bit 下的 assembly 与 32-bit 下差不多,不同的 64 位下可以使用全部寄存器,使用 64 位的内存寻址模式。

同样看看这条指令在 64-bit 的情形:

vblendvps xmm1, xmm2, [eax], xmm4

结果和在 16-bit 下 assembly 是完全一样的:

67  C4 E3 69 4A 08 40
--  -----------------
|           |
|           |
+-----------+------------------> 64-bit:  address-size override
            |
            |
            +------------------> 32-bit:  encodes

由于在 64-bit 下的 default address-size 是 64 位。因此,对于 32 位的内存寻址同样需要加上 address-size override prefix,那么将上面这条指令形式变得复杂一些:

vblendvps ymm1, ymm2, [rax*8 + r10 + 0x11223344], ymm4

主要的变化是,寄存器使用了 256 位的 ymm 寄存器,内存寻址模式变复杂了

C4  C3  6D  4A  8C  C2  44 33 22 11  40
    --  --      --  --  -----------  --
     |   |       |   |       |        |
     +---+-------+---+-------+--------+-------------> 110 00011
         |       |   |       |        |               --- -----
         |       |   |       |        |               RXB mmmmm
         |       |   |       |        |
         +-------+---+-------+--------+-------------> 0 1101 1 01
                 |   |       |        |               - ---- - --
                 |   |       |        |               W vvvv L pp
                 |   |       |        |
                 +---+-------+--------+-------------> 10 001 100
                     |       |        |               -- --- ---
                     |       |        |              mod reg r/m
                     |       |        |
                     +-------+--------+-------------> 11  000  010
                             |        |               --  ---  ---
                             |        |             scale index base
                             |        |
                             +--------+-------------> displacement
                                      |
                                      |
                                      +-------------> 0100 0000
                                                      ---- ----
                                                      ymm4

看看这的几个操作数:

ModRM.mod = 10 用来寻址 [SIB+disp32],base 寄存器是 VEX.B + SIB.base = 0010(r10 寄存器),index 寄存器是 VEX.X + SIB.index = 1000(rax 寄存器)。

这里的 VEX.L 为 1 指示处理 256 位的数据。

8.2 disassembly

我们以这个 encodes 来看看 diassembly 逻辑:

C4 43 35 4A C2 B0

一个纯 binary 的 disassembler 需要为它提供一个提示:dissassemble 为怎样的代码。是 16-bit,还是 32-bit,还是 64-bit

int current_bits = get_currnet_bits();                        // 得到 bits 指示

if (current_bits == BITS_16)
{
        // for 16-bit
}
else if (current_bits == BITS_32)
{
        // for 32-bit
}
else if (current_bits == BITS_64)
{
        // for 64-bit
}

8.2.1 32-bit 的 disassembly

首先应该获取字节的 opcode 属性:

int encode_attribute = get_encode_attribute(0xC4);

if (encode_attribute == OPCODE_ATTRIBUTE)
{
        // it's a opcode
}
else if (encode_attribute == PREFIX_ATTRIBUTE)
{
        // it's prefix
}

 

在 32-bit 下 C4 字节是 LES 指令的 opcode 码,因此目前认定是 LES 指令,然而接着分析下一个 ModRM 字节。

8.2.1.1 ModRM 字节

下一个字节是 43,即 ModRM 字节是 43

char ins[64] = "les ";


switch (MODRM_MOD(0x43))
{
case 0:                                              // ModRM.mod = 00b
        ... ...
case 1:                                              // ModRM.mod = 01b       
       
        char *p = ins;

        if (MODRM_REG(0x43) == REG_EAX)
        {
                strcpy(p, "eax, ");        // 目标操作数是 eax
        }
        else if (...)
        {
                ... ...
        }

        if (MODRM_RM(0x43) == REG_EBX)
        {
                strcpy(p, "[ebx+");        // 源操作数是 [reg+disp8] 寻址
                strcpy(p, "0x35");        // 读下一个字节作为 displacement
        }
        else if (...)
        {
                ... ...
        }       

case 2:                                                // ModRM.mod = 10b
        ... ...
case 3:                                                // ModRM.mod = 11b
        // 无效 opcode 码       
}

ModRM.reg = 000b 那么目标操作数是 eax 寄存器,ModRM.mod 是 01b 那么源操作数是 [register + disp8] 的寻址形式,它需要读取下一个字节 0x35 作为 displacement 字节。而 base 寄存器是 ebx,因此:整条指令就已经完成了:

les eax, [ebx+0x35]

8.2.1.2 下一条指令边界

完成一条指令,接着下一个字节 4A ,这是一条 DEC EDX 指令,无操作数,这样就形成了两条指令:

les eax, [ebx+0x35]
dec edx

下一个字节是 C2 这是一条 RET 指令,这个 RET 需要 2 个字节的立即数操作数,由于这里只剩下一个字节,因此这个 opcode 是无效的,于是形成:

les eax, [ebx+0x35]
dec edx
db 0xC2             ; 无效的 opcode,被 disassemble 为 db 形式

最后一个字节是 B0,这是一条 MOV 指令的 opcode 码,可是已经是最后一个字节了,这个 opcode 不是单字节的 opcode,因此这个也是无效的,最终结果形成:

les eax, [ebx+0x35]
dec edx
db 0xC2             ; 无效的 opcode,被 disassemble 为 db 形式
db 0xB0             ; 无效的 opcode,被 disassemble 为 db 形式

8.2.2 64-bit 下的 disassembly

C4 字节在 64-bit 下是一个 VEX prefix,是 3-bytes VEX prefix 的首字节,因此接下来分析下一个字节 4335

if (encodes[0] == 0xC4)                                // byte 0 == 0xC4
{
        // byte 1

        VEX.R = VEX_R(encodes[1]);
        VEX.X = VEX_X(encodes[1]);
        VEX.B = VEX_B(encodes[1]);
        VEX.mmmmm = VEX_MMMMM(encodes[1]);


        // byte 2
       
        VEX.W = VEX_W(encodes[2]);
        VEX.vvvv = VEX_VVVV(encodes[2]);
        VEX.L = VEX_L(encodes[2]);
        VEX.pp = VEX_PP(encodes[2]);


        // byte3 is AVX's opcode

        VEX.opcode = encodes[3];
}

在这个阶段先分析 VEX prefix 并获取 VEX prefix 的各个域值,接下来字节是 AVX 指令的 opcode 码

8.2.2.1 分析 opcode

在得到了这些信息,包括:VEX prefix 的 byte 0,byte 1 和 byte2,以及 opcode 码,接下来分析这个 VEX prefix 及 opcode 代表什么指令。

根据 VEX.mmmmmVEX.pp 以及 VEX.opcode 来匹配找到相当的指令

get_opcode_attribute(VEX.mmmmm, VEX.pp, VEX.opcode);

据此查到 vblendvps 指令,由于 VEX.L = 1,因此指令的操作数是 256 位的。

8.2.2.2 分析 ModRM 字节

通过分析 ModRM 字节来得到 operand 的寻址,C2 是 ModRM 字节,从 ModRM 得到:

char operand1_id = MAKE_REGID(REX.R, MODRM.reg);
char operand2_id = VEX.vvvv;
char operand3_id = MAKE_REGID(REX.B, MODRM.rm);
char operand4_id = encodes[5] >> 4;

4 个操作数的 ID 值已经获得,接下来就是生成汇编语句。

8.2.2.3 disassembly 结果

vblendvps ymm8, ymm9, ymm10, ymm11

这是上面的 encodes 反汇编后的结果。

 


版权所有 mik 2009-2011