Optimization 英特尔为指令添加前缀,检查优化问题
我想了解更多关于ptrace使用x86_64二进制文件的函数,以及反汇编指令。 目标是检查字节是否是指令前缀之一 我在(第二卷第二章)中找到了一些信息Optimization 英特尔为指令添加前缀,检查优化问题,optimization,x86,intel,disassembly,micro-optimization,Optimization,X86,Intel,Disassembly,Micro Optimization,我想了解更多关于ptrace使用x86_64二进制文件的函数,以及反汇编指令。 目标是检查字节是否是指令前缀之一 我在(第二卷第二章)中找到了一些信息 2.1.1指令前缀部分显示了以下前缀: [0x26]ES段覆盖 [0x36]SS段覆盖前缀 [0x2E]未采用CS段覆盖前缀或分支 [0x3E]DS段覆盖前缀或采用的分支 [0x64]FS段覆盖前缀 [0x65]GS段覆盖前缀 [0x66]操作数大小覆盖前缀 [0x67]地址大小覆盖前缀 [0xF0]锁定前缀 [0xF2]REPNE/REPNZ
2.1.1指令前缀部分显示了以下前缀:
- [
0x26
]ES段覆盖
- [
0x36
]SS段覆盖前缀
- [
0x2E
]未采用CS段覆盖前缀或分支
- [
0x3E
]DS段覆盖前缀或采用的分支
- [
0x64
]FS段覆盖前缀
- [
0x65
]GS段覆盖前缀
- [
0x66
]操作数大小覆盖前缀
- [
0x67
]地址大小覆盖前缀
- [
0xF0
]锁定前缀
- [
0xF2
]REPNE/REPNZ前缀或BND前缀
- [
0xF3
]REP或REPE/REPZ前缀
视觉上,以黄色显示前缀
如果我想知道一个字节是否是前缀,我会尽量提高效率,检查是否可以执行二进制操作
如果我将0x26
、0x36
、0x2E
和0x3E
作为一组。基数2中的这些数字(00100110
、00110110
、00101110
和00111110
)显示一个公共部分:001XX110
如果我的字节在此组中,则可以找到11100111
(0xE7
)的and二进制操作
太好了。现在,如果我取第二组,它包含0x64
,0x65
,0x66
和0x67
(01100100
,01100101
,011011011011
),我找到了另一个公共部分:011001001xx
然后,如果字节在第二组中,则可以找到111111 00
(0xFC
)的和二进制操作
剩余的指令前缀(0xF0
、0xF2
和0xF3
)出现问题:没有公共部分。111111 00
(0xFC
)的and操作将允许字节0xF1
一种解决方案是检查字节是否不是0xF1
因此,在C中可能的实现是:
if ((byte & 0xE7) == 0x26) {
/* This `byte` is a ES, SS, CS or DS segment override prefix */
}
if ((byte & 0xFC) == 0x64) {
/* This `byte` is a FS, GS, Operand-size or address-size override prefix */
}
if ((byte & 0xFC) == 0xF0) {
if (byte != 0xF1) {
/* This `byte` is a LOCK, REPN(E/Z) or REP(_/E/Z) prefix */
}
}
我来自英特尔,希望最后一个组只能签入一个操作
然后,最后一个问题是:如果字节是0xF0、0xF2或0xF3,我可以签入一个操作吗
然后,最后一个问题是:如果字节是0xF0、0xF2或0xF3,我可以签入一个操作吗
最接近一条指令的是:
;ecx = the byte
bt [table],ecx ;Is the byte F0, F2 or F3?
jc .isF0F2orF3 ; yes
但是,有时前缀不被视为前缀(例如,pause
指令,其编码类似于rep nop
,以便与旧CPU兼容)
还请注意,对于高速反汇编程序,最快的方法可能是“跳转表驱动”,其中一个寄存器指向对应于解码器状态的表,另一个寄存器包含指令的下一个字节,如:
;ebx = address of table corresponding to the decoder's current state
movzx eax,byte [esi] ;eax = next byte of the instruction
inc esi ;esi = address of byte after the next byte of this instruction
jmp [ebx+eax*4] ;Go to the code that figures out what to do
在这种情况下,跳转到的部分代码将设置一些标志而不更改当前表(例如,初始表中0xF3的条目将导致跳转到设置“rep prefix was seen”标志的代码),跳转到的部分代码将切换到不同的表(例如,初始表中0x0F的条目将导致跳转到代码,该代码更改为EBX
,指向用于所有以0x0F开始的指令的完全不同的表,…
);跳转到的部分代码将显示一条指令(并重置解码器的状态)
例如,对于pause
,代码可能是:
table0entryF3:
or dword [prefixes],REP
movzx eax,byte [esi] ;eax = next byte of the instruction
inc esi ;esi = address of byte after the next byte
jmp [ebx+eax*4]
table0entry90:
mov edx,instructionNameString_NOP
test dword [prefixes],REP ;Was it a PAUSE or NOP?
je doneInstruction_noOperands ; NOP, current name is right
and dword [prefixes],~REP ; PAUSE, pretend the REP prefix wasn't there
mov edx,instructionNameString_PAUSE ; and use the right name
jmp doneInstruction_noOperands
doneInstruction_noOperands:
call displayPrefixes
call displayInstructionName
mov dword [prefixes],0 ;Reset prefixes
mov ebx,table0 ;Switch current table back to the initial table
movzx eax,byte [esi] ;eax = first byte of next instruction
inc esi ;esi = address of byte after the next byte
jmp [ebx+eax*4]
你可以试着在code golf上问这个问题。如果你认为这个问题是非主题的,那么它需要被改写成一个谜题。@prl如果这个问题不完全失去它当前的意义/要求,几乎没有办法改写成适合PPCG的问题。PPCG 99%的时间都是用于比赛的,并且写一篇文章n关于主题的非挑战真的很难。OP没有说“1个操作”,而不是“1条指令”,而且bt[mem],reg
相当慢(Skylake上的10个UOP,比用shift/mask手动模拟它来将位索引分解为字节偏移量+掩码更糟糕。)只检查这3个字节最好是通过范围检查,然后将bt
转换成即时位图。例如sub-eax,0xf0
/jnc-not\u-fx\u prefix
/bt-ecx,eax
/jnc-not\u-fx\u prefix
在ecx>中保持0b1101
。但是我猜OP的掩码和拒绝0xf1
实际上更好。@PeterCordes:是的;Skylake很慢,因为一条指令只能执行“5次操作”(将索引拆分为dword编号和位编号,将dword编号添加到地址,提取dword,提取位,设置标志)与所有使用较少UOP的CPU相比,它使用的UOP更多。公平地说,自Core2以来,所有Intel上的UOP速度都很慢。奔腾M和P6上的速度稍快。AMD Ryzen将其解码为仅5 UOP,但存在一些人为瓶颈,将其限制为3个周期的吞吐量。在K10上,它是5 UOP,每2个时钟一个,但在其他方面是最好的吞吐量Ryzen为3,推土机为3.5。因此,似乎大多数CPU的运行速度比需要的慢,除非这些只是微代码指令的前端效果。