我目前正在为x86_x64 CISC开发反汇编程序。 关于前缀指令解码,我有两个问题:
对于以下流:
\x9b\x9b\xd9\x30
GCC
和objdump
输出
fstenv [eax]
因此,他们首先读取所有前缀
(不超过15个),然后继续检查正确的说明
使用最后一个读入\x9b
和\xd9
的前缀使其成为fstenv
指示。
Capstone
输出
wait
wait
fnstenv dword ptr [eax]
现在,显然是错误的顶峰
它放置了2条wait
指令,而不仅仅是1条指令,但是应该放置
完全wait
条指令,或者GCC
和objdump
在右侧
在这里使用\x9b
的所有额外fstenv
前缀
指示?
对于以下流:
\xf2\x66\x0f\x12\x00
GCC
和objdump
输出
data16 movddup xmm0,QWORD PTR [eax]
因此,他们正在安排
前缀以特定顺序排列,因此\x66
在\xf2
之前被解释
因此,因此他们仍然使用读为\xf2
的最后一个前缀
确定指令movddup
。他们是在这里吗
使用前缀的这种排列逻辑还是错误?
Capstone
输出
movlpd xmm0, qword ptr [eax]
因此,他们没有以任何顺序排列前缀,而只是
以最后一个读为\x66
的前缀来确定指令
movlpd
在这种情况下比GCC
看起来更合乎逻辑
objdump
在做。
cpu如何实际上解释这些流?
答案 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
并没有架构上的影响。
如安德烈亚斯(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>