试图理解printf()的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:

我试图学习如何理解汇编代码,所以我一直在研究一些愚蠢程序的GCC汇编输出。其中一个是
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个字节实际上从未被任何人读或写过。如果您愿意,请逐步浏览代码,您将看到。