movl 8(%ebp), %eax所以 , 我们可以得出结论是 , 在调用函数前需要把参数逐个压栈 , 而压栈的顺序根据笔者的测试是从右向左的 。
movl %eax, (%esp)
接着调用call指令 , 跳转到f函数 , 我们知道call指令等同于下面的伪代码:
pushl %eip+1即把call指令的后一条指令进栈后 , 将eip赋值为目标函数的第一个指令地址 。 这样做显而易见:当所调用的函数结束后 , 需要返回当前函数继续执行 , 所以必须要保存下一条指令 , 否则回来的时候就找不到了 。
movl %eip f
来到f函数 , 首先是保存main函数的栈基地址 , 然后需要调用g函数 , 于是需要参数先进栈:
movl 8(%ebp), %eax这里重点思考一下 , f函数是如何获得main函数传递过来的参数的 , 我们看到
movl %eax, (%esp)
movl8(%ebp), %eaxmovl 8(%ebp), %eax 为什么参数是从8(%ebp)中获得的呢?我们知道8(%ebp)表示的是以ebp为基准向栈底回溯8个字节得到 , 为什么是8个字节呢?
回想一下 , 在main函数中完成了参数进栈后做了两件事情:
1.由于call f指令的作用 , call f下一条指令的地址被压栈了 , 这占用率4个字节
2.进入f函数后 , 立即将main函数的栈基地址进栈了 , 而且将ebp靠向了栈顶esp , 这又占用了4个字节
于是通过8(%ebp)可以找到前一个函数的第一个整型参数的值 。
一张图告诉你怎么回事:
【算法 | 一段C语言和汇编的对应分析,揭示函数调用的本质】看过了进入函数 , 调用函数的过程 , 再看一下函数是如何退出的 。 观察main和f不难发现 , 退出函数使用的是如下指令
leaveleave指令相当于如下指令:
ret
movl %ebp, %esp●第一条语句是将esp重置到ebp , 可以理解为清空当前函数所使用的栈
popl %ebp
●第二条语句是将栈顶值赋值给ebp , 并弹出 , 栈顶值是什么呢?通过上面的分析不难发现 , 此时的栈顶值实际上是前一个函数的栈基地址 , 所以第二条语句的意思就是把ebp恢复到前一个函数的栈基地址
接着ret就是相当于 , 恢复指令指向:
popl %eip
为什么g函数没有leave呢?因为g函数内部没有任何的变量声明和函数调用栈一直都是空的 , 所以编译器优化了指令总结
最后 , 通过这个例子 , 总结一下函数调用的过程:
进入函数:
当前栈基地址压栈(当前栈基地址实际上是前一个函数的栈基地址)
调用其他函数:
1.参数从右到左进栈
2.下一条指令地址进栈
退出函数:
1.栈顶esp归位 , 回到本函数的ebp
2.基地址回退到上一个函数的基地址
3.eip退回到上一个函数即将要执行的那条语句的地址上
来自:P_Chou Tech Space , 作者:周平
链接:http://www.pchou.info/c-cpp/2015/03/03/c-and-asm.html
特别声明:本站内容均来自网友提供或互联网,仅供参考,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
