2017-09-14 131 views
0

我正在创建一个x86解码器,我正在努力理解并找到计算指令助记符的有效方法。x86解码指令操作码字节

我知道操作码6 MSB是操作码位,但我找不到在助记表中使用这6位的任何地方。我发现唯一的助记表是整个操作码字节本身,而不仅仅是6个MSB。

我想问一下哪些有效的方法可以解码操作码字节中编码的助记符,以及是否有使用6个MSB而不是整个操作码字节的表引用。

+0

底部的2位也是操作码的一部分...例如['jcc'](http://felixcloutier.com/x86/Jcc.html)指令对于每个操作码值从0x70到0x7F的。实际上,ModR/M字节的'/ r'字段也是操作码的一部分。 (例如'shl'与'shr')。 –

+0

现代x86机器代码的问题在于*不是有效/简单的解码方法。例如,'rep nop'实际上解码为'pause',或'rep bsf'解码为'tzcnt'(如果支持BMI1,否则解码为'bsf')。所以你必须检查其他指令的强制性前缀。 –

+0

@PeterCordes我正在使用的资源之一是http://www.c-jump.com/CIS77/CPU/x86/X77_0050_add_opcode.htm 我知道有什么例外,不是只有6个MSB的操作码字节代表助记符,但对于正常的指令来说,它看起来像根据他们所说的方式。我在问如何为这些常规病例使用这6个MSB来确定助记符,就像他们在他们的例子中所做的那样。 – Jorayen

回答

1

但是,没有一种有效的方法来存储没有重复记录的助记表?

这已经成为算法和数据结构的问题。如您所指出的,许多操作码表条目(至少对于不具有0f转义字节的表:http://sparksandflames.com/files/x86InstructionChart.html)以4或2的组重复,即具有相同的6或7位前缀,选择相同的助记符。

显然,一个256条目的结构表很简单,但重复的东西。它非常快速且易于使用,因为它可能仍然很小,不会经常缓存。 (特别是因为常用条目将在高速缓存中保持热点; x86代码使用相同的操作码很多。)

您可以交换简单/空间性能。

你可以有一个64位的结构表,其中一个成员是一个辅助表的指针,用低位2位索引。如果指针为NULL,则表示指令遵循add/and/xor /等的模式,其中低2位告诉您8位与操作数大小和方向(r/m,reg或reg, R M)。

当存在某些前缀时(例如,rep noppause),您的结构也需要用于转为其他指令的条目。另外,AVX VEX前缀使用过去是另一条指令的无效编码。如果你想为所有当前的扩展做一个完整的工作,x86是非常疯狂的解码。

实际上,它可能是最简单的(也是有效的),只需要使用函数指针表。或者是一个带有const char* mnemonicint (*decode)(const char*mnemonic, const char *insn_bytes, unsigned prefix_bitmap)函数的结构,所以很多操作码都可以指向相同的解码函数,但仍然会得到不同的助记符。有时函数会忽略传递的助记符,但其他时候只需要它。对于许多解码函数可能调用的解码模式,您都有一个共同的功能。

这与您可能实现的x86仿真器的解释非常相似,而不是进行动态重新编译。一个普通的解码循环,然后通过函数指针分派。


您可能会使用的更复杂的数据结构是radix trie aka前缀树。另见https://en.wikipedia.org/wiki/Trie#Bitwise_tries

这是进入愚蠢的季节,因为密度非常高,查找表更有意义。(很少有未定义的操作码)。

+0

因此,如果我的目标是性能(速度明智),你会说只是存储256个入口表将是最好的选择?另外为什么我需要一个结构?我想也许创建一个所有助记符的枚举,然后创建一个256条目表作为这个枚举的索引表,你的想法是什么? – Jorayen

+0

是的,我猜想256个入口表会表现最好。额外的分支来选择额外的解码步骤是不值得的。 –

+0

@Jorayen:除非你有其他特殊情况的单独表,你可以使用一个结构来保存助记符,并告诉你如何将剩余的字节解码为操作数。 (例如'jcc' /'call'与'add'与'mul r/m32'比'imul r,r/m32,imm32'与其他特殊情况相比)。对于另外3个操作码位来自ModR/M中的'/ r'字段的特殊情况,用于指示(例如,用指向另一个表的指针)。在每次访问中使用大多数成员的结构都具有良好的空间局部性,因此缓存效果很好。 –