在这个编译器输出中,我试图理解nopw
指令的机器码编码是如何工作的:
00000000004004d0 <main>:
4004d0: eb fe jmp 4004d0 <main>
4004d2: 66 66 66 66 66 2e 0f nopw %cs:0x0(%rax,%rax,1)
4004d9: 1f 84 00 00 00 00 00
在http://john.freml.in/amd64-nopl有一些关于“nopw”的讨论。任何人都可以解释4004d2-4004e0的含义吗?从查看操作码列表看,66 ..
代码似乎是多字节扩展。我觉得我可能会得到一个比这更好的答案,除非我试着把操作码列表弄几个小时。
asm输出来自C中的以下(疯狂)代码,它优化为简单的无限循环:
long i = 0;
main() {
recurse();
}
recurse() {
i++;
recurse();
}
使用gcc -O2
编译时,编译器会识别无限递归并将其转换为无限循环;它实际上做得很好,实际上它实际上在main()
中循环而没有调用recurse()
函数。
编者注:带NOP的填充函数并不特定于无限循环。这是一组具有一系列NOP长度的函数,on the Godbolt compiler explorer.
答案 0 :(得分:21)
0x66
字节是“操作数大小覆盖”前缀。拥有一个以上就相当于拥有一个。
0x2e
在64位模式下是一个'空前缀'(否则它是CS:段覆盖 - 这就是它出现在程序集助记符中的原因)。
0x0f 0x1f
是NOP的2字节操作码,采用ModRM字节
0x84
是ModRM byte,在这种情况下编码使用5个字节的寻址模式。
某些CPU很难解码具有许多前缀的指令(例如,多于3个),因此指定SIB + disp32的ModRM字节是比使用5个以上前缀字节多出5个字节的更好方法。
AMD K8 decoders in Agner Fog's microarch pdf:
每个指令解码器每个时钟可以处理三个前缀 周期。这意味着三个指令每个都有三个前缀 在相同的时钟周期内解码。带有4 - 6个前缀的指令 需要一个额外的时钟周期来解码。
本质上,这些字节是一条长NOP指令,无论如何都永远不会被执行。它在那里确保下一个函数在16字节边界上对齐,因为编译器发出了.p2align 4
指令,因此汇编器用NOP填充。 gcc's default for x86 is
-falign-functions=16
。对于将要执行的NOP,长NOP的最佳选择取决于微体系结构。对于在许多前缀上窒息的微体系结构,如Intel Silvermont或AMD K8,两个带有3个前缀的NOP可能会更快地解码。
博客文章链接到(http://john.freml.in/amd64-nopl)的问题解释了为什么编译器使用复杂的单个NOP指令而不是一堆单字节0x90 NOP指令。
您可以在AMD的技术参考文档中找到有关指令编码的详细信息:
主要在“AMD64架构程序员手册第3卷:通用和系统指令”中。我确信英特尔对x64架构的技术参考将具有相同的信息(甚至可能更容易理解)。
答案 1 :(得分:2)
汇编程序(不是编译器)使用可以找到的最长NOP指令将代码填充到下一个对齐边界。这就是你所看到的。
答案 2 :(得分:1)
我猜这只是分支延迟指令。
答案 3 :(得分:-3)
我相信现在是垃圾 - 它永远不会在您的程序中读取,因此无需增加它。