【问题标题】:x86 instruction prefix decodingx86 指令前缀解码
【发布时间】:2019-02-26 12:57:33
【问题描述】:

我目前正在为 x86_x64 CISC 开发反汇编程序。 我有两个关于前缀指令解码的问题:

  1. 对于以下流:

    \x9b\x9b\xd9\x30
    

    GCCobjdump 输出

    fstenv [eax]
    

    所以他们首先读取所有前缀 (不超过15个)然后继续检查正确的指令 使用最后一个前缀读取\x9b\xd9 使其成为fstenv 说明。

    Capstone 另一方面输出

    wait
    wait
    fnstenv dword ptr [eax] 
    

    现在,显然是错误的 它放了 2 个 wait 指令,而不仅仅是 1 个。但它应该放 wait 说明全部或GCCobjdump 在右侧 在这里消耗所有额外的 \x9b 前缀 fstenv 指令?

  2. 对于以下流:

    \xf2\x66\x0f\x12\x00
    

    GCCobjdump 输出

    data16 movddup xmm0,QWORD PTR [eax]
    

    所以他们正在安排 以特定顺序添加前缀,因此 \x66\xf2 之前解释 因此,他们仍然使用最后一个前缀 \xf2 来 确定指令movddup。所以他们在这里是为了 使用前缀的这种排列逻辑还是错误?

    Capstone 另一方面输出

    movlpd xmm0, qword ptr [eax]

    所以他们没有按任何顺序排列前缀,他们只是 取最后一个前缀 \x66 来确定指令 movlpd 在这种情况下看起来比 GCCobjdump 正在做。

cpu实际上是如何解释这些流的?

【问题讨论】:

  • 1) fstenv 并不存在。指令集参考说:“汇编器为 FSTENV 指令发出两条指令(一条 FWAIT 指令后跟一条 FNSTENV 指令),处理器分别执行这些指令中的每一条。” Capstone 在技术上是正确的。
  • 前缀有强制顺序。忘记是哪一个了,你可以看看说明书。如果前缀顺序错误,则行为未定义。
  • 指令集参考说:“指令前缀 [...] 可以按相对于彼此的任何顺序放置。” 请注意,wait 不是前缀.
  • 2) 将F2 前缀应用于该指令是未定义的,因此很难争论哪个版本是正确的。我的 cpu (amd ryzen 1700) 似乎认为 objdump 是对的,它被执行为movddup。 TBH,我希望它是movlpd...
  • @Jester This answer 声称对于 SSE 指令,如果两个组都出现,则 F2/F3 击败 66。

标签: assembly x86 x86-64 disassembly


【解决方案1】:

可以相对容易地测试您的 CPU 实际解释这些流的方式。


对于第一个流,你可以使用我的工具nanoBench。你可以使用命令

sudo ./nanoBench.sh -asm_init "mov RAX, R14" -asm ".byte 0x9b, 0x9b, 0xd9, 0x30".

此命令首先将RAX 设置为有效的内存地址,然后多次运行您的流。在我的 Core i7-8700K 上,我得到以下输出(用于固定功能性能计数器):

Instructions retired: 3.00
Core cycles: 73.00
Reference cycles: 62.70

我们可以看到CPU执行了三个指令,所以Capstone似乎是正确的。


您可以使用 nanoBench 的调试模式分析第二个流:

sudo ./nanoBench.sh -unroll 1 -asm "mov RAX, R14; mov qword ptr [RAX], 1234; .byte 0xf2, 0x66, 0x0f, 0x12, 0x00" -debug.

这将 - 在 gdb 内部 - 首先执行 asm 代码,然后生成断点陷阱。我们现在可以查看 XMM0 寄存器的当前值:

(gdb) p $xmm0.v2_int64
$1 = {1234, 1234}

所以 XMM0 的高四字和低四字现在与地址 RAX 处的内存具有相同的值,这表明 CPU 执行了 movddup 指令。


您也可以在不使用 nanoBench 的情况下分析第二个流。为此,您可以将以下汇编代码保存在文件 asm.s 中。

.intel_syntax noprefix

.global _start
_start:
    mov RAX, RSP
    mov qword ptr [RAX], 1234   
    .byte 0xf2, 0x66, 0x0f, 0x12, 0x00
    int 0x03 /* breakpoint trap */

然后,您可以使用构建它

as asm.s -o asm.o
ld -s asm.o -o asm

现在您可以使用gdb ./asm 使用gdb 对其进行分析:

(gdb) r
Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000400088 in ?? ()
(gdb) p $xmm0.v2_int64
$2 = {1234, 1234}

【讨论】:

  • 如果它应该是movddupmovlpd 用另一个反汇编程序测试它也无济于事,通过安排前缀也可能是错误的,不是吗?
  • @Jorayen 最后一个示例根据xmm0 寄存器中的实际值判断movddupmovlpd,而不是根据反汇编程序显示的值。 movlpd 会在寄存器中产生不同的结果。
  • 值得一提的是,Linux 进入用户空间时 XMM 寄存器全为零,因此如果解码为 movlpd,我们会看到 {0, 1234}
【解决方案2】:

9B 9B D9 30 Capstone 是正确的,objdump 的fstenv 也基本正确。

fstenv 不是真正的机器指令,它是fwait + fnstenv 的伪指令。请注意,in the manual entry 列出的fnstenv 的机器代码是D9 /6,而fstenv 在此之前添加了一个9B

9B 不是指令前缀,它是一个单独的 1 字节指令,称为 wait aka fwait。在最初的 8086+8087 上,这是必要的,因为 8087 是一个真正独立的协处理器。 How did the 8086 interface with the 8087 FPU coprocessor?。请参阅那里的最佳答案下的 cmets;在 286 之前,它们的耦合不够紧密,无法让主 CPU 知道是否存在未决的 FPU 异常。

我不确定细节,但 8086 / 186 上的 fnstsw 可能会读取旧版本的状态字,该版本没有从掩码异常中设置最新标志。或者也许它只对未屏蔽的异常很重要,用于从乘法或 fnst* 指令之前的任何内容中获取 FP 异常。根据 Stephen Kitt 的 cmets,286 及更高版本“在执行 NPX 指令之前检查其 TEST 行”,自动 FWAITing。

当然,带有集成 FPU 的 CPU 在精确的 FP 异常和同步行为方面没有问题,所以 fwait 在那里浪费空间。


Capstone 的 wait / wait / fnstenv dword ptr [eax] 因此更加明确,因为就 CPU 而言,它确实是 3 条指令。 (正如 Andreas 的回答显示了现代 x86 性能计数器记录)。

Objdump 将两个前面的 fwait 指令视为单个 fstenv 的一部分。将其解码为 fwait 会更准确; fstenv dword ptr [eax] 因为英特尔的手册仅记录 fstenv 包括单个 fwait 操作码。但是额外的fwait 没有架构效果。


第 2 部分

正如 Andreas 的回答所示,f2 66 0f 12 00 在真实硬件上解码为movddup(64 位广播),带有无意义的66(data16 操作数大小)前缀。 objdump 是正确的,至少对于那个 CPU 来说是正确的

movddup is F2 0F 12 的记录编码,其中 F2 是强制前缀,0F 是转义字节。

我们可能希望它解码为66 0F 12 /r MOVLPD,并带有一个无意义的 F2 REP 前缀,但事实并非如此; 顶点错误。强制前缀字节有规则:order for encoding x86 instruction prefix bytes,包括“如果使用 F2 或 F3,则忽略 66 前缀”。

我不能 100% 确定此序列是否保证在所有硬件上解码为 movddup,如果这仅仅是英特尔 Sandybridge 系列恰好解码它的方式。正如@fuz 评论的那样,强制前缀有一个必需的顺序,并且弄错了会导致未定义的行为(即,特定的 CPU 可能会将其解码为任何东西,尤其是某些未来的 CPU,其中某些其他指令必须使用不同的前缀序列。)

【讨论】:

  • 您是否找到手册的哪个部分描述了订购规则?正如 fuz 在链接问题下的 cmets 中抱怨的那样,我在我的手动版本中也找不到任何关于强制订单的信息。
  • @Jester:不,我编造了那个部分/鹦鹉学舌\@fuz。 >.
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-12-23
  • 2016-08-18
  • 1970-01-01
  • 2016-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多