我现在正在学习装配,而且我不会对(可能是)标准功能模板有所了解。
因此,根据this really nice book,“要记住的功能形式如下:”
function_label:
pushl %ebp
movl %esp, %ebp
< normal function code goes here>
movl %ebp, %esp
popl %ebp
ret
好吧,我对它很满意,但有一件小事我不明白。在“正常功能代码”之后,我们恢复esp
的初始(预调用)值,该值先前存储在ebp
中。
现在,我清楚地理解为什么我们希望将esp
值提供给未触及的调用上下文。我不明白的是在函数执行期间可以在哪些条件下更改esp
值。
这个模板中包含的对我们自己的某种保护(如果我们以某种方式破坏我们代码中某处的堆栈)?或者可能更改函数内的堆栈值是正常的做法?
或者,即使我们没有对它执行任何操作,初始esp
值也可能在执行期间最终更改? (事实上,我无法弄清楚这是怎么回事。)
在考虑这个问题时我觉得很傻,并在这个简单的代码中用esp
检查gdb
值:
.section .data
message:
.asciz "> Hello from function #%d\n"
.section .text
.globl main
main:
nop
call overhere
pushl $0
call exit
overhere:
pushl %ebp
movl %esp, %ebp
pushl $1
pushl $message
call printf
add $8, %esp
movl %ebp, %esp
popl %ebp
ret
并且esp
(正如我实际预期的那样)未受影响,因此将ebp
移动到esp
实际上并未改变任何内容。
现在,我希望很清楚我想知道的是什么:
esp
值最终会自行更改吗? (我敢打赌它不能。)esp
值改为错误?提前谢谢你,原谅我的无知。
答案 0 :(得分:0)
我很困惑你错过了明确更改esp
:add $8, %esp
的说明。所以答案显然是肯定的,它可能会在功能期间发生变化而不是错误。请注意,push
和call
也会更改它,实际上add
是为了补偿两个push
指令(ret
在{{}的末尾1}}将平衡printf
)。更改call
的其他典型原因是局部变量的分配。这显示在您显示的功能模板中,在esp
之后通常看起来像sub $size_of_locals, %esp
。
那就是说,你不需要使用movl %esp, %ebp
来记住堆栈指针,只要你确保它在函数出口处具有与进入时相同的值。在启用优化时,最新版本的gcc不会使用ebp
,否则您可以使用ebp
来执行此操作。