试图理解printf()的gcc程序集输出
我试图学习如何理解汇编代码,所以我一直在研究一些愚蠢程序的GCC汇编输出。其中一个是试图理解printf()的gcc程序集输出,c,assembly,x86-64,C,Assembly,X86 64,我试图学习如何理解汇编代码,所以我一直在研究一些愚蠢程序的GCC汇编输出。其中一个是inti=0,我现在差不多完全理解了其中的代码(最大的困难是理解散落在周围的气体指令)。无论如何,我向前走了一步,添加了printf(“%d\n”,I)看看我是否能理解,突然代码变得更加混乱 .file "helloworld.c" .text .section .rodata.str1.1,"aMS",@progbits,1 .LC0:
inti=0代码>,我现在差不多完全理解了其中的代码(最大的困难是理解散落在周围的气体指令)。无论如何,我向前走了一步,添加了printf(“%d\n”,I)代码>看看我是否能理解,突然代码变得更加混乱
.file "helloworld.c"
.text
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d\n"
.section .text.startup,"ax",@progbits
.p2align 4
.globl main
.type main, @function
main:
subq $8, %rsp
xorl %edx, %edx
leaq .LC0(%rip), %rsi
xorl %eax, %eax
movl $1, %edi
call __printf_chk@PLT
xorl %eax, %eax
addq $8, %rsp
ret
.size main, .-main
.ident "GCC: (Gentoo 10.2.0-r3 p4) 10.2.0"
.section .note.GNU-stack,"",@progbits
我使用gcc-S-O3-fno异步展开表来编译它,以删除.cfi
指令,但是-O2
产生相同的代码,因此-O3
是多余的。我对汇编的理解非常有限,但在我看来,编译器在这里做了很多不必要的事情。为什么要在rsp中减去8,然后再加上8?为什么它要执行如此多的异或?只有一个变量。movl$1,%edi
在做什么?我认为可能编译器在尝试优化时做了一些愚蠢的事情,但正如我所说的,它没有在-O2
之外进行优化,而且它甚至在-O1
上执行所有这些操作。老实说,我根本不理解未优化的代码,所以我认为这是低效的
我想到的唯一一件事是调用printf
使用这些寄存器,否则它们就没有用处了。真的是这样吗?如果是的话,如何判断
提前谢谢。目前我正在读一本关于编译器设计的书,我已经阅读了GCC手册的大部分内容(我阅读了关于优化的整个章节),我还阅读了一些介绍x86_64 asm的材料,如果有人能给我介绍一些其他资源的话(除了英特尔x86手册)要了解更多信息,我还希望。对于您正在使用的编译器,它看起来像printf(…)映射到了uuu printf_chk(1,…)
要理解代码。一旦您知道在%rdi、%rsi、%rdx、%rcx中传递了多达4个参数,您就可以了解大部分情况:
subq $8, %rsp ; allocate 8 bytes of stack
xorl %edx, %edx ; i = 0 ; put it in the 3rd parameter for __printf_chk
leaq .LC0(%rip), %rsi ; 2nd parameter for __printf_chk. The: "%d\n"
xorl %eax, %eax ; 0 variadic fp params
movl $1, %edi ; 1st parameter for __printf_chk
call __printf_chk@PLT ; call the runtime loader wrapper for __printf_chk
xorl %eax, %eax ; return 0 from main
addq $8, %rsp ; deallocate 8 bytes of stack.
ret
Nate在评论中指出,中的第3.5.7节解释了%eax=0(无浮点变量参数)。此程序集似乎与您假定编译的代码不匹配。如果调用printf(1,“%d\n”)
,这就是我希望看到的,从godbolt判断,这实际上非常接近调用printf(1,“%d\n”)
@Aplet123时得到的程序集。这是代码:#include main(){int I=0;printf(“%d\n”,I)}
有关许多有用的链接,请参阅。我明白了,这可能会引起特别的兴趣!阅读Linux x86调用约定在我的阅读清单上,我想这比我意识到的更重要。Re:uuu printf_chk,编译器直接在-O0
处调用printf(),但在启用优化时使用\uuuu printf_chk
。我没有想到其他0可能是返回值,我的main()函数有一个不合法的返回值,在main(){int i=0;}
的情况下,没有返回值,只有一个返回语句。现在它有意义了。感谢您的帮助。对于像printf
这样的可变函数,需要将%al
设置为用于传递浮点参数的向量(%xmm
)寄存器的数量。请参阅此处的3.5.7,因为并没有传递浮点参数,所以%al
必须为零。编译器将所有的%eax
(实际上是将所有%rax
归零)归零,因为为什么不归零(可能是为了避免一些部分寄存器依赖关系)。此外,子$8,%rsp
实际上并不是分配8个字节的堆栈本身(函数不会将该空间用于任何事情),而是为了确保符合ABI的要求(参见3.2.2)。@NateEldredge,难道不需要8字节堆栈对齐吗?如果需要,再减去8字节对对齐没有任何作用。这个堆栈存储不是由u printf_chk隐式使用的吗(如果它调用了什么,返回地址不是保存在调用方分配的堆栈存储中吗?)不,所需的对齐方式是16字节。在调用main
之前,堆栈已对齐到16字节,将控制权转移到main
的call
指令已压缩8字节,因此我们必须再减去8以返回到调用\u printf\u chk
的16字节对齐方式。返回地址被压缩到调用指令将插槽堆叠在rsp下面。由子$8、%rsp
分配的8个字节实际上从未被任何人读或写过。如果您愿意,请逐步浏览代码,您将看到。