11

我刚刚开始学习使用 GCC 编译器来组装我的代码的 mac 程序集。不幸的是,如果您是初学者,学习如何做到这一点的资源非常有限。我终于设法找到了一些简单的示例代码,我可以开始琢磨它,并且我得到了它的组装和正确运行。这是代码:

.text                                           # start of code indicator.
.globl _main                                    # make the main function visible to the outside.
_main:                                          # actually label this spot as the start of our main function.
    push    %rbp                            # save the base pointer to the stack.
    mov     %rsp, %rbp                      # put the previous stack pointer into the base pointer.
    subl    $8, %esp                        # Balance the stack onto a 16-byte boundary.
    movl    $0, %eax                        # Stuff 0 into EAX, which is where result values go.
    leave                                   # leave cleans up base and stack pointers again.
    ret

注释解释了代码中的一些内容(我有点理解第 2 - 5 行的作用),但我不明白其中大部分是什么意思。我确实了解寄存器是什么以及每个寄存器(rbp、和)用于什么以及它们有多大的基础知识rsp,我也(通常)了解堆栈是什么,但这仍然让我头疼。谁能告诉我这是在做什么?另外,任何人都可以为我指出一个适合初学者的好教程的方向吗?espeax

4

1 回答 1

20

Stack是一种遵循LIFO原则的数据结构。日常生活中的堆栈(我的意思是在计算机之外)向上增长,而 x86 和 x86-64 处理器中的堆栈向下增长。请参阅有关 x86 堆栈的 Wikibooks 文章(但请注意代码示例是 Intel 语法中的 32 位 x86 代码,而您的代码是 AT&T 语法中的 64 位 x86-64 代码)。

所以,你的代码做了什么(我在这里的解释是英特尔语法):

push %rbp

rbp入堆栈,实际上减去 8 rsp(因为 的大小rbp是 8 个字节),然后存储rbp[ss:rsp].

因此,在 Intel 语法中push rbp实际上是这样做的:

sub rsp, 8
mov [ss:rsp], rbp

然后:

mov     %rsp, %rbp

这是显而易见的。只需存储rspinto的值rbp

subl    $8, %esp

减去 8esp并将其存储到esp. 实际上这是您的代码中的一个错误,即使它在这里没有引起任何问题。在 x86-64 中,任何以 32 位寄存器(eaxebxecxedxebp、或)作为目标的指令都会将相应 64 位寄存器(、、、、、或)的最高 32 位设置为零,从而导致堆栈指向低于 4 GiB 限制的指针,有效地执行此操作(在 Intel 语法中):espesiediraxrbxrcxrdxrbprsprsirdi

sub rsp,8
and rsp,0x00000000ffffffff

编辑:添加以下后果sub esp,8

但是,这在内存小于 4 GiB 的计算机上不会出现问题。在内存超过 4 GiB 的计算机上,可能会导致分段错误。leave在您的代码下面进一步返回一个合理的值到rsp. 通常在 x86-64 代码中,您esp永远不需要(可能不包括一些优化或调整)。要修复此错误:

subq    $8, %rsp

到目前为止的指令是标准的进入顺序($8根据堆栈使用情况替换)。Wikibooks 有一篇关于 x86 函数和堆栈帧的有用文章(但请再次注意,它使用 Intel 语法的 32 位 x86 汇编,而不是 AT&T 语法的 64 位 x86-64 汇编)。

然后:

movl    $0, %eax

这是显而易见的。将 0 存储到eax. 这与堆栈无关。

leave

这相当于mov rsp, rbp跟随pop rbp

ret

最后,设置rip为存储在的值[ss:rsp],有效地将代码指针返回到调用此过程的位置,并将 8 添加到rsp.

于 2013-01-12T21:34:35.090 回答