在gcc内联asm中data32 data32 nopw%cs:0x0(%rax,%rax,1)指令是什么意思?

cmi*_*idi 7 c optimization x86 assembly gcc

在为gcc编译器的-O2优化运行一些测试时,我在反汇编代码中观察到以下指令:

data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)
Run Code Online (Sandbox Code Playgroud)

这个指令做了什么?

为了更详细,我试图了解编译器如何使用O2优化优化无用的递归,如下所示:

int foo(void)
{
   return foo();
}
int main (void)
{
   return foo();
}
Run Code Online (Sandbox Code Playgroud)

上面的代码在没有优化的情况下编译时导致堆栈溢出,但适用于O2优化代码.

我认为使用O2它完全删除了推送函数foo的堆栈,但为什么data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)需要?

0000000000400480 <foo>:
foo():
400480:       eb fe                   jmp    400480 <foo>
400482:       66 66 66 66 66 2e 0f    data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)
400489:       1f 84 00 00 00 00 00

0000000000400490 <main>:
main():
400490:       eb fe                   jmp    400490 <main>
Run Code Online (Sandbox Code Playgroud)

pet*_*ica 6

您会看到cpu管道的操作数转发优化.

虽然它是一个空循环,但gcc也试图优化它:-).

您运行的cpu具有超标量体系结构.这意味着它有一个管道,并且连续指令的执行的不同阶段是并行发生的.例如,如果有

mov eax, ebx ;(#1)
mov ecx, edx ;(#2)
Run Code Online (Sandbox Code Playgroud)

然后,当执行#1时,指令#2的加载和解码可能已经发生.

流水线在分支机构的情况下有很大的问题需要解决,即使它们是无条件的.

例如,在jmp解码时,下一条指令已经预取到流水线中.但是jmp改变了下一条指令的位置.在这种情况下,管道需要通过清空和重新填充,并且将丢失许多有价值的cpu周期.

如果管道在这种情况下用no-op填充,看起来这个空循环将运行得更快,尽管它不会被执行.它实际上是对x86管道的一些不常见功能的优化.

早期的dec alpha甚至可以从这些东西中断行,而空循环必须有很多no-ops.x86只会慢一些.这是因为它们必须与intel 8086兼容.

在这里,您可以从管道中的分支指令处理中阅读很多内容.


Chr*_*odd 6

要回答标题中的问题,说明

data32 data32 data32 data32 nopw %cs:0x0(%rax,%rax,1)
Run Code Online (Sandbox Code Playgroud)

是一个14字节的NOP(无操作)指令,用于填充foo函数和main函数之间的间隙,以保持16字节对齐.

x86体系结构具有大量不同大小的不同NOP指令,可用于将填充插入可执行段,以便在CPU最终执行它们时它们不起作用.然后,英特尔优化手册包含有关可用作填充的不同长度的推荐NOP编码的信息.

在这种特定情况下,它完全不相关,因为NOP永远不会被执行(或者甚至在无条件跳转之后被解码),因此编译器可以填充它想要的任何随机垃圾.