让我们来分解一下:
.file "delta.c"
编译器使用它来告诉您程序集来自哪个源文件。这对于汇编器来说没有多大意义。
.section .rodata
这将开始一个新的部分。 “rodata”是“只读数据”部分的名称。此部分最终将数据写入可执行文件,该可执行文件将内存映射为只读数据。可执行映像的所有“.rodata”页面最终都会被所有进程共享
加载图像。
通常,源代码中无法优化为汇编内部函数的任何“编译时常量”最终都会存储在“只读数据部分”中。
.LC0:
.string "%d"
The .LC0"
部分是标签。它提供了一个符号名称,引用文件中其后出现的再见。在本例中,“LC0”代表字符串“%d”。 GNU 汇编器使用以下约定:以“L”开头的标签被视为“本地标签”。这具有技术意义,编写编译器和链接器的人最感兴趣。在这种情况下,编译器使用它来引用特定目标文件私有的符号。在本例中,它表示一个字符串常量。
.text
这将开始一个新的部分。 “文本”部分是目标文件中存储可执行代码的部分。
.globl main
“.global”指令告诉汇编器将其后面的标签添加到生成的目标文件“导出”的标签列表中。这基本上意味着“这是链接器应该可见的符号”。例如,“C”中的“非静态”函数可以由声明(或包含)兼容函数原型的任何 c 文件调用。这就是为什么你可以#include stdio.h
然后打电话printf
。编译任何非静态 C 函数时,编译器都会生成声明指向函数开头的全局标签的程序集。将此与不应链接的内容(例如字符串文字)进行对比。目标文件中的汇编代码仍然需要一个标签来引用文字数据。这些是“本地”符号。
.type main, @function
我不确定 GAS(gnu 汇编器)如何处理“.type”指令。然而,这指示汇编器标签“main”指的是可执行代码,而不是数据。
main:
这定义了“主”函数的入口点。
.LFB0:
这是一个“局部标签”,指的是函数的开始。
.cfi_startproc
这是一个“调用帧信息”指令。它指示汇编器发出矮格式调试信息。
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 语法,该语法与 Intel 手册使用的语法相反。这意味着“设置 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 代码中的“return 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