最近编写了一个C程序到Find Two-byte Illegal Opcodes for x86-64并将输出粘贴到https://pastebin.com/5xjjFea6
例如,下面是一些非法的两字节操作码
0x0f,0x04 0x0f,0x0a 0x0f,0x0b 0x0f,0x0c 0x0f,0x0e 0x0f,0x0f 0x0f,0x24 0x0f,0x25 0x0f,0x26 0x0f,0x27 0x0f,0x36 0x0f,0x37 0x0f,0xaa
我在谷歌上只找到了几个描述
0x0f,0x0b - ud2 - Generates an invalid opcode.
0x0f,0x37 - getsec - Exit authenticated code execution mode.
0x0f,0xaa - rsm - Resume operation of interrupted program.
- 问题**
- 是否有文档解释大多数非法操作码?
- 为什么会存在非法操作码?
- 为什么它们在x86 - 64上不被当作NOP处理,或者分配给一些有用的东西?
1条答案
按热度按时间unftdfkk1#
#UD
非法指令错误的存在是为了让您知道您试图运行此CPU不支持的指令。对于大多数新指令,最好是旧CPU
#UD
在它们上出错,而不是默默地做错误的事情(或者什么也不做)。例如,getsec
和rsm
不是您可以使用的指令,因为它们可能作为NOP运行,而不是执行您想要的操作。(有趣的事实:8086没有#UD错误;任何字节序列都将作为 * something * 执行。)只有对于像
prefetchw
这样的推测性提示,或者其他回退有效的情况,你才希望旧CPU把它作为NOP运行,这样代码就可以不用检查就可以使用它。你通常可以在现有指令前面使用REP来实现这一点(比如pause
是rep nop
)。有足够的NOP等价操作码供将来使用;最好让其余未使用的编码空间实际上出错。
从CPU供应商的Angular 来看,有一些字节序列#UD错误意味着他们可以确保现有代码不会(意外地或故意地)依赖于它们作为长NOP工作,而不是使用推荐的编码。
是否有文档解释大多数非法操作码?
没有,除了英特尔说执行任何当前手册 * 没有 * 明确定义的东西的结果是不可预测的。我没有检查英特尔或AMD手册的措辞,但我认为他们说这是可能性之一。
请记住,他们 * 不是 * 记录特定的CPU,他们记录x86 - 64 ISA的方式鼓励人们创建向前兼容的程序,这些程序仍然可以在未来的CPU上正常工作,这些字节序列可能是目前尚未提出的新指令的一部分。
UD错误是许多字节序列在实践中的常见行为。但其他序列,比如指令上的
rep
前缀,如果它没有任何意义,通常会忽略rep
前缀。这让CPU供应商引入了一个新的扩展,其中rep xyz
表示其他含义(如rep bsf
变为tzcnt
,这对于非零输入产生相同的结果),在 * 那 * 点上,他们可以记录以前的CPU运行它的行为,只是普通的bsf
。(或者对于pause
,将rep nop
作为nop
运行)。还有其他多个示例,但问题是,这种行为一直存在,只是在设置了一些CPUID功能位时,当
rep xyz
编码被记录为执行其他操作时,才追溯记录旧CPU的行为。相关:
xacquire
/xrelease
是与F2/F3 REP/REPNE前缀相同的字节)lock
前缀在不适用时将显示为#UD
,与其他前缀不同。)rep ret
mean?-一个罕见的忽略案例-rep
行为在 * 没有 * 文档的情况下被广泛依赖。如此广泛的使用(默认情况下由GCC使用多年),以至于CPU供应商在不停止其CPU运行GCC编译的二进制文件的情况下无法合理地改变它,这对于通用CPU来说不是一个商业上可行的选择。为什么不给他们分配一些有用的工作?
32位模式基本上没有剩余的编码空间,也没有空闲的单字节操作码,迄今为止所有的扩展都对32位和64位使用相同的编码;如果你关心32位模式,你希望解码器只需要寻找相同的模式,这是有道理的。这就是为什么VEX(AVX)和EVEX(AVX-512)前缀使用一些反转位,以便它们与32位模式下的无效编码重叠,也是为什么它们在32位模式下仍限于8个矢量寄存器。
不幸的是,还没有任何扩展引入新的仅64位指令来使用一些释放的1字节操作码(BCD指令,如
aaa
或段寄存器的push/pop)。它们可以添加多个,如mov r/m32, sign_extended_imm8
,以提高64位模式下的代码密度,使得像mov ecx, -2
或1
这样的指令变成3字节而不是5字节,并且对于mov rcx, -2
变成4字节而不是7字节。但是微软的软件分发模式是,一个二进制文件在任何地方运行,并检测CPU特性以用于一些特殊功能,这样的特性并不能带来足够的好处。它是一种在整个二进制文件中只会有一点帮助的东西,所以只有像
gcc -march=native
这样的情况。BMI1/2就是这样,大多数情况下,只需一条指令就可以完成2条指令就可以完成的事情。所以在一个程序的所有函数中使用时,它通常是有帮助的。但是可以想象,一些bithack函数可以获得足够的加速,这是值得的。Intel和AMD对引入不会立即有用的扩展没有兴趣,所以在十年左右的时间里当扩展无处不在时,它们会变得最有用,但却被大多数人忽略了(广泛到一些程序可以把它当作他们“基线”的一部分)。尽管他们用BMI 1/2涉足这些沃茨,英特尔多年来继续销售 * 没有 * 那些扩展的CPU,包括Skylake Pentium/Celeron,我认为还有Silvermont系列CPU(上网本和低功耗服务器)。
一些BMI 1/2指令使用VEX编码,禁用VEX解码是英特尔在Ice Lake之前的低端CPU中禁用AVX的方式,BMI 1/2是一个牺牲品。(Silvermont系列直到Gracemont(桤木Lake E核心)根本不支持AVX,他们也根本不需要能够解码VEX前缀。)
AMD最初对AMD 64的设计相当保守,除了删除一些操作码以释放它们以备将来可能的扩展之外,尽可能少地改变机器码的含义。显然,他们不想陷入需要更多解码器晶体管的困境,以防AMD 64在商业上无法流行。为了促进编译器和汇编器的采用,他们保留了32位模式的大部分缺点。包括X1 M28 N1 X而不是X1 M29 N1 X,所以X86在从比较条件有效地具体化整数0/1方面仍然很糟糕。