指令长度可变时的指令解码

时间:2011-11-20 19:33:09

标签: assembly microprocessors

以下是一些说明及其相应的编码:

55                      push   %ebp
89 e5                   mov    %esp,%ebp
83 ec 18                sub    $0x18,%esp
a1 0c 9f 04 08          mov    0x8049f0c,%eax
85 c0                   test   %eax,%eax
74 12                   je     80484b1 <frame_dummy+0x21>
b8 00 00 00 00          mov    $0x0,%eax
85 c0                   test   %eax,%eax
74 09                   je     80484b1 <frame_dummy+0x21>
c7 04 24 0c 9f 04 08    movl   $0x8049f0c,(%esp)

今天的微处理器通常是32位或64位,我猜他们通常以4字节或8字节的块从内存中读取数据。但是,指令可以具有可变长度。微处理器如何解码这些指令,为什么它们不能保持恒定长度以便于实现?

4 个答案:

答案 0 :(得分:7)

编辑:希望让它更具可读性。

硬件不会将内存视为无组织字节的长列表。所有处理器(固定或可变字长)都具有特定的引导方法。通常是处理器存储器/地址空间中的已知地址,其具有到引导代码的第一指令的地址或第一指令本身。从那里开始,对于每条指令,当前指令的地址都是开始解码的地方。

对于x86,例如,它必须查看第一个字节。根据该字节的解码,可能需要读取更多的操作码字节。如果指令需要地址,偏移或其他某种形式的立即值,那么那些字节也存在。处理器很快就知道该指令中确切的字节数。如果解码显示指令包含5个字节并且它从地址0x10开始,则下一条指令位于0x10 + 5或0x15。这将持续下去。无条件分支,取决于处理器可以有各种风格,你不要假设指令后面的字节是另一条指令。有条件或无条件的分支给你一个线索,其中一条指令或一系列指令在内存中开始。

注意,今天的X86在解码指令时一定不能获取一个字节,一次发生大小读取,一次可能是64位,处理器会根据需要从中提取字节数。当从现代处理器读取单个字节时,存储器总线仍然执行全尺寸读取,并且在总线上呈现所有这些位,其中存储器控制器仅拉取它之后的位,或者它可以保持该数据。 。您将看到一些处理器,您可以在背靠背地址处有两个32位读取指令,但在存储器接口上只发生一次64位读取。

我强烈建议您编写反汇编程序和/或模拟器。对于固定长度的指令,它非常简单,您只需从头开始并在通过内存时进行解码。固定字长反汇编程序可能有助于学习解码指令,这是此过程的一部分,但它不会帮助您理解跟随变量字长指令以及如何将它们分开而不会失去对齐。

MSP430是第一个反汇编器的不错选择。有gnu工具asm和C等(并且llvm就此而言)。从汇编程序开始,然后是C或者使用一些预先制作的二进制文件。它们的关键是你必须像处理器一样走代码,从复位向量开始,然后一路走来。当您解码一条指令时,您知道它的长度并知道下一条指令的位置,直到您触及无条件分支。除非程序员故意留下陷阱来欺骗反汇编程序,否则假定所有分支都有条件或无条件地指向有效指令。只需要一个下午或晚上就能完成整个过程或至少获得概念。你不一定需要完全解码指令,不必使这个成为一个完整的反汇编程序,只需要解码足以确定指令的长度并确定它是否是一个分支,如果是这样的话。作为一个16位指令,如果您选择,您可以一次构建一个包含所有可能指令位组合及其长度的表,这可以节省一些时间。你仍然需要通过分支解码你的方式。

有些人可能会使用递归,而是使用一个内存映射,显示哪些字节是指令的开头,哪些字节/字是指令的一部分而不是第一个字节/字以及我还没有解码的字节。我开始接受中断和 重置向量并使用它们标记指令的起始点。然后进去 一个循环,解码指令寻找更多的起点。如果传球发生 没有其他起点,那么我已经完成了那个阶段。如果在任何时候我发现指令起点落在指令中间,则存在需要人工干预才能解决的问题。例如,拆解旧的视频游戏roms你可能会看到这个,手写的汇编程序。编译器生成的指令 往往非常干净和可预测。如果我通过一个干净的指令存储器映射和剩下的内容(假设数据)来完成这一点,我可以知道指令的位置,并解码并打印出来。变量字长指令集的反汇编程序永远不能找到每条指令。如果指令集具有例如跳转表或者某种运行时计算的执行地址,那么在没有实际执行代码的情况下你将找不到所有这些。

有很多现有的模拟器和反汇编器,如果你想尝试跟随而不是自己编写,我自己有几个http://github.com/dwelch67

支持和反对变量和固定字长有利有弊。固定具有确定的优点,易于阅读易于解码,一切都很好和正确,但想想ram,特别是缓存,你可以在与ARM相同的缓存中塞入更多的x86指令。另一方面,ARM可以更容易地解码,更少的逻辑,功率等等。从历史上来说,内存昂贵,逻辑价格昂贵,而且一个字节就是它的工作方式。单字节操作码将您限制为256条指令,因此扩展为需要更多字节的一些操作码,更不用说使其无论如何都变量字长的立即数和地址。保持反向兼容性数十年,你最终到现在的位置。

为了增加所有这些混淆,ARM现在具有可变字长指令集。 Thumb有一个单变量字指令,分支,但你可以很容易地将其解码为固定长度。但他们创建了thumb2,它确实类似于可变字长指令集。此外,许多/大多数支持32位ARM指令的处理器也支持16位拇指指令,因此即使使用ARM处理器,您也不能简单地按字对齐数据并进行解码,因此必须使用可变字长。更糟糕的是ARM向/从拇指过渡通过执行来解码,你通常不能简单地从拇指拆卸和弄清楚手臂。混合模式编译器生成的分支通常涉及加载具有地址的寄存器进行分支,然后使用bx指令分支到它,因此反汇编程序需要查看bx,向后看执行分支中使用的寄存器和希望你在那里找到一个负载,并希望它是从它加载的.text段。

答案 1 :(得分:7)

有很好的理由拥有固定的指令长度,实现简单性是最重要的。这就是为什么许多处理器确实具有固定的指令长度,如RISC processors和许多早期计算机。

像x86一样的{p> CISC instruction sets被设计为microcode按顺序(逐步)解码。 (您可以将微码视为CISC指令的一种解释器)这是80年代早期设计x86时的最新技术。

现在这是一个问题,因为微码已经死了。 x86指令现在被分成更小的µ-ops,与RISC指令不同。但要这样做,必须首先解码x86指令。目前的CPU每个周期最多可解码4条指令。因为没有时间按顺序解码一个接一个的指令,所以这只是通过暴力行为。当一行从the instruction cache引入时,许多解码器并行解码该行。每个可能的字节偏移处的一个指令解码器在解码之后,每条指令的长度是已知的,并且处理器决定哪些解码器实际提供有效指令。这很浪费,但速度非常快。

可变指令大小会引入更多内容,例如:一条指令可以跨越内存中的两个缓存行甚至两页。所以你的观察是正确的。今天没有人会设计像x86这样的CISC指令集。但是,一些RISC最近引入了第二个指令大小来获得更紧凑的代码:MIPS16,ARM-Thumb等。

答案 2 :(得分:4)

我将无法回答它们的解码方式,但我可以回答为什么它们的长度可变。

可变长度的原因是由于希望保持代码大小以及无法预料的指令集扩展。


缩减指令大小

一些指令(本质上)需要更多的空间来编码。如果所有指令都设置在足够大的固定长度以适应这些指令,则指令代码中会浪费大量空间。可变长度指令允许将指令“压缩”到更小的尺寸。


(无法预料)指令集扩展

另一个原因是指令集扩展。最初,x86只有256个操作码。 (1字节)然后需要添加更多指令,因此他们抛出一条指令并将其操作码用作新操作码的转义字符。结果是较新的指令更长。但这是扩展指令集和保持向后兼容性的唯一方法。

至于处理器如何解码这些,这是一个复杂的过程。对于每条指令,处理器需要找到长度并从那里解码。这导致了固有的顺序解码过程,这是一个常见的性能瓶颈。

现代x86处理器具有所谓的uop(微操作)缓存,可将解码后的指令缓存到处理器更易于管理(和RISC类似)的位置。

答案 3 :(得分:2)

您重新发明了RISC

嗯,您对经典x86 (see CISC)的反对意见正是RISC CPU架构的设计者创造简单,对齐,固定大小的指令集架构的动机。

事实证明,x86实际上将用户可见的ISA转换为更像RISC的微操作流,它位于内部缓存中。

很好的观察。

<小时/> 注释。
1。微操作只是一种可用的技术。在一般情况下,只要指令的解码和对齐发生在一个或多个流水线级中,实际所花费的时间就不会被添加到平均指令执行时间。如果分支预测工作且流水线保持满,则解码和对齐指令所花费的额外时间由与实际指令操作并行执行的逻辑处理。今天的设计人员可以使用数百万个门,他们可以投入大量的逻辑来解码复杂的x86 ISA。你提到了内存总线的宽度;事实证明,内存路径通常也大于32位或64位。架构字大小只是指ALU和指针大小。内存和缓存接口的实际宽度通常是架构字大小的2倍或4倍。