我在这里运行了gcc-S:

int main()
{
printf ("Hello world!");
}

我得到了这个汇编代码:

        .file   "test.c"
        .section        .rodata
.LC0:
        .string "Hello world!"
        .text
.globl main
        .type   main, @function
main:
        leal    4(%esp), %ecx
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        movl    %esp, %ebp
        pushl   %ecx
        subl    $20, %esp
        movl    $.LC0, (%esp)
        call    printf
        addl    $20, %esp
        popl    %ecx
        popl    %ebp
        leal    -4(%ecx), %esp
        ret
        .size   main, .-main
        .ident  "GCC: (GNU) 4.3.0 20080428 (Red Hat 4.3.0-8)"
        .section        .note.GNU-stack,"",@progbits

我很想了解这个输出.有人能分享一些理解这个输出的建议吗,或者如果有人能对这些行/行组中的每一行进行注释,解释它的功能,那就太好了.

推荐答案

事情是这样的:

        .file   "test.c"

原始源文件名(调试器使用).

        .section        .rodata
.LC0:
        .string "Hello world!"

".rodata"部分包含以零结尾的字符串("ro"表示"只读":应用程序将能够读取数据,但任何写入数据的try 都将触发异常).

        .text

现在我们把东西写进".text"部分,这就是代码所在的地方.

.globl main
        .type   main, @function
main:

我们定义了一个名为"main"且全局可见的函数(其他对象文件将能够调用它).

        leal    4(%esp), %ecx

我们在寄存器%ecx中存储值4+%esp(%esp是堆栈指针).

        andl    $-16, %esp

%esp稍作修改,使其成为16的倍数.对于某些数据类型(对应于C的doublelong double的浮点格式),当内存访问位于16的倍数时,性能会更好.这在这里并不是真的需要,但如果没有优化标志(-O2…)使用,编译器倾向于生成大量无用的通用代码(即,在某些情况下可能有用,但在这里不可用的代码).

        pushl   -4(%ecx)

这一个有点奇怪:在这一点上,地址-4(%ecx)处的单词是andl之前堆栈顶部的单词.代码检索该单词(顺便说一句,它应该是返回地址)并再次推送它.这种方法模拟了从一个具有16字节对齐堆栈的函数调用所获得的结果.我猜这是一个参数复制序列的残余.由于函数已经调整了堆栈指针,它必须复制函数参数,这些参数可以通过堆栈指针的旧值访问.这里没有参数,只有函数返回地址.请注意,这个词不会被使用(同样,这是没有优化的代码).

        pushl   %ebp
        movl    %esp, %ebp

这是标准函数的开场白:我们保存%ebp(因为我们即将修改它),然后设置%ebp指向堆栈帧.此后,%ebp将用于访问函数参数,使%esp再次可用.(是的,没有参数,因此这对该函数没有用处.)

        pushl   %ecx

我们保存了%ecx(我们需要在函数退出时使用它,将%esp恢复到andl之前的值).

        subl    $20, %esp

我们在堆栈上保留32个字节(请记住,堆栈会"向下"增长).该空间将用于将参数存储到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恢复到初始值.该操作码的作用是将值%ecx-4存储在%esp中.在第一个功能操作码中设置了%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)需要可执行堆栈.在现代处理器上,内核可以生成一个不可执行的堆栈(如果有人试图将堆栈上的某些数据作为代码执行,则会触发异常的堆栈);有些人认为这是一种"安全特性",因为将代码放在堆栈上是利用缓冲区溢出的常见方法.在本节中,可执行文件将被标记为"与非可执行堆栈兼容",内核将乐意提供这样的堆栈.

Linux相关问答推荐

shell中两个日期的天数差异

无法分析nasm中的单词

将十六进制文件名转换为十进制

线程创建会在 Linux 中触发页面错误吗?它与软脏 PTE 有什么关系?

awk 打印除最后一列以外的所有内容 + 最后一列

在服务器目录之外启动 DolphinDB 服务器时出错

Dockerfile:无法复制文件

使用 awk 将 csv 拆分为带有标题的多个文件

PHP factor 30 从 Linux 到 Windows 的性能差异

如何在 linux 上为 JNI 应用程序编译动态库?

Git为每次推送输入长密码

如何更改某些文件模式/扩展名的权限?

安装 mod_ssl 亚马逊 Linux

在 Ubuntu 中重启 Nginx

错误:ld.so:无法预加载对象 LD_PRELOAD:忽略

如何链接到 GCC 中特定版本的共享库

Linux 守护进程

Linux 的文本编辑器(除了 Vi)?

使用 WGET 运行 cronjob PHP

将 BlueZ Stack 用作外设(广告商)