让我们分解一下:
.file "delta.c"
编译器使用此信息告知你汇编代码来自哪个源文件。对于汇编器而言,这并不重要。
.section .rodata
这是一个新章节的开始。"rodata"是指"只读数据"部分的名称。该部分最终会将数据写入可执行文件,并映射到只读数据中。可执行镜像的所有".rodata"页面最终会被加载镜像的所有进程共享。
通常情况下,源代码中任何无法优化为汇编内核函数的"编译时常量"最终都会存储在"只读数据"部分中。
.LC0:
.string "%d"
.LC0"
部分是一个标签。它提供了一个符号名称,引用文件中它之后出现的字节。在这种情况下,“LC0”表示字符串“%d”。GNU汇编器使用以“L”开头的标签作为“局部标签”的约定。这具有技术含义,主要是对编写编译器和链接器的人感兴趣。在这种情况下,编译器用它来引用特定对象文件中私有的符号。在这种情况下,它代表一个字符串常量。
.text
这是一个新的章节。"text" 章节是存储可执行代码的目标文件中的章节。
.globl main
".global"指令告诉汇编器将其后面的标签添加到生成的目标文件“导出”的标签列表中。这基本上意味着“这是一个应该对链接器可见的符号”。例如,在“C”中的“非静态”函数可以被任何声明(或包含)兼容函数原型的c文件调用。这就是为什么您可以
#include stdio.h
,然后调用
printf
的原因。当编译任何非静态C函数时,编译器会生成一个声明指向函数开头的全局标签的汇编代码。与不应链接的字符串字面值相对比,它们是“局部”符号。
.type main, @function
我不确定GAS(GNU汇编器)如何处理“.type”指令。然而,这个指令告诉汇编器,“main”标签是可执行代码,而不是数据。
main:
这定义了你“main”函数的入口点。
.LFB0:
这是一个“本地标签”,它指向函数的起始位置。
.cfi_startproc
这是一个“调用帧信息”指令。它指示汇编器发出dwarf格式的调试信息。
pushl %ebp
这是汇编代码中“前奏”的标准部分。它保存了当前的“ebp”寄存器值。“ebp”或“基址”寄存器用于在函数内存储堆栈帧的“基址”。而“esp”(“堆栈指针”)寄存器可能会在函数内调用其他函数时发生变化,但“ebp”保持不变。任何函数参数都可以相对于“ebp”访问。按照ABI调用约定,在函数修改EBP寄存器之前,必须先保存它,以便在函数返回之前恢复原始值。
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
我还没有详细调查过这些内容,但我相信它们与DWARF调试信息相关。
movl %esp, %ebp
GAS使用AT&T语法,与英特尔手册使用的语法相反。这意味着“将ebp设置为esp”。这基本上为函数的其余部分建立了“基指针”。
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
这也是函数结尾的一部分。它会对齐堆栈指针,并从中减去足够的空间以容纳函数中的所有本地变量。
movl $4, 28(%esp)
这将32位整数常量4加载到堆栈帧中的一个插槽中。
movl $.LC0, %eax
这将"%d"字符串常量加载到eax中。
movl 28(%esp), %edx
这将从栈中偏移量为28的位置加载存储的值"4"到edx寄存器。很可能你的代码是在关闭优化的情况下编译的。
movl %edx, 4(%esp)
接着将数值4移动到堆栈中,在调用printf时,它需要在正确的位置。
movl %eax, (%esp)
这将把字符串“%d”加载到调用printf时需要的堆栈位置。
call printf
这个调用了printf。
movl $0, %eax
这将eax设置为0。考虑到接下来的指令是“leave”和“ret”,这相当于在C代码中返回0。 EAX寄存器用于保存函数的返回值。
leave
这个指令清理调用框架。它将ESP设置回EBP,然后从修改后的堆栈指针中弹出EBP。与下一个指令一样,这是函数结尾部分的一部分。
.cfi_restore 5
.cfi_def_cfa 4, 4
这是更多关于DWARF的东西
ret
这是实际的返回指令,它从函数中返回。
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3"
.section .note.GNU-stack,"",@progbits