Assembly x86指令前缀解码
我目前正在为x86_x64 CISC开发一个反汇编程序。 关于前缀指令解码,我有两个问题:Assembly x86指令前缀解码,assembly,x86,x86-64,disassembly,Assembly,X86,X86 64,Disassembly,我目前正在为x86_x64 CISC开发一个反汇编程序。 关于前缀指令解码,我有两个问题: 对于以下流: \x9b\x9b\xd9\x30 \xf2\x66\x0f\x12\x00 GCC和objdump输出 fstenv [eax] 所以他们首先读取所有前缀 (不超过15),然后继续检查正确的说明 使用最后一个前缀read\x9b和\xd9使其成为fstenv 指示 另一方面,Capstone wait wait fnstenv dword ptr [eax] 现在,很明显,这是错误
\x9b\x9b\xd9\x30
\xf2\x66\x0f\x12\x00
GCC
和objdump
输出
fstenv [eax]
所以他们首先读取所有前缀
(不超过15),然后继续检查正确的说明
使用最后一个前缀read\x9b
和\xd9
使其成为fstenv
指示
另一方面,Capstone
wait
wait
fnstenv dword ptr [eax]
现在,很明显,这是错误的
它放入2条等待指令,而不仅仅是1条。但它是否应该
wait
指令或GCC
和objdump
在右边
此处用于使用fstenv
指令\x9b\x9b\xd9\x30
\xf2\x66\x0f\x12\x00
GCC
和objdump
输出
data16 movddup xmm0,QWORD PTR [eax]
所以他们正在安排
前缀按特定顺序排列,因此在\xf2
之前解释\x66
因此,他们仍然使用最后一个前缀read\xf2
来
确定指令movdup
。他们就在这里等你
使用前缀的这种排列逻辑,还是它们错了
另一方面,Capstone
wait
wait
fnstenv dword ptr [eax]
movlpd xmm0,qword ptr[eax]
所以他们没有按任何顺序排列前缀,只是
读取最后一个前缀\x66
以确定指令
movlpd
在这种情况下比GCC
更符合逻辑
objdump
正在执行cpu实际如何解释这些流?您的cpu实际如何解释这些流可以相对容易地进行测试
对于第一个流,您可以使用我的工具。您可以使用该命令
sudo./nanoBench.sh-asm_init“mov RAX,R14”-asm.字节0x9b,0x9b,0xd9,0x30”
此命令首先将RAX
设置为有效的内存地址,然后多次运行流。在我的核心i7-8700K上,我得到以下输出(对于固定功能性能计数器):
我们可以看到CPU执行三条指令,因此Capstone
似乎是正确的
您可以使用nanoBench的调试模式分析第二个流:
sudo./nanoBench.sh-展开1-asm“mov-RAX,R14;mov-qword-ptr[RAX],1234;。字节0xf2,0x66,0x0f,0x12,0x00”-调试
这将在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使用gdb./asm
对其进行分析:
(gdb) r
Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000400088 in ?? ()
(gdb) p $xmm0.v2_int64
$2 = {1234, 1234}
9B 9B D9 30
顶石是正确的,objdump的fstenv
也基本正确。
fstenv
不是真正的机器指令,它是fwait+fnstenv
的伪指令。请注意,列出的fnstenv
的机器代码是D9/6
,而fstenv
在这之前添加了一个9B
9B
不是指令前缀,它是一个单独的1字节指令,称为。在最初的8086+8087上,这是必要的,因为8087是一个真正独立的协处理器。请参见顶部答案下方的评论;在286之前,它们的耦合不够紧密,主CPU无法知道是否存在未决的FPU异常
我不确定细节,但8086/186上的fnstsw
可能会读取旧版本的状态字,该状态字没有从屏蔽异常设置最新标志。或者,它可能只与未屏蔽异常有关,用于从乘法或fnst*
指令之前的任何内容获取FP异常。根据Stephen Kitt的评论,286和更新版本“在执行NPX指令之前检查其测试行”,自动等待
当然,带有集成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的回答所示,
F266 0f 12 00
在实际硬件上解码为movddup
(64位广播),前缀为无意义的66
(数据16操作数大小)objdump是正确的,至少对于该CPU是正确的
有文档记录的编码,其中F2为必填前缀,0F为转义字节
我们可能期望它像解码一个毫无意义的F2 REP前缀一样,但事实并非如此顶点错误。强制前缀字节有一些规则:包括“如果使用F2或F3,则忽略66前缀”
我不能100%确定这个序列在所有硬件上都能保证解码为movddup
,当然如果我