如果我有一个x86的指令缓冲区,有一种简单的方法可以检查一条指令是否是一条SSE指令,而不必检查操作码是否在SSE指令的范围内?我的意思是,是否存在可以检查的公共指令前缀或处理器状态(例如寄存器)?
答案 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
(通常:操作数大小覆盖前缀)选择双精度版本,66
(F3
)选择打包单个版本,最后repz
(F2
)选择打包的双版本。
此外,它们有时可以组合在一起,在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指令是可变长度的,所以无论如何都需要它。