对组装说明感到困惑
Posted
技术标签:
【中文标题】对组装说明感到困惑【英文标题】:Confused about assembly instructions 【发布时间】:2014-06-25 23:32:08 【问题描述】:我正在阅读本关于汇编的教程:http://orangejuiceliberationfront.com/intel-assembler-on-mac-os-x/,我遇到了这个基本的汇编代码:
.text
.globl _main
_main:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
movl $0, %eax
leave
ret
我对其中的大部分内容有点了解,但我不知道为什么会调用 subl $8, %esp
。我知道它从 esp 中减去 8 个字节,但我不知道为什么这是必要的或为什么这样做。教程说它将堆栈平衡到 16 字节边界,但我不知道“平衡”堆栈是什么意思,也不知道为什么使用数字 8 会产生 16 字节边界。
本教程后面会展示如何定义一个函数,并像这样调用它:
.text
.globl _doSomething
_doSomething:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
nop
leave
ret
.globl _main
_main:
pushl %ebp
movl %esp, %ebp
subl $24, %esp
movl $3, (%esp)
call _doSomething
movl $0, %eax
leave
ret
教程中有“8 对齐,16 用于我们的 4 字节参数和填充”:subl $24, %esp
但是如果有一个 4 字节的参数和填充,为什么我们使用数字 16?还有,什么参数?
我在 Intel Core mac 上,运行 OS X 10.9.3,使用 gcc -S -m32 编译。
我对组装很陌生,所以请让你的答案尽可能简单。谢谢!
【问题讨论】:
看看agner.org/optimize/optimizing_assembly.pdf。您现在可以跳过优化部分,但它也是关于调用约定和对齐等的非常好的文章。 谢谢!我会开始阅读这篇文章。 【参考方案1】:让我们看一下指令序列:
1. nop #call-stack is aligned to 16 bytes (sp is multiple of 16) to start.
2. call function #pushes return address (4 bytes) onto stack.
---(called function)
3. push %ebp #pushes base-pointer (4 bytes) onto stack, which is now 8-byte aligned
---cannot call function that expects to find 16-byte aligned stack---
4. sub $8, %esp #aligns stack to 16 bytes
5. call other_function
【讨论】:
你怎么知道 %ebp 是 4 个字节?%ebp
是一个 4 字节(32 位)寄存器。不可能是别的。
@addison 好吧,如果堆栈是 16 字节(%esp = 0 模 16)对齐 在call
-指令之前,它将是 4 字节对齐(准确地说,%esp = 4 模 16)在被调用函数的开头。【参考方案2】:
如何在机器语言级别调用函数是由调用约定所规定的,该约定通常是体系结构和操作系统特定的。它的设计使您可以以可互操作的方式调用其他函数。在这种情况下,调用约定指定被调用函数可以预期,调用函数时堆栈指针在 16 字节边界上对齐。
这意味着堆栈指针 (esp
) 是 16 字节的倍数。
这样做是因为某些指令仅适用于存储在 16 倍数的内存位置中的数据,主要是出于性能原因。其他指令甚至适用于未对齐的数据,但效率要低得多。
【讨论】:
谢谢。所以在我的第一个例子中,当 subl $8, %esp 被调用时,我怎么知道它需要减去 8 个字节? 当你的函数被调用时,你可以知道,堆栈对齐将是什么返回地址的 4 个字节(64 位上的 8 个字节)和每个传递的参数的适当字节数堆栈。【参考方案3】:如果你想问“为什么程序看起来不像这样?”:
_main:
mov $0, %eax ; sub %eax, %eax would be more efficient
ret
你完全正确。以下四个指令:
pushl %ebp
movl %esp, %ebp
subl $8, %esp
leave
在示例中根本没有用。
如果您手动编写此汇编程序函数(或对 C 编译器输出进行后优化),您将不会编写(或删除)这四个指令。
然而,大多数 C 编译器都针对“正常情况”进行了优化:只返回一个常量值(这里就是这种情况)的 C 函数并不经常出现。
在大多数 C 函数(具有变量和实际代码)中,上面显示的四个指令是有意义的!这就是为什么大多数 C 编译器总是插入这四个指令,即使在特殊情况下不需要它们。
我猜汇编教程插入了四个指令只是因为在下一步中将向函数添加更多指令...
【讨论】:
以上是关于对组装说明感到困惑的主要内容,如果未能解决你的问题,请参考以下文章