【问题标题】:How does the CPU decode variable length instructions correctly?CPU 如何正确解码变长指令?
【发布时间】:2014-08-04 23:41:35
【问题描述】:

在大多数架构上,指令都是固定长度的。这使得程序加载和执行变得简单。在 x86/x64 上,指令是可变长度的,因此反汇编程序可能如下所示:

File Type: EXECUTABLE IMAGE

  00401000: 8B 04 24           mov         eax,dword ptr [esp]
  00401003: 83 C4 04           add         esp,4
  00401006: FF 64 24 FC        jmp         dword ptr [esp-4]
  0040100A: 55                 push        ebp
  0040100B: E8 F0 FF FF FF     call        00401000
  00401010: 50                 push        eax
  00401011: 68 00 30 40 00     push        403000h
  00401016: E8 0D 00 00 00     call        00401028
  0040101B: 83 C4 08           add         esp,8
  0040101E: 33 C0              xor         eax,eax
  00401020: 5D                 pop         ebp
  00401021: 83 C4 04           add         esp,4
  00401024: FF 64 24 FC        jmp         dword ptr [esp-4]
  00401028: FF 25 00 20 40 00  jmp         dword ptr ds:[00402000h]

  Summary

        1000 .data
        1000 .rdata
        1000 .reloc
        1000 .text

似乎很难想象 CPU 是如何“知道”一条指令在哪里结束以及下一条指令在哪里开始的。例如,如果我将字节 0x90 (NOP) 添加到 XOR EAX,EAX 操作码的中间,则程序将反汇编为:

File Type: EXECUTABLE IMAGE

  00401000: 8B 04 24           mov         eax,dword ptr [esp]
  00401003: 83 C4 04           add         esp,4
  00401006: FF 64 24 FC        jmp         dword ptr [esp-4]
  0040100A: 55                 push        ebp
  0040100B: E8 F0 FF FF FF     call        00401000
  00401010: 50                 push        eax
  00401011: 68 00 30 40 00     push        403000h
  00401016: E8 0D 00 00 00     call        00401028
  0040101B: 83 C4 08           add         esp,8
  0040101E: 33 90 C0 5D 83 C4  xor         edx,dword ptr [eax+C4835DC0h]
  00401024: 04 FF              add         al,0FFh
  00401026: 64 24 FC           and         al,0FCh
  00401029: FF
  0040102A: 25
  0040102B: 00 20              add         byte ptr [eax],ah
  0040102D: 40                 inc         eax

  Summary

    1000 .data
    1000 .rdata
    1000 .reloc
    1000 .text

可以预见的是,它在运行时会崩溃。

我很好奇指令解码器究竟看到了什么额外的字节,这使得它认为0040101E 行有 6 个字节长,而最初位于00401028 的行是四个单独的指令。

【问题讨论】:

  • CPU 的工作方式与反汇编程序完全相同。
  • 是的,但我在问为什么他们都用额外的字节完全搞砸了解码后续指令
  • 同样的道理,把“10 32 5”改成“10132 5”,“10”和“32”只要换个空格就变了。
  • 至于 0xFF/0x25,目前可能未定义。到目前为止,没有搞砸 - 你看到的是这些字节现在编码的内容。在运行时,CPU 将触发无效指令错误(假设它尚未在 xor 上进行 AV 处理)并且永远不会执行 add/inc 对。

标签: assembly x86 decode instructions


【解决方案1】:

当获取一条指令时,CPU 首先分析它的第一个字节(操作码)。有时知道指令的总长度就足够了。有时它会告诉 CPU 分析后续字节以确定长度。但总而言之,编码是没有歧义的。

是的,如果你在中间随意插入随机字节,命令流就会被搞砸。这是可以预料的;并非每个字节序列都构成有效的机器码。

现在,关于您的具体示例。原始命令是XOR EAX, EAX (33 C0)。 XOR 的编码是那些第二个字节相关的编码之一。第一个字节 - 33 - 表示 XOR。第二个字节是 ModR/M 字节。它对操作数进行编码——无论是寄存器对、寄存器和内存位置等。32位模式下的初始值C0对应操作数EAX、EAX。您插入的值 90 对应于操作数 EDX,[EAX+offset],这意味着 ModR/M 字节后跟 32 位偏移量。命令流的接下来的四个字节不再被解释为命令 - 它们是损坏的 XOR 命令中的偏移量。

因此,通过弄乱第二个字节,您已经将一个 2 字节的命令变成了一个 6 字节的命令。

然后 CPU(和反汇编程序)在这四个之后继续获取。它在ADD ESP, 4 指令的中间,但CPU 无法知道这一点。它从 04 字节开始,即 ADD 编码中的第三个字节。此时的前几个字节仍然可以作为命令,但由于您最终处于中间位置,因此原始指令序列完全丢失了。

【讨论】:

  • 有趣的事实:可以制作跳回之前运行的指令中间的混淆机器代码。从该点解码会给出不同的指令,不会重复该跳转。这让许多反汇编程序感到困惑,但 CPU 只是像往常一样解码指令。关键是指令解码需要起始地址,因为 x86 指令没有对齐要求,长度可以从 1 到 15 个字节(包括前缀字节:第一个字节甚至不必是操作码)。
猜你喜欢
  • 2012-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-03
  • 2010-10-02
相关资源
最近更新 更多