函数过程调用
这一章中先以如下代码来讨论一下函数调用的大致过程:



解释:
首先明确一点:
在函数执行的过程中,变量,参数,地址等都是放到栈中:

而且需要注意的是:
栈的栈底是在高地址,每一次入栈,栈指针寄存器ESP都会 -=
Call指令 会将Call指令的下一条指令的地址入栈,这条保存的数据被称为返回地址
Ret指令 会将目前ESP所指向的地址,其保存的数据放回到EIP(程序计数器)中
然后EIP在不断地取指,执行
注意每一次执行Ret时,都保证ESP目前指向的是当时入栈的返回地址(如果不是的话当然会出错了)
这样保证了当函数调用返回时,可以延续上一个函数中继续执行
返回值 (即函数return 后面跟着的变量)都是保存到EAX中的

《函数调用的机器级代码表示》

我们以上面这个代码为例,继续深入讲述函数调用过程
上述代码是 函数P 调用 函数 caller ,函数 caller再调用 add
如下在上面一段函数的汇编代码:


这个过程中 栈中如下:

解释:
首先 pushl %ebp 将EBP中的数据保存到栈中
movl %esp,%ebp 将ESP(栈指针)保存到EBP ,
这个过程相当于让两个指针 ebp 和 esp 指向栈中同一个地址

然后subl $24 ,%esp 即 %esp - 24
这个操作是说明有数据要入栈了,栈指针要移动
然后形成了:

这个时候EBP 和 ESP 分别形成了 一个函数的栈底和栈顶的感觉

这一段汇编代码都是在栈底和栈顶之间 保存数据
这个时候 调用 call指令了 其将下一条指令 movl %eax,-4(%ebp) 的地址入栈
形成返回地址
然后转移到add函数的代码段处执行(这个过程还有保存现场的操作,这里就不说了)
然后add函数代码段的汇编代码开头和结尾其实都与其他函数差不多
都是:

然后接着是形成 add函数 的栈底和栈顶的过程(即通过移动EBP和ESP)

注意这里ESP栈底指向的内容是上一个函数中EBP的内容
即上一个函数中栈底的地址
然后执行。。。执行。。。
执行到add函数要结束了

将 目前EBP中的值 送到 ESP中,即让当前函数(add)的栈顶返回到栈底
这个函数中全部保存的数据随着 栈顶的返回而没用了
然后popl %ebp 即将当前栈顶中的内容 压入到 EBP中
即EBP现在保存了 上一个函数(caller)中栈底的内容
即EBP现在又回到 caller函数的栈底了
然后 因为pop了 ESP中的内容(即栈指针)肯定要+
则回到了
然后执行ret指令,取出 当前栈顶中的内容送到EIP中
即取出返回地址中的内容(即caller函数中
的地址)
这个时候已经彻底回到 caller函数中继续执行了

《函数过程调用案例》

思考一下变量在我们程序的虚拟内存中是如何存放的

应该如图存放,再想一想如果一个数组超过了其原先定义的大小
(即我们常常说的:你数组下标越界了)
这个时候在我们程序的虚拟内存中是如何变换的呢?

如图所示:
既然我们定义了a这个数组,那么在响应的虚拟地址中就会有其存放位置
从汇编代码和图中可以知道:
a[0]被放到了ESP(即栈顶)的位置处
然后

这条汇编指令是将a[i]=1073.....24这个操作的
在汇编中如何知道a[i]的位置?
当然是以a[0]为基址+i*4(4是因为一个int4个字节 )
现在你可以想一下,以我们这个案例为列:
如果i超过了2,是不是a[i]会被保存到

这个上面来?
对的,这样会覆盖原先的已经保存好的值
导致十分奇怪的情况发生


浙公网安备 33010602011771号