缓冲区中的SSE指令

时间:2011-08-19 00:40:02

标签: assembly x86 sse

如果我有一个x86的指令缓冲区,有一种简单的方法可以检查一条指令是否是一条SSE指令,而不必检查操作码是否在SSE指令的范围内?我的意思是,是否存在可以检查的公共指令前缀或处理器状态(例如寄存器)?

2 个答案:

答案 0 :(得分:6)

(更新)

根据您的定义方式,答案是是或否:)

指令格式在Intel 64 and IA-32 Architectures Software Developer's Manual Combined Volumes 2A and 2B: Instruction Set Reference, A-Z的第2节中描述。其中一个有问题的部分是前缀。其中一些是某些SSE指令(66 F2 F3)必需的,而它们对其他操作码具有不同的含义(操作数大小覆盖,REPNZ和{{1 }})。

要了解如何使用前缀来区分不同的指令,请考虑将两个xmm寄存器一起添加的两种形式(使用REPZ获得的输出):

objdump -D -b binary -m i386:x86-64:intel --insn-width=12

似乎默认是添加两个单精度标量,0f 58 c0 addps xmm0,xmm0 66 0f 58 c0 addpd xmm0,xmm0 f3 0f 58 c0 addss xmm0,xmm0 f2 0f 58 c0 addsd xmm0,xmm0 (通常:操作数大小覆盖前缀)选择双精度版本,66F3)选择打包单个版本,最后repzF2)选择打包的双版本。

此外,它们有时可以组合在一起,在64位模式下,您还必须担心REX前缀(pg. 2-9)。下面是一个示例,它是大致相同的基本指令的不同版本,在64位模式下具有不同的前缀。我不知道你是否关心AVX指令,但我还是把它作为一个例子:

repnz

因此,据我所知,您将始终必须遍历所有前缀以确定指令是否为SSE指令。

更新: 另一个复杂因素是存在仅在ModRM编码方面不同的指令。考虑一下:

0f 51 ca                                sqrtps xmm1,xmm2
0f 51 0c 85 0a 00 00 00                 sqrtps xmm1,XMMWORD PTR [rax*4+0xa]
65 0f 51 0c 85 0a 00 00 00              sqrtps xmm1,XMMWORD PTR gs:[rax*4+0xa]
67 0f 51 0c 85 0a 00 00 00              sqrtps xmm1,XMMWORD PTR [eax*4+0xa]
65 67 0f 51 0c 85 0a 00 00 00           sqrtps xmm1,XMMWORD PTR gs:[eax*4+0xa]
f0 65 67 0f 51 0c 85 0a 00 00 00        lock sqrtps xmm1,XMMWORD PTR gs:[eax*4+0xa]
c5 fd 51 ca                             vsqrtpd ymm1,ymm2
c5 fc 51 0c 85 0a 00 00 00              vsqrtps ymm1,YMMWORD PTR [rax*4+0xa]
65 c5 fc 51 0c 85 0a 00 00 00           vsqrtps ymm1,YMMWORD PTR gs:[rax*4+0xa]
67 c5 fc 51 0c 85 0a 00 00 00           vsqrtps ymm1,YMMWORD PTR [eax*4+0xa]
65 67 c5 fc 51 0c 85 0a 00 00 00        vsqrtps ymm1,YMMWORD PTR gs:[eax*4+0xa]
f0 65 67 c5 fc 51 0c 85 0a 00 00 00     lock vsqrtps ymm1,YMMWORD PTR gs:[eax*4+0xa]

要找到这些以及所有其他编码方式,最简单的方法是使用opcode map

因为无论如何我一直想写一个反汇编程序,我认为看看它需要什么才是一个有趣的挑战。它应该找到大多数SSE指令,但显然我不能也不会保证。我将上面的操作码映射转换为代码传递的一系列测试(tests.c - 对于内联来说太大了)。该代码测试一系列包含操作码编码的十六进制数字的文本字符串(它停止在第一个非十六进制数字处解析,字符串中的最后一个字符表示它是否是SSE指令)。

它首先扫描所有前缀,然后使用操作码表来测试指令是否与额外的逻辑匹配,以处理多字节操作码所需的嵌套表以及需要匹配以下modrm字节中的数字。

ssedetect.c:

df 00 fild              WORD PTR [rax] # Non-SSE instruction: DF /0
df 08 fisttp            WORD PTR [rax] # SSE instruction: DF /1

inst_table.h:

#include <stdio.h> 
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>

#include "inst_table.h"

enum { PREFIX_66=OP_66_SSE, PREFIX_F2=OP_F2_SSE, PREFIX_F3=OP_F3_SSE  };

static int check_prefixes(int prefixes, int op_type) {
    if (op_type & OP_ALWAYS_SSE) return 1;
    if ((op_type & OP_66_SSE) && (prefixes & PREFIX_66)) return 1;
    if ((op_type & OP_F2_SSE) && (prefixes & PREFIX_F2)) return 1;
    if ((op_type & OP_F3_SSE) && (prefixes & PREFIX_F3)) return 1;
    return 0;
}

int isInstructionSSE(const uint8_t* code, int length)
{
    int position = 0;

    // read prefixes
    int prefixes = 0;
    while (position < length) {
        uint8_t b = code[position];

        if (b == 0x66) {
            prefixes |= PREFIX_66;
            position++;
        } else if (b == 0xF2) { 
            prefixes |= PREFIX_F2;
            position++;
        } else if (b == 0xF3) { 
            prefixes |= PREFIX_F3; 
            position++;
        } else if (b >= 0x40 && b <= 0x4F) {
            //prefixes |= PREFIX_REX;
            position++;
            break; // opcode must follow REX
        } else if (b == 0x2E || b == 0x3E || b == 0x26 || b == 0x36 || b == 0x64 || b == 0x65 || b == 0x67 || b == 0xF0) {
            // ignored prefix
            position++;    
        } else {
            break;
        }
    }

    // read opcode
    const uint16_t* op_table = op;
    int op_length = 0;
    while (position < length) {
        uint8_t b = code[position];
        uint16_t op_type = op_table[b];
        if (op_type & OP_EXTENDED) {
            op_length++;
            position++;
            // hackish
            if (op_length == 1 && b == 0x0F) op_table = op_0F;
            else if (op_length == 2 && b == 0x01) op_table = op_0F_01;
            else if (op_length == 2 && b == 0x38) op_table = op_0F_38;
            else if (op_length == 2 && b == 0x3A) op_table = op_0F_3A;
            else { printf("\n\n%2.2X\n",b); abort(); }
        } else if (op_type & OP_DIGIT) {
            break;
        } else {
            return check_prefixes(prefixes, op_type);
        }
    } 

    // optionally read a digit

    // find digits we need can match in table
    uint8_t match_digits = (op_table[code[position]] & OP_DIGIT_MASK) >> OP_DIGIT_SHIFT;

    // consume the byte
    op_length++;
    position++;
    if (position >= length) {
        return 0;
    }

    uint8_t digit = (code[position]>>3)&7; // reg part of modrm

    return (match_digits & (1 << digit)) != 0;
}

static int read_code(const char* str, uint8_t** code, int* length)
{
    int size = 1000;
    *length = 0;
    *code = malloc(size);
    if (!*code) {
        printf("out of memory\n");
        return 0;
    }

    while (*str) {
        char* endptr;
        unsigned long val = strtoul(str, &endptr, 16);
        if (str == endptr) {
            break;
        } 

        if (val > 255) {
            printf("%lX is out of range\n", val);
            goto error;
            return 0;
        }

        (*code)[*length] = (uint8_t)val;

        if (++*length >= size) {
            printf("needs resize, not implemented\n");
            goto error;
        }

        str = endptr;
    }

    if (*length == 0) {
        printf("No instruction bytes found\n");
        goto error;
    }

    return 1;

error:
    free(*code);
    return 0;
}

static void test(const char* str)
{
    uint8_t* code;
    int length;
    if (!read_code(str, &code, &length)) {
        puts(str);
        exit(1);
    }
    char is_sse = isInstructionSSE(code, length) ? 'Y' : 'N';
    char should_be_sse = str[strlen(str)-1];
    free(code);
    if (should_be_sse != is_sse) {
        printf("(%c) %c %s\n", should_be_sse, is_sse, str);
        exit(1);
    }
}

int main() 
{
#include "tests.c"
    test("48 ba 39 00 00 00 00 00 00 00           # movabs rdx,0x39 N");
    test("48 b8 00 00 00 00 00 00 00 00           # movabs rax,0x0 N");
    test("48 b9 14 00 00 00 00 00 00 00           # movabs rcx,0x14 N");
    test("48 6b c0 0a                             # imul   rax,rax,0xa N");
    test("48 83 ea 30                             # sub    rdx,0x30 N");
    test("48 01 d0                                # add    rax,rdx N");
    test("48 ff c9                                # dec    rcx N");
    test("75 f0                                   # jne    0x1e N");
    test("0f 51 ca                                # sqrtps xmm1,xmm2 Y");
    test("0f 51 0c 85 0a 00 00 00                 # sqrtps xmm1,XMMWORD PTR [rax*4+0xa] Y");
    test("65 0f 51 0c 85 0a 00 00 00              # sqrtps xmm1,XMMWORD PTR gs:[rax*4+0xa] Y");
    test("67 0f 51 0c 85 0a 00 00 00              # sqrtps xmm1,XMMWORD PTR [eax*4+0xa] Y");
    test("65 67 0f 51 0c 85 0a 00 00 00           # sqrtps xmm1,XMMWORD PTR gs:[eax*4+0xa] Y");
    test("f0 65 67 0f 51 0c 85 0a 00 00 00        # lock sqrtps xmm1,XMMWORD PTR gs:[eax*4+0xa] Y");
    test("f0 65 67 f3 43 0f 5c 8c 81 2a 2a 00 00  # lock subss xmm1, [gs:r8d*4+r9d+0x2A2A] Y");
    test("0f 58 c0                                # addps  xmm0,xmm0 Y");
    test("66 0f 58 c0                             # addpd  xmm0,xmm0 Y");
    test("f3 0f 58 c0                             # addss  xmm0,xmm0 Y");
    test("f2 0f 58 c0                             # addsd  xmm0,xmm0 Y");
    test("df 04 25 2c 00 00 00                    # fild   WORD PTR ds:0x2c N");
    test("df 0c 25 2c 00 00 00                    # fisttp WORD PTR ds:0x2c Y");
    test("67 0f ae 10                             # ldmxcsr DWORD PTR [eax] Y");
    test("67 0f ae 18                             # stmxcsr DWORD PTR [eax] Y");
    test("0f ae 00                                # fxsave [rax] N");
    test("0f ae e8                                # lfence Y");
    test("0f ae f0                                # mfence Y");
    test("0f ae f8                                # sfence Y");
    test("67 0f ae 38                             # clflush BYTE PTR [eax] Y");
    test("67 0f 18 00                             # prefetchnta BYTE PTR [eax] Y");
    test("0f 18 0b                                # prefetcht0 BYTE PTR [rbx] Y");
    test("67 0f 18 11                             # prefetcht1 BYTE PTR [ecx] Y");
    test("0f 18 1a                                # prefetcht2 BYTE PTR [rdx] Y");
    test("df 08                                   # fisttp WORD PTR [rax] Y");
    test("df 00                                   # fild   WORD PTR [rax] N");

    printf("All tests passed\n");
    return 0;
}

答案 1 :(得分:1)

没有明确的SSE前缀。一些SSE指令以0F开头,一些以F3开头,但并非所有0F和F3指令都是SSE指令。您需要一个更全面的解码器来判断指令是否为SSE。由于x86指令是可变长度的,所以无论如何都需要它。