当前位置: 首页 > 面试题库 >

C hello世界的汇编输出的每一行是什么意思?

楚洋
2023-03-14
问题内容

我在上面运行了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”表示“只读”:应用程序将能够读取数据,但是任何写入数据的尝试都会触发异常)。

        .text

现在,我们将内容写入代码所在的“ .text”部分。

.globl main
        .type   main, @function
main:

我们定义了一个称为“ main”的函数,并且该函数在全局范围内可见(其他目标文件将可以调用它)。

        leal    4(%esp), %ecx

我们在寄存器中存储%ecx4+%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到其初始值。此操作码的作用是存储%espvalue中%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),需要可执行堆栈。在现代处理器上,内核可以创建一个不可执行的堆栈(如果有人试图以代码的形式执行堆栈中的某些数据,则该堆栈会触发异常)。某些人将其视为“安全功能”,因为将代码放在堆栈上是利用缓冲区溢出的常用方法。在本节中,可执行文件将被标记为“与不可执行的堆栈兼容”,内核将很高兴地提供这样的文件。



 类似资料:
  • 本文向大家介绍什么是汇编语言,包括了什么是汇编语言的使用技巧和注意事项,需要的朋友参考一下  汇编语言(assembly language)是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符(Mnemonics)代替机器指令的操作码,用地址符号(Symbol)或标号(Label)代替指令或操作数的地址。在不同的设备中,汇编语言对应着不同的机器

  • 我是java初学者,我对上述程序的输出感到困惑。请给我解释一下输出的原因。

  • 问题内容: 在有效的Java书中,它指出: 语言规范保证,除非变量的类型或类型为[JLS,17.4.7],否则读写变量是原子的。 在Java编程或一般编程中,“原子”是什么意思? 问题答案: 这是一个示例,因为一个示例通常比冗长的解释更清晰。假设是类型为的变量。以下操作不是原子操作: 实际上,变量是使用两个单独的操作写入的:一个操作写入前32位,第二个操作写入后32位。这意味着另一个线程可能读取的

  • 那么当http2被广泛采用的时候,世界将会成什么样呢?或者说,它会被真正的采用吗? 8.1. http2会如何影响普通人? 到目前为止,http2还没被大范围部署使用,我们也无法确定到底会发生什么变化,但至少可以参考SPDY的例子和曾经做过的实验来进行大概的估计。 http2减少了网络往返传输的数量,并且用多路复用和快速丢弃不需要的流的办法来完全避免了head of line blocking(线

  • 问题内容: 我想遍历输出的每一行: 现在我正在尝试: 但是,这会分别遍历行中的每个元素,因此我得到: 但是,我想遍历整个行。我怎么做? 问题答案: 将IFS设置为换行符,如下所示: 如果您不想永久设置IFS,请在其周围放一个子外壳: 或同时使用| 改为阅读: 还有一个选项,它在同一shell级别上运行while / read:

  • 我正在查看英特尔提供的指令的参考实现。页面是英特尔数字随机数生成器(DRNG)软件实现指南,代码来自英特尔数字随机数生成器软件代码示例。 以下是英特尔的相关部分。它读取一个随机值并将其置于val中,并在成功时设置进位标志。 索里不得不问。我认为GNU Extended Assembler并没有涵盖它,搜索“=qm”会产生虚假的点击。 扩展汇编器中的是什么意思?