6

我决定在暑假期间学习 x86 汇编会很有趣。所以我从一个非常简单的 hello world 程序开始,借用免费的例子gcc -S可以给我。我最终得到了这个:

HELLO:
    .ascii "Hello, world!\12\0"
    .text

.globl _main
_main:
    pushl   %ebp        # 1. puts the base stack address on the stack
    movl    %esp, %ebp  # 2. puts the base stack address in the stack address register
    subl    $20, %esp   # 3. ???
    pushl   $HELLO      # 4. push HELLO's address on the stack
    call    _puts       # 5. call puts
    xorl    %eax, %eax  # 6. zero %eax, probably not necessary since we didn't do anything with it
    leave               # 7. clean up
    ret                 # 8. return
                        # PROFIT!

它编译甚至工作!我想我理解了大部分

不过,魔法发生在第 3 步。如果我删除这一行,我的程序将在调用putsxor未对齐堆栈错误之间终止。我会更改$20为另一个值,它也会崩溃。所以我得出结论,这个值很very重要。

问题是,我不知道它的作用以及为什么需要它。

谁能解释我?(我在 Mac OS 上,这有什么关系。)

4

3 回答 3

3

注释的一般形式应该是“为局部变量分配空间”。为什么随意更改它会崩溃我不确定。如果你减少它,我只能看到它崩溃。6 的正确注释是“准备从此函数返回 0”。

于 2010-06-06T02:48:56.147 回答
3

在 x86 OSX 上,函数调用的堆栈需要 16 字节对齐,请参阅此处的 ABI 文档。所以,解释是

推送堆栈指针 (#1) -4
奇怪的增量(#3)-20
推论(#4)-4
调用推送返回地址(#5)-4
总计 -32

要检查,将第 3 行从 $20 更改为 $4,这也有效。

此外,Ignacio Vazquez-Abrams 指出,#6 不是可选的。寄存器包含先前计算的残余,因此必须明确归零。

我最近也学习了(仍在学习)组装。为避免您感到震惊,64 位调用约定有很大不同(在寄存器上传递的参数)。发现对 64 位汇编非常有帮助。

于 2010-06-06T04:05:26.000 回答
1

请注意,如果您使用 -fomit-frame-pointer 进行编译,则该%ebp指针样板将消失。基指针有助于调试,但在 x86 上实际上不是必需的。

此外,我强烈建议使用 Intel 语法,所有 GCC/binutils 都支持这种语法。我曾经认为 AT&T 和 Intel 语法之间的区别只是一种口味问题,但有一天我遇到了这个例子,其中 AT&T 的助记符与 Intel 的助记符完全不同。由于所有官方 x86 文档都使用 Intel 语法,因此这似乎是一种更好的方法。

玩得开心!

于 2010-06-06T04:53:40.077 回答