x86架构中的指令解码

4
我正在为实验室开发一个操作系统项目,需要处理指令指针和指令操作码。目前,我只需要知道指令的类型。为此,我正在从指令指针指向的地址读取数据。这个数据的第一个字节告诉我指令的类型。例如,如果第一个字节是0xC6,那么它就是一个MOVB指令。现在有一些情况,当指令指针的第一个字节是0x0F时,根据文档,0x0F表示这是一个两个字节的指令。我的问题是如何找出这种类型的指令的指令类型。
其次,我的第二个优先事项是查找指令的操作数。我不知道如何通过代码来完成这个任务。任何样例代码都将不胜感激。
第三个问题是要找出指令的大小。由于x86指令集长度可变,因此我想知道每个指令的大小。起初,我计划使用一个查找表来维护指令名称和其大小。但后来我发现,同一指令的长度可能不同。例如,当我在.o文件上使用对象转储时,我发现了两条指令:C6 00 62C6 85 2C FF FF FF 00。在这里,两个指令的类型是相同的(C6),但长度不同。
所以我需要这些问题的答案。如果有人能给我一些解决方案,我将不胜感激。

1
你所询问的问题相当复杂,不容易在SO格式中回答。你可以查看流行的开源反汇编器如何处理它(gdbobjdump)。并且阅读文档 - rodrigo
请提供更多上下文。一个操作系统项目为什么涉及模拟x86 CPU? - Seva Alekseyev
@SevaAlekseyev 我的项目涉及使进程返回到其早期状态。我应该使用mprotect()来完成它。想法是保护每个页面免受写操作。因此,当有写操作时,我将捕获它。在处理程序函数内部,我将保存旧数据,从指令中提取新数据,使页面不受保护,再次运行指令,然后再次使页面受保护。由于在处理程序内部我不知道操作将要写入什么值,所以我能做的就是找出IP,获取指令操作数并再次执行它。 - azizulhakim
可能还有其他技术,比如二进制仪器。但是我已经使用mprotect()这种方法了,没有足够的时间考虑新的解决方案。 - azizulhakim
1
为什么不使用T标志和调试基础设施呢?这就是在x86上单步执行的方法。 - Seva Alekseyev
显示剩余2条评论
3个回答

9
基本上你需要一组嵌套的case语句,实现一个有限状态机扫描程序,其中每个级别都检查某个字节(通常从左到右)的操作码以确定它的作用。
你的顶层case语句将几乎是256个case,每个case对应一个操作码字节;你会发现一些操作码(特别是所谓的“前缀”字节)会导致顶层循环(获取多个在主操作码字节之前的前缀字节)。子case将根据x86的操作码结构获得结构;你几乎肯定会得到MODRM和SIB寻址模式字节解码器/子例程。
我已经做过这个了;工作很烦人,因为细节很多,但不难。如果你小心的话,可以用几百行代码得到一个相当不错的解决方案。如果你坚持要处理整个指令集(向量寄存器和操作码,尤其是针对Haswell等的操作码),你可能会得到更大的东西;英特尔一直在把指令塞进他们能找到的每个黑暗角落。
你真的需要一个操作码映射表;我非常确定英特尔手册中有一个。我发现这个链接非常有用:http://www.ref.x86asm.net/coder32.html

2015年9月更新:在这里,我提供了实现此功能的C代码: https://dev59.com/Xn_aa4cB1Zd3GeqP9Oao#23843450


6
有时候邪恶的行为也是正确的选择 :) - Ira Baxter
1
在PC中,0x0F与指令解码无关。它表示程序跳转到了0x0F。通常,操作系统会将虚拟内存的第一页和最后一页设置为非法页面,因为这可以捕获许多汇编代码中的愚蠢错误,例如将小的正数或负数常量移动到PC中。无法将JMP跳转回这样的页面;用户程序只是犯了一个巨大的错误,您的操作系统应该关闭它。 - Ira Baxter
@IraBaxter 感谢您的回答。您确定吗? - azizulhakim
当你说“指令指针的值为0x0f”时,我理解为“EIP == 0x0000000F”。也许你试图告诉我“*(dword)EIP == 0x0000000F”?由于x86是小端模式,这意味着“*(byte)EIP == 0x0F”,因此你想要查找的操作码是“0x0F”。 - Ira Baxter
2
EIP-> 0F 00 .. 这里的0F是一个LOCK或REPNE前缀字节。整个指令看起来有点像“LOCK ADD modrm...”,我没有解码modrm部分,这部分就留给你了。 - Ira Baxter
显示剩余3条评论

5

另一种方法是使用众多解析器生成框架之一(例如广泛使用的yacc)实际构建汇编的正确解析器。这可能会导致比使用大量case语句的嵌套switch语句更易于维护和更易读的实现。

还有一种中间方法,可以手动实现基于表的解析器。这里有一个例子:https://github.com/libcpu/libcpu/blob/master/arch/x86/x86_decode.cpp


你建议使用解析器生成器来处理指令字节流,以指令字节作为标记?这很有趣。但我相当确定真正的CPU并不使用那种强大的工具;它们最多只是从FSAs运行。更接近实际情况的可能是使用词法分析器生成器,它可以产生FSAs。 - Ira Baxter
1
实际上,我链接中的漂亮的解码器实现似乎是在FPGA上进行解码器的最直接方法(使用LUT块和简单的FSA)。但是当涉及到软件时,我可能被boost::spirit的解析器方式宠坏了(这些方式可以兼作FSA,并且没有词法分析步骤,结果是非常简单和高效的软件结构)。然而,从事实来看,boost::spirit可能会在第一眼看起来有些令人生畏,因此我决定提到一些更平凡的东西,比如yacc。 - oakad

1

kvm有一个非常复杂的x86模拟器/解码器,可能可以被您的项目重用。


实际上有很多工具可用。但它们的源代码对我来说太大了。我只想理解我提出的三个要点。从那里开始,我相信我能制作一个简单的工具。 - azizulhakim
1
@azizulfahim,针对你的问题,CPU状态已经被简化为一个单一的指令指针,这本来就是问题被过度简化了。x86指令解码器本质上是一个以二进制字符串作为输入的确定有限状态自动机(DFA),其转换表由文法确定。你不能简化文法并希望DFA仍然可以正确处理全文法中的二进制字符串。 - liuyu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接