x86指令前缀解码

时间:2019-02-26 12:57:33

标签: assembly x86 x86-64 disassembly

我目前正在为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在这种情况下比GCC看起来更合乎逻辑 objdump在做。

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

2 个答案:

答案 0 :(得分:4)

您的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执行了3条指令,因此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}

答案 1 :(得分: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?。请参阅顶部答案下方的评论;在286之前,它们耦合不够紧密,主CPU无法知道是否有未决的FPU异常。

我不确定细节,但是8086/186上的fnstsw可能会读取状态字的旧版本,该旧版本的状态字未从屏蔽的异常中设置最新标志。也许它仅与未屏蔽的异常有关,对于从乘法或在fnst*指令之前执行的操作获取FP异常。根据史蒂芬·基特(Stephen Kitt)的评论,286及更高版本“自动在执行NPX指令之前检查其TEST行”。

当然,具有集成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是转义字节。

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

我不确定百分百保证在所有硬件上都可以将该序列解码为movddup,如果这仅仅是Intel Sandybridge系列碰巧对其进行解码的方式。正如@fuz所评论的那样,必须对强制性前缀进行排序,并且弄错了会导致未定义的行为(即,特定的CPU可能会将其解码为任何内容,尤其是将来的某些其他指令中必须使用不同前缀序列的CPU。) / p>