试图了解 printf() 的 gcc 程序集输出
Trying to understand the gcc assembly output for printf()
我正在努力学习如何理解汇编代码,所以我一直在研究一些愚蠢程序的 GCC 汇编输出。其中之一就是 int i = 0;
,我现在或多或少完全理解了它的代码(最大的困难是理解散布的 GAS 指令)。反正我往前加了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 , %rsp
xorl %edx, %edx
leaq .LC0(%rip), %rsi
xorl %eax, %eax
movl , %edi
call __printf_chk@PLT
xorl %eax, %eax
addq , %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-asynchronous-unwind-tables
编译它以删除 .cfi
指令,但是 -O2
生成相同的代码,所以 -O3
是多余的。我对汇编的理解非常有限,但在我看来编译器在这里做了很多不必要的事情。 rsp
为什么减去然后加 8?为什么要执行这么多xor
?只有一个变量。 movl , %edi
在做什么?我想也许编译器在尝试优化时做了一些愚蠢的事情,但正如我所说,它没有优化超过 -O2
,而且它甚至在 -O1
时执行所有这些操作。老实说,我根本不理解未优化的代码,所以我认为它效率低下。
唯一想到的是对 printf
的调用使用了这些寄存器,否则它们未被使用并且没有任何作用。事实真的如此吗?如果是,怎么可能知道?
提前致谢。我正在阅读一本关于编译器设计的书,并且我已经阅读了大部分 GCC 手册(我阅读了关于优化的整章)并且我已经阅读了一些介绍性的 x86_64 asm material,如果有人可以向我指出一些其他资源(除了 Intel x86 手册)以了解更多信息,我也将不胜感激。
对于您正在使用的编译器,它看起来像 printf(...) 映射到 __printf_chk(1, ...)
要理解代码,you need to understand the parameter passing conventions for the platform (part of the ABI)。一旦你知道在 %rdi、%rsi、%rdx、%rcx 中最多传递了 4 个参数,你就可以理解其中的大部分内容了:
subq , %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 , %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 , %rsp ; deallocate 8 bytes of stack.
ret
Nate 在评论中指出 ABI 中的第 3.5.7 节解释了 %eax = 0(无浮点可变参数。)
我正在努力学习如何理解汇编代码,所以我一直在研究一些愚蠢程序的 GCC 汇编输出。其中之一就是 int i = 0;
,我现在或多或少完全理解了它的代码(最大的困难是理解散布的 GAS 指令)。反正我往前加了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 , %rsp
xorl %edx, %edx
leaq .LC0(%rip), %rsi
xorl %eax, %eax
movl , %edi
call __printf_chk@PLT
xorl %eax, %eax
addq , %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-asynchronous-unwind-tables
编译它以删除 .cfi
指令,但是 -O2
生成相同的代码,所以 -O3
是多余的。我对汇编的理解非常有限,但在我看来编译器在这里做了很多不必要的事情。 rsp
为什么减去然后加 8?为什么要执行这么多xor
?只有一个变量。 movl , %edi
在做什么?我想也许编译器在尝试优化时做了一些愚蠢的事情,但正如我所说,它没有优化超过 -O2
,而且它甚至在 -O1
时执行所有这些操作。老实说,我根本不理解未优化的代码,所以我认为它效率低下。
唯一想到的是对 printf
的调用使用了这些寄存器,否则它们未被使用并且没有任何作用。事实真的如此吗?如果是,怎么可能知道?
提前致谢。我正在阅读一本关于编译器设计的书,并且我已经阅读了大部分 GCC 手册(我阅读了关于优化的整章)并且我已经阅读了一些介绍性的 x86_64 asm material,如果有人可以向我指出一些其他资源(除了 Intel x86 手册)以了解更多信息,我也将不胜感激。
对于您正在使用的编译器,它看起来像 printf(...) 映射到 __printf_chk(1, ...)
要理解代码,you need to understand the parameter passing conventions for the platform (part of the ABI)。一旦你知道在 %rdi、%rsi、%rdx、%rcx 中最多传递了 4 个参数,你就可以理解其中的大部分内容了:
subq , %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 , %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 , %rsp ; deallocate 8 bytes of stack.
ret
Nate 在评论中指出 ABI 中的第 3.5.7 节解释了 %eax = 0(无浮点可变参数。)