x86解码指令操作码字节
x86 decoding instruction opcode byte
我正在创建一个 x86 解码器,我正在努力理解和寻找一种计算指令助记符的有效方法。
我知道操作码 6 MSB 是操作码位,但我找不到在助记符中使用这 6 位的地方 table。我发现的唯一助记符 table 是整个操作码字节本身,而不仅仅是 6 个 MSB。
我想问一下有什么有效的方法可以继续解码操作码字节中编码的助记符,以及是否有任何 table 引用使用 6 个 MSB 而不是整个操作码字节。
But isn't there an efficient way to store a table for the mnemonics without duplicates?
这已经成为一个算法和数据结构问题。
正如您所指出的,许多操作码 table 条目(至少对于没有 0f
转义字节的 table:http://sparksandflames.com/files/x86InstructionChart.html)会成组重复4 或 2,即具有相同的 6 或 7 位前缀选择相同的助记符。
显然,256 个条目 table 的结构很简单,但重复了一些东西。它非常快速且易于使用,因为它可能仍然足够小,不会经常缓存未命中。 (特别是因为公共条目将在缓存中保持热状态;x86 代码经常使用相同的操作码。)
您可以用简单性/性能换取 space。
你可以有一个 64 条目 table 结构,其中一个成员是指向辅助 table 的指针,用低 2 位索引.如果指针为 NULL,则表示指令遵循 add
/ and
/ xor
/ 等模式,其中低 2 位告诉您 8 位与任何操作数大小是和方向 (r/m,reg or reg,r/m).
当某些前缀存在时(例如 rep nop
是 pause
),您的结构还需要用于转换为其他指令的条目。此外,AVX VEX 前缀使用了曾经是另一条指令的无效编码。如果你想对所有当前的扩展做一个完整的工作,x86 是非常疯狂的解码。
实际上,使用 table 函数指针 可能是最简单(也最有效)的方法。或具有 const char* mnemonic
和 int (*decode)(const char*mnemonic, const char *insn_bytes, unsigned prefix_bitmap)
函数的结构,因此许多操作码可以指向相同的解码函数,但仍会获得不同的助记符。有时函数会忽略传递的助记符,但有时这就是它所需要的。您将拥有一个用于解码许多解码函数会调用的寻址模式的通用函数。
这与您实现解释而不是进行动态重新编译的 x86 模拟器的方式非常相似。一个普通的解码循环,然后通过函数指针进行调度。
您可能会使用的更复杂的数据结构是 radix trie aka prefix tree. See also https://en.wikipedia.org/wiki/Trie#Bitwise_tries。
这是一个愚蠢的季节,因为密度如此之高,以至于查找 table 更有意义。 (很少有未定义的操作码)。
我正在创建一个 x86 解码器,我正在努力理解和寻找一种计算指令助记符的有效方法。
我知道操作码 6 MSB 是操作码位,但我找不到在助记符中使用这 6 位的地方 table。我发现的唯一助记符 table 是整个操作码字节本身,而不仅仅是 6 个 MSB。
我想问一下有什么有效的方法可以继续解码操作码字节中编码的助记符,以及是否有任何 table 引用使用 6 个 MSB 而不是整个操作码字节。
But isn't there an efficient way to store a table for the mnemonics without duplicates?
这已经成为一个算法和数据结构问题。
正如您所指出的,许多操作码 table 条目(至少对于没有 0f
转义字节的 table:http://sparksandflames.com/files/x86InstructionChart.html)会成组重复4 或 2,即具有相同的 6 或 7 位前缀选择相同的助记符。
显然,256 个条目 table 的结构很简单,但重复了一些东西。它非常快速且易于使用,因为它可能仍然足够小,不会经常缓存未命中。 (特别是因为公共条目将在缓存中保持热状态;x86 代码经常使用相同的操作码。)
您可以用简单性/性能换取 space。
你可以有一个 64 条目 table 结构,其中一个成员是指向辅助 table 的指针,用低 2 位索引.如果指针为 NULL,则表示指令遵循 add
/ and
/ xor
/ 等模式,其中低 2 位告诉您 8 位与任何操作数大小是和方向 (r/m,reg or reg,r/m).
当某些前缀存在时(例如 rep nop
是 pause
),您的结构还需要用于转换为其他指令的条目。此外,AVX VEX 前缀使用了曾经是另一条指令的无效编码。如果你想对所有当前的扩展做一个完整的工作,x86 是非常疯狂的解码。
实际上,使用 table 函数指针 可能是最简单(也最有效)的方法。或具有 const char* mnemonic
和 int (*decode)(const char*mnemonic, const char *insn_bytes, unsigned prefix_bitmap)
函数的结构,因此许多操作码可以指向相同的解码函数,但仍会获得不同的助记符。有时函数会忽略传递的助记符,但有时这就是它所需要的。您将拥有一个用于解码许多解码函数会调用的寻址模式的通用函数。
这与您实现解释而不是进行动态重新编译的 x86 模拟器的方式非常相似。一个普通的解码循环,然后通过函数指针进行调度。
您可能会使用的更复杂的数据结构是 radix trie aka prefix tree. See also https://en.wikipedia.org/wiki/Trie#Bitwise_tries。
这是一个愚蠢的季节,因为密度如此之高,以至于查找 table 更有意义。 (很少有未定义的操作码)。