这是怎么回事:
.file "test.c"
原始源文件名(调试器使用)。
.section .rodata
.LC0:
.string "Hello World!"
以零结尾的字符串包含在“ .rodata”部分中(“ ro”表示“只读”:应用程序将能够读取数据,但是任何写入数据的尝试都会触发异常)。
.text
.globl main
.type main, @function
main:
我们定义了一个称为“ main”的函数,并且该函数在全局范围内可见(其他目标文件将可以调用它)。
leal 4(%esp), %ecx
我们在寄存器中存储%ecx
值4+%esp
(%esp
是堆栈指针)。
andl $-16, %esp
%esp
稍作修改,使其变为16的倍数。对于某些数据类型(对应于C double
和的浮点格式long double
),当内存访问位于16的倍数处时,性能会更好。 ,但是在不使用优化标志(-O2
…)的情况下,编译器往往会生成大量通用的无用代码(即,在某些情况下可能有用但在此处无效的代码)。
pushl -4(%ecx)
这有点奇怪:在这一点上,address-4(%ecx)
处的单词是之前位于堆栈顶部的单词andl
。该代码检索该单词(顺便说一句,它应该是返回地址)并再次推送它。这种类型的仿真将通过具有16字节对齐堆栈的函数的调用获得的结果。我的猜测是,这push
是参数复制序列的残余。由于函数已经调整了堆栈指针,因此它必须复制函数参数,可以通过堆栈指针的旧值访问这些参数。这里,除了函数返回地址外,没有参数。请注意,将不使用该词(再次,这是未经优化的代码)。
pushl %ebp
movl %esp, %ebp
这是标准函数的序言:保存%ebp
(因为我们将要对其进行修改),然后设置%ebp
为指向堆栈框架。此后,%ebp
将用于访问函数参数,使其%esp
再次释放。(是的,没有参数,因此对于该函数没有用。)
pushl %ecx
我们保存%ecx
(在函数退出时将需要它,以恢复%esp
到之前的值andl
)。
subl $20, %esp
我们在堆栈上保留32个字节(请记住,堆栈增长为“向下”)。该空间将用于存储to的参数printf()
(这太过分了,因为只有一个参数,它将使用4个字节[这是一个指针])。
movl $.LC0, (%esp)
call printf
我们将参数“推入”printf()
(即,确保%esp
指向包含该参数的单词,此处$.LC0
为rodata节中常量字符串的地址)。然后我们打电话printf()
。
addl $20, %esp
当printf()
返回时,我们除去分配给参数的空间。这addl
取消了subl
上面的操作。
popl %ecx
我们恢复%ecx
(如上所示);printf()
可能已经对其进行了修改(调用约定描述了可以在退出时不恢复功能的情况下修改功能的寄存器;%ecx
此类寄存器就是其中之一)。
popl %ebp
功能结语:恢复%ebp
(对应于pushl %ebp
以上内容)。
leal -4(%ecx), %esp
我们恢复%esp
到其初始值。此操作码的作用是存储在%esp
value中%ecx-4
。%ecx
在第一个功能操作码中设置。这会取消对的任何更改%esp
,包括andl
。
ret
功能出口。
.size main, .-main
这设置了main()
函数的大小:在汇编过程中的任何时候,“ .
”都是“我们现在要添加内容的地址”的别名。如果在此处添加了另一条指令,它将到达“.
” 指定的地址。因此,“ .-main
”是函数代码的确切大小main()
。该.size
指令指示汇编程序将该信息写入目标文件中。
.ident "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"
GCC只是喜欢留下其行动的痕迹。该字符串最终作为目标文件中的一种注释。链接器将其删除。
.section .note.GNU-stack,"",@progbits
GCC在其中写了一个特殊的部分,该代码可以容纳不可执行的堆栈。这是正常情况。对于某些特殊用途(不是标准C),需要可执行堆栈。在现代处理器上,内核可以创建一个不可执行的堆栈(如果有人试图以代码的形式执行堆栈中的某些数据,则该堆栈会触发异常)。某些人将其视为“安全功能”,因为将代码放在堆栈上是利用缓冲区溢出的常用方法。在本节中,可执行文件将被标记为“与不可执行的堆栈兼容”,内核将很高兴地提供这样的文件。