举个例子,假设我正在构建一个虚拟机。我有一个字节数组和一个while循环,我如何知道下一条指令要从字节数组中读取多少字节来解释类似Intel 8086的指令?
CPU读取指令指针处的操作码,使用8086和CISC,您有一个字节和两个字节的指令。我如何知道下一条指令是F还是FF?
我在http://www.swansontec.com/sintel.html的这篇文章中找到了答案
操作码或操作码位于任何可选前缀之后。操作码告诉处理器执行哪个指令。此外,操作码包含描述预期操作数大小和类型的位字段。例如,NOT指令的操作码为1111011w。在此操作码中,w位确定操作数是字节还是字。OR指令的操作码为000010dw。在此操作码中,d位确定哪些操作数是源和目标,w位再次确定大小。有些指令有几种不同的操作码。例如,当OR与累加器寄存器(AX或EAX)和常量一起使用时,它具有特殊的节省空间的操作码0000110w,这消除了对单独ModR/M字节的需要。从大小编码的角度来看,不需要记住精确的操作码位。对特定指令可用的操作码类型有一个大致的了解更为重要。
CPU只是简单地解码指令。IN8086的情况下,第一个字节告诉处理器还需要多少。它不一定是第一个字节,第一个字节必须以某种方式表明你需要得到更多,更多可以表明你需要更多。对于像x86系列这样的8位指令集,你从一个字节开始,然后看看你还需要多少,而且是不对齐的,你必须将指令流视为字节流才能解码。
你应该给自己写一个非常简单的指令集模拟器,只有少量的指令,也许足以加载寄存器,添加一些东西,然后循环。
该解决方案比固定大小的数组更复杂。
这都是关于上下文的,这就是为什么像IDA这样的反编译器有复杂的算法来做到这一点。
指令对于x86来说是可变长度的。但是如果你知道一条指令的开始,你就知道这条指令在哪里结束。因此,你可能知道下一个指令从哪里开始。我很快就会解释异常。但首先,这里有一个例子:
ASM:
mov eax, 0
xor eax, eax
Machine:
b8 00 00 00 00
31 c0
移动到eax是B8
,后跟一个32位(4字节)的值移动到eax(因为eax是32位)。换句话说,mov eax,立即
将始终是5字节。因此,如果您知道您正在从一条指令开始(并不总是一个安全的假设),并且字节是B8
,则您知道它是一条5字节的指令,并且下一条指令应该在5字节之后开始。
请注意,两个指令(mov eax,0
和xor eax, eax
)实际上做了同样的事情,将eax清除为0。
跳转/调用可能会变得棘手。有可能跳转到位于“指令中间”的地址空间…但仍然执行。
让我们看看:
mov eax, 0x90909090
机器代码:
b8 90 90 90 90
如果我们后来有一条jmp指令跳转到上面指令的第3个字节的地址(在它的中间某个地方),它只会做3个NOP(没有操作)并落到它之后的下一条指令(没有将eax设置为0x90909090)。这是因为NOP
是由0x90组成的1字节指令。